useState strange behaviour - reactjs

I have set 2 arrays with useState in 2 different files:
const [filterQuery, setFilterQuery] = React.useState({
instructor: [],
material: [],
})
const [query, setQuery] = React.useState({
instructor: [],
material: [],
})
And i have this function that will update query with values from filterQuery:
const applyFilter = (filterQuery) => {
setQuery((prev) => ({
...prev,
instructor: filterQuery.instructor,
material: filterQuery.material,
}));
}
The strange behavior is when i call setFilterQuery from a different function, an example is updating filterQuery when a button is clicked
onClick = (value) => {
setFilterQuery({
...filterQuery,
instructor: value
})
}
What I expected is it will just update filterQuery, but query will magically updated with filterQuery too whereas it has no connection to the function whatsoever.
What could trigger this ? I have tried a few things, declaring a new variable first before using the setQuery :
const filter = filterQuery;
setQuery((prev) => ({
...prev,
instructor: filter.instructor,
material: filter.material,
}));
not working.
I have also tried changing the set format without the prev, using just ...query or without them and still not working.
Thank you for reading, helps appreciated.. Will provide more code info / what else I have tried if asked.

In JavaScript primitive types are passed around as values: meaning that each time a value is assigned, a copy of that value is created.
On the other side objects (including plain objects, array, functions, class instances) are references. If you modify the object, then all variables that reference that object are going to see the change.
Here what basically happens is that you are passing the reference of query to filterQuery, not the value to query that's why when you change either query or filterQuery they both get updated.
You can see more details and examples at this question's answer and difference between values and reference

Related

Why is my RTK Query "skip" parameter not working?

What I'm trying to do is fetch an array of objects, then fetch data related to the first object in the array.
const progress = useSelector(state => state.progress);
const appInfo = useSelector(state => state.appInfo);
const {
data: steps,
isLoading: isStepsLoading,
isSuccess: isStepsSuccess
} = useGetAllStepsQuery()
const {
data: questions
} = useGetQuestionsQuery({app_id: appInfo.id, step_parameter: steps[progress.currentStepIndex].step_parameter}, {skip: isStepsLoading})
The problem I'm having is that attempting to extract the step_parameter from the steps array is throwing a Cannot read properties of undefined (reading '0') error: in other words, it's as if the useGetQuestionsQuery is being called before the useGetAllStepsQuery is finished. I've added a skip parameter to the request, per this answer, and tried various values with it (e.g. !isStepsSuccess), none of them resolve this problem. I've also tried assigning the step_parameter conditionally, like this:
step_parameter: isStepsSuccess ? steps[progress.currentStepIndex].step_parameter : ''
Which does prevent the error, but it also appears to prevent the questions call from ever being made. Any help would be appreciated!

Why I cannot map data?

I'm trying to map "historicData" and put them in the labels of chart but it gives me error,"u cannot map the undefined".
So,Error is historicData is undefined .When I console.log(historicData),there is an array data.
This is my Code.
https://github.com/Saithiha24/React-Crypto-Beast/blob/master/src/Components/CoinInfo.js
I try to solve this for 2 days still can't find the answer. Please help me.
I think the problem is that historicData is initially undefined, and while waiting for the async call to complete and give historicData a value, you get the error. I suggest you change this
const [historicData, setHistoricData] = useState();
to this
const [historicData, setHistoricData] = useState([]);
so historicData is originally an empty array instead of undefined. You can also change the definition of data likes this
const data = {
labels: historicData ? historicData.map((coin) => {
//your code
}) : []}
so that if historicData is undefined, you don't try to map, just assign an empty array to labels.

Trying to reset a list before filtering, but having trouble with the asynchronous states

So i have a const with an array of objects, named "data", a state that receive that data so I can update the screen when I change it, and I have a function that filter that data.
Every time that the user change the state of an HTML select, I trigger a function named handleChange that calls the filter function, so the user user can see the filtered content.
The problem is that I want to reset the state that receives the data, with the original data before filtering it, so if the user change one of the selects, it filter based on the original data, not the previous changed one, but when I try to update the state with the const data value, it doesn't work.
Here is my code
const [list, setList] = useState<IData[]>([...data]);
const [filter, setFilter] = useState<IFilter>({
name: "",
color: "",
date: "",
});
function handleChange(
key: keyof IData,
event: React.ChangeEvent<{ value: string }>
): void {
const newFilter = { ...filter };
newFilter[key as keyof IData] = event.target.value;
setList([...data]); // reset the data
setFilter({ ...newFilter }); // set the filter value
filterList();
}
function filterList(): void {
const keys = Object.keys(filter) as Array<keyof IData>;
keys.forEach((key: keyof IData) => {
if (filter[key]) {
const newList = list.filter((item: IData) => item[key] === filter[key]);
setList([...newList]);
}
});
}
the problem is here
setList([...data]); // reset the data
setFilter({ ...newFilter }); // set the filter value
filterList();
apparently, when the filterList happens, the list state is not yet restarted with the data value, since if I console log it inside filterList(), it return me only the list that was previous filtered. Is there a way for me to make sure that the setList happens before filtering, so I'm sure that the list that is filtered is always based on the initial value?
You can access the updated values inside useEffect (more about useEffect), so instead of calling filterList() directly inside the handler you move it inside the hook:
React.useEffect(()=>{filterList();},[list,filter])
Alternatively, you can pass the values as parameters to the function, after you updated the state with them
filterList(newList, newFilter)
UPDATE
As you update list inside filterList you'll need some sort of a flag to indicate if you need to filter of not (I'd use useRef). Note that it would be preferable to pass parameters into filterList instead, because this way there will be one less rendering cycle. Here is a simplified example of how both can work, let me know if they make sense : https://jsfiddle.net/6xoeqkr3/

Better syntax to update nested objects on redux

Given a reducer example like the following
_({
expandAbility: (state, a: { which: string }) => ({
...state,
report: state.report && {
waitingForIt: false,
content: state.report.content && {
...state.report.content,
interaction: {
expandAbilities: !state.report.content.interaction.expandAbilities.contains(a.which)
? state.report.content.interaction.expandAbilities.add(a.which)
: state.report.content.interaction.expandAbilities.remove(a.which)
}
}
}
}),
})
(state type given below just for question context purposes)
const initialState = {
report: undefined as
| {
waitingForIt?: boolean
content?: {
supportedFightIds: number[]
deathCut: number
reportInfo: any
deathsFull: any
deathsByPlayer: any
deathsByAbility: any
interaction: {
expandAbilities: Set<string>
}
}
}
| undefined,
error: undefined as Error | undefined
}
Is there any kind of trick or "flavor-of-the-moment" library which would allow me to write a reducer update operation like expandAbility in a shorter way? (besides maybe creating some vars to reference inner paths)
There are lots of immutable update utilities out there, check out some options at https://github.com/markerikson/redux-ecosystem-links/blob/master/immutable-data.md#immutable-update-utilities and see what would be the best fit for you.
For starters check out Immutability-helper or immer.
So there are two things you could do to help simplify this. The first thing I like to do is move the logic out of the reducer and instead just pass in a value and say set expandAbilities to action. expandAbilities.
The second is actually something we do at work. We use immutableJS and wrote a single reducer that handles all of our state calls because you can give it a path of the parts of state that need to be updated and the value to update it with so we extracted that out and now it is easy to say dispatch(actions.update({path: ['report', 'content', 'interaction', 'expandAbilities'], value: '123' }))
You can even expand this so you can pass in a list of values that need to be updated and even preform validations around the data.

React set state variable

I've an confusion. I'm trying to add array variable in setState. My code is working properly but wanted to confirm some doubt before committing my code.
Which is right way to store array in state variable ?
var names = ['Jake', 'Jon', 'Thruster'];
this.setState({
state: names
});
Or
this.setState((state) => {
state.items.push(names[0]);
return state;
});
What is necessary of return statement here ?
Can some one please explain me the difference here ? I searched in google but I'm still confused.
var names = ['Jake', 'Jon', 'Thruster'];
this.setState({
names //according to Airbnb rules
});
or
this.setState({
names: names
});
this.state.names = ['Jake', 'Jon', 'Thruster'];
setState takes a second argument - callback, that will called after setting State properties
setState({property1: value1, ...}, () => { //some code after State changed })
The first approach is much more common and in my opinion, easier to read. The issue with your code is you don't need to use the key state, because the function you are calling is "setting state". The key should be something like firstNames.
this.setState({
firstNames: names
});
You could also just pass the object in the function like this because setState takes an object as a parameter.
var namesObject = {
firstNames: names
}
this.setState(namesObject);
I would read more about it here and keep doing small tutorials on this an you'll get the hang of it.
https://facebook.github.io/react/docs/react-component.html#setstate

Resources