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!
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!
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
useStatesetter or auseDispatchdispatch). 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.