Categories
React

How to Trigger a Component to Re-render with Hooks in React?

Sometimes, we may want to trigger a component to re-render in a component created with React hooks.

In this article, we’ll look at ways we can trigger a component to re-render in a React component created with hooks.

Update a Prop or State

A component will re-render if a prop or state value changes.

Therefore, we can just trigger a re-rendering of a component when we update a prop or state value.

To update a state, we call a state setter function.

For instance, we can write:

import React, { useState } from "react";

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

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

to add a button that calls setCount to update the count state.

We know the App component re-rendered since the latest value of count is displayed below it when we click the increment button.

Likewise, we can update a prop value to trigger a re-render of a component.

For instance, we can write:

import React, { useState } from "react";

const Count = ({ count }) => <p>{count}</p>;

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

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

We have the Count component that takes the count prop and renders the count value.

To make the Count component re-render, we update the value of the count prop by updating the value of the count state in App .

This part is the same as the previous example.

And we know Count re-rendered since we see the latest value of count rendered again.

Forcing a Re-rendering of a React Component

Sometimes, we may want to force a re-rendering of a React component when something external to a component is updated.

To do this, we can create our own hook.

For instance, we can write:

import React, { useCallback, useState } from "react";

let count = 0;

setInterval(() => {
  count++;
}, 1000);

const useForceUpdate = () => {
  const [, setTick] = useState(0);
  const update = useCallback(() => {
    setTick((tick) => tick + 1);
  }, []);
  return update;
};

export default function App() {
  const update = useForceUpdate();

  return (
    <div className="App">
      <button onClick={() => update()}>update</button>
      <p>{count}</p>
    </div>
  );
}

We have a setInterval callback that sets the count variable’s value.

And we create the useForceUpdate hook that updates a state with the setTick function.

setTick is called in the update function which we return so we can use it in our component code.

In App , we call useForceUpdate to and assigned the returned value to the update variable.

Then in the button we call update when we click on it.

Therefore, when we click on it, we see the latest value of count displayed.

Conclusion

There are several ways to trigger a re-rendering of a React component with React hooks.

Categories
React

What’s the Difference Between useRef and createRef in a React Component?

In React components, we can use the useRef hook or createRef function to create a ref.

A ref is a value that doesn’t trigger a re-render of the component when it’s changed as opposed to states and props.

In this article, we’ll look at the difference between the useRef hook and the createRef function in React components.

Difference Between useRef and createRef

The difference between the useRef hook and the createRef function is that the useRef hook holds its value between re-renders in a function component.

The existing ref persists between re-renders.

createRef is used to create a ref and a new ref is created during each render.

For instance, if we have:

import React, { createRef, useEffect, useState } from "react";

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

  useEffect(() => {
    ref.current = "foo";
  }, []);

  useEffect(() => {
    console.log(count, ref.current);
  }, [count]);

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

We create a ref with createRef .

And we only set ref.current on initial render.

And to trigger a re-render, we update the count state when we click on the button.

We can see from the console log in the useEffect callback that ref.current logs 'foo' on the first render.

But then on subsequent renders, ref.current is null .

On the other hand, if we use the useRef hook by writing:

import React, { useEffect, useRef, useState } from "react";

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

  useEffect(() => {
    ref.current = "foo";
  }, []);

  useEffect(() => {
    console.log(count, ref.current);
  }, [count]);

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

We call the useRef hook to create a ref.

And we set ref.current as we did in the previous example.

The rest of the code is also the same.

The difference is that we see ref.current is 'foo' after each render.

This means in function components, we can use the useRef hook to create a value that doesn’t trigger a re-render when it’s changed, but we can use it to keep values between re-renders.

Conclusion

The difference between createRef and useRef is that createRef creates a new ref on every render in function components.

On the other hand, a ref created with useRef keeps the same value after each render in a function component.

Categories
React

How to Push or Append an Element to a State Array with React Hooks?

On many occasions, we may have states that are arrays.

We may want to push items to the end of the array.

The way to do it may not be immediately obvious to us.

In this article, we’ll look at how we can push or append an element to a state array with React hooks.

Pass in a Callback that Returns a New Array to the State Setter Function

To update the state array with a new element at the end of it, we can pass in a callback that returns a new array with the new item added to the end of it.

To do this, we write:

import React, { useState } from "react";

export default function App() {
  const [arr, setArr] = useState(["foo"]);
  return (
    <div className="App">
      <button onClick={() => setArr((oldArray) => [...oldArray, "foo"])}>
        add
      </button>
      <div>
        {arr.map((a, i) => (
          <p key={i}>{a}</p>
        ))}
      </div>
    </div>
  );
}

We have the setArr state setter function defined with the useState hook.

The initial value of the arr state is an array as we have in the argument of useState .

We have the onClick handler that calls the setArr function with a callback that takes the oldArray parameter, which is the old value of the arr state.

And we return an array with the existing items spread from oldArray and the new item at the end of the array.

In some situations like adding items on click, we can also pass in the new array value directly to the state setter function.

So we can also write:

import React, { useState } from "react";

export default function App() {
  const [arr, setArr] = useState(["foo"]);

  return (
    <div className="App">
      <button onClick={() => setArr([...arr, "foo"])}>add</button>
      <div>
        {arr.map((a, i) => (
          <p key={i}>{a}</p>
        ))}
      </div>
    </div>
  );
}

When we want to add a new array entry on click.

We can also use the concat method to return an array with the new value at the end of it.

For example, we can write:

import React, { useState } from "react";

export default function App() {
  const [arr, setArr] = useState(["foo"]);

  return (
    <div className="App">
      <button onClick={() => setArr((oldArray) => oldArray.concat("foo"))}>
        add
      </button>
      <div>
        {arr.map((a, i) => (
          <p key={i}>{a}</p>
        ))}
      </div>
    </div>
  );
}

We call concat on the oldArray with the argument of it being the new item we want to add.

And so we get the same result as before.

Conclusion

To update a React component state array with a new item at the end of it, we can pass in a callback to the state setter function that takes the old array value and return the new array value.

Categories
React

How to Use the setState Callback with React Hooks?

When we’re using class-based components, we can pass in a callback as the 2nd argument of the setState method to run code after a state has been updated.

Since we moved to using hooks to create components, we’ve to use the useState hook to create and update states.

The state setter function doesn’t take a callback as a second argument to let us run code after a state is updated.

Therefore, we must find a new way to run code after a state is updated.

In this article, we’ll look at how to run code after a state has been updated.

Use the useEffect Hook

To run code after a state is updated, we can use the useEffect hook to watch the value of a state and run code accordingly.

For instance, we can write:

import React, { useEffect, useState } from "react";

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

  useEffect(() => {
    console.log(count);
  }, [count]);

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

to watch the value of the count state with the useEffect hook.

We pass in count to the array in the 2nd argument to watch its value.

Now when we click on the increment button to update the count , the useEffect callback will run.

The same thing works for props since they also trigger a re-render of the component.

Conclusion

To run code after a state is changed, we can use the useEffect hook with an array of state or props we want to watch for changes for.

Then the useEffect callback will run when any of the values in the array changes.

Categories
React

How to Fix the React ‘useEffect function must return a cleanup function or nothing’ Warning Message?

If we have async functions in our useEffect hook, we may get the ‘useEffect function must return a cleanup function or nothing’ warning in our console.

In this article, we’ll look at how to fix this warning.

Moving Async Functions Outside the useEffect Hook

To fix this warning, we shouldn’t use async functions as the callback in the first argument.

useEffect expects a synchronous function in the first argument.

Therefore, instead of writing:

import React, { useEffect, useState } from "react";

export default function App() {
  const [posts, setPosts] = useState({});

  useEffect(async () => {
    try {
      const response = await fetch(`https://www.reddit.com/r/react.json`);
      const json = await response.json();
      setPosts(json.data.children.map((it) => it.data));
    } catch (e) {
      console.error(e);
    }
  }, []);

  return (
    <div className="App">
      <div>{JSON.stringify(posts)}</div>
    </div>
  );
}

We write:

import React, { useEffect, useState } from "react";

export default function App() {
  const [posts, setPosts] = useState({});

const getPosts = async () => {
    try {
      const response = await fetch(`https://www.reddit.com/r/react.json`);
      const json = await response.json();
      setPosts(json.data.children.map((it) => it.data));
    } catch (e) {
      console.error(e);
    }
  };

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

  return (
    <div className="App">
      <div>{JSON.stringify(posts)}</div>
    </div>
  );
}

Instead of using the async function as the callback of the useEffect hook, we create a new getPosts function with our promise code.

We can also have async functions defined inside the useEffect callback.

So we can also write:

import React, { useEffect, useState } from "react";

export default function App() {
  const [posts, setPosts] = useState({});

  useEffect(() => {
    const getPosts = async () => {
      try {
        const response = await fetch(`https://www.reddit.com/r/react.json`);
        const json = await response.json();
        setPosts(json.data.children.map((it) => it.data));
      } catch (e) {
        console.error(e);
      }
    };

    getPosts();
  }, []);

  return (
    <div className="App">
      <div>{JSON.stringify(posts)}</div>
    </div>
  );
}

If we use the function only once, we can keep it inside the useEffect callback.

Conclusion

We can fix the ‘useEffect function must return a cleanup function or nothing’ easily by defining an async function separately and using it instead of passing in an async function as a callback of the useEffect hook.