ReactJS: Child component not triggering parent component rerender on action click

Clash Royale CLAN TAG#URR8PPP
ReactJS: Child component not triggering parent component rerender on action click
I am trying to refactor my component and extract the list items into a separate child component.
The original code below (not refactored) works:
// ListContainer.js
import React, Component from 'react';
import Container, ListGroup, ListGroupItem, Button from 'reactstrap';
import CSSTransition, TransitionGroup from 'react-transition-group';
import connect from 'react-redux';
import getItems, deleteItem from '../actions/itemActions';
import PropTypes from 'prop-types';
class ListContainer extends Component
componentDidMount()
this.props.getItems();
deleteAction = (id) =>
console.log('this.props: ' + JSON.stringify(this.props));
this.props.deleteItem(id);
render()
const items = this.props.item;
return(
<Container>
<ListGroup>
<TransitionGroup className="shopping-list">
items.map((item, i) => (
<CSSTransition key=item._id timeout=500 classNames="fade">
<ListGroupItem>
<Button
className="remove-btn"
color="danger"
size="sm"
onClick=e => this.deleteAction(item._id, e)>
×
</Button>
item.name item._id
</ListGroupItem>
</CSSTransition>
))
</TransitionGroup>
</ListGroup>
</Container>
);
const mapStateToProps = state => (
item: state.item
);
const mapDispatchToProps = dispatch => (
getItems: () => dispatch(getItems()),
deleteItem: id => dispatch(deleteItem(id)),
);
export default connect(mapStateToProps, mapDispatchToProps)(ListContainer);
// ListContainer.js
import React, Component from 'react';
import Container, ListGroup, ListGroupItem, Button from 'reactstrap';
import CSSTransition, TransitionGroup from 'react-transition-group';
import connect from 'react-redux';
import getItems, deleteItem from '../actions/itemActions';
import PropTypes from 'prop-types';
class ListContainer extends Component
componentDidMount()
this.props.getItems();
deleteAction = (id) =>
console.log('this.props: ' + JSON.stringify(this.props));
this.props.deleteItem(id);
render()
const items = this.props.item;
return(
<Container>
<ListGroup>
<TransitionGroup className="shopping-list">
items.map((item, i) => (
<CSSTransition key=item._id timeout=500 classNames="fade">
<ListGroupItem>
<Button
className="remove-btn"
color="danger"
size="sm"
onClick=e => this.deleteAction(item._id, e)>
×
</Button>
item.name item._id
</ListGroupItem>
</CSSTransition>
))
</TransitionGroup>
</ListGroup>
</Container>
);
const mapStateToProps = state => (
item: state.item
);
const mapDispatchToProps = dispatch => (
getItems: () => dispatch(getItems()),
deleteItem: id => dispatch(deleteItem(id)),
);
export default connect(mapStateToProps, mapDispatchToProps)(ListContainer);
The refactored code below doesn't work:
Clicking on the delete button does not rerender the new list correctly even though the action is dispatched, the redux store is updated and the API callout succeeds.
// New ListContainer.js
// Same imports
// Extracted component
const ListElement = props =>
const del = props;
const _id = props.item;
const name = props.item;
return (
<CSSTransition key=_id timeout=500 classNames="fade">
<ListGroupItem>
<Button
className="remove-btn"
color="danger"
size="sm"
onClick=() => del(_id)>
×
</Button>
name _id
</ListGroupItem>
</CSSTransition>
);
// Refactored parent component
class ListContainer extends Component
componentDidMount()
this.props.getItems();
del = (id) =>
this.props.deleteItem(id);
render()
const items = this.props.item;
return(
<Container>
<ListGroup>
<TransitionGroup className="shopping-list">
items.map((item, i) => (
<ListElement key=i item=item del= id => this.del(id) />
))
</TransitionGroup>
</ListGroup>
</Container>
);
// Same redux connector
Why is the ListContainer component not rendered properly?
2 Answers
2
This is a prime example of why you shouldn't use the array index as a key when rendering a list of components. React can't properly detect changes.
Use something unique about the item as the key prop. If they don't have anything unique, generate a unique id and assign it to them first.
key
You can do a bit of research about why using the index as a key causes the issues your seeing, if you're curious. Would probably be a good idea.
id
key
Thanks, Jayce. I have tried that already - it does make any difference.
– Alexey
Aug 8 at 7:38
Can you add your reducer code that deletes the item from the store?
– Jayce444
Aug 8 at 7:43
Sure, see the updated code snippet. The problem is that the error happens only when I try to show each item using a separate child component. If I move the child component's JSX back to the parent - everything works. So, I must be missing something...
– Alexey
Aug 8 at 7:54
In your
ListContainer render inside the map, you have <ListElement ... del= id => this.del(id) /> where is that id argument coming from? Shouldn't it be <ListElement ... del= id => this.del(item._id) />? Since it's a property of the item object (and apparently that key has an underscore too by the looks of it).– Jayce444
Aug 8 at 8:20
ListContainer
<ListElement ... del= id => this.del(id) />
id
<ListElement ... del= id => this.del(item._id) />
A lesson in debugging (and persistence): generate lots of ideas and systematically test them. After looking in every possible corner of the application, I found the issue! The issue was in the presentation layer. react-transition-group didn't work as expected after refactoring. Removing TransitionGroup and CSSTransition fixed the issue.
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
If the
idproperty is unique for each item, use that as thekeyinstead.– Jayce444
Aug 8 at 7:18