DoneDeal0
DoneDeal0

Reputation: 6257

How to update React state once useMutation is done?

I add an user to an api with react-query's useMutation hook. It works. Now, I need to add the new user to my array of users, which is in my state.

I know I'm supposed to query all the users with useQuery and then use onSuccess inside useMutation to modify the cache. But in certain cases, I don't fetch the users with useQuery, so I need to update a local state as I would do with a normal promise.

For the moment, I simply check if the prop "success" is true and if so, I update the array. But it only works on the second click. Why and how to fix this?

It seems the success condition inside onAddUser() is only reached on a second click.

export default function App() {
  const { updateUser } = userService();
  const { update, loading, error, data, success } = updateUser();
  const [users, setUsers] = useState(allUsers);

  const onAddUser = async () => {
    await update(newUser);
    if (success) {
      return setUsers((users) => [...users, data]);
    }
  };

  return (
    <>
      <div>
        {users.map((user) => (
          <div key={user.id}>
            {user.name} - {user.job}
          </div>
        ))}
      </div>
      {loading && <div>sending...</div>}
      {error && <div>error</div>}
      <button onClick={() => onAddUser()}>add user</button>
    </>
  );
}

Here is also a sandbox: https://codesandbox.io/s/usemutation-test-co7e9?file=/src/App.tsx:283-995

Upvotes: 8

Views: 14429

Answers (1)

TkDodo
TkDodo

Reputation: 28833

The success prop returned from updateUser (I assume this is somehow the result returned by useMutation) will only update on the next render cycle. Functions will still keep the reference to whatever they closured over, even if you have an async await in there. This is nothing react-query specific, this is just how react works.

I would suggest to use the onSuccess callback of the mutation function, or use mutateAsync and check the result, though you have to keep in mind to catch errors manually if you use mutateAsync. You can read about mutateAsync here, I'm gonna show you a mutate example:

const { mutate, loading } = useMutation(() => ...);
const onAddUser = () =>
    mutate(newUser, {
        onSuccess:  (newData) => setUsers((users) => [...users, data]);
    });
  };

also, please don't violate the rules of hooks. You can only call hooks from functional components or other hooks (=functions that start with use), but in your codesandbox, you call useMutation from the updateUser function ...

Upvotes: 8

Related Questions