Redux: useSelector doesn't update immediatly value after dispatching action

Created on 29 Oct 2019  路  4Comments  路  Source: reduxjs/redux

Hello,

For some reason I can't get the useSelector values updated after dispatching an action, even using async/await (take a look at the comment in useEffect).

useSelectors and dispatches:

  const product = useSelector(state => state.productReducer.product)
  const operatingSystems = useSelector(state => state.operatingSystemReducer.operatingSystems)
  const productTypes = useSelector(state => state.productTypeReducer.productTypes)

  const dispatch = useDispatch()

  const onGetProductById = id => dispatch(actions.getProductById(id))
  const onFetchProductTypes = () => dispatch(actions.fetchProductTypes())
  const onFetchOperatingSystems = () => dispatch(actions.fetchOperatingSystems())

useEffect with async call:

 useEffect(() => {
    const getSelectedElement = () => onGetProductById(props.selectedElement.id)

    //product is {}
    console.log(product)

    if (Object.keys(props.selectedElement).length > 0) getSelectedElement()
 }, [])

getProductById action:

export const getProductById = (id) => {
    return dispatch => {
        dispatch(getProductByIdStart())
        return axios.get(`${url}/${id}`)
            .then(response => {
                dispatch(getProductByIdSuccess(response.data))
            })
            .catch(error => {
                if (error.response !== undefined) dispatch(getProductByIdFail(error.response.data))
                else dispatch(getProductByIdFail(error))
            })
 }
}

My reducer:

import * as actionTypes from '../actions/actionTypes'
import { updateObject } from '../../tools/utility'

const initialState = {
    products: [],
    loading: false,
    product: {},
    result: null,
    total: null
}

const createProductStart = (state) => {
    return updateObject(state, { loading: true })
}

const createProductSuccess = (state) => {
    return updateObject(state, { loading: false })
}

const createProductFail = (state) => {
    return updateObject(state, { loading: false })
}

const fetchProductsStart = (state) => {
    return updateObject(state, { loading: true })
}

const fetchProductsSuccess = (state, action) => {
    return updateObject(state, { products: action.products, total: action.total, loading: false })
}

const fetchProductsFail = (state) => {
    return updateObject(state, { loading: false })
}

const getProductByIdStart = (state) => {
    return updateObject(state, { loading: true })
}

const getProductByIdSuccess = (state, action) => {
    return updateObject(state, { product: action.product, loading: false })
}

const getProductByIdFail = (state) => {
    return updateObject(state, { loading: false })
}

const deleteProductStart = (state) => {
    return updateObject(state, { loading: true })
}

const deleteProductSuccess = (state) => {
    return updateObject(state, { loading: false })
}

const deleteProductFail = (state, action) => {
    return updateObject(state, { result: action.error, loading: false })
}

const updateProductStart = (state) => {
    return updateObject(state, { loading: true })
}

const updateProductSuccess = (state) => {
    return updateObject(state, { loading: false })
}

const updateProductFail = (state) => {
    return updateObject(state, { loading: false })
}


const reducer = (state = initialState, action) => {
    switch (action.type) {
        case actionTypes.CREATE_PRODUCT_START: return createProductStart(state)
        case actionTypes.CREATE_PRODUCT_SUCCESS: return createProductSuccess(state)
        case actionTypes.CREATE_PRODUCT_FAIL: return createProductFail(state)
        case actionTypes.FETCH_PRODUCTS_START: return fetchProductsStart(state)
        case actionTypes.FETCH_PRODUCTS_SUCCESS: return fetchProductsSuccess(state, action)
        case actionTypes.FETCH_PRODUCTS_FAIL: return fetchProductsFail(state)
        case actionTypes.GET_PRODUCT_BY_ID_START: return getProductByIdStart(state)
        case actionTypes.GET_PRODUCT_BY_ID_SUCCESS: return getProductByIdSuccess(state, action)
        case actionTypes.GET_PRODUCT_BY_ID_FAIL: return getProductByIdFail(state)
        case actionTypes.DELETE_PRODUCT_START: return deleteProductStart(state)
        case actionTypes.DELETE_PRODUCT_SUCCESS: return deleteProductSuccess(state)
        case actionTypes.DELETE_PRODUCT_FAIL: return deleteProductFail(state, action)
        case actionTypes.UPDATE_PRODUCT_START: return updateProductStart(state)
        case actionTypes.UPDATE_PRODUCT_SUCCESS: return updateProductSuccess(state)
        case actionTypes.UPDATE_PRODUCT_FAIL: return updateProductFail(state)
        default: return state
    }
}

export default reducer

What is a bit stranger is that it doesn't happen with class components, only when using hooks. Is that a bug? Did anyone pass by it?

Thanks!

Most helpful comment

Actually, looking at your code snippet, it seems like you're confused on how both hooks, React, and React-Redux work.

You cannot access a new value from a hook within a callback after something that would result in a state change (such as a useState setter or a useDispatch dispatch). The callback has already captured the original value from the hook, _at the time of the render_.

See Dan's post at https://overreacted.io/a-complete-guide-to-useeffect/ for explanations why.

So no, this is not a bug, this is simply how JS closures work.

All 4 comments

This is the Redux core repo, not React-Redux. If you have a React-Redux issue, please comment over at https://github.com/reduxjs/react-redux . Thanks!

Actually, looking at your code snippet, it seems like you're confused on how both hooks, React, and React-Redux work.

You cannot access a new value from a hook within a callback after something that would result in a state change (such as a useState setter or a useDispatch dispatch). The callback has already captured the original value from the hook, _at the time of the render_.

See Dan's post at https://overreacted.io/a-complete-guide-to-useeffect/ for explanations why.

So no, this is not a bug, this is simply how JS closures work.

Also, as a side suggestion, I'd strongly encourage you to try out our Redux Starter Kit package (https://redux-starter-kit.js.org), which will drastically simplify the Redux logic you just showed.

Hello Mark,

You're right. It makes sense.

It works on componentDidMount because it is a method, and not a callback passed as argument like the snippet I sent.

Thanks for the help, and sorry for the confusion!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

amorphius picture amorphius  路  3Comments

parallelthought picture parallelthought  路  3Comments

mickeyreiss-visor picture mickeyreiss-visor  路  3Comments

benoneal picture benoneal  路  3Comments

ramakay picture ramakay  路  3Comments