DEV Community

CarolinaCobo
CarolinaCobo

Posted on

React Hooks 1: useState, useEffect, useContext, useRef & useReducer

I have been working in React for about nine months, and after finishing Brian’s Holt Complete Intro to React and Intermediate React and digging into the React Docs, I thought I would put together a summary of React Hooks and also my personal experience using some of them.

In this first part, I’ll cover useState, useEffect, useContext, useRef and useReducer, being the two first ones the ones use mostly at the moment.

Reusing state between components in React is hard. With Hooks, it’s possible to extract stateful logic from a component, this makes it possible to be tested independently and reused.

In conclusion, Hooks allow you to reuse stateful logic without changing your component hierarchy.

I’m going to list them all but first, I’ll go into more detail about the ones I use every day at work, useState and useEffect.

useState

useState allows you to make components stateful. In older React to do so, it required using a class component. Hooks give the ability to write it using just functions.

This allows us to have more flexible components. In the example component below, every time the user clicks on the h1, it’ll change colours. It’s doing this by keeping that bit of state in a hook which is being passed that new value every render, so it always has and displays the latest state.

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

useEffect

Compared to old React, Effects are how you recreate componentDidMount, componentDidUpdate, and componentDidUnmount.

Inside useEffect, you can do any sort of side effect action that you would have previously done in one of React's lifecycle methods. With it, you can fire API requests, integrate with third-party libraries (like jQuery), or do anything else that you need to happen on the side for your component.

A good example I’m going to use is a clock. We want our component to continually update to show the time so we use setTimeout inside our effect. After the timeout calls the callback, it updates the state. After that render happens, it schedules another effect to happen, and that’s why it continues to update.

You could also provide a second parameter of [] to useEffect (after the function) which would make it only update once. This second array is a list of dependencies and it will only re-run this effect if one of these parameters changes. In our case, we want to run after every render so we don't give it this second parameter.

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

  useEffect(() => {
    setTimeout(() => {
      setCount((count) => count + 1);
    }, 1000);
  });

  return <h1>I've rendered {count} times!</h1>;
}
Enter fullscreen mode Exit fullscreen mode

useContext

A problem you might encounter while building a React app is thee called “prop drilling”. This will happen when you have a parent component and a child component way down let’s say level four which also needs that data available.

We could pass the data to the children in levels one, two and three but that will mean that they will know about this data even when it’s not relevant to them as the one that needs it is a child of theirs. This would be prop drilling, having unnecessary intermediaries.

There is when context comes in, it would create a passage for the data from the parent component to the child component, so the components in the middle don’t know about it.

Also, you don’t need to use useState and useContext together (data can have different shapes, not just useState) but you might find it convenient when child components need to also update the context as well.

Often using Context adds complexity to an application, and also a bit of drilling is usually fine. Try to only put things that are truly application-wide state in context, such as user information or keys and then use the local state for the rest.

As mentioned above those two are the ones that I use pretty much every day at work. I use useEffect to get all the data I need from the APIs and display and then useEffect to work on what should render depending on the state or let the user modify the state through inputs.

useRef

Refs aver are useful for different things, but before you understand why it’s needed to understand how closure works.useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

A common use case is to access a child imperatively:

function App() {
  const [inputValue, setInputValue] = useState("");
  const count = useRef(0);

  useEffect(() => {
    count.current = count.current + 1;
  });

  return (
    <>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <h1>Render Count: {count.current}</h1>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

You can think of useRef as a “box” that can hold a mutable value in its .current property.

You probably have seen refs used primarily as a way to access the DOM. If you pass a ref object to React with

, its.current property will be set to the corresponding DOM node whenever that node changes.

But this hook is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.

This works because useRef() creates a plain JavaScript object. The only difference between useRef() creating an {current: ...} object yourself is that useRef will give you the same ref object on every render.

Also useRef won’t notify when its content has changed. Mutating the .current property doesn’t cause a re-render.

I recently had to use it in a Popup component, that would be rendered when clicking a button, would close if clicking out of it and had edit and delete options linked to the ID of the selected element.

useReducer

useReducer is an alternative to useState. It accepts a reducer of type (state, action) => newStateand returns the current state paired with a dispatch method.

It’s preferable touseState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. It also allows you to optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Thanks for reading! As I mentioned above this is a summary for reference after completing Brian Holt courses and going over the official React Docs, so if you want to extend the information here I’d advise you to go check them out!

Thanks for reading, I appreciate your time. If you need any help please reach out!

If you have any questions feel free to drop me a message on LinkedIn or send me an email. 😊

Have a nice day!

Top comments (0)