I'm trying to update my react state before making changes but it is not updating. Changing state is async but I cannot figure out how to make update the state inside my context in the code example below:
const initialState = {
user: null
}
const [state, dispatch] = useReducer(reducer, initialState);
const updateUser = async () => {
const res = await axios.get('currentUser')
//res.data.user is the user stored in the database
dispatch({ type: SET_USER, payload: res.data.user })
}
const getUser = async () => {
try {
await updateUser()
if(state.user) {
console.log('User is not null')
} else {
console.log('User is null')
}
} catch (err) {
//Handle error
}
}
Here is the reducer:
export default (state, action) => {
{ ... }
case SET_USER: return { ...state, user: action.payload }
{ ... }
}
I am calling the getUser function inside my component with the useEffect hook:
useEffect(() => {
getUser()
}, [])
This code example always returns User is null because the state is not updated when calling the updateUser function.
Note: This is a simple version of the context and the reducer
You are waiting for the fetch but not for the state to be updated.
useEffect(() => {
getUser()
}, [])
useEffect(() => {
if(state.user) {
console.log('User is not null')
} else {
console.log('User is null')
}
}, [state.user])
Related
variable updated by redux state does not trigger useEffect
not sure what i am missing but i can see state.user.fen updating but it does not trigger useEffect to be called?
export default function BoardSquare({ piece, black, position,isFromSquare,isToSquare}) {
dispatch(setFen(fen))
}
//userActions.js
export const setFen = (fen) => (dispatch) => {
dispatch({
type: SET_FEN,
payload: fen,
});
}
//userReducer.js
export default function userReducer(state = initialState,action) {
switch(action.type){
case SET_FEN:
return{
...state,
fen: action.payload
}
}
function GameApp() {
const fen = useSelector(state => state.user.fen)
useEffect(() => {
alert("should be working now ?")
console.log("should be working now ??????")
setBoard(fen)
}, [fen])
}
I need help. I don't understand why my dispatch action doesn't work. I've redux store currency list and current currency.
My reducer:
export const currencyReducer = (
state: typeState = initialState,
action: TypeActionCurrency
): typeState => {
switch (action.type) {
case types.CURRENCY_FILL_LIST:
return { ...state, list: action.payload }
case types.CURRENCY_SET_CURRENT:
return {
...state,
current:
state.list.find(currency => currency._id === action.payload) ||
({} as ICurrency),
}
default:
return state
}
}
My actions:
export const setCurrencyList = (currencies: ICurrency[]) => ({
type: types.CURRENCY_FILL_LIST,
payload: currencies,
})
export const setCurrentCurrency = (_id: string) => ({
type: types.CURRENCY_SET_CURRENT,
payload: _id,
})
My useEffect:
useEffect(() => {
if (!list.length) {
const fetchCurrencies = async () => {
try {
const data = await $apiClient<ICurrency[]>({ url: '/currencies' })
dispatch(setCurrencyList(data))
if (!current._id) dispatch(setCurrentCurrency(data[0]._id))
} catch (error) {
console.log(error)
}
}
fetchCurrencies()
}
}, [])
I want make request when load page and write currency list to Redux store, if we don't have current currency we write default currency from data.
There is one more strange thing, my redux extension shows that the state has changed, but when I receive it via the log or useSelector, it is empty
enter image description here
Thanks!
I am not 100% sure but it should work.
const [loader, setLoader] = useState(false);
const list = useSelector(state => state.list)
useEffect(() => {
if (!list.length) {
const fetchCurrencies = async () => {
try {
setLoader(true)
const data = await $apiClient<ICurrency[]>({ url: '/currencies' })
dispatch(setCurrencyList(data))
if (!current._id) dispatch(setCurrentCurrency(data[0]._id))
} catch (error) {
console.log(error)
} finally {
setLoader(false)
}
}
fetchCurrencies()
}
}, [])
useEffect(() => {
console.log(list);
}, [loader])
My axios transaction is all done in the redux actions so that I can re-use the function. The issue is that, I need to fetch the data first which is done by redux and then re-assign the value in a state, but the data cannot be populated in the state. Below is how my code looks like.
Setting.js
...
import { getUserDetail } from './redux/actions/settingActions';
export default function Setting() {
const dispatch = useDispatch()
const { user } = useSelector(state => state.settingReducer)
const [userDetail, setUserDetail] = useState()
useEffect(() => {
dispatch(getUserDetail())
setUserDetail(user) // I want to set the user here
}, [])
...
}
settingActions.js
export const getUserDetail = () => (dispatch, getState) => {
axios.get('url-goes-here')
.then(res => {
dispatch({
type: SET_USER_DETAIL,
payload: { res.data }
})
})
.catch(error => {
throw error;
})
}
settingReducer
function initialState() {
return {
...
user: {}
}
}
export default function (state = initialState(), action) {
const { type, payload } = action;
switch (type) {
case SET_USER_DETAIL:
return {
...state,
user: payload
}
default:
return state
}
}
My purpose of doing this is because I want to do some user details update but I want it to be done within the same file.
put user and dispatch as dependency in useEffect
useEffect(() => {
dispatch(getUserDetail())
setUserDetail(user)
}, [user,dispatch])
I have a simple useEffect that I'm not sure how to stop from invoking endlessly. It keeps firing the first if conditional endlessly. I've been reading a lot about hooks and I assume (maybe erroneously) that each render of the component results in a new invocation of my useAuth() and useUser() hooks. Since they have new references in memory it's triggering the useEffect's deps since technically it's a new function that exists in the scope of this new component render?
Thats my thought at least, no clue how to fix that if that's indeed that case.
const RootPage = ({ Component, pageProps }): JSX.Element => {
const { logoutUser } = useAuth(); // imported
const { fetchUser } = useUser(); // imported
const router = useRouter();
useEffect(() => {
// authStatus();
const unsubscribe = firebaseAuth.onAuthStateChanged((user) => {
if (user) {
console.log(1);
return fetchUser(user.uid); // async function that fetches from db and updates redux
}
console.log(2);
return logoutUser(); // clears userData in redux
});
return () => unsubscribe();
}, [fetchUser, logoutUser]);
...
}
fetchUser
const fetchUser = async (uid) => {
try {
// find user doc with matching id
const response = await firebaseFirestore
.collection('users')
.doc(uid)
.get();
const user = response.data();
// update redux with user
if (response) {
return dispatch({
type: FETCH_USER,
payload: user,
});
}
console.log('no user found');
} catch (error) {
console.error(error);
}
};
logoutUser
const logoutUser = async () => {
try {
// logout from firebase
await firebaseAuth.signOut();
// reset user state in redux
resetUser();
return;
} catch (error) {
console.error(error);
}
};
when I refresh the page with this useEffect on this is output to the console:
useEffect(() => {
function onAuthStateChange() {
return firebaseAuth.onAuthStateChanged((user) => {
if (user) {
fetchUser(user.uid);
} else {
resetUser();
}
});
}
const unsubscribe = onAuthStateChange();
return () => {
unsubscribe();
};
}, [fetchUser, resetUser]);
Keeping everything the same && wrapping fetchUser and resetUser with a useCallback, this solution seems to be working correctly. I'm not entirely sure why at the moment.
Guys i am having some trouble or quite doubtful.
am having one component and one reducer.
Reducer.js
import {
ASSET_POPUP_GET_ENDPOINT,
} from 'apiCollection';
import { performGet } from 'services/rest-service/rest-service';
export const GET_ASSETS_LIST = 'stories/GET_ASSETS_LIST';
const initialState = {
imgGroup: [],
isLoading: false,
};
const modalUploadReducer = (state = initialState, action) => {
switch (action.type) {
case GET_ASSETS_LIST: {
return {
...state,
ImageJson:action.payload.imageGroup,
};
}
case GET_ASSETS_LIST_ERROR: {
return {
...state,
isLoading:false,
};
}
default:
return state;
}
};
export const getModalClose = () => (dispatch) => {
dispatch({ type: CLOSE_MODAL });
}
export const getListActionDispactcher = () => (dispatch) => {
performGet(`${ASSET_POPUP_GET_ENDPOINT}`)
.then((response) => {
const payload = response.data;
dispatch({ type: GET_ASSETS_LIST,
payload: {
...payload,
data: payload.results,
} });
})
.catch((err) => {
dispatch({ type: GET_ASSETS_LIST_ERROR, payload: err });
throw err;
});
};
export default modalUploadReducer;
and my component look like
it do have mapStateToProps and mapDispatchToProps
and one of the function
const mapDispatchToProps = dispatch => ({
getCollection: () => dispatch(getListActionDispactcher()),
});
addDocumentClick = () =>{
this.props.getAssetsCollection();
}
and is it possible to have some setState/manipulation of response after api response got from reducer in the component
based on the response i need to do some changes in addDocumentClick.
Means something like this
addDocumentClick = () =>{
this.props.getAssetsCollection().then(...based on response;
}
The correct way for solving this is setting a global loading flag and in your componentDidUpdate() method, checking for the value to determine that the action has just succeeded. You already seem to have the isLoading flag. Just set it when the action's dispatched, and unset it after it succeeds/fails. And in componentDidUpdate():
function componentDidUpdate(prevProps) {
if (prevProps.isLoading && !this.props.isLoading) {
// do something
}
}
Of course, you need to connect() your loading flag to your component to achieve this.
If all you care about is whether the assets list has changed, you can simply check for the change of that prop in componentDidUpdate():
function componentDidUpdate(prevProps) {
if (prevProps.ImageJson !== this.props.ImageJson) {
// do something
}
}
Another solution is sending a callback to your action dispatcher, which makes your code more tightly coupled and I don't recommend, but it does work too. So, when you connect(), you can:
getCollection: (onSuccess) => dispatch(getListActionDispactcher(onSuccess)),
In your action dispatcher:
export const getListActionDispactcher = (onSuccess) => (dispatch) => {
// ...once API finished/failed
onSuccess(someData);
}
Finally, in your component:
this.props.getCollection((result) => {
console.log('succeeded!', result);
// hide modal, etc..
}
You are using redux-thunk, and calling thunk will return a promise which will resolve in whatever you return in your thunk. Therefore, all you need to do is to add return value to getListActionDispactcher
export const getListActionDispactcher = () => (dispatch) => {
// return this promise
return performGet(`${ASSET_POPUP_GET_ENDPOINT}`)
.then((response) => {
const payload = response.data;
dispatch({ type: GET_ASSETS_LIST,
payload: {
...payload,
data: payload.results,
} });
// return whatever you want from promise
return payload
})
.catch((err) => {
dispatch({ type: GET_ASSETS_LIST_ERROR, payload: err });
throw err;
});
};
.
addDocumentClick = () => {
this.props.getAssetsCollection().then(payload => console.log(payload))
}
You should, however, look for ways to avoid this pattern to have your components decoupled from actions as much as possible for the sake of modularity