I want to create a Redux store that has this shape:
store = {
loaded: Boolean,
loading: Boolean,
view: Object, // uses combineReducers
layers: Object // uses combineReducers
}
So far, my root reducer looks like this:
rootReducer.js
import view from './view';
import layers from './layers';
const initialState = {
loaded: false,
loading: false,
};
function loadState(state = initialState, action = {}) {
switch (action.type) {
case 'LOADED':
return {
...state,
loaded: true,
loading: false,
};
case 'LOADING':
return {
...state,
loaded: false,
loading: true,
};
default:
return state;
}
}
export default combineReducers({
view,
layers,
// hmmmm, putting loadState here would give me a loadState object property,
// not loose 'loaded' and 'loading' properties
});
How do I also have these "loose" properties like loaded and loading alongside them?
Sometimes I find writing individual reducers for a few simple properties to be obnoxious overhead, so I have a combineReducersWithRoot utility I use sometimes.
export function combineReducersWithRoot(rootReducer, reducers) {
return (state, action) => {
// Ensure the root state object is a new object; otherwise
// React may not re-render.
let newState = {...rootReducer(state, action)};
Object.keys(reducers).forEach(domain => {
let obj = state ? state[domain] : undefined;
newState[domain] = reducers[domain](obj, action);
});
return newState;
};
}
Now, given a state structure something like this:
{
loading: bool
loaded: bool
data: {
filter: string
arr: object[]
}
}
You can do this:
function rootReducer(state = {loading: false, loaded: false}, action) {
switch(action.type) {
case STARTED_LOADING:
return {...state, loading: true, loaded: false};
case FINISHED_LOADING:
return {...state, loading: false, loaded: true};
default:
return state;
}
}
function dataReducer(state = {filter: '', arr: []}, action) {
switch (action.type) {
case SET_FILTER:
return {...state, filter: action.value};
case SET_DATA:
return {...state, arr: action.arr};
default:
return state;
}
}
export default combineReducersWithRoot(rootReducer, {data: dataReducer});
#PhiNguyen is right, I need to turn these loaded/loading properties into their own reducers!
import { LOADED, LOADING } from '../ActionTypes';
export function loaded(state = false, action = {}) {
switch (action.type) {
case LOADED:
return true;
case LOADING:
return false;
default:
return state;
}
}
export function loading(state = false, action = {}) {
switch (action.type) {
case LOADED:
return false;
case LOADING:
return true;
default:
return state;
}
}
rootReducer.js
import { loaded, loading } from './load';
import view from './view';
import layers from './layers';
export default combineReducers({
loaded,
loading,
view,
layers
});
Related
So this is my current reducer:
import { Reducer } from 'redux';
import {
EventState,
LOAD_EVENTS,
LOAD_EVENT_BY_ID,
FETCH_MORE_EVENTS
} from '../types/eventTypes';
export const initialState = {
eventsList: [],
event: undefined,
isLastPage: false
};
const eventReducers: Reducer<EventState, any> = (
state = initialState,
action
) => {
switch (action.type) {
case LOAD_EVENTS:
return {
...state,
eventsList: action.eventsList
};
case FETCH_MORE_EVENTS:
return {
state,
eventsList: state.eventsList.concat(action.eventsList),
isLastPage: action.eventsList.length === 0
};
default:
return state;
}
};
export default eventReducers;
As you see both cases LOAD_EVENTS and FETCH_MORE_EVENTS share the key eventsList, on fetch more events I am calling state like this state instead of ...state because it seems to re init the state of the whole reducer. But, is that the proper way? I think that if this reducer grows up, that will be a bug.
So what can I do to clean that reducer properly to make? Like all I need is that LOAD_EVENTS fires then eventsList should get clear and fill out again by what LOAD_EVENTS brings. And basically I only need to reset the state of eventsList but rest should remain the same.
when you calling state like state instead of ...state, you aren't re-init the state, but storing the previous state inside the new state, like this example below:
state = {
eventsList: [...someEvents],
event: undefined,
isLastPage: false,
state: {
eventsList: [...someEvents],
event: undefined,
isLastPage: false,
state: {
eventsList: [...someEvents],
event: undefined,
isLastPage: false
}
}
};
This is not a good pattern/practice, only if is super necessary.
So the correct, it's reset the previous state with initialState when fetch more events.
export const initialState = {
eventsList: [],
event: undefined,
isLastPage: false
};
const eventReducers: Reducer<EventState, any> = (
state = initialState,
action
) => {
switch (action.type) {
case LOAD_EVENTS:
return {
...state,
eventsList: action.eventsList
};
case FETCH_MORE_EVENTS:
return {
...initialState,
eventsList: state.eventsList.concat(action.eventsList),
isLastPage: action.eventsList.length === 0
};
default:
return state;
}
};
But how you say, it's only need to reset the state of eventsList but rest should remain the same, you can keep the same to this reducer:
case LOAD_EVENTS:
return {
...state,
eventsList: action.eventsList
};
Because when you set eventsList like the example above, you are reset the eventsList and fill out again with new data. But don't forget the problem about the first example that I say.
I came across these reducers in the codebase I am working on at my job.
const ACTION_HANDLERS = {
[LOGIN_REQUEST]: (state, action) => ({
...state,
isAuthenticating: true
}),
[LOGIN_SUCCESS]: (state, action) => ({
...state,
isAuthenticating: false,
isAuthenticated: true,
userId: action.userId,
authToken: action.auth,
authTTL: action.ttl,
authCreatedAt: action.created,
isNewUser: action.isNewUserFlag
}),
};
export default function authReducer(state = initialAuthState, action) {
const handler = ACTION_HANDLERS[action.type];
if(handler!==undefined){
console.log('login handler',handler);
// debugger;
}
return handler ? handler(state, action) : state;
}
My concern is related to how to debug this method of pre-written reducers.
I introduced console logs before every [] in ACTION_HANDLERS but they are syntactically wrong.
I had written reducers before and they were like this.
export default function FundsReducer(state=INITIAL_STATE,action={}){
switch(action.type){
case GET_ALL_FUNDS_FAILED:{
return{
...state,
funds:{
...state.funds,
failed:true,
pending:false,
}
};
}
case GET_ALL_FUNDS_PENDING:
{
let {options}=action.payload;
return{
...state,
funds:{
...state.funds,
data:[],
failed:null,
pending:true,
}
};
}
case GET_ALL_FUNDS:
{
let data;
data=action.payload.response.data;
return{
...state,
funds:{
...state.funds,
data,
pending:false,
}
}
}
I am having difficulty in debugging these reducers and introducing console logs .
You can use redux middleware as mentioned by #remix23 or just change your action as below so you will able to log state or action.
[LOGIN_REQUEST]: (state, action) => {
console.log(action);
return {
...state,
isAuthenticating: true
}
}
Hope this will help you.
How would i convert this with usage of Immutable JS.
fromJS to wrap initial state and setIn method for nested cases.
const initialState = {
allMovies: []
};
const movieReducer = (state = initialState, action) => {
const {type, payload} = action;
switch (type) {
// some actions...
case movieActionTypes.FETCH_MOVIES_SUCCESS: {
return {
...state,
allMovies: {
...state.allMovies,
[payload.movie.id]: {
...state.allMovies[payload.movie.id],
...payload.movie,
ready: false,
},
},
};
}
default:
return state;
}
};
Ok so i'll try to sum it up. I've modified my code to have 2 reducers instead of 1 (for better reading) and i started getting an infinite loop.
The redux props seem to keep updating, however there's no change in them.
This is my reducer file.
import { ADD_LISTITEM } from "../constants/action-types.jsx";
import { SET_SPOTOKEN } from '../constants/action-types.jsx';
import { SET_LISTVIEW } from '../constants/action-types.jsx';
import { CHECK_SPOTOKEN } from '../constants/action-types.jsx';
import { SET_FORMPANEL } from '../constants/action-types.jsx';
import { SET_FORMPANELTYPE } from '../constants/action-types.jsx';
import { combineReducers } from 'redux'
const initialState = {
listItems: [],
spoToken: '',
listView: "All Items",
checkToken: "200",
showNotice: true,
//showFormPanel: false,
//formPanelType: ''
};
const listState = {
showFormPanel: false,
formPanelType: ''
}
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_LISTITEM:
return { ...state, listItems: [...state.listItems, action.payload] };
case SET_SPOTOKEN:
return { ...state, spoToken: action.payload };
case SET_LISTVIEW:
return { ...state, listView: action.payload };
case CHECK_SPOTOKEN:
return { ...state, checkToken: action.payload };
default:
return state;
}
};
const listReducer = (state = listState, action) => {
switch (action.type) {
case SET_FORMPANEL:
return { ...state, showFormPanel: action.payload };
case SET_FORMPANELTYPE:
return { ...state, formPanelType: action.payload };
default:
return state;
}
}
const allReducers = combineReducers({
rootReducer,
listReducer
});
export default allReducers;
Any idea why this might cause a problem?
Edited with Redux State ->
{
root: { … }, newItem: { … }, forms: { … }
} forms: {
$form: { … }, newItem: { … }
} newItem: { Title: "", ItemID: "", OfferingID: "", DeliveryModality: "", Status: "", … }
root: listReducer: {
showFormPanel: false, formPanelType: ""
} rootReducer: {
listItems: Array(4), spoToken: { … }, listView: "All Items", checkToken: "200", showNotice: true
}
Puttin the above into words since it looks kinda messed up.
State contains -> forms, newItem, root. Root contains listReducer and rootReducer.
FIXED: only 1 combineReducers allowed.
Apparently since i didnt have different reducers before adding this one i had another combineReducers in my redux-form file. I assume that was the problem (having 2 combineReducers) because it seems to work fine after modifying it.
I'm having surprisingly difficult time figuring this out, essentially I'm trying to set state to initial state, so far I tried:
// -- Initial state ------------------------------------------------------------
const INITIAL_STATE = {
search: {
listings: []
},
listings: []
}
// -- Story structure for story editor -----------------------------------------
export default function(state = INITIAL_STATE, action) {
switch(action.type) {
case ACTIONS.RESET_STATE:
return { ...state, INITIAL_STATE }
default:
return state;
}
}
this just adds initial state to existing one
case ACTIONS.RESET_STATE:
return { ...state, state = INITIAL_STATE }
this returns error
case ACTIONS.RESET_STATE:
return { ...state, state: INITIAL_STATE }
this is adding initial state to existing one gain
case ACTIONS.RESET_STATE:
return { ...state, search: { listings:[] }, listings: [] }
This works, but I start getting weird mutation errors.
The proposed solution of Anders is right, but has potential problem with immutables. This generates always new object.
case ACTIONS.RESET_STATE:
return { ...INITIAL_STATE };
Look at Jiri Fornous solution instead, as this will mutate your data.
An even easier way is to just return INITIAL_STATE.
case ACTIONS.RESET_STATE:
return INITIAL_STATE;
If you simply want to reset state completely, just return the value of INITIAL_STATE:
export default function(state = INITIAL_STATE, action) {
switch(action.type) {
case ACTIONS.RESET_STATE:
return {
search: {
listings: []
},
listings: []
};
default:
return state;
}
}
If you want to keep the INITIAL_STATE in a single place. Change the initial state creator to a function:
function get_INITIAL_STATE => {
return { search: {
listings: []
},
listings: []
}
}
export default function(state = get_INITIAL_STATE(), action) {
switch(action.type) {
case ACTIONS.RESET_STATE:
return get_INITIAL_STATE();
default:
return state;
}
}