React error when removing input component - javascript

I am not sure if it´s a react issue or not but I am struggling a lot with this issue. I tried to create just a simple example to post out of my project:
https://codepen.io/as3script/pen/VMbNdz?editors=1111
There are four buttons on the example.
To reproduce the problem press each of the first three buttons to create a text input from and enter a value. For example enter 100 into the first one, 200 into the second and 300 into the third. Now press the fourth button to remove the first input.
It should keep the second and third with their respective values, 200 and 300, but instead it´s showing 100 in the second and 200 in the third.
This code is the same as it is on CodePen, it just didn't allow the link to be posted without this.
class ButtonComponent extends React.Component {
constructor(props) {
super(props);
this.state = {filtersArr:[]}
this.addButtons = this.addButtons.bind(this);
this.removeButtons = this.removeButtons.bind(this);
this.getInput = this.getInput.bind(this);
this.onChangeHandler = this.onChangeHandler.bind(this);
}
addButtons(e){
let tempArr = this.state.filtersArr;
tempArr.push({comp:this.getInput(e.target.id), id:e.target.id});
this.setState({filtersArr:tempArr});
}
removeButtons(e){
console.log(e.target.id);
let newArr = this.state.filtersArr.filter((filter)=>{
return (filter.id !=='FirstButton')
})
this.setState({filtersArr:newArr});
}
onChangeHandler(e){
console.log(e.target.value);
}
getInput(id){
return (
<div>
<h6>{id}</h6>
<input
id="min"
type="text"
placeholder="min"
onChange={this.onChangeHandler}/>
</div>
)
}
render() {
let styles = {
display:'inline-block'
}
return (
<div>
<p>Add three buttons and enter the number in each input, and remove amt.</p>
<button id="FirstButton" onClick={this.addButtons}>FirstButton</button>
<button id="SecondButton" onClick={this.addButtons}>SecondButton</button>
<button id="ThirdButton" onClick={this.addButtons}>ThirdButton</button>
<button id="FirstButton" onClick={this.removeButtons}>Remove firstButton</button>
<ul>
{this.state.filtersArr.map((filter, index)=>{
return <li style={styles} key={index}>{filter.comp}</li>
})
}
</ul>
</div>
);
}
}
ReactDOM.render(
<ButtonComponent/>,
document.getElementById('root')
);

The problem is that you're using the array index as your key, so React will reuse the first two li elements and drop the last one. Change key={index} to key={filter.id}, and it works as you would expect.
Update concerning the comment & downvote: I assumed uniqueness on filters in the actual code, given that the field is called id. The CodePen seems more of a stripped down version to show the problem. But if you do actually wish to let each button create multiple text fields, you'd indeed need to add something extra to distinguish the keys (e.g. a counter). This doesn't affect the problem as stated in the question though.
Looking at the code again, I noticed getInput would be an ideal candidate to extract into a separate (stateless) component, e.g. FilterInput. This fits better with the react model than keeping child renderings in the component state.

The code produces 3 textboxes in divs. These textboxes are updated by entering the numbers (100, 200, 300). When you click the RemoveFirstButton, the state, which stores these components, is updated and render is called.
The render function does a diff of the current state and the previous state and removes the last div, which contains the number 300. This is because, for the render function, the first element of the array changed from FirstButton to SecondButton, the second element changed from SecondButton to ThirdButton and the third element does not exist anymore.
To make it work as expected, you need to change the key of the elements from the index of the array to the id of the element, so that the render method can tell the difference between the elements.

Edit:
Please avoid using your ids as keys as the other answers suggest if multiple of the same elements can be added as your initial example suggests because keys need to be unique and this way they can (and will) be repeated!
Your problem lies in the fact that you are using the key attribute wrongly.
React expects your components to have constant keys, so it can decide when and which ones it needs to update.
If you are modifying an array, the keys of the objects are changing, and the element that had the key '1' before the removal is gone afterwards and the component that previously had key '2' becomes the one with key '1' and so forth.
The React documentation advises against using the index as key as well:
"We don’t recommend using indexes for keys if the items can reorder"
To avoid this you should use some sort of unique key generation and store each components key in it's own object, for example like this:
In the constructor:
this.nextKey = 0
When adding a new component:
tempArr.push({comp: comp, id: e.target.id, key: this.nextKey++});
In the render function:
return <li style={styles} key={filter.key}>{filter.comp}</li>
I modified your example to include these lines, you can find it here:
https://codepen.io/Isti115/pen/MEmMZP?editors=1111
Also, some other tips:
When creating an object that has a key with a value assigned from a variable with the same name, you can shorten it so that this:
{comp: comp, id: e.target.id}
becomes this:
{comp, id: e.target.id}.
If you need something more robust, you could use a separate unique key generator like this:
Create GUID / UUID in JavaScript?
or this:
https://gist.github.com/gordonbrander/2230317
ps.: The switch statement you are using is completely useless right now. What have you tried to accomplish with it?

Related

How can I use a reactive function inside a class attribute in Svelte?

How can I set an elements class attribute based on the return value of a function in svelte?
I am trying to make a speed typing game with svelte. I need to display the currently typed letters in different colors based on their state (inactive/active/right/wrong).
I have a function that gets the state of a typed letter from the array sentenceState where x is the words index and y is a letters index.
function getLetterStyle(x, y) {
const state = sentenceState[x][y];
switch (state) {
case null:
return '';
case 0:
return 'text-black border-l border-black';
case 1:
case 2:
return 'text-green-500';
case -1:
return 'text-red-500';
}
}
The sentenceState is a reactive array of arrays where each value is either null, 0, 1, 2 or -1.
Based on these values I want to dynamically change the class attribute of an element.
I tried to use this function like this:
{#each activeSentence as word, wordIndex}
{#each word as letter, letterIndex}
<span class={getLetterStyle(wordIndex, letterIndex)>{letter}</span>
{/each}
{/each}
This does not seem to work the way I would have imagined. It works on the initial page load. But when sentenceState updates, the class does not change accordingly.
getLetterStyle() will only be called once. Is there a way to use a function with parameters for the class attribute? What am I doing wrong here?
Thanks in advance!
Edit: here is a REPL
https://svelte.dev/repl/4b02dd3eacee43748e05de3071e59f0b?version=3.42.2
There's nothing making your #each loop reactive.
The #each loop is generating elements based on sentence, but this variable never gets updated, so it never re-renders.
One solution here is to instead calculate your styles per-letter reactively and then letting the #each generate elements based on that.
Working REPL: https://svelte.dev/repl/77977b39b8964c8ba32acb063ceeb407?version=3.42.2

React: Are keys useful for any situations other than lists?

All the information I could find highlight using keys when rendering lists, for example:
<ul>
{array.map((item, index) => <li key={index}>{item}</li>)}
</ul>
Are there situations other than lists where it's also helpful to provide keys?
Are there downsides to simply providing keys for every non-static element on the page?
You may use keys to reset a component state. See this article for more information : https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
A key is recommended for lists in react because react uses this to identify items that are added, changed or removed.
Keys
Keys help React identify which items have changed, are added, or are
removed. Keys should be given to the elements inside the array to give
the elements a stable identity.
An example with a function component:
function MyListComponent (props) {
const list = props.list;
const items= list.map((item) =>
<li key={item.toString()}>
{item}
</li>
);
return (
<ul>{items}</ul>
);
}
const array = [1, 2, 3, 4, 5];
ReactDOM.render(
<MyListComponent list={array} />,
document.getElementById('root')
);
The only rule is that it must be unique among its siblings.
If you don't use a key - if you don't use a key you'll get a warning.
An id (identifier of each item in your list) is best for this purposes - if you don't have any identifier that you can use, an index of each item in the list can be used, but its not recommended because this can cause problems in cases where the order of items change.
Keys only give a performance benefit when used in lists. When one value in a list is changed, React knows to only update that value and not the whole list. Keys are helpful because they help keep track of elements when the index changes (note: only use index as key for last resort because if the index changes (i.e. an element is added to the front/middle of a list) React will have to re render the whole list). You can read more about it here: https://reactjs.org/docs/lists-and-keys.html. As for the downsides, I'm going to take an educated guess and say including an extra key attribute that is not used anywhere will slow down your compilation time an unnoticeable amount

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.

What is the significance of keys in ReactJS?

I want to understand what happens if I don't use keys in dynamically added components. I removed keys and it renders without any issue and just gave warning messages regarding key usage. Would someone please give some example of what the consequences are if we don't use keys?
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity:
Example:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
TL;DR Use unique and constant keys when rendering dynamic children, or expect strange things to happen.
One of the tricky aspects I've found during the few weeks I've been using React.js is to understand the key property you're expected to pass to a component when it's part of an array of children. It's not that you have to specify this property, things will work most of the time apart from getting this warning on the console:
Each child in an array should have a unique "key" prop. Check the render method of undefined.
By reading the linked documentation it can be easy to not see the implications of this affirmation:
When React reconciles the keyed children, it will ensure that any child with key will be reordered (instead of clobbered) or destroyed (instead of reused).
At first it looked to me it was all about performance but, as Paul O’Shannessy pointed, it's actually about identity.
The key here is to understand not everything in the DOM has a representation in React "Virtual DOM" and, because direct manipulations of the DOM (like a user changing an value or a jQuery plugin listening an element) are unnoticed by React, not using unique and constant keys will end up with React recreating the DOM node of a component when the key is not constant (and losing any untracked state in the node) or reusing a DOM node to render another component when the key is not unique (and tying its state to this other component).
Here you have a live demo showing how awful the results are:
http://jsfiddle.net/frosas/S4Dju/
Just add an item, change it, add more items and see what happens.
Also see
Source
Another useful usage of React keys other than creating dynamic elements is reseting elements when their keys change, for example in a project I had an <input/> element of type file and I wanted the element to be initialized to its initial value (no file chosen) each time the component renders, so I did the following:
Parent constructor:
this.state = {
fileInputKey: Date.now()
// other properties
};
The state object also had other properties, I just added this one for the sake of this example
Each time I wanted the input element in the child component be reset I did:
this.setState({fileInputKey: Date.now()});
Parent render:
<Child fileInputKey={this.state.fileInputKey}/>
Child render:
<input key={this.props.fileInputKey} type="file" onChange={this.onSelectFile}/>
Also see this example from React blog:
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key

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