DEV Community

Dilantha
Dilantha

Posted on

A Practical approach to learning react hooks

Everybody talks about react hooks these days. Here in this article let's look into a few aspects of react hooks with some practical examples. I have added step by step guides so that you can try it out while you are reading. we will be talking,

  • Using states and effects in functions
  • Writing custom hooks

If you would like to read about why react hooks were introduced and what kind of problems are solved by using them in detail, you may like to take a look at react official documentation. But here we will look at some examples first, in terms of how to use them.

Step 1

Let's start with a simple function component. I recommend using codesandbox so that it's easier to follow along. And the example provided is been git tagged. So you can move to each step by 'git checkout step'. For example git checkout step1.
repo : https://github.com/dilantha111/react-hooks-example-01
codesandbox: https://codesandbox.io/s/react-hooks-example-01-9pu6w?file=/src/App.js

import React from "react";
import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

Step 2

Let's add the famous counter to see how we can introduce state to this function component.

import React, { useState } from "react"; // we imports useState
import "./styles.css";

export default function App() {
  const [count, setCount] = useState(0); // count and setCount

  return (
    <div className="App">
      <h1> {count} </h1>
      <button onClick={() => setCount(count + 1)}> Click </button>
    </div>
  );
}

Now if you click on the button you will see that count value is get updated. Now we have count in our state, and we have a method to update our state as well. And we are not using this keyword or method binding or anything. Things are very simple. All we had to do is use 'useState' hook. And if you notice 'useState(0)' you can see the initial value of our count is 0. This could be null as well depending on your use case. And can be an object, array etc. it's just same like how you initialize a state field in a class component. Let's say we want to introduce another state variable like name. we just call useState function once again.

import React, { useState } from "react";
import "./styles.css";

export default function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');

  return (
    <div className="App">
      <h1> {count} </h1>
      <button onClick={() => setCount(count + 1)}> Click </button>
    </div>
  );
}

Now this will be enough to start our next step.

Step 3

Now let's do something useful. Let's add a button where when we click it, we get a piece of new advice by making an HTTP request. And for this, we will be using axios. Feel free to use fetch if that's your choice. we will be using a free api service which is https://api.adviceslip.com/advice. With that, we can get a piece of free advice. which would be very useful these days ;)

import React, { useState } from "react";
import * as axios from "axios";
import "./styles.css";

export default function App() {
  const [advice, setAdvice] = useState(null);

  const fetchAnAdvice = async () => {
    try {
      const { data } = await axios.get("https://api.adviceslip.com/advice");
      setAdvice(data.slip.advice);
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <div className="App">
      <h1> {advice} </h1>
      <button onClick={fetchAnAdvice}> Get a new Advice </button>
    </div>
  );
}

This is something more practical. which a user perform an event, And an HTTP request is made, and show the result to the user. One thing missing here is until user clicks there is no advice to be shown. Here what we can do is have an initial value for this. How we can do this ? in a class component we will be doing an initial fetch in componentDidMount(). But in function compoent we don't have it. But instead of that we can use useEffect() for this. Let's do it in the next step.

Step 4

with useEffect hook we can execute something when component is updated. This is similar to the combined componentDidMount and componentDidUpdate. And we can return a call back function which will be called similar to what we want in componentWillUnmount. But for this, we don't need that. we will be looking into it in a future article. So let's check how we can use the useEffect.

import React, { useState, useEffect } from "react";
import * as axios from "axios";
import "./styles.css";

export default function App() {
  const [advice, setAdvice] = useState(null);

  useEffect(() => {
    fetchAnAdvice();
  });

  const fetchAnAdvice = async () => {
    try {
      const { data } = await axios.get("https://api.adviceslip.com/advice");
      setAdvice(data.slip.advice);
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <div className="App">
      <h1> {advice} </h1>
      <button onClick={fetchAnAdvice}> Get a new Advice </button>
    </div>
  );
}

If you run the above code, you will notice that our advice is keep getting updated without we ever even clicking it. what's going on ? Let's take a closer look,

useEffect(() => {
    fetchAnAdvice();
});

Now whenever the component get updated fetchAnAdvice will be called. So let's say at the first render, we call it one time and in fetchAnAdvice function we update the state, which causes another render and it calls the effect again. And so on. This is not the behaviour we want. we want fetchAnAdvice only a one time. How we can do that ?. we can pass an optional array to useEffect as the second argument. Where effect will only fire if one of the attributes in this array is changed. But what if we want to fire the effect only at the beginning ? we can pass an empty array. So we will change our code like below.

useEffect(() => {
    fetchAnAdvice();
}, []);

Now the final code should look like below

import React, { useState, useEffect } from "react";
import * as axios from "axios";
import "./styles.css";

export default function App() {
  const [advice, setAdvice] = useState(null);

  useEffect(() => {
    fetchAnAdvice();
  }, []);

  const fetchAnAdvice = async () => {
    try {
      const { data } = await axios.get("https://api.adviceslip.com/advice");
      setAdvice(data.slip.advice);
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <div className="App">
      <h1> {advice} </h1>
      <button onClick={fetchAnAdvice}> Get a new Advice </button>
    </div>
  );
}

Now just to make the user experience better let's add a loading state. So that user is aware about that an HTTP request is being made.

import React, { useState, useEffect } from "react";
import * as axios from "axios";
import "./styles.css";

export default function App() {
  const [advice, setAdvice] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    fetchAnAdvice();
  }, []);

  const fetchAnAdvice = async () => {
    try {
      setIsLoading(true);
      const { data } = await axios.get("https://api.adviceslip.com/advice");
      setAdvice(data.slip.advice);
      setIsLoading(false);
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <div className="App">
      <h1> {isLoading ? '...': advice} </h1>
      <button onClick={fetchAnAdvice}> Get a new Advice </button>
    </div>
  );
}

Notice how we have added another isLoading state variable.

Step 5

Step 4 was a little bit longer. But now we are at the last step, where we will create a custom hook so we can reuse this logic in another component as well. And will clear the component code as well. What's a custom hook ? simply it's another function where you would call other hooks. "A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks." this is taken from the official documentation. And note that custom hooks should start with the 'use' keyword. So that linting plugins can detect that it's a hook rather than a regular javascript function. We have the flexibility over input values and output values to a custom hook function. We can use hooks exactly like we used them in a function component. Custom hooks solve a very important problem. This is taken from the official documentation "Traditionally in React, we’ve had two popular ways to share stateful logic between components: render props and higher-order components. We will now look at how Hooks solve many of the same problems without forcing you to add more components to the tree". So let's extract our stateful logic to a separate custom hook.

import { useState, useEffect } from "react";
import * as axios from "axios";

export function useAdvice() {
  const [advice, setAdvice] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  const fetchAnAdvice = async () => {
    try {
      setIsLoading(true);
      const { data } = await axios.get("https://api.adviceslip.com/advice");
      setAdvice(data.slip.advice);
      setIsLoading(false);
    } catch (error) {
      console.error(error);
    }
  };

  useEffect(() => {
    fetchAnAdvice();
  }, []);

  return [advice, isLoading, fetchAnAdvice];
}

Let's add this code snippet to a separate file named use-advice.js. Here you can see it's exactly like in the function component. The difference is we are returning advice, isLoading, and fetchAnAdvice which we will be using in the function component. Now let's see how we use our custom hook in our component.

import React from "react";
import "./styles.css";

export default function App() {




  return (
    <div className="App">
      <h1> {isLoading ? '...': advice} </h1>
      <button onClick={fetchAnAdvice}> Get a new Advice </button>
    </div>
  );
}

First you can see we will be removing most of the things, which are associated with handling the stateful logics. But we kept isLoading, advice and fetchAnAdvice in the return jsx. Now we will be using custom hook.

import React from "react";
import "./styles.css";
import { useAdvice } from './use-advice';

export default function App() {
  const [advice, isLoading, fetchAnAdvice ] = useAdvice();

  return (
    <div className="App">
      <h1> {isLoading ? '...': advice} </h1>
      <button onClick={fetchAnAdvice}> Get a new Advice </button>
    </div>
  );
}

Now our stateful logic resides in our custom hook and if we want to reuse it all we have to do is adding this to a function component.

const [advice, isLoading, fetchAnAdvice ] = useAdvice();

with that we can finish our little walkthrough.

What's next

With this now you can try modifying this example a bit to get yourself familiarize with hooks. But before you go and implement them in a real-world application it's recommended that you go through rules of react hooks.
https://reactjs.org/docs/hooks-rules.html. Rest of the concepts you can follow along leisurely.

And we didn't cover the part where we will be returning a function in an effect hook. This is useful when you want to unsubscribe to avoid possible memory leaks. We will be covering that in a future article. But you can create something using rxjs or maybe with firebase cloud messaging and try it out yourself.

And if anything to improve or any question please don't forget to comment. cheers !!! Happy Coding !!!

Top comments (0)