This is my reducrer:
export default function dashboardReducer(state=initialState.alerts, action){
switch(action.type){
case constActions.GET_ALERTS_SUCCESS:
return action.alerts;
case constActions.GET_WL_STATISTICS_SUCCESS:
return action.wlStatistics;
default:
return state;
}
};
My root reducer:
const rootReducer = combineReducers({
dashboard
});
In the component, this is the mapStateToProps:
function mapStateToProps(state, ownProps){
return{
alerts: state.dashboard
};
}
Now I have 2 actions GET_ALERTS_SUCCESS and GET_WL_STATISTICS_SUCCESS.
In the component I have the props for actions.alerts, but how can I get a reference to action.wlStatistics in the component? can i call the reducer with an action type?
Your dashboardReducer either returns 'alerts' initialState OR 'alerts' OR 'wlStatistics' for the next state. It should return an object with both of those action payloads as properties:
const initialState = {
alerts: null,
wlStatistics: null
};
export default function dashboardReducer(state=initialState, action){
switch(action.type){
case constActions.GET_ALERTS_SUCCESS:
return Object.assign({}, state, { action.alerts });
case constActions.GET_WL_STATISTICS_SUCCESS:
return Object.assign({}, state, { action.wlStatistics });
default:
return state;
}
};
Your props will now be mapped as
this.props.alerts
and
this.props.wlStatistics
Whenever either action updates the state in the 'dashboardReducer', your component will re-render/receiveProps and the props will be updated with the new values
Related
I am getting an error while using combineReducer method in redux (redux#3.7.2). Same code will work when I am using only one reducer.
Running code here
Code
const { createStore, combineReducers, applyMiddleware } = require ('redux')
const aReducer = (state, action) => {
switch (action.type) {
case 'A':
{
return { ...state };
}
default: return state;
}
return state;
}
const bReducer = (state, action) => {
switch (action.type) {
case 'B':
{
return { ...state };
}
default: return state;
}
return state;
}
const configureStore = (initialState) => {
let rootReducer = combineReducers({ a:aReducer, b:bReducer });
console.log('configureStore', initialState);
const str = createStore(rootReducer, initialState);
return str;
};
const store = configureStore({});
console.log('store',store);
store.subscribe(() => {
console.log(store.getState());
});
In the create store line, if i am replacing the rootReducer to aReducer, this code wont have any problem. I did not understand why the reducers returning undefined state, i am passing initial state as a plane object.
There are two things going on here. Firstly, combineReducers also combines the states of each reducer in an object with the same keys as the argument reducers object, so to initialize each state correctly you'll need:
const store = configureStore({a: {}, b: {}});
This is not enough to fix the problem though, as combineReducers also requires that each reducer can handle state undefined and never returns undefined
(see the docs). If it can't you get this error:
Error: Reducer "..." returned undefined during initialization. If the state passed to the
reducer is undefined, you must explicitly return the initial state. The initial state may
not be undefined. If you don't want to set a value for this reducer, you can use null
instead of undefined.
The confusing thing about this is that the check is done when combineReducers is called (ie. before state initialization,) but the error isn't shown until the reducer is used. This means that even if you initialize the state correctly, and your reducers never receive state undefined, you'll still get the error if the reducers can't handle it.
To fix it, replace (state, action) => { in each reducer by (state = {}, action) => {, or explicitly return null if state is undefined. Note that these initial states are only used if you don't pass an initial state to createStore. To prevent confusion, I usually do all initialization in the reducers and not at createStore.
I've tried all of the related questions here in Stack Overflow and still didn't find a solution to this problem.
I have a reducer called me and I'm trying to update an array of objects in it called folders, whenever I update the me reducer the component doesn't update.
Here's how I'm updating the reducer in my component:
class ComponentA extends Component {
...
updateUploadedFiles(file) {
console.log(this.props.store);
const newFolders = this.props.me.folders.map(
folder =>
folder._id === file.parent._id
? {
...folder,
files: [...folder.files, file.file]
}
: folder
);
this.props.updateMe({
...this.props.me,
folders: newFolders
});
}
...
}
function mapStateToProps(state) {
return {
me: state.me,
path: state.path,
filesToUpload: state.uploads
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
updatePath,
updateMe,
updateUploads
},
dispatch
);
}
export default connect(mapStateToProps, mapDispatchToProps, null, {
pure: false
})(Upload);
this is my updateMe action's code:
export const updateMe = state => ({
type: "UPDATED_ME",
payload: state
});
And this is the me reducer's code:
export default function(state = "NOT_AUTHENTICATED", action) {
switch (action.type) {
case "UPDATED_ME":
return action.payload;
default:
return state;
}
}
Also here's how I'm combining the reducers:
import me from "./me";
...
import { combineReducers } from "redux";
const reducers = combineReducers({
me,
...
});
export default reducers;
This is not how redux works.
In order to update any part of your Redux store you must dispatch an action in order to let Redux "know" that the store changed and update any dependent component.
You state object must be immutable.
I'm learning Redux and have come across an issue that I have not encountered before when using React without redux. I'm trying to display a piece of my state inside one of my components name loginStatus. The reducer I have setup this state with has an initial state but whenever I try and launch the application I get the console error:
Cannot read property 'loginStatus' of undefined
Here is my code:
Component
import React, {Component} from 'react';
import {connect} from 'react-redux';
import { bindActionCreators } from 'redux';
import * as authActions from './userAuthActions';
class App extends Component {
constructor(props) {
super(props);
}
render() {
if(typeof(this.props.userAuthReducer) !== 'undefined') {
test = this.props.userAuthReducer.loginStatus;
console.log(test)
} else {
console.log("it's undefined")
}
return (
<div className={"popup-logins " + this.props.userAuthReducer.loginStatus}>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
userAuthReducer:state.userAuthReducer
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(authActions,dispatch);
};
export default connect(mapStateToProps,mapDispatchToProps)(App);
userAuthActions.js
export const loginUser = () => {
return {
type:'loginUser',
loggedIn:true
}
}
export const toggleRegLog = () => {
return {
type:'toggleRegLog'
}
}
userAuthReducer
let initialState = {
loginStatus: "not-logged-in"
, toggleRegLog: 'login'
};
const userAuthReducer = (state = initialState, action) => {
switch (action.type) {
case 'loginUser':
let newState;
if (action.loggedIn) {
Object.assign({}, state, {
loginStatus: "logged-in"
})
}
else {
Object.assign({}, state, {
loginStatus: "not-logged-in"
})
}
return newState;
break;
default:
return state;
}
}
export default userAuthReducer;
combine reducers
import {combineReducers} from 'redux';
import userAuthReducer from './userAuthReducer';
function lastAction(state = null, action) {
return action;
}
export default combineReducers({
lastAction,userAuthReducer
});
What's strange is that I initially get a console.log of it's undefined" when I first start up the app and then immediately after I get the value "not-logged-in". I need to use this to hide/ show certain parts of my app if the user is logged in.
Normally if I use React without Redux I use this method all the time without any issues but can't understand what I might have done wrong here?
Thanks
You're not really assigning a value to newState in your reducer, so essentially you're returning undefined, which of course doesn't have a loginStatus property. Changing your reducer so to something like this will probably solve the problem:
let initialState = {
loginStatus: "not-logged-in"
, toggleRegLog: 'login'
};
const userAuthReducer = (state = initialState, action) => {
switch (action.type) {
case 'loginUser':
let newState;
if (action.loggedIn) {
newState = Object.assign({}, state, {
loginStatus: "logged-in"
})
}
else {
newState = Object.assign({}, state, {
loginStatus: "not-logged-in"
})
}
return newState;
break;
default:
return state;
}
}
export default userAuthReducer;
Object.assign returns a new object, applies the data from state and the last argument containing the loginStatus property and passes that to the newState variable, which gets returned at the end of the switch case.
Edit
This edit below makes it easier to reason about the logic in the reducer:
let initialState = {
loginStatus: "not-logged-in"
, toggleRegLog: 'login'
};
const userAuthReducer = (state = initialState, action) => {
switch (action.type) {
case 'loginUser':
if (action.loggedIn) {
return Object.assign(state, { loginStatus: "logged-in" })
}
return Object.assign(state, { loginStatus: "not-logged-in" })
default:
return state;
}
}
export default userAuthReducer;
I am new to redux, Is this correct way of doing redux in following code, please?
This is a reducer method when action called to execute currentTime.
import { combineReducers } from 'redux';
import { UPDATE_TIME } from './actions';
import { Map } from 'immutable';
const initialState = Map({update:false, currentTime: ""});
function currentTime(state = initialState, action) {
switch (action.type) {
case UPDATE_TIME:
return {...state, update: true, currentTime: action.time };
default:
return state;
}
}
const currentTimeReducer = combineReducers({
currentTime
});
export default currentTimeReducer
There are multiple ways to do it
You can set the value using set() function
case UPDATE_TIME:
state = state.set('update', true);
return state.set('currentTime', action.time);
or even
case UPDATE_TIME:
return state.set('update', true)
.set('currentTime', action.time);
However this is not feasible when you have multiple changes
The other option is merge()
case UPDATE_TIME:
return state.merge({update: true, currentTime: action.time})
However in case of a nested state update you would need to do a deepMerge. See the details of mergeDeep
We use immutable JS to create new instance on each small change in the existing object. Immutable JS MAP has a set method to set attribute and return new instance of the object.
Here you can find api doc for MAP
import { combineReducers } from 'redux';
import { UPDATE_TIME } from './actions';
import { Map } from 'immutable';
const initialState = Map({update:false, currentTime: ""});
function currentTime(state = initialState, action) {
switch (action.type) {
case UPDATE_TIME:
let newState = state;
newState = newState.set('update', true );
newState = newState.set('currentTime', action.time);
return newState;
default:
return state;
}
}
const currentTimeReducer = combineReducers({
currentTime
});
export default currentTimeReducer
Look best practices in this doc
How can I change the state of MenuReducer in SideBarReducer? The meaning of this is to show a warning message in the menu when the user does an action in the sidebar. The user will in the future also do action in other reducers that will cause the warning in the MenuReducer to pop up.
MenuReducer:
import actionTypes from '../action-types';
export const initialState = {
showWarning: false,
};
export default function MenuReducer(state = initialState, action) {
switch (action.type) {
case actionTypes.REQUEST_REFRESH:
if (action.payload.unsavedChanges) {
return { ...state, showWarning: true };
}
default:
return state;
}
}
SidebarReducer:
import actionTypes from '../action-types';
export default function sideBarReducer(state = initialState, action) {
switch (action.type) {
case actionTypes.REQUEST_CLOSE:
if (action.payload.unsavedChanges) {
// Change the state of MenuReducer
}
default:
return state;
}
}
You can create an action that dispatches 2 actions (using Thunk middleware).
You can also catch the same action in both reducers and update stuff accordingly.
you should not fire action from another reducer, that's anti-pattern. All action need to fire in React component lifecycle or in action creator.
Reducer is only meant for the state change.