Why 'map()' method in my react component is changing/updating state? - reactjs

I am using map() method in one of my function in React component to generate a new array which I will use to update the state using setState() method. but when I generate the new array (modified by map method) then state of my component is also getting updated. Eventhough I havent used setState method yet. (ofcourse the change of state is not visible, it didnt updated in browser yet, cuz setState() method hasnt been called yet, but backstage the state is updated.) I dont know WHY!!!
class Color1 extends Component {
state = {
colors: [
{ key: 1, color: 'green' },
{ key: 2, color: 'red' },
{ key: 3, color: 'red' },
{ key: 4, color: 'green' },
],
};
changeColor = color => {
console.log(this.state.colors);
const pehli = this.state.colors.map(e => {
if (e.key === color.key) {
e.color = e.color === 'green' ? 'red' : 'green';
return e;
} else {
return e;
}
});
console.log(
this.state.colors
);** /* For checking the state and it is updated */**
};
// So 'pehli' was supposed to be a new seperate array, it was supposed to mutate the colors array in state. But it does

you are mutating the original object here e.color = e.color === 'green' ? 'red' : 'green'; ... instead make a copy as
changeColor = color => {
console.log(this.state.colors);
const pehli = this.state.colors.map(e => {
const el = {...e}; // a copy here
if (el.key === color.key) {
el.color = el.color === 'green' ? 'red' : 'green';
}
return el
});
console.log(
this.state.colors
);
}

Related

How to update redux store tree and then update another tree with data from first?

I have a chart and ui with data inputs for it. I use redux to store data for chart. I have 2 trees. One - for chart component. It contains array of numbers (axis coordinates). And second for ui. It contains array of objects: {axis coordinate, visibility}.
This structure is based on ability to show/hide some data from chart.
Redux store
const initialState = {
chartSettings: {
...
axisValues: [7, 9, 1, 13, 24, ...]
},
chartSettingsData: {
...
axisValues: [
{value: 7, visibility: true},
{value: 9, visibility: false},
...
]
}
};
I want my chart component to get clear data array from store, without visibility logic. But I need it for ui. So I came to this structure. May be it's wrong. But I have not other ideas.
So, when I click checkbox, I change visibility in store object chartSettingsData. And then, I need to update another tree with data fo chart. I tried clone filtered array after chartSettingsData update in reducer.
case "UPDATE_ITEM_VISIBILITY":
return {
...state,
chartSettingsData: {
...state.chartSettingsData,
axisValues: state.chartSettingsData.axisValues.map((item, i) =>
i === index
? { value: item.value, visibility: !item.visibility }
: item
),
},
chartSettings: {
...state.chartSettings,
axisValues: (() => {
const filtered = state.chartSettingsData.axisValues.filter(
(item) => item.visibility
);
return filtered.map((item, i) => item.value);
})(),
}
But I get data in chartSettings before chartSettingsData been updated. What is the right way to update redux store in this case?
You should construct the two parts of the store in variables (so you can use the new chartSettingsData) and then return:
case "UPDATE_ITEM_VISIBILITY":
const chartSettingsData = {
...state.chartSettingsData,
axisValues: state.chartSettingsData.axisValues.map((item, i) =>
i === index
? { value: item.value, visibility: !item.visibility }
: item
),
};
const chartSettings = {
...state.chartSettings,
axisValues: (() => {
const filtered = chartSettingsData.axisValues.filter(
(item) => item.visibility
);
return filtered.map((item, i) => item.value);
})(),
};
return {
...state,
chartSettingsData,
chartSettings
};

Change xAxis label style on data series click, Highcharts/React

I am trying to achieve a behavior in simple column chart in React, where I can click on series point and have xAxis label change style. Also, when you click again, that style should be removed. It is the same behavior as we have for mouse over and mouse out but for click event. I can get it to work with mouse events, but not click event.
Is this possible to achieve? This is a code sample I have.
Do the following:
Maintain a state say current update its value with the current axis number upon onClick
Define x-Axis and labels in your config-options
Use formatter function inside label. This function provides current axis value as argument. use it and compare it with your current state and adjust the style dynamically.
Working copy of code sample is here
Code Snippet
class App extends React.Component {
state = {
current: "black"
};
options = {
tooltip: {
enabled: false
},
xAxis: {
labels: {
formatter: item => {
const color = this.state.current === item.value ? "red" : "black";
const fontWeight =
this.state.current === item.value ? "bold" : "normal";
return `<span style="color: ${color}; font-weight: ${fontWeight}">${
item.value
}</span>`;
}
}
},
series: [
{
data: [1, 2, 3, 4],
type: "column",
colors: ["#000000"],
cursor: "pointer",
point: {
events: {
click: (e, x, y) => {
this.setState({ current: e.point.x });
console.log(e.target, e.point.x);
}
// mouseOver: function(e) {
// $(this.series.chart.xAxis[0].labelGroup.element.childNodes[this.x]).css({fontWeight: 'bold'});
// },
// mouseOut: function() {
// $(this.series.chart.xAxis[0].labelGroup.element.childNodes[this.x]).css({fontWeight: 'normal'});
// }
}
}
}
]
};
render() {
return (
<div>
<h2>Highcharts</h2>
<ReactHighcharts config={this.options} />
</div>
);
}
}
Just use the click event function to change the label CSS style. For example:
series: [{
...,
point: {
events: {
click: function() {
var ticks = this.series.xAxis.ticks,
label,
fontWeight;
if (ticks[this.x]) {
label = ticks[this.x].label;
fontWeight = (
label.styles.fontWeight && label.styles.fontWeight === 'bold'
) ? 'normal' : 'bold';
ticks[this.x].label.css({
'fontWeight': fontWeight
});
}
}
}
}
}]
Live demo: http://jsfiddle.net/BlackLabel/6m4e8x0y/4991/
API Reference:
https://api.highcharts.com/highcharts/series.column.events.click
https://api.highcharts.com/class-reference/Highcharts.SVGElement#css

How can I update a single array value using the useState()?

I am trying to generate several checkboxes. I want that when you click on a checkbox the value of the check property changes to its opposite value. This would normally be done with:
check =! check
but I'm using hooks,
const [data,setData]=data;
and I don't know how to update this item without affecting the rest of my object. How can I do it?
this is my data, and my hooks definition:
let data={
"documents": {
"trucker": {
"checkDocuments": [
{
'label': 'licencia',
'check': false
},
{
'label': 'identificacion',
'check': false
},
{
'label': 'telefono',
'check': false
},
],
"section": "camionero"
},
"client": {
"checkDocuments": [
{
'label': 'comodato',
'check': false
}
],
"section": "cliente"
},
"container": {
"checkDocuments": [
{
'label': 'BL',
'check': false
}
],
"section": "contenedor"
}
}
}
const [data,setData]=data;
this is the code that goes in the render
return (
<List>
{
Object.keys(data).map((section, i) => {
return
<ListItem itemHeader first key={i}>
<Text>{data[section].section}</Text>
</ListItem>
data[section].checkDocuments.map((document, j) => {
return
<ListItem onPress={() => document.check = !document.check} key={j} > ***I need set only update this element in my data
<CheckBox checked={document.check} onPress={() => document.check = !document.check; } color="blue" /> ****I need set only update this element in my data
<Body>
<Text>{document.label}</Text>
</Body>
</ListItem>
})
})
}
</List>)
How can I have the whole object that I defined in my hook updated from the modification of an element, so that my code is rendered again?
I would recommend you make yourself a compound state hook that can update individual keys. It would behave like the setState method in a React Class Component.
Check this out for reference: https://github.com/fvaldez421/shopping-demo/blob/master/src/hooks/common.js
If one level of interaction isn't enough, you can copy that key from your state, edit the copy, then use it to update your state:
const { state, setState } = useCompound(initialState);
const updateTrucker = (type, updates) => {
if (type === 'documents') {
const { trucker: truckerOriginal } = state;
// copy it to prevent mutations
const trucker = { ...truckerOriginal };
trucker.checkDocuments = trucker.checkDocuments.map(
doc => { /* your document check update logic here */ }
);
setState(trucker);
}
}
The code above should update the trucker key in your state.

ReactJS: How to set state of an object selected via radio button?

In my UsedComponents component I have a list of mapped radio buttons that each return componentType as value.
this.props.usedComponents.map((component, index) => (
<RadioButton
key={index}
id={component.componentType}
icon={ButtonIco}
label={component.componentName}
name="select"
value={component.componentType}
inputClass={classes.RadioInput}
labelClass={classes.RadioLabel}
selected={component.selected}
handleChange={this.props.selectComponent}
/>
))
My state looks like this:
constructor(props) {
super(props);
this.state = {
components: components,
usedComponents: [components[0], components[2], components[3]],
};
this.selectComponentHandler = this.selectComponentHandler.bind(this);
}
components is an imported array of objects that each look something like this:
{
componentType: "headerLogoNavigation",
componentName: "Header 02",
padding: "small",
fontSize: "small",
fontColor: "#1f1f1f",
fontFamily: "Sans-serif",
backgroundColor: "#ffffff",
image: placeholderLogo,
selected: false,
isEditing: false,
margins: false,
roundCorners: "none",
mobile: "false"
}
In my Page component I'm trying to pass a selectComponentHandler prop to my UsedComponents component that should select a component based on a value of a selected radio button and set its state to selected: true. For an added bonus it should set the state of any previously selected component to selected: false.So far I managed to figure out how to select the component but I'm not able to update its state. My final attempt to create this handler before I gave up looks like this:
selectComponentHandler = event => {
this.setState(prevState => {
let selected = prevState.usedComponents.filter(item => item.componentType === event.target.value);
selected.selected = 'true';
return { selected };
});
};
and it's an attempt to filter the prevState inside the setState for the componentType that matches event.target.value of the radio button and set it's state, but I messed up the logic or the syntax and my head is about to explode so I can't figure out what I did wrong.
Can someone help me figure this out?
I figured it out. It's a bit hacky but it works.
selectComponentHandler = event => {
const value = event.target.value;
this.setState(prevState => {
let selected = prevState.usedComponents.filter(item => item.componentType === value).shift();
let unSelected = prevState.usedComponents.filter(item => item.selected === true).shift();
if(unSelected) {
unSelected.selected = false;
}
selected.selected = true;
return { unSelected, selected };
});
};

How to update a state which is a array of objects?

My state is as follows
this.state = {
todos: [{
title: 'asas',
status: 'incomplete',
uuid: 11
}, {
title: 'asas',
status: 'incomplete',
uuid: 12
}, {
title: 'asas',
status: 'complete',
uuid: 13
}],
currentTab: "Show All"
}
and whenever a user clicks on any of the todo items's checkBox i want to update the state status of the checkbox and i have written the following code for it
this.state.todos.map(todo => {
if (todo.uuid === uuid) todo.status = (todo.status === 'complete') ? 'incomplete' : 'complete'
});
this.forceUpdate();
Is Using forceUpdate a good approach here? as i have updating only a single value inside an array of objects. Is there a better solution for this problem?
either of the following will call setState with the updated state without modifying the current state.
https://redux.js.org/recipes/structuringreducers/immutableupdatepatterns#inserting-and-removing-items-in-arrays
using the spread operator:
edit.. actually, this is the hard way 8)
see https://redux.js.org/recipes/structuringreducers/immutableupdatepatterns#updating-an-item-in-an-array
this.setState(prevState => {
const idx = prevState.todos.findIndex(todo => todo.uuid === uuid);
return {
todos: [
...prevState.todos.slice(0, idx),
{
...prevState.todos[idx],
status: prevState.todos[idx].status === "complete" ? "incomplete" : "complete",
}
...prevState.todos.slice(idx + 1),
]
}
});
or using immer:
import produce from "immer";
this.setState(prevState => {
const idx = prevState.todos.findIndex(todo => todo.uuid === uuid);
return produce(prevState, draft => {
draft.todos[idx].status = prevState.todos[idx].status === "complete" ? "incomplete" : "complete"
});
});

Resources