Rendering React/Redux app multiple times on a single page? - javascript

I've been working on a React/Redux application for building a quote. A gross simplification of my state would look something like this:
{
account: { name: 'john doe' },
lineItems:[
{ product: {id: 123, ...}, price: 10, units: 5 },
{ product: {id: 124, ...}, price: 10, units: 5 },
],
modifiers: { couponCode: 'asdf', vip: true }
}
and my reducers would be sliced something like this:
const appReducer = combineReducers<GlobalState>({
account: accountReducer,
lineItems: lineItemReducer,
modifiers: modifersReducer,
});
I've just recently gotten a requirements where I would essentially need to be able to render the entire app multiple times on a single page (basically show 1 or more quotes for different accounts on a single page). So a single state would now need to look something like this:
{
quotes: {
"0": {
account: { name: 'john doe' },
lineItems:[
{ product: {id: 123, ...}, price: 10, units: 5 },
{ product: {id: 124, ...}, price: 10, units: 5 },
],
modifiers: { couponCode: 'asdf', vip: true }
},
"1": {
account: { name: 'billy jean' },
lineItems:[
{ product: {id: 123, ...}, price: 10, units: 5 },
],
modifiers: { couponCode: '', vip: false }
},
}
}
But obviously this new state shape doesn't really work with how I've sliced my reducers. Also, seems like I'd have to refactor all my actions so that I know which quote they should be operating on? For example, if I had an action like this:
{
type: 'UPDATE_PRICE'
payload: { productId: 123, newPrice: 15 }
}
Seems like the product 123 on both quotes would be updated.
Maybe there is instead some way I can just render the entire app on the page without having to refactor my entire state? I'm not sure what my best approach would be that wouldn't requirement me to rewrite large portions of the app.

This should give you the idea. It's basically using one reducer inside another one. As simple as using a function within another function body. You can run it on runkit.com as well.
const { createStore, combineReducers } from 'redux';
const UPDATE_ACCOUNT = 'app/updat-account';
const ADD_QUOTE = 'quote/add-quote';
const appActions = {
updateAcount: (q_id, a) => ({ type: UPDATE_ACCOUNT, payload: { q_id, name: a }}),
};
const quoteActions = {
addQuote: q_id => ({ type: ADD_QUOTE, payload: q_id }),
};
const accountReducer = (app = {}, action) => {
const { type, payload } = action;
switch (type) {
case UPDATE_ACCOUNT:
return { ...app, name: payload.name }
default:
return app;
}
};
const appReducer = combineReducers({
account: accountReducer,
lineItems: (app ={}, action) => app, // just a placeholder
modifiers: (app ={}, action) => app, // just a placeholder
});
const quoteReducer = (state = {}, action) => {
const { type, payload } = action;
switch (type) {
case ADD_QUOTE:
return { ...state, [payload]: {} };
case UPDATE_ACCOUNT: {
const app = state[payload.q_id];
return app
? { ...state, [payload.q_id]: appReducer(state[payload.q_id], action) }
: state;
}
default:
return state;
}
}
const store = createStore(quoteReducer);
store.dispatch(quoteActions.addQuote(3));
store.dispatch(quoteActions.addQuote(2));
store.dispatch(appActions.updateAcount(3, 'apple'));
store.dispatch(appActions.updateAcount(4, 'orange')); // non-existent quote
store.getState():
/**
{
"2": {},
"3": {
"account": {
"name": "apple"
},
"lineItems": {},
"modifiers": {}
}
}
*/

Just wanted to add my specific answer here..
Basically I added a new root reducer as norbertpy suggested. However, I also had to add a parameter quoteId to each action to specify which quote the action originated from and should operate on. This was the most time consuming part of the refactor as now each component that dispatches actions must have access to the quote key.
Reducer
const quoteReducer = combineReducers({
account: accountReducer,
lineItems: lineItemReducer,
modifiers: modifersReducer,
});
const rootReducer = (state = {quotes: []}, action) => {
const newQuoteState = quoteReducer(state.quotes[action.quoteId], action);
const newQuotes = {...state.quotes};
newQuotes[action.quoteId] = newQuoteState;
return {...state, ...{quotes: newQuotes}};
};
Action
{
type: 'UPDATE_PRICE'
quoteId: '0',
payload: { productId: 123, newPrice: 15 }
}

Related

TypeError: Cannot read property 'qty' of undefined. React Native Redux

I try to build an application called Cake Shop to sell and make Cakes, I have a problem reducing the number of cakes in my State, Here is my initial State
import { SELL_ONE_CAKE } from "./types";
const initialState = {
cakes: [
{
id: 1,
title: "Classic Cake",
qty: 40,
price: 15,
},
{
id: 2,
title: "Chocolate Cake",
qty: 10,
price: 20,
},
{
id: 3,
title: "Simple White Cake",
qty: 40,
price: 30,
},
],
};
I think the problem is here in my Reducer, I want to reduce the quantity every time I dispatch the action.
export const reducer = (state = initialState, action) => {
switch (action.type) {
case SELL_ONE_CAKE:
return { ...state, cakes: state.cakes[0].qty - 1 }; // => I Think The problem is Here
default:
return state;
}
};
This My Action
import { SELL_ONE_CAKE } from "./types";
export const sellOneCake = () => ({
type: SELL_ONE_CAKE,
});
That's how I call the action on my Screen.
<Text>porfit: {Profits}</Text>
<Text>Number Of Cakes: {cakes}</Text>
<Button title="Sell One Cake" onPress={() => dispatch(sellOneCake())} />
Your current reducer line has a problem:
return { ...state, cakes: state.cakes[0].qty - 1 };
When this is run the first time, it sets cakes to a number -- the quantity of the first item in the array - 1.
What you want to do instead is set cakes to a new array, with the quantity of that one item altered:
return { ...state,
cakes: state.cakes.map((item, index) => {
if (index == 0) {
return {...item, qty: item.qty - 1};
} else {
return item;
}
})
}
In a real-world example, it's unlikely your action would ever just modify the first item of the array in a hard-coded way. The more likely scenario would be to contain an ID to alter in your action and then just update the item with a matching ID.

React state update issue

I try to add new arrays and values in a state.
I'm using react-beautiful-dnd to display this state datas.
my initial state :
const initialData = {
users: {
'user-1': { id: 'user-1', name: 'John'},
'user-2': { id: 'user-2', name: 'Patrick'},
'user-3': { id: 'user-3', name: 'Malorie'},
'user-4': { id: 'user-4', name: 'Eric'},
'user-5': { id: 'user-5', name: 'Bob'},
'user-6': { id: 'user-6', name: 'Blob'}
},
areas: {
'area-0': {
id: 'area-0',
title: 'Main Area',
userIds: ['user-1', 'user-2','user-3', 'user-4','user-5', 'user-6']
},
'area-1': {
id: 'area-1',
title: 'Area 1',
userIds: []
},
'area-2': {
id: 'area-2',
title: 'Area 2',
userIds: []
}
},
areaOrder: ['area-0','area-1', 'area-2'],
}
class MyClass {
constructor() {
super();
this.state = {
data: initialData,
}
}
...
}
I have a dropdown menu to choose the number of areas I want to display in total, when I trigger it, I try to add the new areas in the 'areas' array and in 'areOrder' array.
If I update the number of areas again, I need to reset the state to 'initialData'.
the code in MyClass:
// apply is triggered by the dropdown menu
apply = (numOfAreas) => {
// clear state to initial data
this.setState({
data: initialData
})
for (let i = 3; i <= numOfBOR; i++ ) {
this.addNewArea(i);
}
}
addNewArea = (newRoomId) => {
const areas = { ...this.state.data.areas };
let p = "area-";
let key = newAreaId;
const newAreaKey = p.concat(key);
const areaTitle = "Area ".concat(newAreaId);
let obj = [];
obj[newAreaKey] = { id: newAreaKey, title: areaTitle, userIds: [] };
const currentAreas = { ...areas };
const newAreaObj = Object.assign(currentAreas, obj);
const newState = {
...this.state.data,
areas: newAreaObj,
areaOrder: [...this.state.data.areaOrder, newAreaKey]
};
this.setState({data: newState});
};
When I use the code above, only the last area is displayed(i.e. when I chose 8 areas, the area 8 is display after area 2)
I'm aware that setState is asynch, so I'd like to know which method will allow me to do what I want.
In your case setState doesn't use previous state, so each setState change areas to its initial value plus one new element. You can use this.setState(prevState => newState) to get previous state.

Format object in javascript

My application is in react and redux technology.
I created a helper that formats my account bill and money, like this:
export const formatBill = (bill) => ({
...bill,
amountMoney: bill.amountMoney?.toLocaleString(undefined, {
minimumFractionDigits: 2,
}),
accountBillNumber: bill.accountBillNumber
.replace(/(^\d{2}|\d{4})+?/g, '$1 ')
.trim(),
});
Thanks to this, I can do something in the redux reducer and it works great:
...
case GET_BILLS_SUCCESS:
draft.bills = action.bills.map(formatBill);
break;
...
Now I have a problem because I would like to use this function for the transfer history. The problem is that my payload doesn't look so friendly. It's similar to this:
transactions: {
data: [
{
amountMoney: '10.00',
recipientBill: {
uuid: '8b9cef86-3987-4a71-badc-996caca8d8e0',
accountBillNumber: '19282292972339385206612752',
currency: {
name: 'PLN',
},
user: {
uuid: 'c58a9b81-2d16-4b07-804d-0b1fc34381ed',
}
},
senderBill: {
uuid: '6308be25-5bde-4f63-83d1-7bd47a829e61',
accountBillNumber: '76282292974456140174811708',
currency: {
name: 'USD',
},
user: {
uuid: 'af031321-f64b-4e91-948b-0c8c8ac4804c',
}
}
}
],
meta: {
page: 1,
take: 10,
itemCount: 1,
pageCount: 1
}
};
so now I would like to formatBill() the bill in recipientBill.accountBillNumber and senderBill.accountBillNumber How can I save this nicely in the redux reducer? I tried to solve this problem similar to this, but this is not the right solution:
case GET_TRANSACTION_HISTORY_SUCCESS:
draft.transactions.data = [
action.transactions.data.map((transaction) =>
formatBill(transaction.recipientBill),
),
action.transactions.meta,
];
break;

Update a nested object while maintaining the rest the same

I'm starting with redux, and I want to do the following modification to my
state:
From this:
state = {
loaded: true,
fetching false,
byId: {
"employeeID1": {
id: "employeeID1",
name: "Steve"
},
"employeeID2": {
id: "employeeID2",
name: "Susan"
}
}
}
To this:
{
loaded: true,
fetching false,
byId: {
"employeeID1": {
id: "employeeID1",
name: "Steve",
data: data // <---- add a new property
},
"employeeID2": {
id: "employeeID2",
name: "Susan"
}
}
}
This const modifEmployee = {...state.byId["employeeID1"], data: data} will give me the modified employee with the data.
But, how can I add the modified employee in byId while mantaining the others unchanged?
You could do something like this using spread syntax:
{
...state,
byId: {
...state.byId,
employeeID1: { ...state.byId.employeeID1, data }
}
}
If "employeeID1" value is a fetched from a variable employeeId, then you could use computed property names:
{
...state,
byId: {
...state.byId,
[employeeId]: { ...state.byId[employeeId], data }
}
}

Replace previous array of object is adding new element every time

I am new to the react js and Redux. Here , I have an array of object which is like ,
const initialState = {
Low: [
{
id: 0,
type: '',
count: '',
allowded: 6,
level: 'EASY'
}
],
Medium: [
{
id: 0,
type: '',
count: '',
allowded: 7,
level: 'MEDIUM'
}
],
High: [
{
id: 0,
type: '',
count: '',
allowded: 7,
level: 'TOUGH'
}
]
}
this is in the reducer .
Now,I do have an onChange function which actually changes the values in this array.
onChange(event, tobeupdated, id, type, noc, data) {
let newData = { ...this.props.data };
if (newData) {
let data = newData[type].map((object, index) => {
if (object.id === id) {
object[tobeupdated] = event.target.value;
const tobeData = newData[type];
this.props.updateLowLevel({tobeData, type}).then(() => {
let criteria_filled = this.disableAddbutton({ ...this.props.data }, type);
addedRow = `new${type}RowAdded`;
this.setState({
[addedRow]: criteria_filled ? true : false
})
});
}
From this, I am updating the value of that object. Depends upon the type .
Now, In action creator,
return (dispatch) => {
dispatch({
type: QUIZ_DATA,
data: tobeUpdated,
});
return Promise.resolve();
}
}
In my reducer, I am updating it like,
case QUIZ_DATA:
return {
...state,
[action.data.type]: [action.data.tobeData],
error: false,
}
Now, Here what is happening when I change the let's say type then it adds that array of object to that, but when I try to change the diff key that time what it does is,
In the array of object, one more obj gets added in the element. SO, because of that, I am not able to get that render properly.
Can anyone help me with this?

Categories

Resources