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);
},
});}, [])