in the process of developing an application, I'm facing a question about whether I'm using Redux correctly.
I have a fav:[] in which I add product objects and render their list. However, in order for the data not to be lost, I have to copy this fav:[] to favCopy:[] and only after that execute .filter
Example code:
case "fav":
state.fav = action.payload.filter === 'all'
? state.favCopy
: state.favCopy.filter((item: any) => item[type] === action.payload.filter)
break;
I would like to understand how right I am by keeping the original array intact? Maybe there is a way not to multiply arrays and use only one state?
We would recommend not doing filtering directly in the reducer most of the time. Instead, keep the original array in state as-is, and then also store a description of how you want the filtering to be done. From there, use selector functions to derive the filtered value as needed:
https://redux.js.org/usage/deriving-data-selectors
Related
In ReactQuery, the useQuery(..) hook takes a key that can contain complex dependencies (in an array). Or even just an int, like todoId that can change (cf the documentation).
Or a filters object like below:
function Component() {
const [filters, setFilters] = React.useState()
const { data } = useQuery(['todos', filters], () => fetchTodos(filters))
// ✅ set local state and let it "drive" the query
return <Filters onApply={setFilters} />
}
I'm unable to find an explanation regarding how it does monitor changes under the hood.
If the hashing of the key is well explained in the source code and this blog post the event-handling/monitoring of the value changing is a mystery to me.
So the question is: how does it keep track of changes, even inside complex typed passed in the Query Key array? Is there some introspection happening connecting events to value and/or reference changes?
PS: It is a question also applicable to dependencies in the useEffect(..) hook. There is a general perplexity from me, coming from non-interpreted languages.
Ok, since my comment was stolen as an answer even without a mention, I just repost it as an answer myself:
Query restarts when key hash changes.
how does the system know to recompute and compare the Hashkey? How does it "respond" to a change?
It recomputes hash on every render basically, no magic here.
Hash algoritm is an implementation detail, but by default it uses JSON.stringify (the only detail is that the object keys are sorted).
In the opposite, useEffect hook compares deps just by reference (if you can say so, technically it probably uses tc39.es/ecma262/#sec-isstrictlyequal e.g. ===).
The query keys are hashed deterministically. Basically, we JSON.stringify the key, but sort the keys of objects inside it so that they are stable. After that, we have just strings (you can also see them in the devtools), and strings are easy to compare to see if something changed (just ===).
how does the system know to recompute and compare the Hashkey?
we just do this on every render.
I am using a react class component, which holds a state, with (lets say..) a lot of key-value pairs stored into it.
Upon user action (button press/toggle), I need to REMOVE / ADD a new key-value pair to the component's state. Adding one is relatively easy, but pulling out a key-value pair from the state can be done in several different ways, so I was wondering which one is best, most readable, most performant and most preferred by the ReactJS audience..
1) Option 1:
onRemove = key => {
const newState = this.state;
delete newState[key] // Interacts directly with the state, which might not be a good practice, or expended behaviour?
this.setState(newState);
}
2) Option 2:
onRemove = key => {
const {[key]: removedKey, ...newState} = this.state; // Defines two new variables, one of which won't be used - "removedKey";
this.setState(newState);
}
There might be more ways to do it, and I am wondering which might be the best one, that can be used in any circumstance, no matter how 'big' the state gets...
Please share your thoughts based on your work experience with React & State management!
Thanks!
When I do something like this, I generally do a modified version of your "Option 1." As you currently have it, Option 1 mutates the state object, which you shouldn't do. Instead, create a shallow copy of state and then delete the key.
onRemove = key => {
const newState = {...this.state};
delete newState[key];
this.setState(newState);
}
The reason I like this way over your Option 2 is subjective--It's very readable to me. Make a copy, delete a key. Simple and to the point.
Option 1 isn't an option. You can't directly modify state in React.
Option 2 is fairly standard practice.
A third (to my mind lesser) option is to remove the property after cloning the object, but deleting properties from objects isn't what JavaScript engines optimize for, so I don't see any advantage to it over Option 2.
I'm rendering a list of attributes (artists) in React. While rendering, a separate HTTP request has to be made for each list item to display additional information (genres). To solve this issue, I put an empty array in the state beforehand - to update this state for each item during rendering:
let newState = Object.assign({}, this.state);
newState.categoryList[item.id] = names; // names is a single string containing all genres
this.setState(newState);
Eventually, I'm trying to display the genres (which are put into a single string) in a text field, after calling the method in the JSX: {this._getCategories(item)}.
/* this._getCategories is called to make an additional HTTP request for
every item in the list. this.state.categoryList[item.id] should contain
the required value (string). */
{this._getCategories(item)}
<Text style={styles.categories}>this.state.categoryList[item.id]</Text>
I've made a reproducible version on Expo.io, which can be found here: Snippet. To make things easier to understand, I added additional comments at each part of the code.
Edit
The problem appeared to be the way I used to update the state, which caused that the expected values weren't visible. The accepted answer has helped me to solve this.
If the problem you're having is that you're not seeing things get re-rendered when your HTTP requests complete, it's because you're breaking a React rule here:
// Incorrect
let newState = Object.assign({}, this.state);
newState.categoryList[item.id] = names; // names is a single string containing all genres
this.setState(newState);
Whenever you're setting new state based on existing state (as in your first code block), you must use the callback version of setState and the state object it passes you; you can't do it as you have above; docs.
You've said categoryList is an array. From your description, it sounds like it's a sparse array (at least initially). When setting the state, you have to make a copy of the array containing the changes, you can't directly modify it. Since it seems to be sparse, we can't do it the usual way (with spread notation in a literal), we have to use Object.assign instead:
// Correct
this.setState(({categoryList}) => ({categoryList: Object.assign([], categoryList, {[item.id]: names})}));
or the possibly-clearer, verbose version:
this.setState(state => {
const categoryList = Object.assign([], state.categoryList);
categoryList[item.id] = names;
return {categoryList};
});
I have an array of number that I wish to render in a tabular form. The array is returned from an API call, not generated by my app.
The data may change but is unlikely to do so, and in any case there are only twenty odd values, so re-rendering the whole table is not really a problem.
A simple data.map(value => <td>{value}</td> should do it.
But I keep getting an Each child in an array or iterator should have a unique "key" prop. warning. Is there any way that I can tell React that there is no key and that I wish it to re-render the whole table if anything changes.
Alternatively, is there any way that I can generate a unique key for each entry? The data items are not guaranteed to be unique.
I should add that I understand what keys are for and why they are useful, but in this instance I do not have any and the easiest thing would be not to use them, since there is unlikely to be a re-render.
You can use the index as the key. I think its worth reiterating that using the index as the key only works fine in the very specific scenario that the OP is facing.
This is particularly annoying when requirements change and all of sudden the list is being modified. This shows up as items not being updated during the render because the item updated has the same key (the index), its value is different, but react only cares about the key.
In cases where your data has no unique key. You should use some function that generates a unique id for each item. A simple version of that function just increments a global counter:
// Declared globally (as in attached to window object or equivalent)
var myuniqueidcounter = 0;
function uniqueId() {
myuniqueidcounter += 1
return myuniqueidcounter;
}
// Do this in the props change or whereever your data gets passed in
let keyedData = data.map(value => Object.assign(value, { Id: uniqueId() });
// In render
data.map(value => <td key={value.Id}>{value}</td>
That way, on multiple render calls, the ids returned are always unique. We assign the key when we get the data to avoid having to re-render the entire list on each call to render().
However, this case is actually pretty rare as you can usually find some combination of the backing data that will produce a unique key for each entry.
If you do go index-as-key
This article lists 3 conditions that should be met when choosing index-as-key approach that I think is a good check list:
The list and items are static–they are not computed and do not change;
The items in the list have no ids;
The list is never reordered or filtered.
data.map((value,index) =>{
<td key={index}>{value}</td>}
)
or
data.map((value,index) =>{
let i = Math.floor(Math.random() * 1000+1)
<td key={i}>{value}</td>}
)
You can use index as your key as it is unique each time
Based on the question asked, it might be worth saying that there is an another solution to this that doesn't use keys:
e.g. The following will complain about not having unique keys:
React.createElement('div', {}, [<span>1</span>, <span>2</span>]);
However, the following renders all children with no problems (This is what JSX transformed to JS looks like for nodes with multiple children):
React.createElement('div', {}, <span>1</span>, <span>2</span>);
So if you have e.g. a smallish list of generated react element fragments and unique keys don't offer and advantage in your situation, you can do:
React.createElement.apply(null, ['div', {}, ...elementList])
Notes:
elementList is passed as arguments to React.createElement which might be an issue if the list is huge.
It will re-render all the children with each render.
Using unique keys is generally the recommended approach, and is more performant for re-rendering.
However there are occasions where you just want to render in a single shot and don't care about re-rendering, or the data is not structured in a way that you can make good use of unique keys. You can use this as a work-around if you really need to.
I am building a game that players can attack each other by turn. So first I set the name, jobmanually and generate life,damage,magic randomly in componentWillMount().
I hope that every time I submit the attack form, certain amount of life with be reduced from the attacked person. But now every time I submit, the whole state is regenerated(with all kinds of bugs).
Can I do something to solve it?
app.js: https://ghostbin.com/paste/ype2y
attack.js: https://ghostbin.com/paste/wzm3m
I noticed that you do a lot of:
let players = this.state.players
which you are not supposed to do. Array is an object in js so here you are passing by reference. This means that every modification to the var players actually has side effects and modifies the state which you should never do. I generally recommend to never use in-place operations like splice, and to always use a copy of the state. In this case you can do:
let players = this.state.players.slice()
and from then on any modification to the players var does NOT affect the state. Double check you are not doing this anywhere else in your code. On top of that you should use the constructor only to set up and initiate your state. Otherwise every time the componentWillMount method is called your state is regenerated which is probably not the behavior you are expecting.
EDIT
I figured I could give you more pointers for what you are trying to do with arrays, as a general rule of thumb I follow this approach. If my new state has an array field which is a subset of the previous one then I use the .filter method, if the array of my new state needs to update some of its entries then I use the .map method. To give you an example on player deletion, I would have done it this way:
handleDeletePlayer(id) {
this.setState(prevState => ({
players: prevState.players.filter(player => player.id !== id)
}));
}
Your initial state should be generated in the constructor. This is done only once and will not be repeated when components props are updated.