ReactJS Wrong Component gets removed from DOM - javascript

I have three files: ShopsContainer.js ShopsComponent.js and ShopsItemComponent.js
ShopsContainer maintains an array of shop items in local state that gets passed down into ShopsComponent as props. ShopsComponent then maps through the items array that is being received as props and renders a ShopsItemComponent for each item in the array.
Within my ShopsContainer file, I have a method that removes a shop item from state using the following code:
removeShop = (shopAccount) => {
this.setState(prevState => ({
items: prevState.items.filter(shop => {
return shop.shopAccount !== shopAccount
})
}));
}
When this happens, the correct item is removed from the items array in state, however, whatever the last ShopItem is that is in the DOM at the time of the removeShop call will get removed no matter if it is the correct item that should be removed or not. In other words, when removeShop gets called and the items array in state gets updated correctly, the wrong ShopItemComponent gets removed from the DOM.
What I would like to happen (or what I think should happen) is when removeShop gets called, that shop gets removed from the items array in state and ShopsContainer re-renders causing ShopsComponent to re-render with the updated props being received. And lastly ShopsComponent would map through the newly updated items array in props displaying a `ShopItemComponent for the correct items. Perhaps the problem has to do with the props being updated?
My code is as follows:
ShopsContainer.js
class ShopsContainer extends Component {
constructor() {
this.state = {
items: null
}
this.getAll();
this.removeShop = this.removeShop.bind(this);
}
getAll = () => {
// API request that fetches items and updates state
}
removeShop = (shopAccount) => {
this.setState(prevState => ({
items: prevState.items.filter(shop => {
return shop.shopAccount !== shopAccount
})
}));
}
render() {
return (
<div>
{this.state.items ? <ShopComponent items={this.state.items} removeShop={this.removeShop} /> : <div><h1>Loading...</h1></div>}
</div>
);
}
}
ShopsComponent.js
class ShopsComponent extends Component {
constructor() {
this.handleRemove = this.handleRemove.bind(this);
}
handleRemove = (shopAccount) => {
this.props.removeShop(shopAccount);
}
render() {
return (
<React.Fragment>
<Header />
{this.props.items.map((shopItem, i) => {
return (<ShopItemComponent key={i} item={shopItem} removeShop={this.handleRemove} />);
})}
</React.Fragment>
);
}
}

Your code is working great, but you only has one mistake , your ShopComponent is assign index as a key for each ShopItemComponent and react is tracking those indexes to update the correct component, so you need to set key as a unique value between items, then I realize that shopAccount should be your id for each item.
The solution code is below.
class ShopsComponent extends Component {
handleRemove = (shopAccount) => {
this.props.removeShop(shopAccount);
}
render() {
return (
<React.Fragment>
<Header />
{this.props.items.map((shopItem) => <ShopItemComponent key={shopItem.shopAccount} item={shopItem} removeShop={this.handleRemove} />)}
</React.Fragment>
);
}
}
I hope you can find useful.
Note, when you are using a arrow function into your class, don't bind that method into the constructor, so remove it, because
handleRemove = (shopAccount) => {
this.props.removeShop(shopAccount);
}
is already binded.

Related

Component Doesn't Update once the Parent component's state is updated

import React, {Component} from 'react';
import "./DisplayCard.css";
class DisplayCard extends Component {
runArray = (array) => {
for (var i = 0; i<array.length; i++) {
return <div>{array[i].task}</div>
}
}
renderElements = (savedTasks) =>{
if (savedTasks.length === 0) {
return <div className="noTasks"> <p>You have no saved tasks.</p> </div>
} else {
return this.runArray(savedTasks)
}
}
render() {
return (
<div className="DisplayCardContainer">
{this.renderElements(this.props.saved)}
</div>
)
}
}
export default DisplayCard;
Hey guys,
I am new to react, so this is my child component that takes state from its parent component. My goal is to re-render component every time the array this.props.saved is changed.
This component renders: <p>You have no saved tasks.</p> when the this.props.saved.length === 0 and it renders <div>{array[0].task}</div> when i enter the first task, but it keeps it at <div>{array[0].task}</div> after that. I do see that the state keeps changing and this.props.saved keeps getting bigger, but my component doesn't change anymore.
Here's your problem:
runArray = (array) => {
for (var i = 0; i<array.length; i++) {
//the first time we get here, it immediately ends the function!
return <div>{array[i].task}</div>
}
}
This loop only ever goes through once (at i=0) and then returns, exiting the runArray function and cancelling the rest of the loop. You probably wanted to return an array of elements, one for each of the tasks. I recommend using Array.map() for this, which takes an array and transforms each element, creating a new array:
runArray = (array) => {
return array.map(arrayElement => <div>arrayElement.task</div>);
}
This should do the trick. Note that React may complain about the fact that your elements lack the key property - see the documentation for more info: https://reactjs.org/docs/lists-and-keys.html
The problem is in your runArray function. Inside your loop, you are returning the first element and that's it. My guess is, you see only the first entry?
When you are trying to render all your tasks, I would suggest to map your tasks, e.g.
runArray = (array) => array.map(entry => <div>{entry.task}</div>)
It is because you write wrong the runArray function. You make a return in the for loop so it breaks after the first iteration. It will not iterate over the full array.
You need to transform your for loop to a map : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
runArray = (array) => {
return array.map(v => <div>{v.task}</div>)
}
Does it fix your issue ?
You have to update state of the component to trigger render function. Your render function is not triggered because you did not update the state when the props changed. There are many ways to update state when props updated. One method may be the following:
componentWillReceiveProps(nextProps){
if (nextProps.saved !== this.props.saved) {
this.setState({ saved: nextProps.saved })
}
}
Also change yoour render function to use state of the component as below:
renderElements = () =>{
if (this.state.savedTasks.length === 0) {
return <div className="noTasks"> <p>You have no saved tasks.</p> </div>
} else {
return this.runArray(this.state.savedTasks)
}
}
Use .map so that it renders your task correctly. You can remove runArray and rely entirely on props so you don't need to pass arguments across functions as it can get messy quickly. Here's a quick running example of how to create a parent component where you can add a task and pass them into a component so that it renders your data when props are changed, therefore making it reactive.
class App extends React.Component {
state = {
taskLabel: "",
tasks: [
{
id: 1,
label: "Do something"
},
{
id: 2,
label: "Learn sometihng"
}
]
};
handleInput = evt => {
this.setState({
[evt.target.name]: evt.target.value
});
};
handleSubmit = evt => {
evt.preventDefault();
this.setState(prevState => ({
taskLabel: "",
tasks: [
...prevState.tasks,
{
id: prevState.tasks.length + 1,
label: this.state.taskLabel
}
]
}));
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input
name="taskLabel"
type="text"
placeholder="Task label"
value={this.state.taskLabel}
onChange={this.handleInput}
/>
<button>Create task</button>
</form>
<DisplayCard tasks={this.state.tasks} />
</div>
);
}
}
class DisplayCard extends React.Component {
renderTasks = () => {
if (this.props.tasks.length !== 0) {
return this.props.tasks.map(task => (
<div key={task.id}>{task.label}</div>
));
} else {
return <div>No tasks</div>;
}
};
render() {
return <div>{this.renderTasks()}</div>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<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>

ComponentDidUpdate doesn't work

I'm trying to render dynamically a collection of component using componentDidUpdate.
This is my scenario:
var index = 0;
class myComponent extends Component {
constructor(props) {
super(props);
this.state = {
componentList: [<ComponentToRender key={index} id={index} />]
};
this.addPeriodHandler = this.addPeriodHandler.bind(this);
}
componentDidUpdate = () => {
var container = document.getElementById("container");
this.state.componentList.length !== 0
? ReactDOM.render(this.state.componentList, container)
: ReactDOM.unmountComponentAtNode(container);
};
addHandler = () => {
var array = this.state.componentList;
index++;
array.push(<ComponentToRender key={index} id={index} />);
this.setState = {
componentList: array
};
};
render() {
return (
<div id="Wrapper">
<button id="addPeriod" onClick={this.addHandler}>
Add Component
</button>
<div id="container" />
</div>
);
}
}
The problem is that componentDidUpdate work only one time, but it should work every time that component's state change.
Thank you in advance.
This is not how to use react. With ReactDOM.render() you are creating an entirely new component tree. Usually you only do that once to initially render your app. Everything else will be rendered by the render() functions of your components. If you do it with ReactDOM.render() you are basically throwing away everything react has already rendered every time you update your data and recreate it from scratch when in reality you may only need to add a single node somewhere.
Also what you actually store in the component state should be plain data and not components. Then use this data to render your components in the render() function.
Example for a valid use case:
class MyComponent extends Component{
state = {
periods: []
};
handleAddPeriod = () => {
this.setState(oldState => ({
periods: [
...oldState.periods,
{/*new period data here*/}
],
});
};
render() {
return (
<div id="Wrapper">
<button id="addPeriod" onClick={this.handleAddPeriod}>
Add Component
</button>
<div id="container">
{periods.map((period, index) => (
<ComponentToRender id={index} key={index}>
{/* render period data here */}
</ComponentToRender>
))}
</div>
</div>
);
}
}
}
Also you should not work with global variables like you did with index. If you have data that changes during using your application this is an indicator that is should be component state.
try
addHandler = () =>{
var array = this.state.componentList.slice();
index++;
array.push(<ComponentToRender key={index} id={index}/>);
this.setState=({
componentList: array
});
}
if that works, this is an issue with the state holding an Array reference that isn't changing. When you're calling setState even though you've added to the Array, it still sees the same reference because push doesn't create a new Array. You might be able to get by using the same array if you also implement shouldComponentUpdate and check the array length of the new state in there to see if it's changed.

Child Component Isn't Updating with New Props

I'm having an issue getting my child component to update with new props. I'm deleting an item from the global state and it's successful. But when the item gets deleted, I'm expecting that item to no longer show. It's still being shown but if I were to move to another screen then back, it's gone. Any idea on what I might be missing here?
Thanks!!
export default class Summary extends Component {
constructor(props) {
super(props);
this.state = {
pickupData: this.props.pickup
};
}
handleDelete(item) {
this.props.deleteLocationItem(item);
}
render() {
const pickup = this.state.pickup;
return (
<View>
{pickup.map((item, i) => (
<LocationItem
name={item}
onPressDelete={() => this.handleDelete(item)}
/>
))}
</View>
);
}
}
const LocationItem = ({ onPressDelete, name }) => (
<TouchableOpacity onPress={onPressDelete}>
<Text>Hi, {name}, CLICK ME TO DELETE</Text>
</TouchableOpacity>
);
------- Additional Info ------
case 'DELETE_LOCATION_INFO':
return Object.assign({}, state, {
pickup: state.pickup.filter(item => item !== action.action)
})
export function deleteLocationInfo(x){
return {
type: DELETE_LOCATION_INFO,
action: x
}
}
Your deleteLocationItem must be something like this:
deleteLocationItem(id) {
this.setState({
items: this.state.items.filter(item => item.id !== id)
});
}
Then inside your Summary class you dont need to set the prop again. Just receive pickup from props like this:
render (
const { pickup } = this.props;
return(
<View>
{ pickup.map
...
Render is happening based on the state which is not updated other than in constructor. When the prop updates from parent, it is not reflected in the state.
Add componentWillReceiveProps method to receive new props and update state, which will cause new data to render
But more preferably, if the state is not being changed in any way after initialization, render directly using the prop itself which will resolve this issue

setState in action handler using ReactJS not updating

New to React.
I have a handler, as follows, that updates state of an array. The data is a set of animal pairs.
class Animal extends Component {
state = {
pairs: [
{ fromAnimal: 'Dog', toAnimal: 'Cat' },
{ fromAnimal: 'Lion', toAnimal: 'Tiger' },
{ fromAnimal: 'Rabbit', toAnimal: 'Bear' }
]
};
closePairHandler = (fromAnimal, toAnimal) => {
let newPairs = this.state.pairs.filter((pair) => {
return !(pair.fromAnimal === fromAnimal && pair.toAnimal === toAnimal);
});
console.log('pairs', newPairs); // This shows that the correct pair was removed from the array.
this.setState({ pairs: newPairs });
};
render() {
return (
<div>
{
this.state.pairs.map((pair, index) => {
return <SomeComponent key={index} pair={pair} closePair={(fromAnimal, toAnimal) => this.closePairHandler(fromAnimal, toAnimal)} />;
}
}
</div>
);
};
};
export default Animal;
This is a super simplified version of the code I have. BUT, when the closePairHandler is called to remove an animal pair (for example, Lion/Tiger). The console.log in the closePairHandler shows that the array has been updated successfully.
However, when the components render. It is removing the LAST component in the array and not the one that was selected. It's reducing the array size by 1, but not removing the correct item in the mapping (in render), althought the closePairHandler console.log is showing the array correctly updated before setting the state.
Can anyone explain to me what is going on here?
Thanks again!
You are not providing the key for your mapped data while rendering SomeComponent and hence react is not able to correctly identify what element got changed. You can use index as the key if you don't have a unique id in your pair object else you should use that for performance reasons
return (
<div>
{
this.state.pairs.map((pair, index) => {
return <SomeComponent key={index} pair={pair} closePair={(fromAnimal, toAnimal) => this.closePairHandler(fromAnimal, toAnimal)} />;
}
}
</div>
);

ReactJS: how to append a component to the App component in React

I am trying to append a "Item" component which consists of some array items, in the main "App" Component. But the component is getting replaced with the new array items instead of getting appended. Following is the code snippet:
//the App render function
render: function() {
return (
<div>
{
this.state.productDisplayed.map(function(product, i) {
return (
<Item source = {product.url} prodId = {product.id} key = {product.id} />
)
})
}
</div>
)
}
//The Item render function
render: function(){
return(
<div className = "col-sm-4" >
<img src = {this.props.source} width = "70%" className = "img-responsive"></img>
<div>{this.props.prodId}
</div>
</div>
)
}
"ProductDisplayed" is an array which gets replaced by new items which are then displayed using a "for" loop.
How can i append the items as if I am adding some extra items to the main App component. I am trying to implement infinite scrolling.
In order to append items to your app component, you need to append data to your productDisplayed state array
For this you can do something like
addItem=(item)=>{
var productDisplayed=[...this.state.productDisplayed];
productDisplayed.push(item);
this.setState({productDisplayed});
}
And you can call this function addItem on some event.
You need to append them to the array in your state. I have used the function argument to setState because your nextState is dependent on your previous state.
component ProductList extends React.Component {
constructor() {
super()
this.setState({
productDisplayed: []
});
}
getMoreItems = ( startingId ) => {
Api.getMoreItems(startingId).then(this.addItems);
}
addItems = ( items ) => {
this.setState(( prevState ) => ({
updatedItems: [...prevState.productDisplayed, ...items]
}));
}
render() {
// no changes
// something triggers this.getMoreItems(id)
}
}

Categories