Categories
React

How to Use the useState Hook with Objects?

In JavaScript programs, we often have to deal with object values.

Therefore, we’ll often have to create object states and update them.

In this article, we’ll look at how to update object states in our React components.

Merging Objects

The main way to update object states with new property values is to merge the old object with new properties.

For instance, we can write:

import { useState } from "react";

export default function App() {
  const [name, setName] = useState({
    firstName: "",
    middleName: "",
    lastName: ""
  });

  return (
    <div className="App">
      <input
        value={name.firstName}
        type="text"
        onChange={(e) =>
          setName((name) => ({ ...name, firstName: e.target.value }))
        }
        name="firstName"
        placeholder="first name"
      />
      <br />
      <input
        value={name.middleName}
        type="text"
        onChange={(e) =>
          setName((name) => ({ ...name, middleName: e.target.value }))
        }
        name="middleName"
        placeholder="middle name"
      />
      <br />
      <input
        value={name.lastName}
        type="text"
        onChange={(e) =>
          setName((name) => ({ ...name, lastName: e.target.value }))
        }
        name="lastName"
        placeholder="last name"
      />
    </div>
  );
}

We have the name state which is set to an object as its initial value.

Below that, we added 3 input elements.

Each of them have the onChange prop set to a function that calls setName .

We pass a callback into the setName function that takes the name parameter, which has the current value of the name state.

And it returns an object that copies the name ‘s properties with the spread operator.

And then we set add the property that we want to change at the end of it.

It has to be at the end so that the new property overwrites the property value that already exists in the original state.

e.target.value has the inputted value.

We can also reduce repetition by creating a function that returns a function that changes the property we want.

To do this, we write:

import { useState } from "react";

export default function App() {
  const [name, setName] = useState({
    firstName: "",
    middleName: "",
    lastName: ""
  });

  const handleChange = (field) => {
    return (e) => setName((name) => ({ ...name, [field]: e.target.value }));
  };

  return (
    <div className="App">
      <input
        value={name.firstName}
        type="text"
        onChange={handleChange("firstName")}
        name="firstName"
        placeholder="first name"
      />
      <br />
      <input
        value={name.middleName}
        type="text"
        onChange={handleChange("middleName")}
        name="middleName"
        placeholder="middle name"
      />
      <br />
      <input
        value={name.lastName}
        type="text"
        onChange={handleChange("lastName")}
        name="lastName"
        placeholder="last name"
      />
    </div>
  );
}

We have the handleChange function that returns our state change functions.

It takes the field parameter, which we use as the property name by passing it into the brackets in the returned object in the setName callback.

Conclusion

We can use the useState hook with objects by passing in a callback to our state change functions that returns the copy of the existing state object and set the property to the value we want after that.

Categories
React

How to Reduce the Number of Re-renders When Updating States?

Whenever a state is updated in a React component, a re-rendering is done.

This means when we update many states, then there will be lots of re-rendering done.

In this article, we’ll look at how to reduce the number of re-renders in our React components.

How to Reduce the Number of Re-renderings?

We can reduce the number of re-renderings done by reducing the number of states that are updated.

For instance, we can update one object instead state instead of multiple states.

To do this, we write:

import { useEffect, useState } from "react";

export default function App() {
  const [request, setRequest] = useState({
    loading: false,
    data: undefined
  });

  const getData = async () => {
    setRequest((request) => ({ ...request, loading: true }));
    const res = await fetch("https://yesno.wtf/api/");
    const data = await res.json();
    setRequest((request) => ({ ...request, data }));
  };

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

  useEffect(() => {
    if (request.data) {
      setRequest((request) => ({ ...request, loading: false }));
    }
  }, [request]);

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

We have the request state which has the loading and data properties.

In the getData function, we call setRequest to update the request state object’s loading property by passing in a callback and returning a new object that’s a copy of request , but loading is set to true .

At the end of the function, we call setRequest again with the new value of data .

Also, we have a useEffect callback that checks is request.data is set and call setRequest with a callback that returns a copy of the request object with loading set to false .

Another way to do this is to create our own hook to merge different state object properties together.

For instance, we can write:

import { useEffect, useState } from "react";

const useMergeState = (initialState) => {
  const [state, setState] = useState(initialState);
  const setMergedState = (newState) =>
    setState((prevState) => ({ ...prevState, newState }));
  return [state, setMergedState];
};

export default function App() {
  const [request, setRequest] = useMergeState({
    loading: false,
    data: undefined
  });

  const getData = async () => {
    setRequest({ loading: true });
    const res = await fetch("https://yesno.wtf/api/");
    const data = await res.json();
    setRequest({ data });
  };

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

  useEffect(() => {
    if (request.data) {
      setRequest({ loading: false });
    }
  }, [request, setRequest]);

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

We created the useMergeState hook which has the setMergedState function that calls the setState function to merge newState into prevState with the callback in setState .

Then we call useMergeState in App to return our state and state setter function.

Now instead of passing in a callback to the state setter function to merge the state, we just pass in the properties that we want to change to setRequest to change the property.

In either example, we update what we need in our object all at once instead of updating multiple primitive values.

Conclusion

We can reduce the number of re-renders to components with React and JavaScript by putting states into objects and updating them all at once.

Categories
React

How to Fix a State That is Not Updating When Using React State Hook in setInterval

The setInterval function lets us run a function periodically in our JavaScript code by creating a timer.

We may also use setInterval in our React apps.

Sometimes, we may find that our React component states aren’t updating when we have state updating code in our setInterval callbacks.

In this article, we’ll look at how to make sure that our states are getting updated when we run state updating code in our setInterval callback.

Why is the State Not Updating in the setInterval?

If we have code like the following:

import { useEffect, useState } from "react";

export default function App() {
  const [time, setTime] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setTime(time + 1);
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);

  return <div className="App">{time}</div>;
}

Then we may see the value of time isn’t updating every second.

Instead, it only updates once when the component first mounts.

This is because we passed in an empty array as the value of the 2nd argument in the useEffect hook.

This means the useEffect callback runs when the component mounts.

And since time is 0, time + 1 is 1.

The useEffect callback doesn’t have access to the latest time value since it never runs again after it mounts.

Therefore, we can’t just pass in time + 1 to update the value of time .

To fix this, we need to pass in a callback to setTime with the current time value as the parameter and returns the new time value we want.

To do this, we write:

import { useEffect, useState } from "react";

export default function App() {
  const [time, setTime] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setTime((time) => time + 1);
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);

  return <div className="App">{time}</div>;
}

We pass in a callback to setTime .

The callback takes the current time value as the parameter.

And it returns time + 1 so that time is updated to time + 1 .

Now we should see the time display updated every second as we expect.

Conclusion

To fix a state that’s not updating in the setInterval callback, we should pass in a callback to the state setter function to update the state.

This is because the useEffect callback has to run in order to have access to the latest state values.

Categories
React

How to Push an Element Inside an Array State with React Hook?

With React hooks, it might not be immediately obvious how we can push items into an array state.

In this article, we’ll look at how to push an item into an array if we have an array state with React hooks.

Use the State Setter Function

The state setter function returned by useState lets us pass in a callback that takes the previous value of a state and returns a new one.

Therefore, we can just return a copy of the array and then append the entry we want to push to the end of it.

For instance, we can write:

import { useState } from "react";

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

  return (
    <div className="App">
      <button
        onClick={() => setArr((arr) => [...arr, arr[arr.length - 1] + 1])}
      >
        push
      </button>
      <p>{JSON.stringify(arr)}</p>
    </div>
  );
}

We create the arr state with the useState hook.

The setArr function lets us set the new value of the arr state.

We pass in the initial value into the useState hook.

In the button, we have the onClick prop set to a function that calls setArr with a callback.

The arr parameter has the existing value of the arr state.

And we return a copy of the arr state with a value at the end.

Then below that, we show the stringified version of the arr value.

Therefore, when we click on the push button, we see new numbers being added to the arr array.

This is the most reliable way to push an element into a state array.

We can also just update the array state directly without a callback in some situations.

If we’re adding a click handler we do before, we can also just update the array directly with a new array.

For instance, we can write:

import { useState } from "react";

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

  return (
    <div className="App">
      <button onClick={() => setArr([...arr, arr[arr.length - 1] + 1])}>
        push
      </button>
      <p>{JSON.stringify(arr)}</p>
    </div>
  );
}

We pass a copy of the arr array with the new value at the end directly into the setArr function.

This works for some situations like click handlers.

However, it may not work with mouse move event handlers.

Also, we can write:

import { useState } from "react";

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

  return (
    <div className="App">
      <button onClick={() => setArr(arr.concat(arr[arr.length - 1] + 1))}>
        push
      </button>
      <p>{JSON.stringify(arr)}</p>
    </div>
  );
}

The concat function also returns a new copy of the arr array with the item we want to append at the end.

Conclusion

There are various ways we can push an array item into an array state with state setter functions.

Categories
React

What’s the React Hooks Equivalent of the setState Callback?

If we use React hooks to create components, we can no longer use setState to set our state.

Instead, we’ve to use the state setter function returned from the useState hook.

However, the setState method takes a second argument that lets us run code after a state is set.

There’s no way to do this directly with state setter functions.

However, we can fix this by writing their hooks equivalent.

In this article, we’ll look at the React Hooks equivalent of the setState callback.

React Hooks Equivalent of the setState Callback

The React hooks equivalent of the setState callback is the useEffect hook.

The useEffect hook lets us watch the change of a state.

To watch a state’s value, we can write:

import { useEffect, useState } from "react";

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

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

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

We create the count state that stores a number.

Then we add the useEffect hook with a callback that runs when the count state changes.

It runs when count state changes because count is passed into an array in the 2nd argument of useEffect .

The count state is updated when we click on the increment button.

This is because we passed a function into the onClick hook.

The function calls setCount with a callback that returns the current value of count increased by 1.

Therefore, when we click on the increment button, the console.log in the useEffect callback will run.

Conclusion

We can create a React hooks equivalent of the setState callback by using the useEffect hook with an array of the states we want to watch in the 2nd argument.