'Component shows old state after action happened

After action is happened component gets an old state anyway. I'm not mutating the state btw. The changes appears only after I refresh the page

My component TodoList.tsx
Here's I'm trying to get state via useEffect hook


export const TodoList: FC = () => {
    const {
        todoList, isLoading, isError, message
    } = useTypedSelector(
        (state) => state.todo
    )

    console.log(todoList)

    const { getTodo, reset } = useActions()

    useEffect(() => {
        getTodo()
        console.log("render")
        if (isError) {
            console.log(message)
        }
        return () => {
            reset()
        }
    }, [])

    const statusHandler = (event: React.ChangeEvent<HTMLParagraphElement>) => {
        const status = event.target.innerText
        if (status === "completed") {
            getTodo({ complete: true })
        } else if (status === "uncompleted") {
            getTodo({ complete: false })
        } else if (status === "all") {
            getTodo()
        }
    }

    return (
        <>
        ...
        </>
    )
}


my Reducer
I'm not mutating the state also.

const initialState: TodoState = {
  todoList: [],
  isError: false,
  isSuccess: false,
  isLoading: false,
  message: ""
}

export const todoReducer = (state = initialState, { type, payload }: TodoAction): TodoState => {
  switch (type) {
    case "GET_TODOS":
      return { ...state, isLoading: true }
    case "GET_TODOS_SUCCESS":
      return { ...state, isLoading: false, isSuccess: true, todoList: payload }
    case "GET_TODOS_ERROR":
      return { ...state, isLoading: false, isError: true, message: payload.error }
    case "CREATE_TODO":
      return { ...state, isLoading: false }
    case "CREATE_TODO_SUCCESS":
      return { ...state, isLoading: false, isSuccess: true, todoList: [...state.todoList, payload]}
    case "CREATE_TODO_ERROR":
      return { ...state, isLoading: false, isError: true, message: payload.error }
    case "TOGGLE_TODO":
      return { ...state, isLoading: true }
    case "TOGGLE_TODO_SUCCESS":
      return {
        ...state, isLoading: false, isSuccess: true, todoList: state.todoList.map((todo: ITodoItem) => {
          if (todo._id === payload.id) {
            console.log(payload)
            return {
              ...todo,
              complete: !todo.complete
            }
          }
          return todo
        })
      }
    case "TOGGLE_TODO_ERROR":
      return { ...state, isLoading: false, isError: true, message: payload.error }
    case "DELETE_TODO":
      return { ...state, isLoading: true }
    case "DELETE_TODO_SUCCESS":
      return {
        ...state, isLoading: false, isSuccess: true, todoList: state.todoList.filter(
          (todo: ITodoItem) => todo._id !== payload.id
        )
      }
    case "DELETE_TODO_ERROR":
      return { ...state, isLoading: false, isError: true, message: payload.error }
    case "EDIT_TODO":
      return { ...state, isLoading: true }
    case "EDIT_TODO_SUCCESS":
      return {
        ...state, isLoading: false, isSuccess: true, todoList: state.todoList.map((todo: ITodoItem) => {
          if (todo._id === payload.id) {
            return {
              ...todo,
              text: payload.text
            }
          }
          return todo
        })
      }
    case "EDIT_TODO_ERROR":
      return { ...state, isLoading: false, isError: true, message: payload.error }
    case "RESET_TODOS":
      return { ...state, todoList: state.todoList }
    default:
      return state
  }
}

my action creator
Here I'm making a request from my own api.

...
export const getTodo = (data?: GetTodos) => {
    return async (dispatch: Dispatch<TodoAction>): Promise<void> => {
        try {
            dispatch({ type: "GET_TODOS", payload: {} })
            let response
            if (data) {
                response = await todoService.getTodo((<any>data).complete)
            }
            response = await todoService.getTodo()
            console.log(response)
            dispatch({ type: "GET_TODOS_SUCCESS", payload: response })
        } catch (err) {
            dispatch({
                type: "GET_TODOS_ERROR",
                payload: { error: "Cannot fetch the todos. Please try again later" }
            })
        }
    }
}

export const createTodo = (payload: CreateTodo) => {
    return async (dispatch: Dispatch<TodoAction>): Promise<void> => {
        try {
            dispatch({ type: "CREATE_TODO", payload: payload })
            const response = await todoService.createTodo(payload)
            dispatch({ type: "CREATE_TODO_SUCCESS", payload: response })
        } catch (err) {
            dispatch({
                type: "CREATE_TODO_ERROR",
                payload: { error: "Cannot create todo. Please try again later" }
            })
        }
    }
}

export const toggleTodo = (payload: ToggleTodo) => {
    return async (dispatch: Dispatch<TodoAction>): Promise<void> => {
        try {
            const response = await todoService.updateTodo(payload.id, { complete: payload.complete })
            dispatch({ type: "TOGGLE_TODO_SUCCESS", payload: response })
        } catch (err) {
            dispatch({
                type: "TOGGLE_TODO_ERROR",
                payload: { error: `${err}` }
            })
        }
    }
}
...
}

all code is here: https://github.com/maridoroshuk/todos-ts

the issue



Solution 1:[1]

useEffect(callback, dependencies) triggers only when data in dependencies changes. In your code, you put useEffect( ... , []), meaning you don't have any dependencies, so the callback function only runs once when the component mounts.

If you want useEffect to run on every update, you need to not put any dependencies: useEffect(...).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 cSharp