Home > Software design >  Access latest value of state in aws amplify subscriptions react
Access latest value of state in aws amplify subscriptions react

Time:09-23

I have a useEffect hook with a subscription listener using aws-amplify like this.

useEffect(() => {
        todoUpdatedSubs.subscribe({
          next: (status: SubscriptionStatus<OnTodoUpdatedSubscription>) => {
            setIsTaskUpdated(true);
            // when mutation will run the next will trigger
            console.log("New Updated SUBSCRIPTION ==> ", status);
            if (!status.value.data.onTodoUpdated.success)
              return console.log("error", status);
    
            const newArr = [...todos];
            console.log("Prev todos", todos);
            const { id, todo, isCompleted } = status.value.data.onTodoUpdated;
            newArr[editingIndex] = { id, todo, isCompleted };
            setTodos(newArr);
          },
        });}, [])

The problem is that the next function in useEffect does not have the latest value of todo state because the useEffect only ran on mount. If I do something like this, multiple listeners are mounted:

useEffect(() => {
        todoUpdatedSubs.subscribe({
          next: (status: SubscriptionStatus<OnTodoUpdatedSubscription>) => {
            setIsTaskUpdated(true);
            // when mutation will run the next will trigger
            console.log("New Updated SUBSCRIPTION ==> ", status);
            if (!status.value.data.onTodoUpdated.success)
              return console.log("error", status);
    
            const newArr = [...todos];
            console.log("Prev todos", todos);
            const { id, todo, isCompleted } = status.value.data.onTodoUpdated;
            newArr[editingIndex] = { id, todo, isCompleted };
            setTodos(newArr);
          },
        });}, [todos])

How can I get the latest value of state in the listener?

CodePudding user response:

Use the functional updates option of the useState hook. When setting the state using a callback, the callback is called with the previous values. This saves you the need to define todos as a dependency of the useEffect.

useEffect(() => {
  todoUpdatedSubs.subscribe({
    next: (status: SubscriptionStatus<OnTodoUpdatedSubscription>) => {
      setIsTaskUpdated(true);
      // when mutation will run the next will trigger
      console.log("New Updated SUBSCRIPTION ==> ", status);
      if (!status.value.data.onTodoUpdated.success)
        return console.log("error", status);
        
      setTodos(todos => {
        const newArr = [...todos];
        const { id, todo, isCompleted } = status.value.data.onTodoUpdated;
        newArr[editingIndex] = { id, todo, isCompleted };
        
        return newArr;
      });
    },
});}, [])

If you need to use the updated todos value inside useEffect, but not when updating the state, you can use useRef instead.

In your case I would use the functional updates option, but I'm adding this as an example:

const todosRef = useRef();

useEffect(() => {
  todosRef.current = todos;
});

useEffect(() => {
  todoUpdatedSubs.subscribe({
    next: (status: SubscriptionStatus<OnTodoUpdatedSubscription>) => {
      setIsTaskUpdated(true);
      // when mutation will run the next will trigger
      console.log("New Updated SUBSCRIPTION ==> ", status);
      if (!status.value.data.onTodoUpdated.success)
        return console.log("error", status);
        
      const newArr = [...todosRef.current];
      const { id, todo, isCompleted } = status.value.data.onTodoUpdated;
      newArr[editingIndex] = { id, todo, isCompleted };
      setTodos(newArr);
    },
});}, [])
  • Related