React Hooks From Scratch A-Z

kirill ibrahim
The Startup
Published in
13 min readFeb 22, 2020

Introduction To Hooks:

The React team decided to bring React Hooks into our lives. Many Developers are starting to use Hooks in the new code side by side with classes.

React Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class. It is a fundamental shift in how you’ll approach writing React Component.

Important Notes before beginning our Journey:

There are no Breaking Changes, Completely opt-in. You can try Hooks in a few components without rewriting any existing code. There are no plans to remove classes from React. 100% backward-compatible. Hooks don’t contain any breaking changes. Hooks don’t replace your knowledge of React concepts, just provide a more direct API to the React concepts you already know: props, state, context, refs, and lifecycle.

We will go through four main points in the article:

1-useState Hook

2-useEffect Hook

3-Tips for using useEffect Hook

4-Rules of React Hooks

Most of the codes within an article from the official documentation of hooks.

Please follow me over Medium, to get a notification about next new articles. I’m also active on Twitter @IbraKirill.

1-useState Hook:

This example renders a counter. When you click the button, it increments the value:

Here, useState is a Hook (we’ll talk about what this means in a moment). We call it inside a function component to add some local state to it. React will preserve this state between re-renders. useState returns a pair: the current state value and a function that lets you update it. You can call this function from an event handler or somewhere else. It’s similar to this.setState in a class, except it doesn’t merge the old and new state together.

The only argument inuseState is the initial state. In the example above, it is 0 because our counter starts from zero.

What are the Hooks? Hooks are functions that let you “hook into” React state and lifecycle features from function components. Hooks don’t work inside classes — they let you use React without classes.

When would I use a Hook? If you write a function component and realize you need to add some state to it, previously you had to convert it to a class. Now you can use a Hook inside the existing function component. We’re going to do that right now!

What does useState return? It returns a pair of values: the current state and a function that updates it. This is why we write const [count, setCount] = useState(). This is similar to this.state.count and this.setState in a class, except you get them in a pair.

If you’re not familiar with the syntax we used, here is the explanation:

You might have noticed the square brackets when we declare a state variable:

const [count, setCount] = useState(0);

The names on the left aren’t a part of the React API. You can name your own state variables:

const [car, setCar] = useState('bmw');

This JavaScript syntax is called “array destructuring”. It means that we’re making two new variables car and setCar, where car is set to the first value returned by useState, and setCar is the second. It is equivalent to this code:

var carStateVariable = useState('bmw'); // Returns a pairvar car = carStateVariable[0]; // First item in a pairvar setCar = carStateVariable[1]; // Second item in a pair

Note that unlike this.state, the state here doesn’t have to be an object — although it can be if you want. The initial state argument is only used during the first render:

const [state, setState] = useState({searchText: “”,searchedColumn: “”,pageSize: 10,loading: true,showModal: false,currentRecord: {}});

Declaring multiple state variables

You can use the State Hook more than once in a single component:

Declaring state variables as a pair of [something, setSomething] is also handy because it lets us give different names to different state variables if we want to use more than one:

In the above component, we have age, fruit, and todos as local variables, and we can update them individually:

function handleOrangeClick() {// Similar to this.setState({ fruit: 'orange' })setFruit('orange');}

You don’t have to use many state variables. State variables can hold objects and arrays just fine, so you can still group related data together. However, unlike this.setState in a class, updating a state variable always replaces it instead of merging it.

Reading State

When we want to display the current count in a class, we read this.state.count:

<p>You clicked {this.state.count} times</p>

In a function, we can use count directly:

<p>You clicked {count} times</p>

Updating State

In a class, we need to call this.setState() to update the count state:

<button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button>

In a function, we already have setCount and count as variables so we don’t need this.setState:

Line 7: When the user clicks, we call setCount with a new value. React will then re-render the Example component, passing the new count value to it.

2-useEffect Hook

React brings a new hook called useEffect that lets us implement life cycle methods that were native to Class-based components. By using this hook we tell React that the component needs to do something after render. React will remember the function we passed, and call it later after performing the DOM updates, So Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. Whether or not you’re used to calling these operations “side effects” (or just “effects”).

Tip

If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

Quick Review to lifecycles:

There are four different phases of React component’s lifecycle:

1. Initialization: In this phase react component prepares and setting up the initial state and default props.

2. Mounting: The react component is ready to mount in the browser DOM. This phase covers componentWillMount (will be deprecated) and componentDidMount lifecycle methods.

3. Updating: In this phase, the component gets updated in two ways, sending the new props and updating the state. This phase covers shouldComponentUpdate, componentWillUpdate (will be deprecated) and componentDidUpdate lifecycle methods.

4. Unmounting: In this last phase, the component is not needed and get unmounted from the browser DOM. This phase includes componentWillUnmount lifecycle method.

So UseEffect cover 3 life cycles:

  • componentDidMount: Executed after first rendering and here all AJAX requests, DOM or state updates, and set up event Listeners should occur.
  • componentDidUpdate: Mostly it is used to update the DOM in response to prop or state changes.
  • componentWillUnmount: It will be used to cancel any outgoing network requests, or remove all event listeners associated with the component.

There are two common kinds of side effects in React components: those that don’t require cleanup, and those that do. Let’s look at this distinction in more detail.

I advise the readers for courses with online degrees from European universities, many of them are free.

Effects Without Cleanup

Sometimes, we want to run some additional code after React has updated the DOM. Network requests, manual DOM mutations, and logging are common examples of effects that don’t require a cleanup. We say that because we can run them and immediately forget about them. Let’s compare how classes and Hooks let us express such side effects.

Example Using Classes

In React class components, the render method itself shouldn’t cause side effects. It would be too early — we typically want to perform our effects after React has updated the DOM.

This is why in React classes, we put side effects into componentDidMount and componentDidUpdate.

componentDidMount: Executed after first rendering.

componentDidUpdate: Mostly it is used to update the DOM in response to prop or state changes.

Coming back to our example, here is a React counter class component that updates the document title right after React makes changes to the DOM:

Note how we have to duplicate the code between these two lifecycle methods in a class.

This is because in many cases we want to perform the same side effect regardless of whether the component just mounted, or if it has been updated. Conceptually, we want it to happen after every render — but React class components don’t have a method like this. We could extract a separate method but we would still have to call it in two places.

Now let’s see how we can do the same with the useEffect Hook.

Example Using Hooks

What does useEffect do? By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates. In this effect, we set the document title, but we could also perform data fetching or call some other imperative API.

Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.

Tip:

Experienced JavaScript developers might notice that the function passed to useEffect is going to be different on every render. This is intentional. In fact, this is what lets us read the count value from inside the effect without worrying about it getting stale. Every time we re-render, we schedule a different effect, replacing the previous one. In a way, this makes the effects behave more like a part of the render result — each effect “belongs” to a particular render. We will see more clearly why this is useful later.

Effects with Cleanup

Earlier, we looked at how to express side effects that don’t require any cleanup. However, some effects do. For example, we might want to set up a subscription to some external data source. In that case, it is important to clean up so that we don’t introduce a memory leak! Let’s compare how we can do it with classes and with Hooks.

Example Using Classes:

In a React class, you would typically set up a subscription in componentDidMount, and clean it up in componentWillUnmount. For example, let’s say we have a ChatAPI module that lets us subscribe to a friend’s online status. Here’s how we might subscribe and display that status using a class:

Notice how componentDidMount and componentWillUnmount need to mirror each other. Lifecycle methods force us to split this logic even though conceptually code in both of them is related to the same effect.

Example Using Hooks

Let’s see how we could write this component with Hooks.

You might be thinking that we’d need a separate effect to perform the cleanup. But code for adding and removing a subscription is so tightly related that useEffect is designed to keep it together. If your effect returns a function, React will run it when it is time to clean up:

Why did we return a function from our effect? This is the optional cleanup mechanism for effects. Every effect may return a function that cleans up after it. This lets us keep the logic for adding and removing subscriptions close to each other. They’re part of the same effect!

When exactly does React clean up an effect? React performs the cleanup when the component unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time.

Note

We don’t have to return a named function from the effect. We called it cleanup here to clarify its purpose, but you could return an arrow function or call it something different.

If you want to Dive into React 18 with practical examples, I advise you with the following course.

If you want to Dive into Best Practices Patterns in React. I advise you with the following Course.

Tips for using useEffect Hook

Use Multiple Effects to Separate Concerns

Just like you can use the State Hook more than once, you can also use several effects. This lets us separate unrelated logic into different effects, and related logic into one effect:

Hooks let us split the code based on what it is doing rather than a lifecycle method name. React will apply every effect used by the component, in the order they were specified.

Optimizing Performance by Skipping Effects

In some cases, cleaning up or applying the effect after every render might create a performance problem. In class components, we can solve this by writing an extra comparison with prevProps or prevState inside componentDidUpdate:

componentDidUpdate(prevProps, prevState) { if (prevState.count !== this.state.count) {   document.title = `You clicked ${this.state.count} times`; }
}

This requirement is common enough that it is built into the useEffect Hook API. You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect:

useEffect(() => {document.title = `You clicked ${count} times`;}, [count]); // Only re-run the effect if count changes

In the example above, we pass [count] as the second argument. What does this mean? If the count is 5, and then our component re-renders with count still equal to 5, React will compare [5] from the previous render and [5] from the next render. Because all items in the array are the same (5 === 5), React would skip the effect. That’s our optimization.

When we render with count updated to 6, React will compare the items in the [5] array from the previous render to items in the [6] array from the next render. This time, React will re-apply the effect because 5 !== 6. If there are multiple items in the array, React will re-run the effect even if just one of them is different.

In the future, the second argument might get added automatically by a build-time transformation.

VIP NOTE:

If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.

In React 18: There is tricky behavior of useEffect hook:

Rules of Hooks

1-Only Call Hooks at the Top Level

You should not call React Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders.

Example:

So how does React know which state corresponds to which useState call? The answer is that React relies on the order in which Hooks are called. Our example works because the order of the Hook calls is the same on every render:

// ------------// First render// ------------useState('Mary')           // 1. Initialize the name state variable with 'Mary'useEffect(persistForm)     // 2. Add an effect for persisting the formuseState('Poppins')        // 3. Initialize the surname state variable with 'Poppins'useEffect(updateTitle)     // 4. Add an effect for updating the title// -------------// Second render// -------------useState('Mary')           // 1. Read the name state variable (argument is ignored)useEffect(persistForm)     // 2. Replace the effect for persisting the formuseState('Poppins')        // 3. Read the surname state variable (argument is ignored)useEffect(updateTitle)     // 4. Replace the effect for updating the title

As long as the order of the Hook calls is the same between renders, React can associate some local state with each of them. But what happens if we put a Hook call (for example, the persistForm effect) inside a condition?

// We’re breaking the first rule by using a Hook in a conditionif (name !== ‘’) {useEffect(function persistForm() {localStorage.setItem(‘formData’, name);});}

The name !== ‘’ condition is true on the first render, so we run this Hook. However, on the next render the user might clear the form, making the condition false. Now that we skip this Hook during rendering, the order of the Hook calls becomes different:

useState(‘Mary’) // 1. Read the name state variable (argument is ignored)// useEffect(persistForm) // This Hook was skipped!useState(‘Poppins’) // 2 (but was 3). Fail to read the surname state variableuseEffect(updateTitle) // 3 (but was 4). Fail to replace the effect

React wouldn’t know what to return for the second useState Hook call. React expected that the second Hook call in this component corresponds to the persistForm effect, just like during the previous render, but it doesn’t anymore. From that point, every next Hook call after the one we skipped would also shift by one, leading to bugs.

If we want to run an effect conditionally, we can put that condition inside our Hook.

The first rule was particularly interesting to me. It seemed easy enough to follow, but why did the rule exist?

why we should follow the order?

If Hooks aren’t called in the same order each time a component is rendered, you could have cascading failures where you miss one Hook and none of the subsequent ones are recognized properly. The Hook calls could end up shifted and you’ll get unexpected bugs.

Well, that seems pretty bad. Let’s definitely not allow that to happen. So we now know why the rule shouldn’t be broken, but not why things break. I still really wanted to understand why call order was so important to Hooks.

I noticed that hooks are actually using linked lists under the hood. It’s always cool to run across an implementation or use case of a data structure.

The linked list is a data structure that consists of nodes. Each node has its data and also a reference that points to the next node in the list. The order of nodes is entirely dependent on each node’s reference to the next. In Javascript this is can look something like this:

{
value: 1,
next: {
value: 2,
next: {
value: 3,
next: {
value: 4,
next: null
}
}
}
}

Read More About Linked List.

This is why we need to make sure Hooks are called in the same order on every render. If one of the Hooks is wrapped in a conditional instead of being called at the top level you could end up with a different number of Hooks on re-render. That means you would end up with a node missing on your linked list of Hooks. If a node is missing, the previous node won’t be referencing the correct thing and the subsequent nodes will be out of order.

2-Only Call Hooks from React Functions

Don’t call Hooks from regular JavaScript functions. Instead, you can:

· Call Hooks from React function components.

· Call Hooks from custom Hooks

Conclusion:

React Hooks are great. They allow for using function components in ways that weren’t previously possible, and the community is clearly moving in the direction of using function components and hooks when possible. In the next article, I will explain Hooks for Redux, the Main differences between classes and hooks, and why we should move in the direction of using function components and hooks.

I hope I added value, If you enjoy reading the article and want to support me as a writer, you can buy me a coffee!

If you want to Dive in and Learn Reactjs, Hooks, Redux, and React Routing from scratch!, I advise you with the following Course.

The Next Article (second part) about React Redux Hooks:

The Next Article (third part) about Building Our Own React Custom Hook:

React 18:

--

--

kirill ibrahim
The Startup

I am a Web Front-End Engineer with 8 years of commercial experience working in various web-related roles. https://kirillibrahim.work/ My Twitter: @IbraKirill