React/redux, displaying multiple components, sharing same actions, but with different state - reactjs

Let's say I have a reusable container. It is a wizard with multiple pages.
The wizard state is driven by redux/actions. When an action is fired, I use a reducer to update my state.
What if I want to have multiple wizards duplicated, with it's own state?
I am thinking there must be a way to have actions handled by a certain dynamic reducer (that can be created/destroyed), and then have each individual wizard driven from these dynamic pieces of the store/state.
Is this recommended? Are the libraries out there that make this easier?

Just partition your main state into as many wizard states as you need and send a wizard id along with each action so that your reducer knows which one to tackle.
As Array
{
wizards: [
{ id: 'A', state: true },
{ id: 'B', state: false },
{ id: 'C', state: true }
]
}
You can write a wizard reducer which understands how to reduce a single wizard state.
function wizardReducer(wizard, action) {
switch(action) {
case 'TOGGLE':
return {
id: wizard.id,
state: !wizard.state
};
default:
return wizard;
}
}
Then write a wizardsReducer which understands how to reduce a list of wizards.
function wizardsReducer(wizards, action) {
return wizards.map(function(wizard) {
if(action.id == wizard.id) {
return wizardReducer(wizard, action);
} else {
return wizard;
}
});
}
Finally, use combineReducers to create a root reducer which delegates responsibility for the wizards property to this wizardsReducer.
combineReducers({
wizards: wizardsReducer
});
As Object
If you're storing your wizards in an object instead, you'll have to construct your wizardsReducer slightly differently.
{
wizards: {
A: { id: 'A', state: true },
B: { id: 'B', state: false },
C: { id: 'C', state: true }
}
}
It wouldn't make much sense to map over the states, when we can just select the state we need straight away.
function wizardsReducer(wizards, action) {
if(!(action.id in wizards)) return wizards;
const wizard = wizards[action.id];
const updatedWizard = wizardReducer(wizard, action);
return {
...wizards,
[action.id]: updatedWizard
};
}

The OP asked for a lib for this, so I am just throwing it here.
I created infra functions which will intercept the actions and add meta-data to each action. (following FSA)
You can use this easily to create multiple containers without them affecting each other.
reducer-action-interceptor

Related

Update deeply nested state object in redux without spread operator

I've been breaking my head for a week or something with this !!
My redux state looks similar to this
{
data: {
chunk_1: {
deep: {
message: "Hi"
}
},
chunk_2: {
something: {
something_else: {...}
}
},
... + more
},
meta: {
session: {...},
loading: true (or false)
}
}
I have an array of keys like ["path", "to", "node"] and some data which the last node of my deeply nested state object should be replaced with, in my action.payload.
Clearly I can't use spread operator as shown in the docs (coz, my keys array is dynamic and can change both in values and in length).
I already tried using Immutable.js but in vain.. Here's my code
// Importing modules ------
import {fromJS} from "immutable";
// Initializing State ---------
const InitialState = fromJS({ // Immutable.Map() doesn't work either
data: { ... },
meta: {
session: {
user: {},
},
loading: false,
error: "",
},
});
// Redux Root Reducer ------------
function StoreReducer(state = InitialState, action) {
switch (action.type) {
case START_LOADING:
return state.setIn(["meta"], (x) => {
return { ...x, loading: true };
});
case ADD_DATA: {
const keys = action.payload.keys; // This is a valid array of keys
return state.updateIn(keys, () => action.payload); // setIn doesn't work either
}
}
Error I get..
Uncaught TypeError: state.setIn (or state.updateIn) is not a function
at StoreReducer (reducers.js:33:1)
at k (<anonymous>:2235:16)
at D (<anonymous>:2251:13)
at <anonymous>:2464:20
at Object.dispatch (redux.js:288:1)
at e (<anonymous>:2494:20)
at serializableStateInvariantMiddleware.ts:172:1
at index.js:20:1
at Object.dispatch (immutableStateInvariantMiddleware.ts:258:1)
at Object.dispatch (<anonymous>:3665:80)
What I want ?
The correct way to update my redux state (deeply nested object) with a array containing the keys.
Please note that you are using an incredibly outdated style of Redux. We are not recommending hand-written switch..case reducers or the immutable library since 2019. Instead, you should be using the official Redux Toolkit with createSlice, which allows you to just write mutating logic in your case reducers (and thus also just using any helper library if you want to use one).
Please read Why Redux Toolkit is how to use Redux today.
you could use something like that:
import { merge, set } from 'lodash';
export default createReducer(initialState, {
...
[updateSettingsByPath]: (state, action) => {
const {
payload: { path, value },
} = action;
const newState = merge({}, state);
set(newState, path, value);
return newState; },
...}

React Redux: update local state with a new value on action dispatch

I have an action that creates a 'supplier' in the database, and when it finishes it calls another action that is supposed to create a supplier locally (through redux).
This is very basic but I am a bit lost on how to add the new supplier to the already existing supplier state.
Here is the action (The newly created supplier is passed to it):
export const createSup = (sup) => {
return {
type: "CREATE_SUP",
sup,
};
};
Here is my reducer file along with the error line (Everything working fine except for the CREATE_SUP case):
const initialState = {
value : {},
loaded: false,
error: false
}
const supReducer = (state = initialState, action) => {
switch (action.type) {
case 'LOAD_SUPPLIERS':
return {
value: action.sups,
loaded: false, //this might need to be set to true
error: false
}
case 'LOAD_SUPPLIERS_ERROR':
console.log("load suppliers error")
return {
...state,
loaded: false,
error: true
}
case 'LOAD_SUPPLIERS_SUCCESS':
return {
...state,
loaded: true,
error: false
}
case 'CREATE_SUP':
return {
value: {...state.value, action.sup}, //************ERROR *************//
loaded: true,
error: false
}
default:
return state;
}
};
export default supReducer;
It gives me an error mark under action.sup (I guess it expects only one value). Should I not worry about returning the old value of the state, and just return the new supplier? (I think I am missing a point or two on how exactly reducers work).
Thank you for any help.
My guess would be that you meant to use the spread operator, like so:
case 'CREATE_SUP':
return {
value: { ...state.value, ...action.sup },
loaded: true,
error: false
}
action.sup isn't an object property, it's an object. I assume you just want to apply those properties to your state object.
I should also note that your reducer should typically always return a state object in the same "shape" for every action. So if your initial state object is { prop1: 1, prop2: 2, prop3: 3 }, it should always return something similar to that, just with different values. I say this because it looks like your "LOAD_SUPPLIERS" action is kinda doing its own thing and not copying the current state. Just remember that your state object is meant to be accessed "globally" (I use this term loosely) with Redux. The components that use your Redux state will expect a certain object shape and certain properties to always be there, so you need to make sure you always return that with your reducer. Hope that makes sense.

Change state from reducer not from root state using React-Redux when data is inherited

Good afternoon. I am writing an application using react-redux and faced a dilemma. I have already re-thought it several times and can't choose how to organize the project and data structure correctly and conveniently. Design data by inheriting or composing data. I initially went along the path of composition, but I realized that it is inconvenient when there is a one-to-one relationship. I decided to change it to inheritance, because it seemed logical from the point of view of data organization, but there was a big difficulty with reducers, more precisely, I am confused that it turns out to be a single root reducer with a lot of actionTypeskeys . I remember about performance, when elements inherit a data chain from a common ancestor, that this is very bad. And yet I chose this path and I have a question: Please tell me if it is possible to split into several reducers for each level of nesting data. Example
onst initState: IPages = {
idActive: 0,
pages: [
{
id: 1,
title: `Tab #1`,
workspace: {
idActiveDraggableElements: [],
idActiveLines: [],
attributes: {
height: string,
width: string,
viewBox: [0, 0, 5000, 5000]
},
draggableElements: [], // more data
lines: [], // more data
}
},
]
}
Reducer:
export function pagesReducer(
state: IPages = initState,
action: IPageActionTypes
) {
switch (action.type) {
case "ADD_PAGE":
let uniqId = getUniqKeyIdOfArrayList(state.pages);
return {
...state,
pages: state.pages.concat({id:uniqId, title:`Вкладка - ${uniqId}`})
}
case "REMOVE_PAGE": return {
...state,
pages: state.pages.filter(item => item.id !== action.id)
}
case "CHOSE_PAGE": return {
...state,
idActive: action.id
}
case "RENAME_PAGE":
let indexPage = state.pages.findIndex(item => item.id === action.id);
state.pages[indexPage].title = action.title;
return {
...state
}
// ===================
// LONG LIST WHAT BAD...
// It's a bad idea to add editing to the `workspace` field and then `draggableElements`. `lines`
// ... but I understand that this will happen, because I don't know if there is another way.
default:
return state
}
}
Can I edit the `workspace' node without updating the entire application state?
Thanks you for any help.
For data modeling aspect for a 1-to-1 relationship, you can choose either to reference by id or to embed the data. It depends on your query pattern.
In your case which is embedding, you can make use of memoized selectors.
Ideally, since you have an idActive, update your pages data structure to be an object instead of a list.
Like so:
{
pages: {
'1': {
workspace: { ... },
}
}
}
Then for your reducer, think of it as slicing a tree (or nested attribute). Your reducer would then look something like:
function workspaceReducer(state, action) {
// TODO
}
function pagesReducer(state, action) {
switch (action.type) {
case 'UPDATE_WORKSPACE': {
const { id } = action;
const page = Object.assign({}, state.pages[id]);
return {
...state,
pages: {
...state.pages,
[id]: {
...page,
workspace: workspaceReducer(page.workspace, action)
}
}
}
}
}
}
Then to prevent unnecessary re-renders, using memoized selectors,
it would be like:
import { createSelector } from 'reselect';
const pages = state => state.pages;
const activePage = state => state.idActive;
const getActivePage = createSelector(
activePage,
pages,
(id, pages) => pages[id]
);
const getWorkspace = createSelector(
getActivePage,
page => page.workspace
);

ReactJS - Proper way for using immutability-helper in reducer

I have the following object which is my initial state in my reducer:
const INITIAL_STATE = {
campaign_dates: {
dt_start: '',
dt_end: '',
},
campaign_target: {
target_number: '',
gender: '',
age_level: {
age_start: '',
age_end: '',
},
interest_area: [],
geolocation: {},
},
campaign_products: {
survey: {
name: 'Survey',
id_product: 1,
quantity: 0,
price: 125.0,
surveys: {},
},
reward: {
name: 'Reward',
id_product: 2,
quantity: 0,
price: 125.0,
rewards: {},
},
},
}
And my reducer is listening for an action to add a reward to my object of rewards:
case ADD_REWARD:
return {
...state, campaign_products: {
...state.campaign_products,
reward: {
...state.campaign_products.reward,
rewards: {
...state.campaign_products.reward.rewards,
question: action.payload
}
}
}
}
So far so good (despite the fact that every object added is named "question")... its working but its quite messy. I've tried to replace the reducer above using the immutability-helper, to something like this but the newObh is being added to the root of my state
case ADD_REWARD:
const newObj = update(state.campaign_products.reward.rewards, { $merge: action.payload });
return { ...state, newObj }
return { ...state, newObj }
First, you must understand how the object shorthand works. If you're familiar with the syntax before ES2015, the above code translates to:
return Object.assign({}, state, {
newObj: newObj
});
Note how the newObj becomes a key and a value at the same time, which is probably not what you want.
I assume the mentioned immutability-helper is this library: https://www.npmjs.com/package/immutability-helper. Given the documentation, it returns a copy of the state with updated property based on the second argument.
You're using it on a deep property so that it will return a new value for that deep property. Therefore you still have to merge it in the state, so you have to keep the approach you've labelled as messy.
What you want instead is something like:
const nextState = update(state, {
$merge: {
campaign_products: {
reward: {
rewards: action.payload
}
}
}
});
return nextState;
Note how the first argument is the current state object, and $merge object is a whole object structure where you want to update the property. The return value of update is state with updated values based on the second argument, i.e. the next state.
Side note: Working with deep state structure is difficult, as you've discovered. I suggest you look into normalizing the state shape. If applicable, you can also split the reducers into sub-trees which are responsible only for the part of the state, so the state updates are smaller.

Comment on the validity of this redux simplify

I have some coding experience with React & Redux one and half year ago. And then I have been using Vue & Vuex in this year. I like the simplicity of the Vuex structure. So when I return to React & Redux. I want to try some way to make Redux code structure become more simplify.
I have try to define the State and Action using a single object(like Vuex). Then use reduxGenerator function to generate 'reducer' and 'action creator'.
But I didn't think deeply about this. I always worried about this method will cause some problems.
I know that Redux splits the 'state', 'reducer', 'action type' and 'action creator' to make the structure clearer. The team can work together to write different part of the entire state. But more often in my team. Everyone is assigned a different module. One developer needs to face all 'state', 'reducer', 'action type' and 'action creator' code in multiple place. And the Developer need to manually write some 'mechanized' code. But why developers still maintain this initial Redux code structure all the time. Is my method causing some serious problems?
Example code:
https://codesandbox.io/s/redux-simplify-v07n9
Redux Object
const lists = {
naming: "lists",
initState: [
{
id: idGenerator(),
name: "List 1"
},
{
id: idGenerator(),
name: "List 2"
}
],
actions: {
addList(state) {
return [
...state,
{
id: idGenerator(),
name: "New List"
}
];
},
// ....
}
}
export default reduxGenerator(lists);
reduxGenerator
export default ({ naming, initState, actions }) => {
const redux = {};
redux.actionCreator = Object.fromEntries(
Object.entries(actions).map(action => [
action[0],
args => ({
type: `${naming}/${action[0]}`,
args
})
])
);
redux.reducer = (state = initState, action) => {
console.log(`%c action: ${action.type}`, "color: #00BEBE");
const actionType = /.+\/.+/.test(action.type)
? action.type.split("/")[1]
: action.type;
if (Object.keys(actions).includes(actionType)) {
return actions[actionType](state, action.args);
}
console.error(`${action.type} not found!`);
return state;
};
return redux;
}
So I hope to discuss the rationality of this. To help me jump out of my personal limitations.
This may not be a general question in stackoverflow. Please forgive me.

Resources