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.
Related
I'm wondering about others approach to Redux useSelector hook. For example in my code:
const selectedStage = useSelector(state => state.basket.stage);
IntelliJ/WebStorm are giving me warning Unresolved variable basket
I see two options: turning off this warning or use destructuring like:
const { basket: { stage: selectedStage} } = useSelector(state => state);
I think it is much less readable. Is there anything I miss or could be done better?
------- EDIT ---------
If I simplify my Redux setup to basic like:
const basketReducer = (state = initialState.basket, action) => {
switch (action.type) {
case CHANGE_BASKET_SETTINGS:
return { ...state, ...action.payload };
...
export default combineReducers({
basket: basketReducer,
...
import allReducers from "../reducers";
export const store = createStore(allReducers);
I get same warning. It's really annoying.
I am new to react and redux ... so please forgive me for noobie mistakes. I read several documentations of redux and came to conclusion that this is how i should store state of react component. I require redux because there are many more nested components which need quick access of data. however... when I try to export store ... I can't find how to do so.
My App.js
export default class App extends Component {
state = {
xx: null,
yy: null
}
componentDidMount(){
//some logic
// State gets data from api
this.setState({
xx: someval,
yy: someval2
});
}
render() {
const obj = {
xx: this.state.xx,
yy: this.state.yy
};
userReducer(obj,updateUserDetails());
const store = createStore(userReducer);
return (
<Provider store={store} >
<UserDetails props ={this.state} />
</Provider>
);
}
}
// Reducer function
export const userReducer = (state, action) => {
console.log("in reducer " + JSON.stringify(state));
switch(action.type) {
case 'UPDATE_USER_INFO':
state = {
...state,
xx: action.payload.xx,
yy: action.payload.yy
}
break;
}
return state;
}
// Doesn't work
export const store = createStore(userReducer)
// Action
export const updateUserDetails = () => {
return {
type: 'UPDATE_USER_INFO'
}
}
I can't figure out way to export store so that it is accessible to nested components. Kindly help
Thanks in advance!
From looking on your code, I can see a few issues that each one can be your problem.
while reducer first loads, it has to hold initial value for the state to begin with, not related to the one you want to be set at component mount
// assiging empty obj as initial value
export const userReducer = (state = {}, action)
Actions of redux are higher order functions, returning object to be dispatch
export const updateUserDetails = () => (dispatch, getState) => {
return dispatch ({
type: 'UPDATE_USER_INFO'
})
}
About your createStore, declare here as well initial value
// assign empty obj as initial value
export const store = createStore(userReducer, {})
hope it is helpful, anyhow I recommended on looking through the docs again
I have this reducer
import { FETCH_WEATHER } from "../actions/index";
export default function(state = [], action) {
switch (action.type) {
case FETCH_WEATHER:
console.log(state)
return [action.payload.data,...state];
default:
return state
}
}
but whenever I console.log the state the result is always undefined
and here is my action
import axios from 'axios';
const API_KEY = '...';
const ROOT_URL = `...`
export const FETCH_WEATHER = 'FETCH_WEATHER';
export function fetchWeather(city){
const url = `${ROOT_URL}&q=${city},us`;
const request = axios.get(url)
return {
type : FETCH_WEATHER,
payload: request,
}
}
I'm thinking to create a constant to hold the data overall and just pass it in reducer but I don't think it's the right approach.
Question:
How can I access the previous state in the reducer so that whenever I console.log there is a value.
Here is the code codesandbox
Previous state is stored in your state variable, as defined in your reducer. Previous state can therefore be accessed in your reducer via the state variable.
It looks like you have a block syntax error that may be causing you problems (see correction below).
Also, if you want to log previous state to the console when ever your reducer is run, consider placing the call to console.log at the beginning of your reducer:
import { FETCH_WEATHER } from "../actions/index";
export default function(state = [], action) {
// console.log(state) // Add this to log previous state for every call to reducer
switch (action.type) {
case FETCH_WEATHER: { // Added { here
console.log(state)
return [action.payload.data,...state];
} // Added } here
default:
return state
}
}
Why do I get undefined returned when I access the state? I use the Redux DevTools and see the state correctly updated via an action but I just cannot access the state values for some reason. I get this sort of object returned when I access state.dog which seems wrong:
ct {size: 1, _root: pt, __ownerID: undefined, __hash: undefined, __altered: false}
Here is my container code:
import { connect } from 'react-redux';
import Message from '../../components/message';
const mapStateToProps = (state) => {
console.log(state.dog.hasBarked);
return {
message: state.dog.hasBarked ? 'Barked' : 'It is quiet',
};
};
export default connect(mapStateToProps)(Message);
Here is the dog reducer:
import * as Immutable from 'immutable';
import { MAKE_BARK } from '../actions/dog';
const initialState = Immutable.Map({
hasBarked: false,
});
const dogReducer = (state: Object = initialState, action: Object) => {
switch (action.type) {
case MAKE_BARK:
return state.set('hasBarked', action.payload);
default:
return state;
}
};
export default dogReducer;
Seems like you are using immutable. state.dog is not a simple js array but a immutable map or list. You can access it natively with state.dog.toObject().hasBarked.
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