component not updating - issue with how to use key - reactjs

I have a card component that is used across the site. The card doesn't fully update when the data is updated, or if it changes to another card, it will sometimes retain information in the card, from the previous one. The information that isn't changing all the time, are the areas where it's being rendered from a function this.renderPrice(), this.renderPerks(), and this.renderButtons(). I've tried adding a key to the actual component, and when the component is being called from another component, but neither seem to solve the issue.
class ChooseMembershipCard extends React.Component {
return(
<div className="membership_card" key={this.props.product.id}>
<h6 className="membership_banner">{this.renderSpotsAvailable()}</h6>
<h6 className="membership_title">{this.props.product.title}</h6>
{this.renderPrice()}
<p className="membership_description">{this.props.product.description}</p>
{this.renderPerks()}
<div style={{padding: 10, marginTop: 20}}>
{this.renderButtons()}
</div>
</div>
)
export default ChooseMembershipCard
I am using in this component, where I've tried adding the key to the component, but it doesn't seem to make a difference
return (
<ChooseMembershipCard
key={this.state.products[this.state.selectedProductIndex]}
product={this.state.products[this.state.selectedProductIndex]}
isUser={this.state.isUser}
selectedProductIndex={this.state.selectedProductIndex}
monthlyFrequency={this.state.monthlyFrequency}
userHasSubscription={this.userHasSubscription}
loadUser={this.props.loadUser}
/>
)
And then I map through products here, and render a card for each one. When I update information inline, the data saves and is updated, but the actual component doesn't re-render, so the updated information is not seen unless the page is refreshed.
renderProducts() {
return this.state.products.map((product, i) => {
return (
<ChooseMembershipCard
key={product.id}
product={product}
isUser={true}
isEditable={true}
archiveProduct={this.archiveProduct}
editProduct={this.editProduct}
/>
)
})
}
In the above example, I always get 1 error:
ChooseMembershipCard: "key" is not a prop. Trying to access it will result in "undefined" being returned. If you need to access the same value within the child component, you should pass it as a different prop.
But if I remove the key, I then get this error:
Each child in a list should have a unique "key" prop.
I feel like the issue is how key is being used. Any insight is appreciated!

You are passing key into ChooseMembershipCard Component. Why dont you use the same key props while assigning into that div.
class ChooseMembershipCard extends React.Component {
return(
<div className="membership_card" key={this.props.key}>
<h6 className="membership_banner">{this.renderSpotsAvailable()}</h6>
<h6 className="membership_title">{this.props.product.title}</h6>
{this.renderPrice()}
<p className="membership_description">{this.props.product.description}</p>
{this.renderPerks()}
<div style={{padding: 10, marginTop: 20}}>
{this.renderButtons()}
</div>
</div>
)
export default ChooseMembershipCard
You need to add key to the component which is being rendered multiple times. In renderProducts() map function you are adding key prop but ur not actually assigning it into ChooseMembershipCard Component instead passing a prop named key to that Component and your not using it anywhere in your code.

Related

How does the 'key' prop work in a non-dynamic child component and why is it essential for rendering updates?

I understand that the special 'key' prop when used with child components that are created dynamically from arrays helps React to uniquely identify components and render updates efficiently. But I would like to know when and why would it be necessary to use the key prop for a 'non-dynamic' component.
My application uses a Reducer and useContext hook to manage state for a Functional Component A. The state object has a maximum 3 levels of nesting. Component A updates state and passes part of the state object as props to two instances of a child component B. B uses these props to render a switch component and 2 input components. Here's the simplified code for this hierarchy.
Component A:
const A: FC = () => {
// ....
// graphql queries to get data and update state using reducer
// ...
return (
<B
enabled={data.a.b.enabled}
value1={data.a.b.value1}
value2={data.a.b.value2}
/>
<B
enabled={data.a.b.enabled}
value1={data.a.b.value1}
value2={data.a.b.value2}
/>
);
};
Component B:
const B: FC = props => {
const { value1, value2, enabled} = props; // there are other props as well
return (
<>
<div className={someClassLogic}>
<Switch
onChange={onValueChange}
isChecked={enabled}
disabled={disabled}
/>
</div>
<div className={someClassLogic} >
<Input
input={value1}
disabled={disabled}
/>
</div>
<div className={someClassLogic}>
<Input
input={value2}
disabled={disabled}
/>
</div>
</>
);
};
A tablerow click event is used to update the state and the component B displays the 'settings' of this selected item which the user can mutate using the component B.
Here's the problem I'm facing- when the state is updated by a user action (selecting a row from a table, not shown in the snippet), I can see that both A and B receive the new data in the react developer tools and by printing to the console. But, a render does not take place to show the new data. I would like to understand why this is the case.
After looking up this issue, I figured I need a key prop while instantiating component B (the answers don't clearly explain why). With the following addition, the values did render correctly. Why is a key necessary here and why does it only work when the key contains all props that can change values? If I only use the uniqueId as the key, the value1 and value2 do not render correctly again. If I have many changing-props, do I have to them to add the key as well? Isn't there a less clumsy approach?
Updated Component A:
const A: FC = () => {
return (
<B
key={`${data.a.uniqueId}-
${data.a.b.value1}-
${data.a.b.value2}
enabled={data.a.b.enabled}
value1={data.a.b.value1}
value2={data.a.b.value2}
/>
<B
key={`${data.a.uniqueId}-
${data.a.b.value1}-
${data.a.b.value2}
enabled={data.a.b.enabled}
value1={data.a.b.value1}
value2={data.a.b.value2}
/>
);
};
Also, I noticed that although clicking on a table row rendered the correct value in component B now, but clicking on a row that is not changed by the user so far would cause the previously rendered values to remain on the Input1 and Input2 components (instead of blank). So I had to add keys to the Inputs as well with the enabled state attached to it which fixed this issue.
Updated Component B:
const B: FC = props => {
const { value1, value2, enabled} = props; // there are other props as well
return (
<>
<div className={someClassLogic}>
<Switch
onChange={onValueChange}
isChecked={enabled}
disabled={disabled}
/>
</div>
<div className={someClassLogic} >
<Input
key={`value1-${enabled}`}
input={value1}
disabled={disabled}
/>
</div>
<div className={someClassLogic}>
<Input
key={`value2-${enabled}`}
input={value2}
disabled={disabled}
/>
</div>
</>
);
};
Here again, why is a key needed? Doesn't react figure out that the props have changed and automatically render again?

Can one React component render by 2 classes?

Can one React component render by 2 classes? Just like I did in the picture.
I tried the above. It gives me another error Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of "Groups".
The button component Im using in Groups method(Groups.jsx) like this way.
const Groups = (props) => (
<div className = 'panel'>
<h2>Groups</h2>
<button >Get Groups</button>
<div className = 'group-list'>
{props.groups.map((group) =>
<GroupsEntry name = {group.name}
members = {group.members}/>
)}
</div>
</div>
);
Do you guys have any idea about this? Thank you
I will try to clarify a little.
You can render a component from whatever parent component you want.
By in the case of your picture, what is telling you that the first component in tree, was App.js, and then App.js rendered Groups.js component, and Groups.js rendered your actual component.
In the same page, the warning you are seeing about using "key" is because you need to set a unique key value for each element that you are rendered as a list, a repeated item. This is because the internal react work to compare if it has to rerender again your component needs it. You will have performance problems (not in an easy example...) if you dont add it. Normally you use the id of the object you are rendering.
I hope i clarified a little.
Yes, a component can be rendered as many times as you would like. The issue is that you are mapping over an array and returning an element. React requires that you put a unique key prop on these elements that ideally are consistent between renders.
You can try to update your code to be something like the following:
const Groups = props => (
<div className="panel">
<h2>Groups</h2>
<button>Get Groups</button>
<div className="group-list">
{props.groups.map(group => (
<GroupsEntry key={group.name} name={group.name} members={group.members} />
))}
</div>
</div>
);
This is assuming group.name is unique. If you have a unique identifier (eg: group.id) that would be ideal.
For more examples and why this is necessary you can checkout the official docs: https://reactjs.org/docs/lists-and-keys.html

ReactJS: Is there a way to see the values used for the key property required for lists

I'm getting the common error: Warning: Each child in an array or iterator should have a unique "key" prop.
In general, finding the offending code is easy, but this time it is not. What I would like to know is what are the key values for the items being displayed.
(Q) Is there a way for me to see what each key property's value in the DOM that is rendered?
When I inspect the HTML generated lists that react has rendered, I don't see the key property on the DOM elements (nor would I expect to see it), but that is what I'm asking to see. Is there a flag I can set to ask React to add that key property value to the DOM so I can see it?
This would let me see where I'm missing a key, or have two keys with the same value (this is the problem I think I have). See Find Duplicated Key in a complicated React component
At this point, I'm doing the methodical approach of rendering one item at a time to isolate which item has the problem so I can zero in and find the offending code.
Searching for solutions
When searching for solutions there are several good write ups on how this works and why keys are needed, and they are shown below.
Similar question (OKs same question as mine, but no answer) - Cant find react error which child is missing key prop
Excellent question about how keys work and a great answer. See Understanding unique keys for array children in React.js
The ReactJS docs on using Keys and mis-using them. See https://reactjs.org/docs/lists-and-keys.html#extracting-components-with-keys.
An npm library that helps solve this problem. See https://www.npmjs.com/package/react-key-index
How do you track down duplicate keys - Find Duplicated Key in a complicated React component
Here is some explanation why Key is important and how to provide it to an array of children in React:
There are three List component in the example below. If you open it in the Chrome DevTools and click on the "Randomize sort" you will realize that the children of the lists are changing (they blink) but in the second example some more of the li elements are changing, the reason is React is removing and re-creating the li tag.
The efficient examples are the first and third List component because React knows instead of removing these group of elements it can to re-order them which is way faster!
I would NOT recommend the third (JSON.stringify) example, better always rely on a unique property from the data structure to avoid extra work.
const items = Array(10).fill(null).map((_, idx) => ({ id: String(idx) + Math.floor(Math.random() * 9999), title: 'Item '+(idx+1) }));
function List({ children, items, keyProp, indexType }) {
return (
<div style={{ borderBottom: '1px solid black', padding: '1em' }}>
<h2>{children}</h2>
<ul>
{items.map((data, idx) => {
const key = indexType === 'dataStructure'
? data[keyProp]
: indexType === 'jsonStringify'
? JSON.stringify(data)
: idx;
return (
<li key={key}>{data.title}, key: {key}</li>
)
})}
</ul>
</div>
);
}
class App extends React.Component {
constructor(props) {
super(props)
this.state = { items };
this.handleRandomize = this.handleRandomize.bind(this);
}
handleRandomize() {
this.setState({ items: items.slice().sort(() => Math.round(Math.random() * 2) - 1) });
}
render() {
return (
<div style={{ background: 'red', marginBottom: '2em' }}>
<button onClick={this.handleRandomize}>Randomize sort</button>
<List items={this.state.items} indexType='dataStructure' keyProp='id'>
<p>List renderd with unique "id" from data structure</p>
</List>
<List items={this.state.items} indexType='arrayIndex'>
<p>List rendered with index of the loop</p>
</List>
<List items={this.state.items} indexType='jsonStringify'>
<p>List rendered with JSON.stringify()</p>
</List>
</div>
)
}
}
ReactDOM.render(<App />, document.querySelector('#root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Prevent Component/PureComponent with styled-components from re-rendering all items after state change

After adding styled-components to this example, we've noticed that our list component updates everything when only one item in state is modified.
The list rendering highlights (from React dev-tools) are excessive when adding/removing a single entry. One item is removed/added, then all items get highlighted.
code samples
github: https://github.com/chancesmith/reactjs-optimize-list-updates/tree/master/src
codesandbox: https://codesandbox.io/s/wn45qw44z5
Here is an example of the right list component (CategorizedList.js)
import styled from "styled-components";
const Item = styled.li`
color: #444;
`;
class CategorizedList extends PureComponent {
render() {
return (
<div>
{this.props.categories.map(category => (
<ul>
<li key={this.props.catStrings[category]}>
{this.props.catStrings[category]}
</li>
<ul>
{this.props.items.map((item, index) =>
item.category === category ? (
<div key={item.label + index}>
<Item>{item.label}</Item>
</div>
) : null
)}
</ul>
</ul>
))}
</div>
);
}
}
Preference
I'd prefer to use PureComponent so that shouldComponentUpdate() gets handled automatically.
Question
How can we make sure only the modified objects in items state are re-rendered?
If the data changes , the view will re-render. It shouldn't be an expensive process since it happens once on add/remove action. If you find performance issues it might be caused from something else.
In general this would be the way you can have some control on pure-components re-render:
https://reactjs.org/docs/react-api.html#reactmemo

When should I be using React.cloneElement vs this.props.children?

I am still a noob at React and in many examples on the internet, I see this variation in rendering child elements which I find confusing. Normally I see this:
class Users extends React.Component {
render() {
return (
<div>
<h2>Users</h2>
{this.props.children}
</div>
)
}
}
But then I see an example like this:
<ReactCSSTransitionGroup
component="div"
transitionName="example"
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
>
{React.cloneElement(this.props.children, {
key: this.props.location.pathname
})}
</ReactCSSTransitionGroup>
Now I understand the api but the docs don't exactly make clear when I should be using it.
So what does one do which the other can't? Could someone explain this to me with better examples?
props.children isn't the actual children; It is the descriptor of the children. So you don't have actually anything to change; you can't change any props, or edit any functionality; you can only read from it. If you need to make any modifications you have to create new elements using React.CloneElement.
https://egghead.io/lessons/react-use-react-cloneelement-to-extend-functionality-of-children-components
An example:
main render function of a component such as App.js:
render() {
return(
<Paragraph>
<Sentence>First</Sentence>
<Sentence>Second</Sentence>
<Sentence>Third</Sentence>
</Paragraph>
)
}
now let's say you need to add an onClick to each child of Paragraph; so in your Paragraph.js you can do:
render() {
return (
<div>
{React.Children.map(this.props.children, child => {
return React.cloneElement(child, {
onClick: this.props.onClick })
})}
</div>
)
}
then simply you can do this:
render() {
return(
<Paragraph onClick={this.onClick}>
<Sentence>First</Sentence>
<Sentence>Second</Sentence>
<Sentence>Third</Sentence>
</Paragraph>
)
}
Note: the React.Children.map function will only see the top level elements, it does not see any of the things that those elements render; meaning that you are providing the direct props to children (here the <Sentence /> elements). If you need the props to be passed down further, let's say you will have a <div></div> inside one of the <Sentence /> elements that wants to use the onClick prop then in that case you can use the Context API to do it. Make the Paragraph the provider and the Sentence elements as consumer.
Edit:
Look at Vennesa's answer instead, which is a better explanation.
Original:
First of all, the React.cloneElement example only works if your child is a single React element.
For almost everything {this.props.children} is the one you want.
Cloning is useful in some more advanced scenarios, where a parent sends in an element and the child component needs to change some props on that element or add things like ref for accessing the actual DOM element.
In the example above, the parent which gives the child does not know about the key requirement for the component, therefore it creates a copy of the element it is given and adds a key based on some unique identifier in the object. For more info on what key does: https://facebook.github.io/react/docs/multiple-components.html
In fact, React.cloneElement is not strictly associated with this.props.children.
It's useful whenever you need to clone react elements(PropTypes.element) to add/override props, without wanting the parent to have knowledge about those component internals(e.g, attaching event handlers or assigning key/ref attributes).
Also react elements are immutable.
React.cloneElement( element, [props], [...children] ) is almost equivalent to:
<element.type {...element.props} {...props}>{children}</element.type>
However, the children prop in React is especially used for containment (aka composition), pairing with React.Children API and React.cloneElement, component that uses props.children can handle more logic(e.g., state transitions, events, DOM measurements etc) internally while yielding the rendering part to wherever it's used, React Router <switch/> or compound component <select/> are some great examples.
One last thing that worth mentioning is that react elements are not restricted to props.children.
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}
They can be whatever props that makes sense, the key was to define a good contract for the component, so that the consumers of it can be decoupled from the underlying implementation details, regardless whether it's using React.Children, React.cloneElement, or even React.createContext.

Resources