Store.Dispatch() Resetting Redux Store - javascript

I dispatch(action()) to trigger an action from outside my react component. It is working correctly in that it is triggering my action and updating the new item in my store. The problem is that it seems to be completely resetting everything else in my store, which to me at least makes it more of a problem than its worth.
Worth noting: I am using next.js.
Here is a basic idea of my flow:
I have a utils folder with a service where I am dispatching this action from:
import store from './store';
store.dispatch(myAction());
I have my actions
export const myAction = () => ({
type: HELP_ME,
payload: true,
});
const initialState = {
reducerVar: fase,
otherExistingVar: {},
otherExistingVarTwo: null,
otherExistingThree:null,
otherExistingVarFour: false,
};
const myReducer = (state = initialState, action) => {
switch (action.type) {
case HELP_ME: {
const reducerVar = action.payload;
}
default: {
return state;
}
}
};
export default myReducer;
I am not sure if I am misusing store.dispatch() because I dont see why anyone would use this technique if it completely wipes out the existing data in the store. Or is there a better way to trigger this simple action from outside my component.
Basically I want to dispatch this action without completely wiping out my store just like I would dispatch the action if I were in a component.
Thank You!

you should return the state with value in reducer like this.
const myReducer = (state = initialState, action) => {
switch (action.type) {
case HELP_ME: {
return {...state, reducerVar : action.payload}
}
default: {
return state;
}
}
};

What you are trying to do is fine. But your reducer must return the whole state with your change done by the action like below.
const myReducer = (state = initialState, action) => {
switch (action.type) {
case HELP_ME:
const reducerVar = action.payload;
return {...state, reducerVar }
default:
return state;
}
};

Related

I want to get a piece of state for a sibling reducer

I have a list of reducers in my combineReducer:
export default createRootReducer =>
combineReducers({
profile,
settings,
});
I want to get some state from profile, into settings, as I need the current user id, when making an API call.
My settings reducer:
export default (state = initialState, action) => {
switch (action.type) {
case Actions.SettingsSave:
api.patch("settingsAPI", { body: action.payload, params: { user_id } });
let finalPayload = Object.assign({}, state, action.payload);
return finalPayload;
default:
return state;
}
};
How would I get the data from profile into settings? Is there any way? I tried to get it directly from the store, but that's not allowed in a reducer that is in the middle of execution.
You could use a getState in the action creator and pass it in through the action. Something like
export default saveSettings(params){
const currState = store.getState()
return {
type: "SAVE_SETTINGS",
foo: params,
profileSlice: currState.settings
}
}
You would probably need to use thunk for accessing your global state. Here's an example:
export function updateProduct(product) {
return (dispatch, getState) => {
const { accountDetails } = getState();
dispatch({
type: UPDATE_PRODUCT,
stateOfResidenceId: accountDetails.stateOfResidenceId,
product,
});
};
}

How can I set input value to the state in redux reducer file?

This is my input
<input
type="text"
onChange={(e) => {
props.setInputVal(e.target.value);
}}
/>
This is the action file
export const setInputVal = (val) => {
return {
type: SET_INPUT,
payload: val
};
};
This is the state I want to set input's value to in redux reducer
const initialState = {
inputVal: ''
};
export const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_INPUT:
return {
inputVal: [ action.payload ]
};
default:
return state;
}
};
But I'm getting the an error so How can I properly set the input value when user types in to the inputVal state in redux reducer ?
You should read Using Redux with React. Pay close attention to the mapStateToProps() function and how it is used with the connect() higher-order component.
I guess the error you get has to do with the fact that inputVal becomes an array instead of string on SET_INPUT action dispatch. Fix your reducer as follows:
export const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_INPUT:
return {
...state,
inputVal: action.payload
};
default:
return state;
}
};
You also have to connect inputVal to input value using connect() function:
<input type="text" value={props.inputVal} {...} />

REDUX: store variables accessed via this.props are outdated but store.getState() works

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.

How might I reset state of just one reducer when I have multiple reducers and prevent reload on state reset of just one reducer

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
});

Async Action with Redux

I have a React component that's connected to Redux store. I'm fetching resources(posts) in the componentWillMount life-cycle method.
componentWillMount() {
this.props.fetchPosts();
}
The component will subscribe to Redux store and getting isFetching and posts from the store.
const mapStateToProps = (state) => {
return {
posts: getAllPosts(state),
isFetching: getIsFetchingPosts(state),
}
}
I'd like to show a spinner when it's still fetching, so in the render method I'd like to do this.
render() {
if (this.props.isFetching) {
return <Spinner />
}
return this.props.posts.map(post => <PostItem key={post.id}{...post}/>)
}
BUT if I console.log isFetching in the render method, first it shows false and thentrue and then finally false.
Ideally when this container renders for the first time isFetching state is already set to true and showing the spinner. What changes do I need to make to make that happen?
Here is code for the action creator and reducers
/*** Action Creator ***/
export const fetchPosts = () => (dispatch) => {
dispatch({
type: REQUEST_POSTS,
});
return axios({
method: 'get',
url: `${API_URL}/posts`,
})
.then(({data}) => {
dispatch({
type: RECEIVE_POSTS,
payload: data.posts,
})
})
.catch((response) => {
// some error handling.
});
}
/*** Reducers ***/
const initialState = {
isFetching: false,
allIds: [],
byId: {},
};
const isFetching = (state = initialState.isFetcthing, action) => {
switch (action.type) {
case REQUEST_POSTS:
return true;
case RECEIVE_POSTS:
return false;
default:
return state;
}
}
const allIds = (state = initialState.allIds, action) => {
switch (action.type) {
case RECEIVE_POSTS:
return action.payload.map(post => post.id);
default:
return state;
}
}
const byId = (state = initialState.byId, action) => {
switch (action.type) {
case RECEIVE_POSTS:
return action.payload.reduce((nextState, post) => {
nextState[post.id] = post;
return nextState;
}, {...state});
default:
return state;
}
}
const posts = combineReducers({
isFetching,
allIds,
byId,
});
export default posts;
/*** Selectors in 'posts.js' file ***/
export const getAllPosts = (state) => {
const { allId, byId } = state;
return allIds.map(id => byId[id]);
}
/*** rootReducer file ***/
import posts, * as fromPosts from './posts';
const rootReducer = combineReducers({
posts,
})
export default rootReducer;
export const getAllPosts = (state) => {
return fromPosts.getAllPosts(state.posts);
};
Thank you in advance!
The plain answer is that this is the expected behaviour, given your implementation. You're mapping the isFetching state to a prop. Here's what's happening:
The initial value of isFetching in the state tree is false, so the isFetching prop value is false, so it renders as false.
You dispatch an action which changes isFetching to true in the state tree. This new state is mapped to a new isFetching prop value of true, which causes a re-render, where it renders as true.
You (asynchronously) dispatch another action which changes isFetching back to false in the state tree. Same as in (2), this causes a re-render, with isFetching as false.
The plain solution if you want simply a render of true, false*, with the current implementation, is to set isFetching to true in your reducer's initial state.
Whether or not this implementation makes sense on a design level for this component is a broader question, that there's not enough context to answer here :-)
*update for completeness I should say I don't know if the render() function would be called twice, with isFetching resolved as true,false or three times with true,true,false in this case. I suspect that react-redux may optimise the rendering of the component such that no re-render occurs if the mapped isFetching prop changes from true->true, but don't know this for sure - would be grateful and interested if you could let me know what your logging outputs?
In any case, at the DOM level certainly only two renders would occur, due to the standard react virtual-DOM diffing optimisation, so in effect the result is the same either way

Categories

Resources