I know there are already a couple of questions similar to this, but none of the solutions provided are working for me. I am dispatching an action in my React Native component, but when I try to access the state afterwards with useSelector, it is still just the previous state, not the updated one.
My component:
const userSettings = useSelector((state: any) => state.user.settings);
const loadUserSettings = async () => {
await dispatch(userActions.fetchUserSettings());
}
const checkUserEnabled = async () => {
await loadUserSettings();
// The old state shows here rather than the updated one
console.log(userSettings.enabled)
}
and this is my reducer:
const initialState = {
settings = UserSettings,
};
export default (state = initialState, action) => {
switch (action.type) {
case SET_USER_SETTINGS:
return {
...state,
settings: action.settings,
};
default:
return state;
}
};
userSettings is a stale closure, as Nicolas suggested you cannot dispatch an action and expect the state value to have changed immediately because a new state is created after every action and your current function is still using the old value of the state. When the component re renders it will be re rendered with the new state value.
Related
Well hello there!
I'm having some issues - that I never had before - by accessing store variables through mapStateToProps. Namely, they never change and always have their default value I setup in the store BEFORE changing them in any way. If I call them by store.getState().reducer.x my code works!
Here's my store:
export const initialState = {
isKeyManagementWindowOpen: false
};
const rootReducer = combineReducers({
some: someReducer,
settings: settingsComponentReducer
)};
const store = createStore(rootReducer, compose(applyMiddleware(thunk), window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : variable => variable));
export default store;
settingsComponentActions.js
export const TOGGLE_KEY_MANAGEMENT_WINDOW = 'TOGGLE_KEY_MANAGEMENT_WINDOW';
export const toggleKeyManagementWindow = isKeyManagementWindowOpen => {
return { type: TOGGLE_KEY_MANAGEMENT_WINDOW, isKeyManagementWindowOpen};
}
settingsComponentReducer.js
export const settingsComponentReducer = (state = initialState, action) => {
console.log(action);
switch (action.type) {
case Actions.TOGGLE_KEY_MANAGEMENT_WINDOW:
return Object.assign({}, state, {
isKeyManagementWindowOpen: action.isKeyManagementWindowOpen
});
default: return state;
}
};
One thing that may be causing issues is that I am calling this.props in my websocket's subscribe method.
Key.js
connectToWebsocket = ip => {
const stompClient = Stomp.client(`url/receivekey`);
stompClient.heartbeat.outgoing = 0;
stompClient.heartbeat.incoming = 0;
stompClient.debug = () => null;
stompClient.connect({ name: ip }, frame => this.stompSuccessCallBack(frame, stompClient), err => this.stompFailureCallBack(err, ip));
}
stompSuccessCallBack = (frame, stompClient) => {
stompClient.subscribe(KEY_READER_NODE, keyData => {
if (!this.props.isKeyManagementWindowOpen) {
this.loginWithKey(keyData.body);
} else {
this.addToKeyList(keyData.body);
}
});
}
Even though I set isKeyManagementWindowOpen beforehand to true it still resolves to false. If I swap !this.props.isKeyManagementWindowOpen with !store.getState().settings.isKeyManagementWindowOpen the code works and it goes into this.addToKeyList(keyData.body).
So, if I swap those but LEAVE every store call in this.addToKeyList as this.props. then those are all default valued too, which doesn't make sense. It only works if I swap every this.props. line with store.getState()....
const mapStateToProps = state => ({
...
...
isKeyManagementWindowOpen: state.settings.isKeyManagementWindowOpen,
});
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Key));
As of now, my code works but I'd like to call the props as this.props... and not via store.getState().... Any idea why this could happen?
Thanks!
Seems like you're using deep state
Object assign only makes shallow copies of objects. So let's try to eliminate the easiest possible cause.
export const settingsComponentReducer = (state = initialState, action) => {
const newState = JSON.parse(JSON.stringify(state));
Then use newState instead of state below.
This will make a deep copy of your state and will always be a new object forcing your app to see it as a new prop and re-render correctly.
Why not use something like this, as you shouldn't directly mutate the overall state of the app, only update it if an action is triggered but spread the original state in prior to updating.
export const settingsComponentReducer = (state = initialState, action) => {
console.log(action);
switch (action.type) {
case Actions.TOGGLE_KEY_MANAGEMENT_WINDOW:
return {
...state,
isKeyManagementWindowOpen: action.isKeyManagementWindowOpen
});
default:
return state;
}
};
The problem is that React cannot have updated any value in this.props by the time the next line of code has executed.
This is not actually a Redux-specific problem. In any React component, triggering a state change on a line will still result in the same props and state values on the next line, because the current function is still executing and React has not re-rendered yet.
I maintain the data entered by a user in form in a reducer's state.I also have another reducer that has state related to some other functionality.Now I have a reset button for form,clicking which,I want the state update for only the formReducer and not for the other reducer's state.Also I don't want my app to reload on formReducer's state reset
Have added the code snippet that I used to implement the functionality,but what happens is that the whole app reloads on clicking reset and states for both reducers eventually get reset
My index.js has the following code
const appReducers = combineReducers({
r1: reducer1,
formSubmissionReducer: formSubmissionReducer
});
const rootReducer = (state, action) => {
if (action.type === "RESET_FORM") {
const { r1 } = state;
console.log(r1);
state = { r1};
}
return appReducers(state, action);
};
You have to separate your reducers, now when you get the RESET_FORM action, just the
formSubmissionReducer will react to that, the reducer1 will just return the state.
Take a read on redux docs, they explain that better than me.
const reducer1 = (state, action) => {
... // swtich statment
return state;
};
const formSubmissionReducer = (state, action) => {
if (action.type === "RESET_FORM") {
// it will update the form variable inside the `formSubmissionReducer` state
return state = {...state, form: action.payload};
}
return state;
};
const rootReducer = combineReducers({
r1: reducer1,
formSubmissionReducer: formSubmissionReducer
});
I have a component that makes an API call and then updates the state through a reducer. The problem is, this doesn't work so well cause the data don't get updated in the component, it's like the react didn't notice a state change a never re-rendered the component, but I'm not sure if that's the real issue here. So the component looks like this:
class MyComponent extends Component {
componentDidMount() {
// ajax call
this.props.loadData(1);
}
render() {
return (
<Grid>
<MySecondComponent
currentData={this.props.currentData}
/>
</Grid>
);
}
}
const mapStateToProps = state => ({
reducer state.myReducer,
currentData: state.myReducer.currentData
});
const mapDispatchToProps = dispatch => {
return {
loadData: () => {
HttpClient.getData(id, (data) => {
dispatch(
action_loadCurrentData(
data
)
);
});
},
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyComponent);
I am doing 2 things here: issuing an API call as soon as component is mounted, and then after data is fetched, dispatching action_loadCurrentData
This action looks like this:
//Action
export function action_loadCurrentData(
data
) {
return {
type: 'LOAD_CURRENT_DATA',
payload: {
currentData: data,
}
};
}
and the reducer:
//Reducer
const defaultState = {
};
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'LOAD_CURRENT_DATA':
state = {
...state,
currentData: {
myData: {
...state.currentData.myData,
0: action.payload.currentData
}
}
};
}
};
export default myReducer;
So the issue here is that the this.props.currentData that I'm passing to MySecondComponent will end up empty, as if I didn't set the data at all. However, If I stop the execution in the debugger and give it a few seconds, the data will be populated correctly, so I'm not sure what I'm doing wrong here?
Don't reassign state, return the newly created object instead
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'LOAD_CURRENT_DATA':
return {
...state,
currentData: {
myData: {
...state.currentData.myData,
0: action.payload.currentData
}
}
};
}
};
Your reducer needs to return the new state object, which needs to be a different instance from the previous state to trigger components update.
According to redux documentation:
The reducer is a pure function that takes the previous state and an action, and returns the next state.
And
Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().
I am trying to learn redux.
I am trying to add favorites functionality through Redux.
so I created actions addFavoriteSPORTSs, reducers SPORTSReducer, and then dispatched in tab-demo.js where i am doing mapDispatchToProps and
mapStateToProps
when I click the heart icon I am adding favorites in session storage window.sessionStorage.setItem(
"favoriteValues",
JSON.stringify(action.payload)
);
but the problem is after the refresh the color is not staying in the heart.
I debugged in componentDidMount and I am able to print the favotites get item value but still colr not maintaining.
can you tell me how to fix it.
so that in future I will fix itmyself.
providing my code snippet below
https://codesandbox.io/s/5x02vjjlqp
actions/index.js
import {
ADD_SPORTS,
DELETE_SPORTS,
DELETE_ALL_SPORTS,
ADD_ALL_SPORTSS
} from "./types";
export const addFavoriteSPORTSs = data => ({
type: ADD_ALL_SPORTSS,
payload: data
});
actions/types.js
export const ADD_ALL_SPORTSS = "ADD_ALL_SPORTSS";
tab-demo.js
import { deleteAllPosts, addFavoriteSPORTSs } from "./actions/index";
componentDidMount() {
let favorites = window.sessionStorage.getItem("favoriteValues");
console.log("componentDidMount favorites--->", favorites);
if (favorites) {
this.props.addFavoriteSPORTSs(JSON.parse(favorites));
}
// debugger;
}
const mapDispatchToProps = dispatch => {
return {
onDeleteAllSPORTS: () => {
// console.log("called");
dispatch(deleteAllPosts());
},
addFavoriteSPORTSs: data => {
dispatch(addFavoriteSPORTSs(data));
}
};
};
const mapStateToProps = state => {
return {
SPORTSs: state.SPORTSs
};
};
export default withStyles(styles)(
connect(
mapStateToProps,
mapDispatchToProps
)(ScrollableTabsButtonForce)
);
SPORTSReducer.js
switch (action.type) {
case ADD_ALL_SPORTSS:
window.sessionStorage.setItem(
"favoriteValues",
JSON.stringify(action.payload)
);
return action.payload;
case ADD_SPORTS:
state = state.filter(comment => comment.id !== action.payload.id);
value = [...state, action.payload];
console.log("ADD_SPORTS state--->", state);
console.log("ADD_SPORTS value--->", value);
//return [...state, action.payload];
// state = state.filter(SPORTS => SPORTS.SPORTSID !== action.payload.SPORTSID);
// value = [...state, action.payload]
window.sessionStorage.setItem("favoriteValues", JSON.stringify(value));
console.log("JSON.stringify(value)--->", JSON.stringify(value));
console.log("state--->", state);
return state;
When the component mounts you retrieve your favourties and set the redux state via calling your prop method. Your component will receive this new state via mapStateToProps, but it won't update without a suitable lifecycle method like componentDidUpdate or componentWillReceiveProps.
You can check out the lifecycle methods here.
Also, you are mutating your state in redux which is something you want to avoid. See this line:
state = state.filter(comment => comment.id !== action.payload.id);
I would also recommend Redux middleware for these tasks. You can set up middleware that will write to session storage whenever a specific action occurs and you can then rehyrdate Redux from that as well.
I am using repo from https://github.com/caspg/simple-data-table-map to implement into my existing app. However, in my existing app it contains various reducers with its own particular initialState. In the repo, the initialState is in the main.js
const initialState = {
regionData: statesData,
emptyRegions: [],
sortState: { key: 'regionName', direction: 'ASC' }
};
const store = createStore(rootReducer, initialState);
I separate out the reducers in the repo ( without using the index.js to combine 3 of them ) so I can add them into my existing code.
const reducers = {
User: require('../reducers/User.js'),
Alert: require('../reducers/Alert.js'),
Auth: require('../reducers/Auth.js'),
Header: require('../reducers/Header.js'),
PasswordPolicy: require('../reducers/PasswordPolicy.js'),
AuditTrail: require('../reducers/AuditTrail.js'),
StoragePolicy: require('../reducers/StoragePolicy.js'),
DragAndDrop: require('../reducers/DragAndDrop.js'),
UserProfile: require('../reducers/UserProfile.js'),
emptyRegions: require('../reducers/map/emptyRegions.js'), //here is it
regionData: require('../reducers/map/regionData.js'), //here is it
sortState: require('../reducers/map/sortState.js'), //here is it
Storage: require('./storage/Storage.js'),
router: routerStateReducer
};
module.exports = combineReducers(reducers);
I have a file to combine all the reducers and each of their initialstate(which in the same file with the particular reducer)
stores/index.js
module.exports = function(initialState) {
const store = redux.createStore(reducers, initialState,
compose(reduxReactRouter({ createHistory }), window.devToolsExtension ? window.devToolsExtension() : f => f)
)
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextReducer = require('../reducers')
store.replaceReducer(nextReducer)
})
}
return store
}
I tried to put the initiateState into sortState.js but it is not working.
The data do not show up. It must be something that from the author's repo
const store = createStore(rootReducer, initialState);
that set the initialState into the application state.
Please enlighten me. Thanks
If you are using combineReducers(…), each Reducer needs to return its initial state on the first run.
const DEFAULT_STATE = { … }
const emptyRegionsReducer = (state = DEFAULT_STATE, action) => {
switch (action.type) {
…
default:
return state;
}
}
Have look at line 60 in the redux repo. For each reducer in the object:
const initialState = reducer(undefined, { type: ActionTypes.INIT })
what is triggered right away when combineReducers() is called.
There are two ways to initialize the state in Redux - the docs explain the details very well.
You can set the global initialState by passing it as an optional second argument to the createStore function, like this:
const initialState = { /*..initial state props..*/ }
const store = createStore(rootReducer, initialState);
OR, you can set the initialState for an individual reducer in the following way (from official docs):
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT': return state + 1;
case 'DECREMENT': return state - 1;
default: return state;
}
}
Passing state = 0 means that state is given the value 0 within that reducer when it is first initialized. After the counter reducer is initialized, the state value becomes whatever counter is in the Redux store. One important thing to note is that you must include the line default: return state; for this method to work.
In your case, it looks like you are using both methods. If you are happy with the initialState that you set within your reducers, then you should remove the initialState argument from createStore:
const store = createStore(rootReducer);