Search code examples
javascriptreactjsreduxreact-reduxredux-toolkit

React-Redux - State value incorrect in setInterval()


I have an issue with react-redux / react-toolkit. I have a state called todos. This state is correctly filled with 3 items as you can see here:

enter image description here

This is the code of my todo slice:

import { createSlice } from '@reduxjs/toolkit';


const initialState = {
  value: []
};

export const todoSlice = createSlice({
  name: 'todos',
  initialState,
  reducers: {
    setTodos: (state, action) => {
      state.value = action.payload
    },
  },
});

export const { setTodos } = todoSlice.actions;

export default todoSlice.reducer;

I'm using Indexed Db to store the todos locally. Therefore I have an interval (with setInterval in my app.container.js, which checks if the todos needs to be reloaded.

function App(props) {

    const [interval, setInterval] = useState(null);
    const dispatch = useDispatch();
    const todos = useSelector((state) => state.todos.value);
    const { loadTodos, loadTodosFromIndexedDB } = useTodo();
    //...

    useEffect(() => {
        //...
        initializeInterval();
        //...
    },[]);

    async function initializeInterval() {
        setInterval(window.setInterval(async () => {
                await getTodosFromIndexedDB();
            },3000)
        );
    }

    async function getTodosFromIndexedDB() {
        let todosFromIndexedDb = await loadTodosFromIndexedDB();
        if(todos?.length !== todosFromIndexedDb?.length && !Utils.isNullOrUndefined(todosFromIndexedDb)) {
            dispatch(setTodos(todosFromIndexedDb));
        }
        //...
    }

    return (
        //...
    )
}

The thing is, that in the function getTodosFromIndexedDB(), the value of todos is like the initial todos value empty array ([]).

enter image description here

This causes, that the update is being triggered each 3 seconds, but the value of todos in this function stays on empty array ([]). The update which is being triggered provides the correct 3 todo items:

enter image description here

What causes that the value of todos is not correctly in the getTodosFromIndexedDB() function? Is it an async issue or is it because of a different scope (window.setInterval)?


Solution

  • On first render, you register the getTodosFromIndexedDB function as an interval, which as a closure is scoped over the variables of that execution of the component. You could use a ref to get around this:

    const todos = useSelector((state) => state.todos.value);
    const todosRef = useRef(todos)
    useEffect(() => { todosRef.current = todos })
    
    // in `getTodosFromIndexedDB`, access `todosRef.current` instead of `todos`