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.
Related
Question, I have this state coming from the backend It's a array of messages that will be store in state using useState. This state will be pass on in the child component. The problem is I want to change value of a specific key before storing it into the state.
Sample
Messages Array sample data
const messages = [
{
value: 'sample value one',
status: false,
},
{
value: 'sample value two',
status: false,
},
];
UseSelector
const messageGetById = useSelector((state) => state.messageGetById);
const { message } = messageGetById;
UseEffect
useEffect(() => {
if (message) {
setCurrentMessage(message);
}
}, [message]);
The output that I want is before passing the message into setCurrentMessage, all the value of status will be change to true.
Thanks!
You can use map method to map thought the array and change the status to true.
useEffect(() => {
if (message) {
const newMessages = messages?.map((mess) => {
return {...mess, status: true}})
setCurrentMessage(newMessages);
}}, [message]);
Set the mapped state with useEffect
useEffect(() => {
const data = [...message];
if (data.length > 0) {
data.map((ele) => ({
value: "YOUR CHANGED VALUE",
status: ele.status,
}));
setCurrentMessage(data);
}
}, [message]);
My Component looks like this:
import cloneDeep from "clone-deep";
import { Context } from "../../context";
const Component = () => {
const context = useContext(Context);
const [state, setState] = useState(
{
_id: "123",
users: [
{
_id: "1",
points: 5
},
{
_id: "2",
points: 8
}
]
}
);
useEffect(() => {
context.socket.emit("points");
context.socket.on("points", (socketData) => {
setState(prevState => {
const newState = {...prevState};
const index = newState.users
.findIndex(user => user._id == socketData.content._id);
newState.users[index].points = socketData.content.points;
return newState;
})
});
return () => context.socket.off("points");
}, []);
return <div>(There is table with identificators and points)</div>
};
I wonder if this is the right approach. I just want to write the code in the right way.
Or maybe it's better with the use of deep cloning? Does it matter?
setState(prevState => {
const newState = cloneDeep(prevState);
const index = newState.users
.findIndex(user => user._id == "2");
newState.users[index].points++;
return newState;
})
EDIT: I added the rest of the code to make it easier to understand.
In your current code:
useEffect(() => {
setState((prevState) => {
const newState = { ...prevState };
const index = newState.users.findIndex((user) => user._id == "2");
newState.users[index].points++;
console.log({ prevState, newState });
return newState;
});
}, []);
You can see that prevState is being mutated (points is 9):
{
_id: "123",
users: [
{
_id: "1",
points: 5
},
{
_id: "2",
points: 9 // Mutated!
}
]
}
To avoid mutating the state, you have to use not mutating methods such as spread operator or map function:
useEffect(() => {
setState((prevState) => {
const newState = ({
...prevState,
users: prevState.users.map((user) =>
user._id === "2"
? {
...user,
points: user.points + 1
}
: user
)
})
console.log({ prevState, newState });
return newState
}
);
}, []);
Now you can see that the prevState is not mutated:
{
_id: "123",
users: [
{
_id: "1",
points: 5
},
{
_id: "2",
points: 8 // Not mutated :)
}
]
}
Your code will work, but the problem will start when your state becomes bigger.
You currently have only two properties on the state, the _id and the users. If in the future will add more and more properties like loggedUser and settings and favorites, and more... your application will render everything on every state change.
At this point you will have to start thinking about other solutions to state management, like redux, mobx, or just split the state to smaller useState, also you can look into useReducer in complex structures.
I'm trying to convert a Class-based component to a Functional component. I get the above-mentioned error if I use the same code that was under componentDidMount in useEffect hook.
// Class based component
class Container extends Component {
state = {
elements: [],
counter: 1,
bgColor: "#ffffff",
botTextColor: "#000000",
botBGColor: "#aaaaaa",
userTextColor: "#000000",
userBgColor: "#aaaaaa",
};
componentDidMount = async () => {
this.setState({
bgColor: this.props.chat.currentChat.bgColor,
botTextColor: this.props.chat.currentChat.botTextColor,
botBGColor: this.props.chat.currentChat.botBGColor,
userTextColor: this.props.chat.currentChat.userTextColor,
userBgColor: this.props.chat.currentChat.userBgColor,
});
this.setState({
elements:
this.props.chat.currentChat.elements &&
this.props.chat.currentChat.elements.length > 0
? elements
: [
{
id: "0",
data: {
label: (
<WelcomeNode
id={"0"}
images={this.props.chat.media.map((e) => e.file)}
updateChoices={(choices) =>
this.updateChoices("0", choices)
}
updateMessage={(message) =>
this.updateMessage("0", message)
}
updateImage={(e) => this.updateImage(e, "0")}
addEdge={this.addEdgeCustom}
deleteEdgeChoice={(index) =>
this.deleteEdgeChoice("0", index)
}
isChoiceConnected={(index) =>
this.isChoiceConnected("0", index)
}
></WelcomeNode>
),
message: "",
choices: [],
type: "welcome",
id: "0",
},
className: "node-elements",
position: { x: 100, y: 100 },
},
],
counter: elements.length > 0 ? elements.length : 1,
});
}
}
The Following is the functional component where the error occurs
// Functional component
const initialState = {.....}
const Container = () => {
const [state, setState] = useState(initialState);
const { auth, chat } = useSelector((state) => ({ ...state }));
const dispatch = useDispatch();
const history = useHistory();
useEffect(() => {
setState({
...state,
bgColor: chat.currentChat.bgColor,
botTextColor: chat.currentChat.botTextColor,
botBGColor: chat.currentChat.botBGColor,
userTextColor: chat.currentChat.userTextColor,
userBgColor: chat.currentChat.userBgColor,
});
setState({
...state,
elements:
chat.currentChat.elements && chat.currentChat.elements.length > 0
? elements
: [
{
id: "0",
data: {
label: (
<WelcomeNode
id={"0"}
images={chat.media.map((e) => e.file)}
updateChoices={(choices) => updateChoices("0", choices)}
updateMessage={(message) => updateMessage("0", message)}
updateImage={(e) => updateImage(e, "0")}
addEdge={(e) => addEdgeCustom(e)}
deleteEdgeChoice={(index) =>
deleteEdgeChoice("0", index)
}
isChoiceConnected={(index) =>
isChoiceConnected("0", index)
}
></WelcomeNode>
),
message: "",
choices: [],
type: "welcome",
id: "0",
},
className: "node-elements",
position: { x: 100, y: 100 },
},
],
counter: elements.length > 0 ? elements.length : 1,
});
}, []);
}
The following error is thrown and the browser crashes Uncaught (in promise) Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
sorry but your code is too complicated to read please reorganize it to make it more readable and understandable. please try charging useSelector line to this line:
const { auth, chat } = useSelector((state) => state);
this is causing multi render because useSelector detect state is recreating(using spread operator) so it would rerender the component.
plus in useEffect when you are setting the state use setState callback, this will not override your previous state update :
setState(prev=>({...prev,newState}))
useEffect usually requires a dependency array. What you use inside of the useEffect hook should go into that array for example we have a function that sets the id. The useEffect dependency will want the id in the array. Thus only update/run this useEffect hook if the id changes.
useEffect(() => {
setId(id)
}, [id])
If you only want to run the useEffect once on first render you can leave the array blank like this:
useEffect(()=>{
//http fetch request or something
}, [])
I'm trying to write this React component that connects to an API that returns JSON.
The API returns JSON results that look like this small example:
JSON:
{
"id": 22,
"gameTitle": "The Haunted Plains II",
"startDate": "2020-03-31T12:49:50.009",
"endDate": "2020-04-04T04:00:00",
"description": "A simple bitmap style RPG",
"gameUrl": "https://yourgames.org/1/55/3",
"isOpen": true,
"gameFaqId": 25,
"gameTypeId": 3,
"gameCharClasses": ["Bard", "Theif", "Warrior", "Wizard", "Cleric"]
},
{
"id": 25,
"gameTitle": "Downhill Mountain Biking Pro",
"startDate": "2020-02-12T11:22:30.011",
"endDate": "2020-05-02T03:11:00",
"description": "A side-scrolling, downhill mountaining biking game",
"gameUrl": "https://yourgames.org/3/43/6",
"isOpen": true,
"gameFaqId": 11,
"gameTypeId": 6,
"gameCharClasses": ["Beginner", "Pro", "Super Star"]
}
I want to put the data into the 'gameListing' variable that have defined as 'const gameListing'.
Then I have a function called makeData that is called by the main app which is using a React library called 'react-table'.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://localhost:44376/api/projects',
);
setData(result.data);
};
fetchData();
}, []);
const range = len => {
const arr = [];
for (let i = 0; i < len; i++) {
arr.push(i);
}
return arr;
};
const gameListing = data.map(g => {
return {
id: g.id,
gameTitle: g.gameTitle,
startDate: g.startDate,
endDate: g.endDate,
description: g.description,
gameUrl: g.gameUrl,
isOpen: g.isOpen,
gameFaqId: g.gameFaqId,
gameCharClasses: g.gameCharClasses
};
});
export default function makeData(lens = [2]) {
const makeDataLevel = (depth = 0) => {
const len = lens[depth]
return range(len).map(d => {
return {
...gameListing(),
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
}
})
}
return makeDataLevel()
}
The problem is, after hours of debugging, I can't get it to work.
My latest issue is that it's not displaying any data.
Here is a Code Sandbox: https://codesandbox.io/s/trusting-booth-b7wxg
I see one problem is youre using .map wrong
you have to pass in a parameter and that parameter is basically like a placeholder to use that has the data within it
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
for example:
const gameListing = data.map((elem) => {
return {
id: elem.id,
gameTitle: elem.gameTitle,
startDate: elem.startDate,
endDate: elem.endDate,
description: elem.description,
gameUrl: elem.gameUrl,
isOpen: elem.isOpen,
gameFaqId: elem.gameFaqId,
gameCharClasses: elem.gameCharClasses
}
})
Does that help or work?
I have a simple task list app. One of the screens is a "Today & Overdue" list.
The tasks reducer looks like:
{
"data": {
123: {
"id": 123,
"summary": "blah blah",
"dueDate": "2020-03-12",
"completed": true
},
456: {
"id": 456,
"summary": "some other task",
"dueDate": "2020-03-12",
"completed": false
}
},
"byId": [123, 456]
}
My list reducer looks like:
{
"data": {
919: {
"id": 919,
"name": "Today & Overdue"
},
818: {
"id": 818,
"summary": "My Cool List"
}
},
"byId": [919, 818]
}
On the "Today & Overdue" list, I need to fetch all tasks where the dueDate is today or older. I tried using reselect to optimize for the performance of the list screen, via:
# Get end of day today
const now = moment();
const endOfDay = Date.parse(now.endOf("day").utc(true).utcOffset(0).format());
const getTasksTodayOrOlder = (state) => Object.values(state.tasks.data).filter(task => Date.parse(task.dueDate) <= endOfDay);
But it appears that any time a field in the tasks data changes (i.e. completed or summary), the getTasksTodayOrOlder regenerates the selector.
Is the only way to do this to keep a cache on the tasks reducer; something like byDueDate to keep track of an array of arrays of due dates.
{
"data": ...,
"byId": ...,
"byDueDate": {
"2020-03-19": [112,123,141, ...],
"2020-03-20": [922, 939, ...],
}
}
The date cache seems like a lot of overhead and could get out of sync.
What is the recommended way to handle a reselect that will:
Will filter to tasks due today or older
Tasks that are not complete
If the output of a selector is a calculated array that uses Object.keys, Object.values or Array.prototype.filter then you can memoize it in the following way:
const { createSelector, defaultMemoize } = Reselect;
const state = [
{ val: 1 },
{ val: 2 },
{ val: 3 },
{ val: 4 },
{ val: 5 },
{ val: 6 },
{ val: 7 },
];
//pass an array to memArray like [a,b], as long as a and b are the same
// you will get the same array back even if the arrays themselves
// are not the same like when you use filter, Object.values or Object.keys
const memArray = (() => {
const mem = defaultMemoize((...args) => args);
//pass the array that this function receives to a memoized function
// as separate arguments so if [a,b] is received it'll call
// memoized(a,b)
return arr => mem(...arr);
})();//this is called an IIFE
const selectHigher = createSelector(
state => state,
(_, min) => min,
(data, min) =>
memArray(
Object.values(data).filter(({ val }) => val > min)
)
);
const one = selectHigher(state, 5);
const twoState = [...state, { val: 0 }];
const two = selectHigher(twoState, 5);
console.log('two is:',two);
console.log('one and two are equal', one === two);
const threeState = [...state, { val: 8 }];
const three = selectHigher(threeState, 5);
console.log('three is:',three);
console.log('three and two are equal', three === two);
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
What does it mean "regenerates the selector"? Where you using this selector? If you are using it in react functional component via hook (useSelector) for example, then:
// selectors
const now = moment();
const endOfDay = Date.parse(now.endOf("day").utc(true).utcOffset(0).format());
const getTasks = (state) => state.tasks.data;
const getTasksTodayOrOlder = createSelector(getTasks, tasks =>
Object.values(tasks)
.filter(task => Date.parse(task.dueDate) <= endOfDay)
.map(({ id, dueDate }) => {id, dueDate});
// component
import { shallowEqual, useSelector } from 'react-redux';
.
.
.
const tasksTodayOrOlderList = useSelector(getTasksTodayOrOlder, shallowEqual);
Anytime something in state.tasks.data changes, getTasksTodayOrOlder will be recalculating, but you will not get re render if previous state of tasksTodayOrOlderList is shallow equally to current output of getTasksTodayOrOlder selector (all values inside objects are equal), because we passed second argument shallowEqual to our useSelector function. I used map to remove "tracking" from unnecessary properties from our data object.
And we need to split our selector into two, because we only need to recalculate if our state.tasks.data changes, not when any part of our state changes.
Also, i think your should use endOfDay as arg value to selector, because it's dynamic.