Next.js Discord

Discord Forum

Suggestions on hook impovement - custom useOptimistic

Unanswered
Vojin posted this in #help-forum
Open in Discord
Avatar
Hi! I don't really wanna play around with experimental features since there will be a lot of maintenance with it, I want something that simply works. Now this works for my use case and I think it can prove to be useful, but can't stop thinking that there could be a bug somewhere. If you have time, I'd appreciate you taking a look at the code below:

export function useOptimisticState<T>(initial: T) {
  const [state, setState] = useState(initial);
  const [optimistic, setOptimistic] = useState(initial);

  const updateState = async (
    optimisticValue: (prev: T) => T,
    trueValue: (prev: T) => Promise<T>
  ) => {
    setOptimistic((prev) => optimisticValue(prev));
    trueValue(state)
      .then((data) => {
        setState(data);
      })
      .catch((e) => {
        console.log(e);
        setOptimistic(state);
      });
  };

  return [optimistic, updateState] as const;
}

usage:
const [profileOptions, setProfileOptions] = useOptimisticState({
    isFriend: profileData.isFriend,
    friendRequestSent: profileData.friendRequestSent,
    notificationsOn: true,
  });

const handleFriendRequest = async () => {
      setProfileOptions(
        (prev) => ({ ...prev, friendRequestSent: "ok" }),
        async (prev) => {
          const res = await sendFriendRequest(profileData.id);
          if (res.data) return { ...prev, friendRequestSent: res.data.id };
          throw Error(res.error);
        }
      );
    }

3 Replies

Avatar
White-winged Scoter
I tweaked it a little bit

export function useOptimisticState<T>(initialValue: T) {
  const [value, setValue] = React.useState<T>(initialValue)
  const [optimistic, setOptimistic] = React.useState<T>(initialValue)

  const updateState = React.useCallback(
    async (
      optimisticValue: React.SetStateAction<T>,
      trueValue?: (prev: T) => Promise<T | undefined>,
    ) => {
      setOptimistic(optimisticValue)

      try {
        const data = (await trueValue?.(value)) ?? optimisticValue

        setValue(data)
        setOptimistic(data)

        return data
      } catch (error) {
        setOptimistic(value)
      }
    },
    [value],
  )

  return [optimistic, updateState] as const
}


So you can use it the same way you would use a regular useState aswel
Also added the setOptimistic in the response (maybe it has an updates ID or something else that changed server-side. And that's the source of truth most likely)
Avatar
This looks great, thanks, I prefer the args structure the way I wrote them (it's been used a lot since the last post hence the decision), but yours is objectively better, thanks