skip to Main Content

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)?
Any help or hint is highly appreciated! Many thanks in advance.

2

Answers


  1. I am posting this as an answer to get more room and I have not tested this yet but I feel that initially the todos are an empty array and when you create this function it is stored as empty array in it every time the set interval runs it is using the same todo value. that’s why it is not working but you can use useCallback to recreate this function everytime todos value change

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

    updated code

    function App(props) {
    
        const [interval, setInterval] = useState(null);
        const dispatch = useDispatch();
        const todos = useSelector((state) => state.todos.value);
        const { loadTodos, loadTodosFromIndexedDB } = useTodo();
        const getTodosFromIndexDb = useCallback(  async () => {
            let todosFromIndexedDb = await loadTodosFromIndexedDB();
            if(todos?.length !== todosFromIndexedDb?.length && !Utils.isNullOrUndefined(todosFromIndexedDb)) {
                dispatch(setTodos(todosFromIndexedDb));
            }
        },[todos])
        //...
    
        useEffect(() => {
            //...
            initializeInterval();
            //...
        },[]);
    
        async function initializeInterval() {
            setInterval(window.setInterval(async () => {
                    await getTodosFromIndexedDB();
                },3000)
            );
        }
    
      
    
        return (
            //...
        )
    }
    
    Login or Signup to reply.
  2. 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`
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search