React component not redraw when when props are updated - javascript

Quick info to describe the context.
I am working to a tree component that receives as prop this kind of data model:
workfolder: [{
label: "test",
folders: [
{ label: "label 1", id: 1 },
{ label: "label 2", id: 2 },
{ label: "label 3", id: 3 }
]
}];
All data are stored in a Redux store.
The initial value is:
state: { workfolder: [] }
On mount i fetch for default values and merge the results to workfolder in the reducer and i get my tree drawn.
return {
...state,
workfolder: results
}
When i click on one of the labels ( label 3 ) i fetch again for the sub-folders using the id and i get:
[{ label: "label 5", id: 5 },{ label: "label 5", id: 5 }]
At this point in the reducer i use a library to loop deep into the state till i find the property matching the id of the element i clicked ( label 3 in this case ) and want to merge the sub-folders into a new folder attribute.
let newState = JSON.parse(JSON.stringify(state.workfolder));
deepForEach( newState, (value, key, subject, path) => {
const isParentFolder = value === action.payload.parent;
const hasNotFolders = !subject.folders;
if( isParentFolder && hasNotFolders ) {
subject.folders = action.payload.node
}
} );
then i merge the new state in the store:
return {
...state,
workfolder: newState
}
Following this way i get the store updated, but the component won't redraw.
NOTE: this is a dummy example. In real life i have to deal with a multi level nested object having only the id and a string containing the path of the attribute i want to modify in the store.
The example that i found in the redux documentation shows how to do that writing statically the merge...

Related

How can I modify and extend properties of an object located in useState Array?

I'm a react.js beginner, searching for methods to alter my data structure. For example, I want to push new objects into the children-array or remove them by key.
What is the appropriate way to do that?
const [treeData, setTreeData] = useState([
{
title: "parent 1",
key: "0-0",
icon: <UserAddOutlined />,
children: [
{
title: "parent 1-0",
key: "0-0-0",
icon: <UserAddOutlined />,
children: [
{
title: "leaf",
key: "0-0-0-0",
icon: <UserAddOutlined />,
},
{
title: "leaf",
key: "0-0-0-1",
icon: <UserAddOutlined />,
},
],
},
{
title: "parent 1-1",
key: "0-0-1",
icon: <UserAddOutlined />,
children: [
{
title: "sss",
key: "0-0-1-0",
icon: <UserAddOutlined />,
},
],
},
],
},
]);
So you should not update the state directly. It is not allowed.
Maybe where you are receiving data from, suppose via api and the data is response.payload.data etc.
So in your case use the setTreeData(response.payload.data) method to add stuff in it.
Now if you want to update certain value (remove or update using index etc). Obviously you will have to have index somehow.
So for deleting say you will have some click and against that a handler for it
removeItem(e) {
item_to_remove = e.target..... etc // to get the item's reference for matching
setTreeData(treeData.filter(items => item.<someproperty> != item_to_remove))
// In your case could also be targetting children maybe
// setTreeData(treeData.Children.filter(items => item.<someproperty> != item_to_remove))
}
I would say maybe handle childrens' array inside another useState variable (childrenTreeData maybe). But you will have to look it's feasibility too. Just an idea after seeing your data
JUST for INFO
This is something similar I did for updating prices inside each cards in my project
const getCurrentPrice = useCallback(() => { // <======= maybe you do not need this
const updatedTilesData = tilesData.map((tile: any) => {
return {
...tile, // <======= get everything here and then update the price below for item
currentPrice: calculateDNCurrentPrice(
tile.startingPrice,
tile.dnTimestamp
),
};
});
setTilesData(updatedTilesData);
}, [tilesData]);

vue3 blocks tree does not "reload" when changing the data

I am looking for a organization chart plugin for vue 3 and I have found only this one
https://github.com/megafetis/vue3-blocks-tree
The issue that I have is when changing the ref treeData variable the chart is not loading the new structure.
I want to dynamic load the chart. When fetch the data the chart to loads the new data and display it.
Here is the codesandbox example:
https://codesandbox.io/s/flamboyant-gwen-o8vp9?file=/src/App.vue
When press the Add ... the treeData reference variable should load the new data and the chart to display it ... but it doesn't.
Any ideas ?
Should I reload the component on every fetch ?
Try to use documented way make treeData reactive.
Replace ref to reactive for treeDate definition.
Change parts of treeData on onAddData instead replacing all object.
Full changed example:
import { reactive } from "vue";
import VueBlocksTree from "vue3-blocks-tree";
import "vue3-blocks-tree/dist/vue3-blocks-tree.css";
export default {
name: "Diagram",
components: { VueBlocksTree },
setup() {
let treeData = reactive({
label: "root",
children: [
{ label: "child 1" },
{ label: "child 2" },
{
label: "subparent 1",
children: [
{ label: "subchild 1" },
{
label: "subchild 2",
children: [{ label: "subchild 11" }, { label: "subchild 22" }],
},
],
},
],
});
// let treeData = ref({});
const onAddData = () => {
Object.assign(treeData, { label: "Test", children: [{ label: "Test 1" }, { label: "Test 2" }] });
};
return {
treeData,
onAddData,
};
},
};
UPD: Change code on onAddData by adding Object.assign.

Unable to output useState to imported chart

I have a simple form where i will add a task and add the amount of minutes that task has taken, i am having trouble trying to get the chart which i have imported from 'Recharts' to see the state, it looks like the state is returning an empty array and my chart is not seeing the data.
The Recharts chart takes an array with an object inside with two values, example shown below
const data = [
{ name: "Group A", data: 400 },
{ name: "Group B", data: 300 },
{ name: "Group C", data: 300 },
{ name: "Group D", data: 200 },
{ name: "Group E", data: 278 },
{ name: "Test Data", data: 189 }
];
However after i have moved my state as props to the component and mapped the result to take the same names as input the charts is not displaying anything
(my mapped props)
const activityData = [
...props.pieData.map(el => ({
name: el.title,
data: el.amount
}))
];
I have put a simplified version on codesandbox to make it a little bit easier if anyone wants to see the output i'm getting
https://codesandbox.io/s/hidden-dew-676xc
Found the issue as soon as i posted this, the 'data' value which i had mapped in my props was getting outputted as a string, when the object was expecting a number

how to set column name in mui datatables using object key

I'm trying to use an object key to set the name of a column in MUI Datatables.
I'm trying to set one of the column names with the first element of children.childName
so that in that column it will display list of child names, but only the first child.
In Current way that Im trying this, I am getting no errors, and its displaying nothing in the childName Column on the table.
How Can I access an object thats inside an array?
This is my Data:
const data = [
{
name: "Pat",
company: "Test Corp",
city: "Yonkers",
state: "NY",
children: [
{ childName: "Pat Jun", childAge: 2 },
{ childName: "Mary Jun", childAge: 2 }
]
},
];
const columns = [
{
name:name: data[0]["children"][0]["childName"],
label: "Child Name",
options: {
filter: true,
sort: true
}
}]
MuiTable.js
function MuiTable({ forms }) {
console.log("cols", columns);
return (
<MUIDataTable
title={"Title"}
data={data}
columns={columns}
options={options}
/>
);
}
By doing a console.log I can see that it is printing the value instead of the object key name
I would really appreciate any help, Thank you.
You need to use customBodyRender like this:
import React from "react";
import ReactDOM from "react-dom";
import MUIDataTable from "mui-datatables";
import { Typography } from "#material-ui/core";
import "./styles.css";
function App() {
const data = [
{
name: "Pat",
company: "Test Corp",
city: "Yonkers",
state: "NY",
children: [
{ childName: "Pat Jun", childAge: 2 },
{ childName: "Mary Jun", childAge: 2 }
]
}
];
const columns = [
{
name: "children",
label: "Child Names",
options: {
filter: true,
sort: true,
customBodyRender: (value, tableMeta, updateValue) => (
<Typography>
{value.map(child => child.childName).join(",")}
</Typography>
)
}
}
];
return (
<div>
<MUIDataTable title={"Title"} data={data} columns={columns} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Thanks very much to #Klaus your answer. That's pretty much what I had to do, but In my case, I wanted to only display the first childName in the object children which was in an array of objects.
So I had to adapt it a bit and change my data structure as well.
This is what I ended up doing.
I first added a simple array to my data structure completely seperate to the array containing the children objects called childNames, which just came contained only the names.
This made it alot easier to access the childNames as it was just a simple array not nested in anything.
So I simply just displayed the first element in the array on Table
const columns = [
{
name: "childNames",
label: "Child Name",
options: {
filter: true,
customBodyRender: (value, tableMeta, updateValue) => {
return <div>{value[0]}</div>;
}
}
},
The reason why I created an array for just childNames, was because trying to access only just the first childName in the array containing children objects was proving very complicated and difficult.
Thanks very much for all the help.

Two Way Link Using Normalizr

I have a list of regions, which I get from an API. In this list, there are multiple buildings. This will look like this in JS:
const regions = [
{
guid: 'REGION1-GUID',
name: 'Region 1',
buildings: [
{
guid: 'REGION1-BUILDING1-GUID',
name: 'Region 1 Building 1'
},
{
guid: 'REGION1-BUILDING2-GUID',
name: 'Region 1 Building 2'
}
]
},
{
guid: 'REGION2-GUID',
name: 'Region 2',
buildings: [
{
guid: 'REGION2-BUILDING1-GUID',
name: 'Region 2 Building 1'
},
{
guid: 'REGION2-BUIDLING2-GUID',
name: 'Region 2 Building 2'
}
]
}
];
Now I want to normalize this JS Object using normalizr. What I want to do later is to get the region from a building.
So I tried to do the following:
// Define the buildings schema
const building = new schema.Entity('building', {}, { idAttribute: 'guid' });
// Define the regions schema
const region = new schema.Entity(
'regions',
{
buildings: [building]
},
{ idAttribute: 'guid' }
);
const regionList = [region];
const normalizeData = () => {
const normalizedData = normalize(data, regionList);
This does normalize my object, the normalizedData is like this:
{
"entities":{
"building":{
"REGION1-BUILDING1-GUID":{
"guid":"REGION1-BUILDING1-GUID",
"name":"Region 1 Building 1"
},
"REGION1-BUILDING2-GUID":{
"guid":"REGION1-BUILDING2-GUID",
"name":"Region 1 Building 2"
},
"REGION2-BUILDING1-GUID":{
"guid":"REGION2-BUILDING1-GUID",
"name":"Region 2 Building 1"
},
"REGION2-BUIDLING2-GUID":{
"guid":"REGION2-BUIDLING2-GUID",
"name":"Region 2 Building 2"
}
},
"regions":{
"REGION1-GUID":{
"guid":"REGION1-GUID",
"name":"Region 1",
"buildings":[
"REGION1-BUILDING1-GUID",
"REGION1-BUILDING2-GUID"
]
},
"REGION2-GUID":{
"guid":"REGION2-GUID",
"name":"Region 2",
"buildings":[
"REGION2-BUILDING1-GUID",
"REGION2-BUIDLING2-GUID"
]
}
}
},
"result":[
"REGION1-GUID",
"REGION2-GUID"
]
}
But to get the Region of a building i need to iterate over every region and check if the building is contained in the list. I will not get any added value trough the normalization.
It would be perfect if I am able to link in both direction. Every region entitiy has a list of building guids and every building has one region guid.
Is there any way to archieve this in normalizr? What would be the best approach?
Thank you for your help.
I tried some things and found a working solution. I don't know if there is any better approach, but it's very clean code and it's working.
I just had to change the definition of the building entity to:
// Define buildings schema
const building = new schema.Entity(
'building',
{},
{
idAttribute: 'guid',
processStrategy: (entity, parent) => ({ ...entity, regionGuid: parent.guid })
}
);
This will add the property "regionGuid" to the building which holds the guid from the region.

Categories