How to initiate state from another state (by copying without referencing) - reactjs

I have a code as below
useEffect(() => {
{
updateLocalColumns();
}
}, []);
const updateLocalColumns = () => {
let templocalColumns = [...localColumns];
templocalColumns[0]..defaultSortOrder = "descend";
setlocalColumns(templocalColumns);
}
const orderContext = useContext(OrderContext);
const { data, columns } = orderContext;
const [localColumns, setlocalColumns] = useState([...columns]);
return (
<div>
<Table columns={columns} dataSource={data} />
{console.log(columns)}
</div>
)
'columns' is a state that is obtained by using hooks, I am trying to make a copy over local component that is 'localColumns" and thus modify from there without interfering the global 'columns'
I was expecting the original columns to remain as its original state, however when I print out, the original columns is the same as 'localColumns' which is the modified one.
Expected Output
localColumns: [
{
title: "Username",
dataIndex: "username",
key: "username",
defaultSortOrder: "descend"
}
]
columns: [
{
title: "Username",
dataIndex: "username",
key: "username",
}
]
Current Wrong Output
localColumns: [
{
title: "Username",
dataIndex: "username",
key: "username",
defaultSortOrder: "descend"
}
]
columns: [
{
title: "Username",
dataIndex: "username",
key: "username",
defaultSortOrder: "descend"
}
]
I am suspecting the problem is when I initate the state, I did a referencing.
The way I did copy was from this link and this link

I would try a simpler code:
//Import this class in the proper place
//import { clone } from 'ramda';
const orderContext = useContext(OrderContext);
const { data, columns } = orderContext;
let localColumns0 = clone(columns) //This is done to make a deep copy of "columns"
localColumns0[id]['defaultSortOrder']="descend"
const [localColumns, setlocalColumns] = useState(localColumns0);
return (
<div>
<Table columns={columns} dataSource={data} />
{console.log(columns)}
</div>
)

If you don't want only the copy of the state, maybe you need to look how to do the deep copy of an array, after you make a deep copy of the estate you can pass the state to your personal component with <PersonalCom {...deepCopyStateObj}/> and the PersonalCom need a constructor where you receive the props, like the following code.
constructor(props) {
super(props);
}
Or something equivalent in JSX syntax.
However make a deep copy of the state and pass the object to component don't look like, because you need to clone the state each time that your app refresh the UI, and react refresh very frequently the UI, for this reason, I suggest cloning the propriety that you need inside the personal component.

Related

useState hook not "reacting" when state variable modified

I have created a React application to test out the useState hook.
This the variable of concern:
let [blocks, setBlocks] = useState([
{text: "Hello", id: 1},
{text: "This is google", id: 2},
{text: "Wassup", id: 3},
{text: "Last One", id: 4}
]);
I've displayed this using the map function as follows:
return (
<div className="App">
{blocks.map((block) => (
<div className='block-element'>{block.text}</div>
))}
<button onClick={clickFunc}>ClickToChange</button>
</div>
);
As far as I've understood, to make any change in the webpage we have to pass the new-data into setBlocks() and wherever "blocks" was used will be updated.
I tried the following clickFunc() to do so:
const clickFunc = ()=>{
blocks[1].text = "Go Home";
setBlocks(blocks);
console.log(blocks);
}
I expected the output (onclicking the button) to be:
Hello
Go Home
Wassup
Last One
But nothing changed.
Surprisingly when I used the following (similar looking) clickFunc():
const clickFunc = ()=>{
blocks = [
{ text: "Hello", id: 1 },
{ text: "Go Home", id: 2 },
{ text: "Wassup", id: 3 },
{ text: "Last One", id: 4 }
];
setBlocks(blocks);
console.log(blocks);
}
And it worked perfectly as expected.
On click output:
Hello
Go Home
Wassup
Last One
NEVER mutate state directly.
The following code block is mutation and wrong. Javascript is kind of like some other programming languages where only the reference of the object is stored in the memory. So when you mutate the object instead of creating a new one, you are not really changing the content of the object in memory, because it still has the same value(reference) as before.
const blocks = [{text: 'one'}];
blocks[0].text = 'two';
You should create a new object and assign it to state using React setState callback:
setBlocks(blocks => {
clonedBlocks = [ ...blocks];
clonedBlocks[1]= { ...blocks[1], text: "Go Home" };
return clonedBlocks;
});
Or any other way that does not mutate the state directly.
React state is immutable
if you mutate the state react will not know that state is changed
first you need to copy the state using Spread syntax and change it
const clickFunc = () => {
setBlocks(prevBlocks => {
const shallowCopy = [...prevBlocks]
shallowCopy[1].text = "Go Home";
return shallowCopy
});
}

How is destructuring happening in this particular example?

I am new to react and I am trying to build a folder tree component in react.
I am following this tutorial - link
you can view the code here - sandbox
I am confused about how destructing is happening in this case
const Tree = ({ data, children }) => {
const isImparative = data && !children;
return (
<StyledTree>
{isImparative ? <TreeRecursive data={data} /> : children}
</StyledTree>
);
};
Tree.File = File;
Tree.Folder = Folder;
const structure = [
{
type: "folder",
name: "src",
childrens: [
{
type: "folder",
name: "Components",
childrens: [
{ type: "file", name: "Modal.js" },
{ type: "file", name: "Modal.css" }
]
},
{ type: "file", name: "index.js" },
{ type: "file", name: "index.html" }
]
},
{ type: "file", name: "package.json" }
];
export default function App() {
return (
<div className="App">
<Tree data={structure} />
</div>
);
I am confused in this particular line
const Tree = ({ data, children }) => {
How is the destructing happening her?
You are destructing { data, children } from structure array.
How does it automatically decide what to pick inside structure for data and what to pick for children?
There are no field named data and children as well. So how is this working here?
When you define a component (here its Tree) it might expect props. Inside the props there is all the different properties which we are passing for example for the below the data is a prop.
<Tree data={structure} />
So, The destructuring is done for the prop. if you remove the object for the const Tree = ({ data, children }) and write prop instead of it and check you will see the different props passed to the component.
If it was like below, there would be another component called children which would contain the <div>My child</div>. This is useful for making reusable components.
<Tree data={structure}><div>My child</div><Tree/>

Setting Nested Array in React Context

I am trying to figure out how to update an array that is nested within a React Context. Below is the context I am working with. It consists of "lists". Each list contains an array of "items".
import React, {useState, createContext} from 'react';
export const ListerContext = createContext();
export const ListerProvider = (props) => {
const [lists, setLists] = useState([
{
id: 1,
items: [{
itemid: 1,
text: 'Hello'
},
{
itemid: 2,
text: 'world'
}]
},
{
id: 2,
items: [{
itemid: 2,
text: 'Test'
}]
}
]);
return(
<ListerContext.Provider value={[lists, setLists]}>
{ props.children }
</ListerContext.Provider>
);
}
I have been trying to change the nested arrays using the "setLists" method below but it is not working. What am I doing wrong here?
const removeListItem = (e) => {
setLists((prevList)=>{
for(var i = 0; i < prevList.length; i++){
if(prevList[i].id === parseInt(e.target.value[2])){
prevList[i].items = (prevList[i].items.filter(function(item) {
return item.itemid !== parseInt(e.target.value[0]);
}))
}
}
return prevList;
});
}
As #zero298 mentioned in their comment, you need to pass the entire list into your state update function setLists. You are initializing the state and its update function with a "list of dictionaries", so you need to keep treating it as such, you can't selectively change few properties inside the dictionary or few elements in the list without having to update the entire list.
Easy correction of your code will be to conditionally make a new copy of your list with updated values, and then setting that list as your new state using your state update function.
Cheerio!

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 push data into object of arrays

How do I get imported data from importFile.js into dataTable.js?
https://github.com/Romson/CSV-file-uploader/blob/master/src/components/importFile.js
https://github.com/Romson/CSV-file-uploader/blob/master/src/components/dataTable.js
Tried this function to change nested arrays in imported data from importFile.js into a object of arrays.
const newArray = [data].map(
([firstName, lastName, issueCount, dateOfBirth]) => ({
firstName,
lastName,
issueCount,
dateOfBirth
})
);
Then a push into dataTable.js with:
data.rows.push(newArray);
What is the correct way to do this in React?
Expected result is to get the imported data to show in this table:
https://csv-file-uploader.herokuapp.com/
What you want to do is make DataTable component a child of Reader component. You need the array of object from Reader component for the rows property of datatable in DataTable component. As you said you are a beginner so better start from react hooks as it is easy.
Reader component
import React, {useState} from "react";
import CSVReader from "react-csv-reader";
import DatatablePage from "./dataTable";
import "../index.css";
const Reader = () => {
const [data, setData] = useState([]);
return (
<div className="container">
<CSVReader
cssClass="react-csv-input"
label="Upload a new CSV file"
onFileLoaded={(data) => setData(data)}
/>
<DatatablePage uploadedData={data} />
</div>
);
}
export default Reader;
DatatablePage component
import React from "react";
import { MDBDataTable } from "mdbreact";
const DatatablePage = ({uploadedData}) => {
const data = {
columns: [
{
label: "First Name",
field: "name",
sort: "asc",
width: 150
},
{
label: "Last Name",
field: "surname",
sort: "asc",
width: 270
},
{
label: "Issue count",
field: "issuecount",
sort: "asc",
width: 200
},
{
label: "Date of birth",
field: "dateofbirth",
sort: "asc",
width: 100
}
],
rows: []
};
// we append the passed props in the rows. read about spread operator if unaware of it.
const datatableProps = {...data, rows: uploadedData};
return <MDBDataTable striped bordered hover uploadedData={uploadedData} data={datatableProps} />;
};
export default DatatablePage;
We are using react hooks to create a state variable named data and a setter for it. Then we pass this state variable to the child component which can render it.

Resources