DEV Community

Abdulrahman Ismail
Abdulrahman Ismail

Posted on

useState hook

*useState * is a React Hook that lets you add a state variable to your component.

State: A Component's Memory

Components often need to change what’s on the screen as a result of an interaction. Typing into the form should update the input field, clicking “next” on an image carousel should change which image is displayed, clicking “buy” should put a product in the shopping cart. Components need to “remember” things: the current input value, the current image, the shopping cart. In React, this kind of component-specific memory is called state.

This is the form:

const [state, setState] = useState(initialState)

the useState hook return two values the initial value which we can use it and a in that case (state),which It can be a value of any type, but there is a special behavior for functions. This argument is ignored ** after the initial render(it mean it only render at the very first time). and setter function to modify the initial state
as you see we are using array **destructuring
.

If you pass a function as initialState, it will be treated as an initializer function. It should be pure, should take no arguments, and should return a value of any type. React will call your initializer function when initializing the component, and store its return value as the initial state

this is an example:

import { useState } from 'react';

function createInitialTodos() {
  const initialTodos = [];
  for (let i = 0; i < 50; i++) {
    initialTodos.push({
      id: i,
      text: 'Item ' + (i + 1)
    });
  }
  return initialTodos;
}

export default function TodoList() {
  const [todos, setTodos] = useState(createInitialTodos);
  const [text, setText] = useState('');

  return (
    <>
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button onClick={() => {
        setText('');
        setTodos([{
          id: todos.length,
          text: text
        }, ...todos]);
      }}>Add</button>
      <ul>
        {todos.map(item => (
          <li key={item.id}>
            {item.text}
          </li>
        ))}
      </ul>
    </>
  );
}

Enter fullscreen mode Exit fullscreen mode

This example passes the initializer function, so the createInitialTodos function only runs during initialization. It does not run when component re-renders, such as when you type into the input.

about the setter function of a useState:
You can pass the next state directly, or a function that calculates it from the previous state.
like:

import { useState } from "react";
import "./App.css";

function App() {
  const [counter, setCounter] = useState(0);
  const handleIncremeant = () => {
    setCounter(counter + 1);
  };

  return (
    <div className="App">
      <header className="App-header">
        <button onClick={handleIncremeant}>+1</button>
        <p>{counter}</p>
      </header>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

or

import { useState } from "react";
import "./App.css";

function App() {
  const [counter, setCounter] = useState(0);
  const handleIncremeant = () => {
    setCounter((prevCounter) => prevCounter + 1);
  };

  return (
    <div className="App">
      <header className="App-header">
        <button onClick={handleIncremeant}>+1</button>
        <p>{counter}</p>
      </header>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

but what is the difference you may ask?🤔

well the different is If you pass a function as nextState, it will be treated as an updater function. It must be pure, should take the pending state as its only argument, and should return the next state. React will put your updater function in a queue and re-render your component. During the next render, React will calculate the next state by applying all of the queued updaters to the previous state. but what is that mean?

lets take you to the practical learning:
if we modify our code to this :

import { useState } from "react";
import "./App.css";

function App() {
  const [counter, setCounter] = useState(0);
  const handleIncremeant = () => {
**    setCounter(counter + 1);
    setCounter(counter + 1);
    setCounter(counter + 1);**
  };

  return (
    <div className="App">
      <header className="App-header">
        <button onClick={handleIncremeant}>+1</button>
        <p>{counter}</p>
      </header>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

what is the output of the counter? 1 or 3? and why?

after one click, +1 will only be 1 rather than 3! This is because calling the set function does not update the counter state variable in the already running code. So each setCounter(counter + 1) call becomes setCounter(1).

To solve this problem, you may pass an updater function to setCounter instead of the next state:

  const handleIncremeant = () => {
    setCounter((prevCounter) => prevCounter + 1);
    setCounter((prevCounter) => prevCounter + 1);
    setCounter((prevCounter) => prevCounter + 1);
  };
Enter fullscreen mode Exit fullscreen mode

in that modification:
prevCounter => prevCounter + 1 will receive 0 as the pending state and return 1 as the next state.
prevCounter => prevCounter + 1 will receive 1 as the pending state and return 2 as the next state.
prevCounter => prevCounter + 1 will receive 2 as the pending state and return 3 as the next state.

Call *useState * at the TOP level of your component

import { useState } from "react";
import "./App.css";

function App() {
//TOP LEVEL 
**  const [counter, setCounter] = useState(0);**

  return (
    <div className="App">
      <header className="App-header">
        <p>{counter}</p>
      </header>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

if you don't call your state veriable at the top level of your components it'll give you an error like:

import { useState } from "react";
import "./App.css";

function App() {
  const [counter, setCounter] = useState(0);
  if (counter) {
    counter.forEach((element) => {
      const [people, setPeople] = useState(counter);
    });
  }
  return (
    <div className="App">
      <header className="App-header">
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
      </header>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

in that case we get this error

 React Hook "useState" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function 
Enter fullscreen mode Exit fullscreen mode

we can't use it inside of a condition or loop, if in any case you need that extract a new component and move the state into it.

Updating objects and arrays in state

In React, state is considered read-only, so you should replace it rather than mutate your existing objects. For example, if you have a form object in state, don’t mutate it

// 🚩 Don't mutate an object in state like this:
form.firstName = 'Taylor';
Enter fullscreen mode Exit fullscreen mode

Instead, replace the whole object by creating a new one:

// ✅ Replace state with a new object
setForm({
  ...form,
  firstName: 'Taylor'
});
Enter fullscreen mode Exit fullscreen mode

In this example, the form state variable holds an object. Each input has a change handler that calls setForm with the next state of the entire form. The { ...form } spread syntax ensures that the state object is replaced rather than mutated.

import { useState } from 'react';

export default function Form() {
  const [form, setForm] = useState({
    firstName: 'Barbara',
    lastName: 'Hepworth',
    email: 'bhepworth@sculpture.com',
  });

  return (
    <>
      <label>
        First name:
        <input
          value={form.firstName}
          onChange={e => {
            setForm({
              ...form,
              firstName: e.target.value
            });
          }}
        />
      </label>
      <label>
        Last name:
        <input
          value={form.lastName}
          onChange={e => {
            setForm({
              ...form,
              lastName: e.target.value
            });
          }}
        />
      </label>
      <label>
        Email:
        <input
          value={form.email}
          onChange={e => {
            setForm({
              ...form,
              email: e.target.value
            });
          }}
        />
      </label>
      <p>
        {form.firstName}{' '}
        {form.lastName}{' '}
        ({form.email})
      </p>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Resetting state with a key

You’ll often encounter the key attribute when rendering lists. However, it also serves another purpose.

You can reset a component’s state by passing a different key to a component. In this example, the Reset button changes the version state variable, which we pass as a key to the Form. When the key changes, React re-creates the Form component (and all of its children) from scratch, so its state gets reset.

Read preserving and resetting state to learn more.(this link is very important to learn where state live and how state works so definitely you need to check it out!)

import { useState } from 'react';

export default function App() {
  const [version, setVersion] = useState(0);

  function handleReset() {
    setVersion(version + 1);
  }

  return (
    <>
      <button onClick={handleReset}>Reset</button>
      <Form key={version} />
    </>
  );
}

function Form() {
  const [name, setName] = useState('Taylor');

  return (
    <>
      <input
        value={name}
        onChange={e => setName(e.target.value)}
      />
      <p>Hello, {name}.</p>
    </>
  );
}

Enter fullscreen mode Exit fullscreen mode

And Finally there are some troubleshooting:

I’ve updated the state, but logging gives me the old value
Calling the set function does not change state in the running code:

function handleClick() {
  console.log(count);  // 0

  setCount(count + 1); // Request a re-render with 1
  console.log(count);  // Still 0!

  setTimeout(() => {
    console.log(count); // Also 0!
  }, 5000);
}
Enter fullscreen mode Exit fullscreen mode

This is because states behaves like a snapshot. Updating state requests another render with the new state value, but does not affect the count JavaScript variable in your already-running event handler.

I’m getting an error: “Too many re-renders”

You might get an error that says: Too many re-renders. React limits the number of renders to prevent an infinite loop. Typically, this means that you’re unconditionally setting state during render, so your component enters a loop: render, set state (which causes a render), render, set state (which causes a render), and so on. Very often, this is caused by a mistake in specifying an event handler:

// 🚩 Wrong: calls the handler during render
return <button onClick={handleClick()}>Click me</button>

// ✅ Correct: passes down the event handler
return <button onClick={handleClick}>Click me</button>

// ✅ Correct: passes down an inline function
return <button onClick={(e) => handleClick(e)}>Click me</button>
Enter fullscreen mode Exit fullscreen mode

I’m trying to set state to a function, but it gets called instead

You can’t put a function into state like this:

const [fn, setFn] = useState(someFunction);

function handleClick() {
  setFn(someOtherFunction);
}
Enter fullscreen mode Exit fullscreen mode

Because you’re passing a function, React assumes that someFunction is an initializer function, and that someOtherFunction is an updater function, so it tries to call them and store the result. To actually store a function, you have to put () => before them in both cases. Then React will store the functions you pass.

const [fn, setFn] = useState(() => someFunction);

function handleClick() {
  setFn(() => someOtherFunction);
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)