How to only return last api request (ReactJS)

Viewed 867

Alternating between the 2 buttons will display first names or last names, but pressing them together really fast will chain requests and will combine the two. How can I make create a check, and only display the names from the button that was pressed last

export default function App() {
  const [name, setName] = useState();

  return (
    <div className="App">
      <button onClick={() => setName("first_name")}>1</button>
      <button onClick={() => setName("last_name")}>2</button>
      <Users name={name} />
    </div>
  );
}
export default function Users({ name }) {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    setUsers([]);

    axios({
      method: "GET",
      url: `https://reqres.in/api/users?delay=1`
    })
      .then((res) => {
        const allUsers = res.data.data.map((user) => <p>{user[name]}</p>);
        setUsers((prev) => [...prev, ...allUsers]);
      })
      .catch((e) => {
        console.log(e);
      });
  }, [name]);

  return <div className="Users">{users}</div>;
}
3 Answers

Here is a great article by Dan Abramov about the useEffect hook in which he also talks about how to handle race cases- https://overreacted.io/a-complete-guide-to-useeffect/#speaking-of-race-conditions

To solve your issue, create a variable like let didCancel = false at the start of useEffect. Then, you have to return a function from useEffect, which automatically runs at the time when the name changes next time. In that function set didCancel to true. Now, you have to handle fetch response only if didCancel is false. This way, you are discarding all fetch responses received from second-last, third-last, etc. button presses, and handling fetch response only from the last button press.

Here is updated useEffect code:-

  useEffect(() => {
    let didCancel = false;
    setUsers([]);

    axios({
      method: "GET",
      url: `https://reqres.in/api/users?delay=1`
    })
      .then((res) => {
         if (!didCancel) {
            const allUsers = res.data.data.map((user) => <p>{user[name]}</p>);
            setUsers((prev) => [...prev, ...allUsers]);
         }
      })
      .catch((e) => {
        console.log(e);
      });

    return () => {
      didCancel = true;
    };

  }, [name]);

  return <div className="Users">{users}</div>;
}

you have to create a loading state, and the user should not be able to send a new request until the data is received... you can create a hook for this or use SWR:

let me give you an example:

function Users(usersList) {
  return (
    <ul>
      {usersList.map((user, key) => (
        <li key={key}>{user}</li>
      ))}
    </ul>
  );
}

const useFetchUsers = (name) => {
  const [isLoading, setIsLoading] = React.useState(true);
  const [error, setError] = React.useState(null);
  const [data, setData] = React.useState([]);

  React.useEffect(() => {
    setIsLoading(true);
    setError(null);

    fetch('https://blahblahblah.com/api/users')
      .then((res) => res.json())
      .then((response) => setData(response))
      .catch((err) => setError(err))
      .finally(() => setIsLoading(false));
  }, [name]);

  return {
    isLoading,
    error,
    data,
  };
};

function App() {
  const [name, setName] = React.useState('Tom');
  const { isLoading, error, data } = useFetchUsers(name);

  const handleSubmitName = (name) => {
    if (isLoading) alert('wait!');
    else setName(name);
  };

  if (error) return <>an error occured</>;
  if (data)
    return (
      <>
        <button onClick={() => handleSubmitName('first_name')}>1</button>
        <button onClick={() => handleSubmitName('last_name')}>2</button>
        <Users name={name} />
      </>
    );
}

hint/note: it's just pseudocode and there are some tools to do data fetching + caching.

The problem is in this line setUsers((prev) => [...prev, ...allUsers]);. You are assuming that prev is [], but when the second request is resolve prev has data, that is why you see the request are combined:

I recommend to change your useEffect block to avoid the problem you are facing:

useEffect(() => {
    axios({
      method: "GET",
      url: `https://reqres.in/api/users?delay=1`
    })
      .then((res) => {
        const allUsers = res.data.data.map((user) => <p>{user[name]}</p>);
        setUsers(...allUsers); //--> with the last name's value 
      })
      .catch((e) => {
        console.log(e);
      });
  }, [name]);
Related