Pitfalls
from : https://javascript.plainenglish.io/5-react-usestate-mistakes-that-will-get-you-fired-b342289debfe
Getting previous value incorrectly
Not using it previous state can cause unexpected state updates
import { useCallback, useState } from "react";
export default function App() {
  const [counter, setCounter] = useState(0);
  /// Instant increment works
  //   const handleIncrement = useCallback(() => {
  //     setCounter(counter + 1);
  //   }, [counter]);
  /// Instant increment works better without dependency
  const handleIncrement = useCallback(() => {
    setCounter((prev) => prev + 1);
    // Dependency removed!
  }, []);
  //   const handleDelayedIncrement = useCallback(() => {
  //     // counter + 1 is the problem,
  //     // because the counter can be already different, when callback invokes
  //     setTimeout(() => setCounter(counter + 1), 1000);
  //   }, [counter]);
  const handleDelayedIncrement = useCallback(() => {
    // Using prev state helps us to avoid unexpected behaviour
    setTimeout(() => setCounter((prev) => prev + 1), 1000);
    // Dependency removed!
  }, []);
  return (
    <div>
      <h1>{`Counter is ${counter}`}</h1>
      {/* This handler works just fine */}
      <button onClick={handleIncrement}>Instant increment</button>
      {/* Multi-clicking that handler causes unexpected states updates */}
      <button onClick={handleDelayedIncrement}>Delayed increment</button>
    </div>
  );
}
Storing global state in useState
We can access our global state easily from every part of our app. It’s much more convenient and clear than using pure useState .
Use React Context.
import React, { createContext, useContext, useMemo, useState } from "react";
// Created context
const UserContext = createContext();
// That component separates user context from app, so we don't pollute it
function UserContextProvider({ children }) {
  const [name, setName] = useState("Pavel");
  const [surname, setSurname] = useState("Pogosov");
  // We want to remember value reference, otherwise we will have unnecessary rerenders
  const value = useMemo(() => {
    return {
      name,
      surname,
      setName,
      setSurname,
    };
  }, [name, surname]);
  return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}
function PageFirst() {
  const { name } = useContext(UserContext);
  return name;
}
function PageSecond() {
  const { surname } = useContext(UserContext);
  return surname;
}
export default function App() {
  return (
    <UserContextProvider>
      <PageFirst />
      <PageSecond />
    </UserContextProvider>
  );
}
Forgetting to initialize the state
Always initialize states
import React, { useEffect, useState } from "react";
async function fetchUsers() {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users`);
  const users = await response.json();
  return users;
}
export default function App() {
  // If it doesn't cause errors in your case, it's still a good tone to always initialize it (even with null)
  const [users, setUsers] = useState([]);
  useEffect(() => {
    fetchUsers().then(setUsers);
  }, []);
  // You can also add that check
  // if (users.length === 0) return <Loading />
  return (
    <div>
      {users.map(({ id, name, email }) => (
        <div key={id}>
          <h4>{name}</h4>
          <h6>{email}</h6>
        </div>
      ))}
    </div>
  );
}
Avoid mutate state !
You mustn’t mutate React state ever in your life! React does a lot of smart and important stuff when the state changes, and it does that according to shallow comparison
Never mutate state ! 
import { useCallback, useState } from "react";
export default function App() {
  const [userInfo, setUserInfo] = useState({
    name: "Pavel",
    surname: "Pogosov",
  });
  const handleChangeInfo = useCallback((field) => {
    return (e) => {
      /// wrong
      // setUserInfo((prev) => {
      // // Here we are mutating prev state.
      // // That simply won't work as React doesn't recognise the change
      // prev[field] = e.target.value;
      return prev;
      // Now it works!
      setUserInfo((prev) => ({
        // So when we update name, surname stays in state and vice versa
        ...prev,
        [field]: e.target.value,
      }));
    };
  }, []);
  return (
    <div>
      <h2>{`Name = ${userInfo.name}`}</h2>
      <h2>{`Surname = ${userInfo.surname}`}</h2>
      <input value={userInfo.name} onChange={handleChangeInfo("name")} />
      <input value={userInfo.surname} onChange={handleChangeInfo("surname")} />
    </div>
  );
}
Compose Hooks
Useinput will avoid using two hooks for name and surname
import React, { useCallback, useState } from "react";
function useInput(defaultValue = "") {
  // We declare this state only once!
  const [value, setValue] = useState(defaultValue);
  // We write this handler only once!
  const handleChange = useCallback((e) => {
    setValue(e.target.value);
  }, []);
  // Cases when we need setValue are also possible
  return [value, handleChange, setValue];
}
export default function App() {
  const [name, onChangeName] = useInput("Pavel");
  const [surname, onChangeSurname] = useInput("Pogosov");
  return (
    <div>
      <input value={name} onChange={onChangeName} />
      <input value={surname} onChange={onChangeSurname} />
    </div>
  );
}
Using try-catch inside async await
https://javascript.plainenglish.io/stop-using-try-catch-to-catch-async-await-exceptions-6e0215ace654