Component stops updating after using combineReducers - javascript

What is the current behavior?
I have a Header component which should update after login data gets fetched. It checks a user's name and displays a welcome message.
When creating a store without using combineReducers, using only the loginReducer I've created everything works fine, state gets updated and then the component gets updated too. However, when using combineReducers, even though I use the same single reducer inside it, the component stops updating. I'm also using a logger middleware to display state changes and the state seems to be getting updated properly.
Code example:
This works:
Please notice that I'm only showing relevant file parts here.
index.js
const loggerMiddleware = createLogger();
// Here I'm creating the store with a single reducer function
const store = createStore(loginReducer, applyMiddleware(thunkMiddleware, loggerMiddleware));
const router = createRouter();
ReactDOM.render(<Provider store={store}>
{ router }
</Provider>,
document.getElementById('root')
);
loginReducer.js
// Please notice that here I'm not mutating state in any way
// I'm always returning a new state as recommended by the docs
const ACTION_HANDLERS = {
[REQUEST_LOGIN]: (state, action) => {
return ({ ...state, username: action.payload.username, authHeader: 'Basic ' + base64Encode(action.payload.username + ':' + md5(action.payload.password))});
},
[RECEIVE_LOGIN]: (state, action) => {
return ({ ...state, resources: action.payload.resources, isCashier: action.payload.isCashier });
},
[LOGOUT]: (state) => {
return ({ ...state, username: null, resources: {}, authHeader: null, isCashier: null });
}
}
const initialState = { isCashier: null, resources: {}, username: null };
export default function loginReducer (state = initialState, action) {
const handler = ACTION_HANDLERS[action.type]
return handler ? handler(state, action) : state
}
export default loginReducer;
LoginContainer.js
const mapActionCreators = {
doLogin,
doLogout
}
const mapStateToProps = (state) => ({
resources: state.resources,
username: state.username
})
export default connect(mapStateToProps, mapActionCreators)(Login)
This doesn't work:
This is the version which doesn't work, the only changes I've made were:
* I'm now wrapping the loginReducer inside combineReducers
* I changed the mapStateToProps function in order to get those properties from the nested login object inside the state
index.js
const rootReducer = combineReducers({
login: loginReducer
});
const loggerMiddleware = createLogger();
// Now I'm not using the raw `loginReducer` anymore
const store = createStore(rootReducer, applyMiddleware(thunkMiddleware, loggerMiddleware));
const router = createRouter();
ReactDOM.render(<Provider store={store}>
{ router }
</Provider>,
document.getElementById('root')
);
loginReducer.js -> This stays the same as above
LoginContainer.js
const mapActionCreators = {
doLogin,
doLogout
}
// Here I'm getting properties from `state.login` instead of `state` now due to the use of combineReducers
const mapStateToProps = (state) => ({
resources: state.login.resources,
username: state.login.username
})
export default connect(mapStateToProps, mapActionCreators)(Login)
Here's an image in which I describe the states after dispatching each one of the actions:
Whenever an action is dispatched and the previous and next state are logged, they are correct, but my component won't get updated.
Am I missing something here or is this really a bug?

Please test your reducers. keep state flat please. use a high order component for authentication logic. use reselect to improve performance. it a bug test your reduces. this should work. and deep nested state is not good cause it makes you state hard to think about and manage.
my code below:
import { combineReducers } from 'redux';
//import the reducers ou want
const authReducer = combineReducers({
loginData: LoginReducer,
permission: permissionReducer
});
export default rootReducer;
check this http://redux.js.org/docs/recipes/WritingTests.html

Related

useEffect rewrites the entire store

I have a redux store with 3 reducers:
let reducers = combineReducers({
config: configReducer,
data: dataReducer,
currentState: gameStateRecuder})
let store = createStore(reducers, applyMiddleware(thunkMiddleware));
In each of those reducers the initial store is empty, but once the App component mounts I use useEffect to replace each initial store inside a reducer with the one I receive with axios.get using redux-thunk. It looks like this in every reducer:
let initialState = [];
const SET_STATE = 'SET_STATE';
const configReducer = (state = initialState, action) => {
switch (action.type) {
case SET_STATE: {
return { ...action.state};
}
default:
return state;
}
const setState = (state) => ({ type: SET_STATE, state });
export const getConfigState = () => (dispatch) => {
getAPI.getConfig() //I import getAPI with all the REST API logic//
.then(response => {
dispatch(setState(response));
})
};
And the App trigger is:
const App = (props) => {
useEffect(() => {
props.getConfigState();
props.getDataState();
props.getGameState();
}, []);
return (
//JSX//
);
}
export default compose(connect(null, { getConfigState, getDataState, getGameState }))(App);
However, when the App mounts, I have this mess:
In the end, I get the state of each reducer replaced with the state of the one whose promise resolved the last one. I can try to wrap the app 2 more times with a HOC that does nothing but re-writes a state of the precise reducer, but I would still like to understand what causes a promise to affect other reducers besides the one he needs to effect.
A silly mistake, but maybe someone has the exact same problem - the solution is to give different case names for each reducer - SET_STATE need to become SET_GAME_STATE, SET_CONFIG_STATE, SET_DATA_STATE respectivly. I believe that's because of my misunderstanding on how the dispatch works.

Problem with refreshing values in redux reducer. Why can't I create action and assign new values of state?

I can't update my store values using action creator. All needed values I get from component normally, but just can't substitute the initial state values with them. I believe I made a stupid mistake or even misspelled something, but I spent a lot of time already, and the console in the browser still shows me the empty strings in that reducer's state.
let initialstate = {
login: "",
password: ""
}
const formReducer = (state = initialstate ,action) => {
switch (action.type) {
case 'SET-FORM-DATA': {
return {
...state,
login: action.login,
password: action.password
}
}
default: return state;
}
}
export const SetFormData = (login, password) => ({
type: 'SET-FORM-DATA', login, password
})
export default formReducer;
That formReducer is normally assigned in redux-store
import { applyMiddleware, combineReducers, createStore } from 'redux';
import authReducer from './auth-reducer';
import dialogsReducer from './dialogs-reducer';
import profileReducer from './profile-reducer';
import usersReducer from './users-reducer';
import thunkMiddleware from 'redux-thunk';
**import formReducer from './form-reducer';**
let reducers = combineReducers({
profilePage: profileReducer,
dialogPage: dialogsReducer,
usersPage: usersReducer,
auth: authReducer,
form: formReducer
})
let store = createStore(reducers, applyMiddleware(thunkMiddleware));
window.store = store;
export default store;
I call this action creator on submit of form like that:
const onSubmit = formData => {
props.SetFormData(formData.Login, formData.password)
}
And this call seems to be, luckily works alright
I am Brazilian and therefore I speak Portuguese, but I will use the translator to try to help you.
I didn't quite understand the problem, but to handle redux we must use dispatch. You are not using this, you are just passing redux values. Sorry if I got it wrong, but I use something like this:
import { createTypes } from "reduxsauce";
export const gradesTypes = createTypes(
`
SET_GRADES
RESET_STATE
`,
{ prefix: "GRADES/" }
);
const setGrades = (grades) => {
return (dispatch) => dispatch({ type: gradesTypes.SET_GRADES, grades });
};
const resetState = () => {
return (dispatch) => dispatch({ type: gradesTypes.RESET_STATE });
};
export const gradesActions = {
setGrades,
resetState,
};

Set `store` as reducer's `state` parameter

I'm new to Redux (and React as well) so this is probably a very elementar question, but I was unable to find a straight answer for it over the Internet.
There is a sign up screen on my app that I call SignUp. It is pretty straightforward and have just three inputs (email, password and passwordCheck) and one button (signup).
Thing is, I want to make my user life as simple as possible, so as passwordCheck is changed, I check it against password. If them match, signup is setted enabled and if them don't, signup goes disabled and a message is show to the user.
When I was using only React things were pretty simple - I simply made a this.state:
this.state: {
// ... stuff
password: "",
passwordIsValid: false, //test password against a predicate function
passwordMessage: "", //if the password is not valid there is a message
passwordStyle: "", //if password is wrong i put some classes here
passwordCheck: "",
passwordCheckMessage: "", //the 'passwords do not match' message goes here
passwordCheckStyle: "",
passwordsMatch: false
}
And then I handled the app's state inside SignUp.
Now I'm using Redux as well, so I killed this.state, moved everything to store and started using reducers instead of handlers.
Everything works for password, but passwordCheck is a different story. Since I need to know Store's password to check it against passwordCheck I have been unable (so far?) to do it on passwordCheckReducer. Instead, I removed passwordCheckMessage, passwordCheckStyle and passwordsMatch from passwordCheckReducer, calculating this values on SignUp. It seems to me as a very wrong and ugly way to settle the whole thing down though.
So, instead, I would like a more elegant and Redux-like solution.
If I could get store to be on passwordCheckReducer's state I would be able to set passwordsMatch and the others in the reducer while keeping it pure. Unfortunately, I have been unable to do it (so far?).
So, I would like to know if what I want to do is possible or if there's others, more desirables, ways to do it.
OBS1: Wherever I look on the Internet, I found the Redux official documentation on the subject of initializing state1. I do not think preloadedState is the way to resolve my problem, though, since it is used to iniatilize store, not to make it avaiable on my reducers. Instead, I simply need to have store - even when empty - visible to passwordCheckReducer.
OBS2: I know I could pass store as a key of action, but since the reducer pattern includes a state argument it seems redundant to me to define pass such in action.
Here is my Store:
// store.js
import { applyMiddleware, createStore } from 'redux';
import { createLogger } from 'redux-logger';
import thunkMiddleare from 'redux-thunk';
import { Reducers } from '../reducers/reducers';
const logger = createLogger();
const middleware = applyMiddleware(thunkMiddleare, logger);
export const Store = createStore(Reducers, middleware);
I'm using combineReducers:
// reducers.js
import { combineReducers } from 'redux';
import { emailReducer } from './emailReducer';
import { passwordReducer } from './passwordReducer';
import { passwordCheckReducer } from './passwordCheckReducer';
export const Reducers = combineReducers({
emailChange: emailReducer,
passwordChange: passwordReducer,
passwordCheckChange: passwordCheckReducer
});
And here is passwordCheckReducer:
// passwordCheckReducer.js
import { CHANGE_PASSWORDCHECK } from '../actions/actionTypes';
const initialState = {
passwordCheck: ""
};
export const passwordCheckReducer = (state=initialState, action) => {
const { payload, type } = action;
switch(type) {
case CHANGE_PASSWORDCHECK:
const passwordCheck = payload;
return {
...state,
passwordCheck
};
default:
return state;
}
};
Last, this is the implementation of mapDispatchToProps:
// SignUp.js
const mapDispatchToProps = dispatch => ({
handleEmailChange: e => dispatch(updateEmail(e.target.value)),
handlePasswordChange: e => dispatch(updatePassword(e.target.value)),
handlePasswordCheckChange: e => dispatch(updatePasswordCheck(e.target.value))
});
If I remember correctly, when you pass your reducer to combineReducers, that reducer will receive the redux module state (so, what the initialState was, not the entire app's root state object). You don't have access to state from other reducers.
You have a few options.
I think having separate reducers for password and passwordCheck is somewhat overkill. I would go with one reducer for the entire form:
const initialState = {
password: '',
passwordCheck: '',
// ....
}
export const signupReducer = (state=initialState, action) => {
const { payload, type } = action;
switch(type) {
case CHANGE_PASSWORD:
return { ...state, password: action.password}
case CHANGE_PASSWORDCHECK:
const passwordCheck = payload;
if (state.password != state.passwordCheck)
return { ...state, passwordCheck, error: "..."}; // or something like this
return {
...state,
passwordCheck
};
default:
return state;
}
};
If you want to split things up, you can always define reducers, and call them from a parent reducer, passing the whole parent-reducer state. Something like:
import passwordReducer from './passwordReducer'
import passwordCheckReducer form './passwordCheckReducer'
export const signupReducer(state = initialState, action) => {
state = passwordReducer(state, action)
state = passwordCheckReducer(state, action)
return state
}
If you are combining reducers, there is only one store object which stores all of the state from every reducer.
So you can access multiple reducers from one component like this:
const mapStateToProps = state => ({
password: state.passwordChange.password,
passwordCheck: state.passwordCheckChange.passwordCheck,
passwordsMatch: state.passwordCheckChange.passwordsMatch
})
export default connect(mapStateToProps, mapDispatchToProps)(SignUp)
Every time you update passwordCheck or password, your component's props will be updated.
So the best solution is actually to handle the passwordsMatch as a custom prop derived from the state of the two separate reducers. Something like:
const mapStateToProps = state => {
const passwordsMatch = state.passwordChange.password === state.passwordCheckChange.passwordCheck
return {
password: state.passwordChange.password,
passwordCheck: state.passwordCheckChange.passwordCheck,
passwordsMatch: passwordsMatch
}
}

React component not refreshing when redux state has been updated with successful action

New to React/Redux combo and trying to work through an issue.
When a user first visits or logs in / a fetch_user api request is made. The intention is that the page would display differently based on their login status. In redux dev tools I can see the state being updated and fields being populated to 'auth' after the initial state, however, while I am in a subcomponent of the app the value is seen as undefined. Please let me know if you need any more information. Thanks in advance.
// app.js
const initialState = {};
const history = createHistory();
const store = configureStore(initialState, history);
const MOUNT_NODE = document.getElementById('app');
const render = messages => {
ReactDOM.render(
<Provider store={store}>
<LanguageProvider messages={messages}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</LanguageProvider>
</Provider>,
MOUNT_NODE,
);
};
// index.js
class App extends React.Component {
componentDidMount() {
console.log('here');
this.props.fetchUser();
}
render() {
return (
<ThemeWrapper>
<AppContext.Consumer>
.....
App.propTypes = {
fetchUser: PropTypes.any.isRequired
};
export default withRouter(connect(null, actions)(App));
import { FETCH_USER } from '../actions/types';
export default function (state = null, action) {
switch (action.type) {
case FETCH_USER:
console.log('1');
return action.payload || false;
default:
return state;
}
}
// actions
export const fetchUser = () => async dispatch => {
const res = await axios.get('/api/current_user');
// res is the output of the axios request
dispatch({ type: FETCH_USER, payload: res.data });
};
// Banner.js - auth = undefined
render() {
console.log(this.props);
// === auth = undefined. I may be accessing it incorrectly
const mapStateToProps = state => ({
gradient: state.getIn([reducerUI, 'gradient']),
chat: state.getIn([chatUI, 'chatSelected']),
auth: state.auth
});
const BannerMaped = connect(
mapStateToProps,
)(Banner);
// configure store
export default function configureStore(initialState = {}, history) {
// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middlewares = [sagaMiddleware, routerMiddleware(history), reduxThunk];
const enhancers = [applyMiddleware(...middlewares)];
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
/* eslint-disable no-underscore-dangle, indent */
const composeEnhancers =
process.env.NODE_ENV !== 'production' &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// TODO Try to remove when `react-router-redux` is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading
// Prevent recomputing reducers for `replaceReducer`
shouldHotReload: false,
})
: compose;
/* eslint-enable */
const store = createStore(
createReducer(),
fromJS(initialState),
composeEnhancers(...enhancers),
);
// Extensions
store.runSaga = sagaMiddleware.run;
store.injectedReducers = {}; // Reducer registry
store.injectedSagas = {}; // Saga registry
// Make reducers hot reloadable, see http://mxs.is/googmo
if (module.hot) {
module.hot.accept('./reducers', () => {
store.replaceReducer(createReducer(store.injectedReducers));
});
}
return store;
}
Redux store updates are mapped to individual components and not the whole app.
This line means, only the Banner component will be re-rendered when the store is updated and not your entire app.
const BannerMaped = connect(
mapStateToProps,
)(Banner);
So wherever your Banner component is, every time fetchUser() response succeeds and updates the store, only your Banner component will be re-rendered. If you need to re-render other components, they should also subscribe to store with corresponding mapStateToProps.
You also need to pass dispatch actions in connect method. In your case, you have already make fetchUser() action. So, you can pass it in your connect method like this:
const BannerMaped = connect(
mapStateToProps,
fetchUser
)(Banner);
I think this will help.
I was doing everything correctly just not accessing the state object appropriately. Stared at this one a little too long.
const mapStateToProps = state => ({
gradient: state.getIn([reducerUI, 'gradient']),
chat: state.getIn([chatUI, 'chatSelected']),
auth: state.getIn(['auth'])
});

Map state to props not updating after Redux

My Redux Store is correctly being updated which can be seen using React Native Debugger. However, the props inside my component are not updating and are undefined.
In my component below you can see I have correctly mapped to the "sessionModerator" reducer. I have verified this and can see the prop when consoling this.props.
Component:
const mapStateToProps = state => {
return {
session: state.screenReducers.session,
list: state.screenReducers.sessionList,
sessionUser: state.screenReducers.sessionUser,
user: state.sharedReducers.user,
sessionListItem: state.screenReducers.sessionListItem,
sessionSortOrder: state.sharedReducers.sessionSortOrder,
sessionModerator: state.sharedReducers.sessionModerator
};
};
My reducer is added as seen below:
Reducers Index file:
import { reducer as sessionModerator } from './session/reducers/session-moderator';
export const reducers = combineReducers({
sessionModerator: sessionModerator,
});
Actions File:
import Types from '../../../types';
export const start = () => {
return {
type: Types.TYPES_SESSION_MODERATOR_START,
payload: true
};
};
export const stop = () => {
return {
type: Types.TYPES_SESSION_MODERATOR_STOP,
payload: false
};
};
Reducers File:
import Types from '../../../types';
export const reducer = (state = false, action) => {
switch (action.type) {
case Types.TYPES_SESSION_MODERATOR_START:
return action.payload;
case Types.TYPES_SESSION_MODERATOR_STOP:
return action.payload;
default:
return state;
}
};
In the below image you can see that the store is updated as the value for sessionModerator is set to "true", but the console of the actual props during the operation is undefined.
What I have tried:
I have tried various things mostly revolving around the structure of my state, for example, I tried adding the boolean inside an actual object and updating the value as an object property but that didn't seem to work. I feel like I am not updating the boolean correctly but haven't been able to figure it out.
Any help would be greatly appreciated. Thank you.
sessionModerator is in screenReducers in the debugger not in sharedReducers as in your mapStateToProps.
Try this one:
const mapStateToProps = state => {
return {
session: state.screenReducers.session,
list: state.screenReducers.sessionList,
sessionUser: state.screenReducers.sessionUser,
user: state.sharedReducers.user,
sessionListItem: state.screenReducers.sessionListItem,
sessionSortOrder: state.sharedReducers.sessionSortOrder,
sessionModerator: state.screenReducers.sessionModerator
};
};

Categories