React and Redux Architecture - reactjs

I have questions about React and Redux architecture.
Suppose you have normalized data
const user = [
{ userId: 1, name: 'Ian', groupId: 1 },
{ userId: 2, name: 'Tom', groupId: 2 }
]
const groups = [
{ groupId: 1, groupName: 'Facebook developers'},
{ groupId: 2, groupName: 'Google developers'}
]
const chat = {
{ userIdFrom: 1, message: 'Hello!', timestamp: 1324132332 },
}
What is the best practice to manipulate data such as .map, .filter, .reduce, .forEach, .sort, denormalization etc?
I create utils functions to handle huge data manipulation like Utils.getChatsFromUsers etc and invoked from render method on Component.
Is this my solution good practice to handle huge and many data manipulation in Component's render function?
Please give me your advise and insight.
Thanks!

It is not really a good practice to manipulate arrays inside the render method unless the array is the only thing being rendered, or if the array is the prop the will most likely change. You should manipulate them in the componentWillReceiveProps.
So, instead of having something like this:
render() {
const { string1, string2, string3, array } = this.props;
const mappedArray = array.map(element => <li>{element}</li>);
return (
<div>
<p>{string1}</p>
<p>{string3}</p>
<p>{string3}</p>
<ul>{mappedArray}</ul>
</div>
);
}
you should do something like this:
componentWillReceiveProps(nextProps) {
// R is from ramda, but you can compare the arrays any way you want
// even just comparing the array references if that's good enough for you
if (R.equals(nextProps.array, this.props.array)) {
this.mappedArray = array.map(element => <li>{element}</li>);
}
}
render() {
const { string1, string2, string3, array } = this.props;
return (
<div>
<p>{string1}</p>
<p>{string3}</p>
<p>{string3}</p>
<ul>{this.mappedArray}</ul>
</div>
);
}
This way you avoid having to recreate the mapped array every time the render method is called (which could happen from other props changing or the state being changed).
This topic is covered here.

Related

What are risks or side effects of doing shallow copy when updating state in functional component?

I've got the following small example and I assume that if I do a shallow copy before replacing state, that my sessions attribute will not be new, but just a reference to old. I've got the shallow copy defined as the variable newSpeakersDataShallow and the deep copy variable named newSpeakersDataDeep.
If you change what is passed into setSpeakerData, you'll see they both work the same. My question is, what are my risks? if I know that I will not be changing the sessions array, is it OK to use the shallow copy?
(This is just a simplified example so the answer isn't, might as well use deep since it's so easy to do in this case).
https://codesandbox.io/s/clever-varahamihira-ry9x4?file=/pages/index.js:0-1990
Same code as codesandbox:
import { useEffect, useState } from "react";
const data = [
{
id: "1",
first: "Joe",
last: "Smith",
favorite: true,
sessions: [
{
id: "32",
title: "Rails powered by",
},
{
id: "58",
title: "Hello World to .NET 3.5 interoperable Web service",
},
],
},
{
id: "2",
first: "Jon",
last: "Jones",
favorite: false,
sessions: [
{
id: "1011",
title: "scalability and deployability",
},
],
},
{
id: "3",
first: "Sam",
last: "Hulk",
favorite: true,
sessions: [],
},
];
function Speakers() {
const [speakersData, setSpeakersData] = useState([]);
useEffect(() => {
setSpeakersData(data);
}, []);
return (
<>
{speakersData.map(function (speaker) {
return (
<div key={speaker.id}>
<button
onClick={() => {
const newSpeakersDataShallow = speakersData.map(function (rec) {
return speaker.id == rec.id
? { ...rec, favorite: !rec.favorite }
: { ...rec };
});
const newSpeakersDataDeep = speakersData.map(function (rec) {
return speaker.id == rec.id
? {
...rec,
favorite: !rec.favorite,
sessions: [...rec.sessions],
}
: { ...rec };
});
// RESULTS ARE SAME WHETHER DEEP OR SHALLOW COPY USED
setSpeakersData(newSpeakersDataDeep);
}}
>
Toggle Favorite
</button>
{speaker.id} {speaker.first} {speaker.last}{" "}
{JSON.stringify(speaker.sessions)}{" "}
{speaker.favorite === true ? "true" : "false"}
</div>
);
})}
</>
);
}
export default Speakers;
When updating state the optimal way you would be making new copies for only the objects you update, and leave the others untouched. Unchanged pieces of your state won't have any impact by keeping the old reference, no need to copy them.
Your goal is avoid mutating pieces of the state. This way you ensure that your update state reflects correctly without unexpected behaviors. Create deep copies are not necessary, it could be costly for deep nested states.
Your first example is close to how I would approach:
map makes a new array, this way you won't mutate it;
you create a shallow copy for the object you need to change, and update the required values correctly;
The difference is the untouched object don't need to return a new copy {...rec}. Given it's untouched you can return the same object safely, avoiding the cost of a new object.
const newSpeakersDataShallow = speakersData.map(function (rec) {
return speaker.id == rec.id
? { ...rec, favorite: !rec.favorite }
: rec;
});
It's fine to use a shallow copy if the data is not being manipulated in anyway.
The risk of using a shallow copy only exists if the operation you are performing changes the data, as this will result in a direct mutation of the react state bypassing setState, which can cause side effects such as state not matching rendered results.
For deep cloning, you can use a library like lodash.cloneDeep to avoid having to handle the object manually.

Issue with state update approach for nested objects

Major EDIT
I have quite huge object which is 3 level deep. I use it as a template to generate components on the page and to store the values which later are utilized, eg:
obj =
{
"group": {
"subgroup1": {
"value": {
"type": "c",
"values": []
},
"fields_information": {
"component_type": "table",
"table_headers": [
"label",
"size"
],
}
},
"subgroup2": {
"value": {
"type": "c",
"values": []
},
"fields_information": {
"component_type": "table",
"table_headers": [
"label",
"size"
],
}
},
},
}
Thanks to this I can dynamically generate view which is, as a template, stored in DB.
I'm struggling with 2 things. Firstly, updating values basing on user input for textbox, checkboxes and similar.
I'm doing it this way:
const updateObj = (group, subgroup, value) => {
let tempObj = {...obj}
tempObj[group][subgroup].value.value = value
toggleObj(tempObj)
}
I know that the spread operator is not in fact doing deep copy. However it allows me to work on the object and save it later. Is that an issue? Do I have to cloneDeep or it is just fine? Could cloneDeep impact performance?
Second case is described below
export const ObjectContext = React.createContext({
obj: {},
toggleObj: () => {},
});
export const Parent = (props) => {
const [obj, toggleObj] = useState()
const value = {obj, toggleObj}
return (
<FormCreator />
)
}
const FormCreator = ({ catalog }) => {
const {obj, toggleObj} = React.useContext(ObjectContext)
return (<>
{Object.keys(obj).map((sectionName, sectionIdx) => {
const objFieldsInformation = sectionContent[keyName].fields_information
const objValue = sectionContent[keyName].value
...
if (objFieldsInformation.component_type === 'table') {
return (
<CustomTable
key={keyName + "id"}
label={objFieldsInformation.label}
headers={objFieldsInformation.table_headers}
suggestedValues={[{label: "", size: ""}, {label: "", size: ""}, {label: "", size: ""}]}
values={objValue.values}
sectionName={sectionName}
keyName={keyName}/>
)
}
...
})}
</>)
}
const CustomTable= (props) => {
const { label = "", headers = [], suggestedValues = [], values, readOnly = false, sectionName, keyName } = props
const {obj, toggleObj} = React.useContext(ObjectContext)
//this one WORKS
useEffect(() => {
if (obj[sectionName][keyName].value.type === "complex") {
let temp = {...obj}
temp[sectionName][keyName].value.values = [...suggestedValues]
toggleObj(temp)
}
}, [])
//this one DOES NOT
useEffect(() => {
if (obj[sectionName][keyName].value.type === "c") {
let temp = {...obj, [sectionName]: {...obj[sectionName], [keyName]: {...obj[sectionName][keyName], value: {...obj[sectionName][keyName].value, values: [{label: "", size: ""}, {label: "", size: ""}, {label: "", size: ""}]}}}}
toggleObj(temp)
}
}, [])
return (
//draw the array
)
}
Please refer to CustomTable component.
As on the example Object above, I have 2 CustomTables to be printed. Unfortunately, one useEffect that should work is not working properly. I'm observing, that values field is set only for the last "table" in Obj. When I'm doing shallow copy of obj, it works fine. But I'm afraid of any repercussion that might happens in future.
I'm also totally new to using createContext and maybe somehow it is the issue.
Kudos to anyone understanding that chaos :)
The main issue appears to be that you are not providing your context. What you have is literally passing the blank object and void returning function. Hence why calling it has no actual effect, but mutating the value does.
export const ObjectContext = React.createContext({
obj: {},
toggleObj: () => {},
});
export const Parent = (props) => {
const [obj, toggleObj] = useState({})
const value = {obj, toggleObj}
return (
<ObjectContext.Provider value={value}>
<FormCreator />
</ObjectContext.Provider>
)
}
Ideally you would also make this component above wrap around FormCreator and render it as props.children instead. This is to prevent the entire sub-tree being rerendered every time toggleObj is called. See the first part of this tutorial to get an idea of the typical pattern.
As to the question about mutating state, it absolutely is important to keep state immutable in React - at least, if you are using useState or some kind of reducer. Bugs arising from state mutation come up all the time on Stack Overflow, so often in fact that I recently made a codesandbox which demonstrates some of the more common ones.
I also agree with #SamuliHakoniemi that a deeply nested object like this is actually better suited to the useReducer hook, and might even go one further and suggest that a proper state management library like Redux is needed here. It will allow you to subdivide reducers to target the fragments of state which actually update, which will help with the performance cost of deeply cloning state structure if or when it becomes an actual issue.

React: Mutate nested states once

So in typical programming scenarios, if you mutate an object, it mutates the object everywhere. However, in React, since states are immutable and only mutable through each's set*, if states are nested in each other, like the scenario shown below, only the currentMember's name will change, and not the version of currentMember in currentTeam, or teams. I'd have to go through each and mutate them one by one, which is cumbersome.
What would be the best way to mutate multiple states at once, or achieve a similar effect? I've done so by indexing, but it's more cumbersome to work with, and i didnt know if there were a textbook hook that fixes this.
import React, { useState } from 'react'
interface Member {
name: string
}
interface Team {
name: string
members: Member[]
}
export default (props: {}) => {
const [teams, setTeams] = useState<Team[]>([
{
name: 'Team One',
members: [{ name: 'Wyatt' }, { name: 'Michael' }]
}
])
const [currentTeam, setCurrentTeam] = useState<Team>(teams[0])
const [currentMember, setCurrentMember] = useState<Member>(currentTeam.members[0])
return (
<>
<h1>${currentMember.name}</h1>
<button onClick={() => setCurrentMember(currentMember => { ...currentMember, name: 'Zach' })}>
Change current member name to Zach!
</button>
</>
)
}
As mentioned, you are making things a bit complicated by using state this way. You should have one base state that contains all of the teams, then reference the bits that are important to you by index.
For example, your teams state is fine as is. Your currentTeam and currentMember states should be indexes or some other reference to the state within teams you want to map to.
So, in specific terms, I'd change the format of your code here like so (forgive me as I don't write TypeScript, so I'm going to straight vanilla javascript to avoid making typos):
import React, { useState } from 'react'
// interface Member {
// name: string
//}
// interface Team {
// name: string
// members: Member[]
//}
export default (props: {}) => {
const [teams, setTeams] = useState([
{
name: 'Team One',
members: [{ name: 'Wyatt' }, { name: 'Michael' }]
}
])
const [currentTeamIndex, setCurrentTeam] = useState(0)
const [currentMemberIndex, setCurrentMember] = useState(0)
return (
<>
<h1>${teams[currentTeamIndex].members[currentMemberIndex]}</h1>
<button onClick={() => setTeams(teams => ({
// Shallow copy the teams via mapping through them
...teams.map((team, teamIndex) => {
// If the current team index isn't the index we're on right now, then just
// return the existing team in its given place.
if (teamIndex !== currentTeamIndex) return team
// If we're at this point, it means the teamIndex matches the currentTeamIndex
// and we need to mutate this. We'll just do that by returning a new object
return {
...team, // Make sure we don't miss anything
...members.map((member, memberIndex) => {
// Same as the outer map, if the current member index isn't the same as the
// given memberIndex, then just return the member we're on, we're not mutating it
if (memberIndex !== currentMemberIndex) return member
return {
...member,
name: 'Zach'
}
})
}
}
})
>
Change current member name to Zach!
</button>
</>
)
}
As you can see, drilling that far down into an object isn't so simple, but it's possible to do without mutating the original data - you can accomplish what you are looking for by reconstructing the data on the fly with Array functions, spread syntax, and index references in state.
You might also want to consider confining this in a reducer, with a specific action containing the team id index, and member id index to make this quite a bit more composable.

How to write redux selector that handle several levels of nested state?

I would like my container component to be as much reusable as it can be. To achieve that, I want to write reusable selector to use it in mapStateToProps function. I know that this function can accept props of the current component, so I can pass dynamic state key to my selector getAllEntities. The problem appears, when I want from my selector to get first level of state, but in other place - to get some nested state.
Demonstration of state shape:
{
items: { byId: { ... }, allIds: { ... } }
comparison: {
otherItems: { byId: { ... }, allIds: { ... } }
// and so on
}
}
Selector demonstration:
getAllEntities = (state) => state.allIds.map(id => state.byId[id]);
And I use it in my component's mapStateToProps function:
return { items: getAllEntities(state[ownProps.stateKey]) }
The problem with my approach is that it seems that (by keeping reusability of this component) I can only access first level of state. So I cannot pass props to my component that will understand that it should look for state.comparison.otherItems - watch state shape demonstration above.
I tried something like:
getAllEntities = (state, key1) => {
if (has(state, key1) {
return state[key1].allIds.map(id => state[key1].byId[id]);
}
return state.allIds.map(id => state.byId[id]);
}
So if I pass key1 string - it should look deeper in state. If key1 is not passed to component's props - then behave normally and look for first level of state shape.
I don't even know if it's the correct approach... Can somebody help me with this?
You can recursively reuse selector:
getAllEntities = (state, key1) => {
if (has(state, key1) {
return getAllEntities(state[key1])
}
return state.allIds.map(id => state.byId[id]);
}
Thanks #iofjuupasli, that's nice solution. Also, as agreed on discord redux community, it's just better to write two selectors. Then to avoid much code duplication, write something like:
const SomeComponent = () => {};
const WithSomeDataSource = connect(doOneThingHere)(SomeComponent);
const WithAnotherDataSource = connect(doSomethingElseHere)(SomeComponent);

React Redux, how to properly handle changing object in array?

I have a React Redux app which gets data from my server and displays that data.
I am displaying the data in my parent container with something like:
render(){
var dataList = this.props.data.map( (data)=> <CustomComponent key={data.id}> data.name </CustomComponent>)
return (
<div>
{dataList}
</div>
)
}
When I interact with my app, sometimes, I need to update a specific CustomComponent.
Since each CustomComponent has an id I send that to my server with some data about what the user chose. (ie it's a form)
The server responds with the updated object for that id.
And in my redux module, I iterate through my current data state and find the object whose id's
export function receiveNewData(id){
return (dispatch, getState) => {
const currentData = getState().data
for (var i=0; i < currentData.length; i++){
if (currentData[i] === id) {
const updatedDataObject = Object.assign({},currentData[i], {newParam:"blahBlah"})
allUpdatedData = [
...currentData.slice(0,i),
updatedDataObject,
...currentData.slice(i+1)
]
dispatch(updateData(allUpdatedData))
break
}
}
}
}
const updateData = createAction("UPDATE_DATA")
createAction comes from redux-actions which basically creates an object of {type, payload}. (It standardizes action creators)
Anyways, from this example you can see that each time I have a change I constantly iterate through my entire array to identify which object is changing.
This seems inefficient to me considering I already have the id of that object.
I'm wondering if there is a better way to handle this for React / Redux? Any suggestions?
Your action creator is doing too much. It's taking on work that belongs in the reducer. All your action creator need do is announce what to change, not how to change it. e.g.
export function updateData(id, data) {
return {
type: 'UPDATE_DATA',
id: id,
data: data
};
}
Now move all that logic into the reducer. e.g.
case 'UPDATE_DATA':
const index = state.items.findIndex((item) => item.id === action.id);
return Object.assign({}, state, {
items: [
...state.items.slice(0, index),
Object.assign({}, state.items[index], action.data),
...state.items.slice(index + 1)
]
});
If you're worried about the O(n) call of Array#findIndex, then consider re-indexing your data with normalizr (or something similar). However only do this if you're experiencing performance problems; it shouldn't be necessary with small data sets.
Why not using an object indexed by id? You'll then only have to access the property of your object using it.
const data = { 1: { id: 1, name: 'one' }, 2: { id: 2, name: 'two' } }
Then your render will look like this:
render () {
return (
<div>
{Object.keys(this.props.data).forEach(key => {
const data = this.props.data[key]
return <CustomComponent key={data.id}>{data.name}</CustomComponent>
})}
</div>
)
}
And your receive data action, I updated a bit:
export function receiveNewData (id) {
return (dispatch, getState) => {
const currentData = getState().data
dispatch(updateData({
...currentData,
[id]: {
...currentData[id],
{ newParam: 'blahBlah' }
}
}))
}
}
Though I agree with David that a lot of the action logic should be moved to your reducer handler.

Resources