Reputation: 3896
I have the following slice in my store.
//status : idle, loading, succeeded, failed
const initialState = {
info:'a',
status:'idle',
error:null
}
//to get data from server
export const fetchFeatureInfoById = createAsyncThunk('feature/fetchInfoById', async (originalData) => {
console.log('fetchFeatureInfoById originalData ', originalData);
const response = await axios.get(FEATURE_URL+'/feature/'+originalData.id+'/'+originalData.category)
console.log('DATA from server ', response);
return response.data
})
//slice with reducers
export const featuresSlice = createSlice({
name: 'features',
initialState,
reducers: {
featureInfoById:{
reducer(state, action){
//do something with the id
console.log('feature state ', ' -- state : ',state.status, ' -- action : ', action);
},
prepare(category, id){
return{
payload:{
category,
id
}
}
}
}
},
extraReducers(builder){
builder
.addCase(fetchFeatureInfoById.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchFeatureInfoById.rejected, (state, action) => {
state.status = 'failed';
return state;
// state.error = action.error.message;
console.log('rejected data store ', state, action);
})
.addCase(fetchFeatureInfoById.fulfilled, (state, action) => {
// state.status = 'succeeded';
// state.info = action.payload;
let a = {
info:action.payload,
status:'succeeded',
error:null
}
return a;
// console.log('fulfilled data store ', state, action);
// return action.payload;
})
}
})
//exporting just info of feature
export const featureInfo = (state) => state.features.info;
In my Component, I have
import store from '../redux/store';
import {useSelector,useDispatch} from "react-redux";
import { featureInfo, featureError, featureStatus, featureInfoById, fetchFeatureInfoById } from "../redux/features";
import { useRef, useEffect, useState } from 'react';
const featureInfoA = useSelector(featureInfo)
const clickFeatureFromList =( sl, s, id, e, groupid, c) => (event, isExpanded) => {
store.subscribe(()=>{
const state = store.getState()
if (state.features.status=='succeeded' || state.features.status=='failed') {
console.log('BINGO SUBSCRIBE ', featureInfoA);
}
})
dispatch(fetchFeatureInfoById({id, category:'ex'}))
.then((e)=>{
console.log('BINGO THEN ', featureInfoA, featureErrorA, featureStatusA);
accordionDetailsRootGlobal.render(<ContextInfo/>);
})
}
The first time I click a button to call the clickFeatureFromList
the logs I get are
BINGO SUBSCRIBE a
BINGO THEN a null idle
this means that somehow the state never changes, I get the initial one
the second time I click a button to call the clickFeatureFromList
the logs I get are normal, eg
BINGO SUBSCRIBE a
BINGO SUBSCRIBE {success: false, msg: 'resolved error'}
BINGO SUBSCRIBE {success: false, msg: 'resolved error'}
BINGO THEN {success: false, msg: 'resolved error'} null succeeded
this is correct, state changes.
Why state does not change the first time? What am I doing wrong?
EDIT
as you can see in .addCase(fetchFeatureInfoById.fulfilled, (state, action) => {
I tried different syntaxes, nothing works
EDIT
check this sandbox to see what is wrong. Open the console of the sandbox to see errors
Upvotes: 1
Views: 691
Reputation: 203532
I've looked at the sandbox. Upon clicking the "test" button I see the state update on the first click. This is even confirmed in the redux dev tools.
I think there are at least a couple issues occurring here, both related to Javascript closures. The first is about a stale closure over the selected featureInfoA
, featureErrorA
, featureStatusA
states in the callback (clickFeatureFromList
above and testme
in the sandbox), while the second is about accessing the current Redux state while still in the callback scope (the same closure).
Using the code from your sandbox this is what I'd suggest as an implementation so you can (a) access the Redux state correctly in the callback Promise chain and (b) log the states in a meaningful way.
Code:
export function Counter() {
const dispatch = useDispatch();
const featureInfoA = useSelector(featureInfo);
const featureErrorA = useSelector(featureError);
const featureStatusA = useSelector(featureStatus);
useEffect(() => {
console.log("Render ", {
features: {
info: featureInfoA,
error: featureErrorA,
status: featureStatusA
}
});
}, [featureErrorA, featureInfoA, featureStatusA])
const testme = (id, category) => {
dispatch(fetchFeatureInfoById({ id, category })).then((e) => {
const state = store.getState();
console.log("BINGO THEN closure", {
features: {
info: featureInfoA,
error: featureErrorA,
status: featureStatusA
}
});
console.log("BINGO THEN state", state);
});
};
return (
<div>
<button onClick={() => testme(4, "todos")}>test</button>
</div>
);
}
useEffect
hook logs the selected state per render cycle.then
callback, not outside the Promise chain.then
is also logging the selected state that was closed over in callback scope so you could see its stalenessRunning the above code and clicking the "test" button exactly once produces the following log output. I'll annotate the logs to the code.
Render {"info":"a","status":"idle","error":null}
fetchFeatureInfoById originalData {id: 4, category: "todos"}
Render {"info":"a","status":"loading","error":null}
DATA from server {data: Object, status: 200, statusText: "", headers: AxiosHeaders, config: Object…}
BINGO THEN closure {"info":"a","status":"idle","error":null}
BINGO THEN state {"features":{"info":{"userId":1,"id":4,"title":"et porro tempora","completed":true},"status":"succeeded","error":null}}
Render {"info":{"userId":1,"id":4,"title":"et porro tempora","completed":true},"status":"succeeded","error":null}
Upvotes: 2