functional component not re-rendering after setState update hook - reactjs

I have the below component which I thought would be super simple. The data is passed into a child component that renders a bar chart using charts.js. On the first render everything works fine. However, when I run the 'sort data' function, the data is updated but the child component doesn't re-render. Seen similar problems with class based components but can't find the answer for my case.
import React, { useState } from 'react';
const Landing = () => {
const [data, setData] = useState([
{ year: 2017, value: 50 },
{ year: 2016, value: 60 },
{ year: 2013, value: 50 },
{ year: 2014, value: 80 },
{ year: 2019, value: 70 }
]);
const sortData = () => {
const newArray = data.sort(function (a, b) { return a.year - b.year })
setData(newArray);
}
return (
<div>
<BarChart data={data} />
<button onClick={sortData} > sort data </button>
</div>
)
}
export default Landing

The reason why your component doesn't re-render is because you're directly mutating your state when you use data.sort and according to React docs, you should
Never mutate state directly, as calling setState() afterwards may replace the mutation you made. Treat state as if it were immutable.
reactjs.org/docs/react-component.html#state
Since Array.sort method is mutable, you should create a copy of data and then use Array.sort on the new array.
const Landing = () => {
const [data, setData] = React.useState([
{ year: 2017, value: 50 },
{ year: 2016, value: 60 },
{ year: 2013, value: 50 },
{ year: 2014, value: 80 },
{ year: 2019, value: 70 }
]);
const sortData = () => {
// using `spread operator` to create
// a copy of the `data` array
const newArray = [...data].sort(function(a, b) {
return a.year - b.year;
});
setData(newArray);
};
return (
<div>
<BarChart data={data} />
<button onClick={sortData}> sort data </button>
</div>
);
};
If an array method mutates the original array, always make a copy of your array before updating your state.
Have a look here to see which Array methods are mutable and which ones are not:
doesitmutate.xyz

Related

Fill array with key-value values

I'm trying to fill an array with objects. If I execute setState, the new entry is not added but the structure of the array is extended. Example: Initial entry (0), new entry (0->0), another entry (0->0->0). I would like however for each entry a running numbering
const [entrys, setEntrys] = useState([{ date: "", entry: "" }]);
->click on button
setEntrys((prev) => [{ ...prev, date: clickedDay, entry: hinweis }]);
The problem you're having is that you have included the previous state, e.g. "prev" with the spread operator inside the object you are passing.
What you want to do is include the new object you are saving to state AND then include previous state, e.g. "prev" as a separate element in the array - like below.
I've included a codesandbox for you too https://codesandbox.io/s/romantic-solomon-l566yy?file=/src/App.js.
import "./styles.css";
import React, { useState } from "react";
const App = () => {
const [entries, setEntries] = useState([
{
date: "",
text: ""
}
]);
const handleClick = () => {
setEntries((prevEntry) => {
return [...prevEntry, { date: "date", text: "test" }];
});
};
return (
<div>
<button onClick={handleClick}>Click me!</button>
</div>
);
};
export default App;

React State Updation Issue

My component's state is as below:
const [state, setState] = useState({
teamMembersOptions: [],
selectedTeamMember: {},
});
teamMembersOptions are being mapped from the redux state teamMembersList as below:
const teamMembersList = useSelector(state => state.get_all_team_members.team)
useEffect(() => {
if (teamMembersList)
mapTeamMembers();
}, [teamMembersList])
const mapTeamMembers = () => {
const teamMembers = [];
teamMembersList.map(member => {
const memberObject = {
'value': member.id,
'label': member.first_name.charAt(0).toUpperCase() + member.first_name.slice(1) + ' ' + member.last_name.charAt(0).toUpperCase() + member.last_name.slice(1)
}
if (member.is_leader == 1) {
memberObject.label = memberObject.label + ' (owner)'
setState({
...state,
selectedTeamMember: memberObject
})
}
teamMembers.push(memberObject)
})
setState({
...state,
teamMembersOptions: teamMembers
})
}
The state variables of selectedTeamMember and teamMemberOptions are not updating, it keeps consoling empty state. Whenever I console the local array of teamMembers inside mapTeamMembers function, it logs all the values successfully teamMembersList from Redux
also logs successfully that means teamMembersList and teamMembers are not empty. But the state is not updating. Why the setState statement inside mapTeamMembers function is not updating the state?
There are a number of things going on here and lot of them cause renders to trigger more renders which is why you are getting unexpected output.
I have add useMemo() and useCallback() around the data and calculation method respectively, and added their return values to the dependency array for useEffect(). This is to avoid the useEffect dependencies change on every render.
Calling setState() within the .map() function doesn't feel like the right choice either as each time it is called a render might occur, even though you are halfway through the mapping operation. Instead I suggest, and opted for, using .reduce() on the array and returning that result which can then be used to update the state within the useEffect hook.
Have a look at the working code below and a sample output given the defined input from teamMembersList. Note: this doesn't use Redux in the example given that it more setup to prove the concept.
import { useCallback, useEffect, useMemo, useState } from "react";
export default function App() {
const [state, setState] = useState({
teamMembersOptions: [],
selectedTeamMember: {}
});
const teamMembersList = useMemo(
() => [
{ id: 1, first_name: "John", last_name: "Smith", is_leader: 0 },
{ id: 2, first_name: "Maggie", last_name: "Simpson", is_leader: 1 }
],
[]
);
const mapTeamMembers = useCallback(
() =>
teamMembersList.reduce(
(acc, member) => {
const memberObject = {
value: member.id,
label:
member.first_name.charAt(0).toUpperCase() +
member.first_name.slice(1) +
" " +
member.last_name.charAt(0).toUpperCase() +
member.last_name.slice(1)
};
if (member.is_leader === 1) {
memberObject.label = memberObject.label + " (owner)";
acc.leader = memberObject;
}
acc.teamMembers.push(memberObject);
return acc;
},
{
teamMembers: [],
leader: ""
}
),
[teamMembersList]
);
useEffect(() => {
if (teamMembersList) {
const members = mapTeamMembers();
setState({
selectedTeamMember: members.leader,
teamMembersOptions: members.teamMembers
});
}
}, [teamMembersList, mapTeamMembers, setState]);
return (
<div>
<pre>
<code>{JSON.stringify(state, null, 4)}</code>
</pre>
</div>
);
}
The above will render out:
{
"selectedTeamMember": {
"value": 2,
"label": "Maggie Simpson (owner)"
},
"teamMembersOptions": [
{
"value": 1,
"label": "John Smith"
},
{
"value": 2,
"label": "Maggie Simpson (owner)"
}
]
}
I'd consider splitting the state object into individual state items but that's really up to you and how you want to handle the data.

useEffect Hook - how to detect the change of an object's property in a state array

How can useEffect detect the change in an array's object's property
without knowing the state array size because items may be added dynamically
in this particular case the "price" property in one of the objects
The array is a state
Just for example if changing the price property useEffect won't invoke, price will be the same next time (after - localStorage.getItem)
(In my app I change it dynamically in a different way this is for example).
const checkUseEffectLocalS = () => {
array[0]['Price'] = '12';
setItemsArray(array);
};
return (
<>
<div>
<button
onClick={() => checkUseEffectLocalS()}>
Check
</button>
</>
);
useEffect(() => {
localStorage.setItem(userItems, JSON.stringify(array));
}, [array.map((item) => item.price)]); //Tried this way also but it didn't worked
Niether
useEffect(() => {
localStorage.setItem(userItems, JSON.stringify(array));
}, [array]); // won't work
The array structure
array([
{
id: 1,
productName: 'Vitamin',
price: '10$',
},
{
id: 2,
productName: 'Powder',
price: '26$',
},
{
id: 3,
productName: 'Multivitamin',
price: '17.5$',
},
]);
Before asking I checked very similar question but with no real answer - stackoverflow
Thanks in advance.
Without using useEffect
const checkUseEffectLocalS = () => {
let arr= [...array]
arr[0]['Price'] = '12';
localStorage.setItem(userItems, JSON.stringify(arr))
setItemsArray(prev=>arr);
};
return (
<>
<div>
<button
onClick={() => checkUseEffectLocalS()}>
Check
</button>
</>
)
By using useEffect
useEffect(() => {
localStorage.setItem(userItems, JSON.stringify(array))
}, [JSON.stringify(array)])

How Refresh Table component onChange Event

I have react component in which I am showing data in the table, also have Select / dropdown. The table and select are in same component. I need to refresh table component soon the value change in the dropdown. My implementation does refresh the table and call API but there is delay when that happened. I am not sure if I have implemented correctly?
the idea is eziSearchCriteria is in useState and onChange event I am assign value to it.
const MyComponent = () => {
const[eziSearchCriteria, setEziSearchCriteria] = useState<IEziStatusSearchCriteriaForm>();
const eziSitesStatusCovers = [
{ label: 'UNSCHEDULED', value: 'UNSCHEDULED' },
{ label: 'COVERED', value: 'COVERED' },
{ label: 'PART COVERED', value: 'PART COVERED' },
];
useEffect(() =>{
setInitialPageLoad(true);
setDefaultSearchCriteria();
},[]);
const handleSearchFilter = (event) =>{
if(event!=null){
eziSearchCriteria.coverStatus = event.value;
setEziSearchCriteria(eziSearchCriteria);
}
}
return (
<div>
<div className="searchFilter">
<Select
options={eziSitesStatusCovers}
onChange = {handleSearchFilter}
/>
</div>
{ eziSearchCriteria ? (
<TableItems
url={url}
apiUrl ={api.eziTrackerStatus}
columns={columns}
customParams= {eziSearchCriteria}
key={eziSearchCriteria.coverStatus}
></TableItems> ) : null}
</div>
);
};
....
export interface IEziStatusSearchCriteriaForm{
startTime: string,
endTime: string,
scheduleId?: number,
coverStatus: string
}
I have found the issue and answer.
I was changing one value in the object eziSearchCriteria but object is same/ reference variable and react state not considering to render on this change. I created new object and assign to eziSearchCriteria and it worked straight away.
const handleSearchFilter = (event) =>{
if(event!=null){
eziSearchCriteria.coverStatus = event.value;
var searchCriteria2 : IEziStatusSearchCriteriaForm = {
startTime: "12-08-2020", //MM:DD:YYYY
endTime: "12-09-2020",
schedAction_Active: "Active",
coverStatus: event.value
}
setEziSearchCriteria(searchCriteria2);
}
}

Functional component problems React

I transformed a class component into a functional component but it looks like it does not work in a way it suppose to work and I can not find what is wrong. When I create a new object there is no name for the object and when I try to mark the object as a complete it removes all created objects at ones. I created a codesandbox here. Unfortunately, I am not too much familiar with functional component. Any help would be appreciated.
Here is my codesandbox sample:
https://codesandbox.io/s/crazy-sid-09myu?file=/src/App.js
Your Todos:
const [todos, setTodos] = useState([
{ id: uuid(), name: "Task 1", complete: true },
{ id: uuid(), name: "Task 2", complete: false }
]);
onAddHandler:
const addTodo = () =>
setTodos([...todos, { id: uuid(), name: "New Task", complete: false }]);
onSetCompleteHandler:
const setCompleteHandler = id =>
setTodos(
todos.map(todo => {
if (todo.id === id) {
return {
...todo,
complete: todo.complete ? 0 : 1
};
}
return todo;
})
);
I have created your new todos. Check out this link
Todos App
I have updated your code, please check the URL https://codesandbox.io/s/determined-morning-n8lgx?file=/src/App.js
const onComp = id => {
for (let i = 0; i < todos.length; i++) {
if (todos[i].id === id) {
let t = { ...todos[i] };
t.complete = !t.complete;
todos[i] = t;
}
}
setTodos([...todos]); // Here todos reference need to be changed
};
And also
const onSubmit = event => {
event.preventDefault();
setTodos([
...todos,
{
id: generateNewId(),
name: newTodoName,
complete: false
}
]);
setNewTodoName("");
};
While using hooks we need to be careful about state variable updates. While manipulating arrays and objects use the spread operator to create new references which invokes child components and re-render current component.

Resources