Show new state of attribute for each listitem - javascript

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};
});

Related

Redux best practice to filter data

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

The best way to remove a key from react component's state

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.

React list with no keys

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.

(React.js )Initial state generated randomly. How to prevent it from regenerate each time I handle an event?

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.

React: how to compare current props.children with new one

Hi,
i am building component which acts only as wrapper for some other generated content and uses third party library. This library works with props.children of component. So far so good, but this thrird party library is little laggy when applied, or refreshed on element. And because only reason to refresh this library is when props.children changed I am trying to figure how to compare this.props.children and nextProps.children in shouldComponentUpdate. I was thinking that PureRenderMixin should do the work, but for me it does not works. Component is rerendered even if I change only state.listName as it is in example below.
<div>
List name '{this.state.listName}'
<br />
<MyComponent>
<ul>
{listOfLi}
</ul>
</MyComponent>
</div>
Is there any way, how to manage comparing of props.children or any other option how to do something like that?
Thanks for any help!
As Matt S pointed out, the accepted answer is kind of a fragile workaround and would depend on a non-standard use of key. Aside from the list examples he listed, even using something like key={id} would fall down if your ids remained the same but certain fields were modified in the resources they represent.
This issue contains a good discussion on the topic and ends with a more stable workaround. Essentially, you can simplify the children prop in a way that allows you to run a deep comparison. You can use the React.Children utilities to write the simplification methods:
// Flattens all child elements into a single list
const flatten = (children, flat = []) => {
flat = [ ...flat, ...React.Children.toArray(children) ]
if (children.props && children.props.children) {
return flatten(children.props.children, flat)
}
return flat
}
// Strips all circular references and internal fields
const simplify = children => {
const flat = flatten(children)
return flat.map(
({
key,
ref,
type,
props: {
children,
...props
}
}) => ({
key, ref, type, props
})
)
}
Then you can use shouldComponentUpdate or React.memo to prevent re-renders:
const MyComponent = ({ children }) => (
<div>{ children }</div>
)
export default React.memo(MyComponent, (prev, next) => (
JSON.stringify(simplify(prev.children)) ===
JSON.stringify(simplify(next.children))
))
These utilities + JSON.stringify are just one approach, the one mentioned in the comment is similar and you could also leverage utilities like lodash.isequal for the deep comparison. Unfortunately I don't know of any one or two liners for this comparison but please comment if you know a simpler stable way to do this!
You can make use of child key prop that React suggests that arrays of children should be given to uniquely identify them. Because each child has a key, you can reliably tell whether children has changed across prop changes (this is the entire point of keys!). If the keys don't match between new and old then they have changed.
React.render(<App><Child key='1'/><Child key='2'/></App>, document.body)
and do the check in the App component you want to check before each update if the children changed
shouldComponentUpdate(nextProps){
var oldKeys = this.props.children.map( child => child.key);
var newKeys = nextProps.children.map( child => child.key);
//compare new and old keys to make sure they are the same
}
Note that this doesn't tell you if the content of each child has changed, you have to compare each by some criteria (such as deeply comparing props) if you want to know if nothing in the whole tree below this point has changed
as an even further optimization we know that children will never change as result of a state change so we can actually do our comparing in componentWillReceiveProps() and just set some state property like childrenHaveChanged
Something about the design and behavior you describe is off. You should rarely, if ever, have to concern yourself with performing manual diffs of children. That should be left to React. If the library you are using is choking every time the component updates, regardless of whether it's because of children or some other state/prop change, it is not written reactively and you should look for a different one that is written reactively. Or you could contribute to fixing that behavior in the open source project, either by opening an issue or submitting a pull request.
It's not easy to do what you're trying to do because it shouldn't be necessary. React is very, very good at handling reconciliation and will only render when something has actually changed that will change the state of the DOM relevant to it.

Categories

Resources