Child Component(stateful) not understanding parent components index changed - javascript

I am new to ReactJS and really looking forward for your help here. I created a prototype of my doubt. I have an array of Objects which is my State in my parent component(Application) and i am mapping them to a child component(List) and this child component is a stateful component which changes its state only for its visual perspective(that means i don't want to update that state in my parent component, of course this issue is solved if i update that as well in my parent's state). So if i update the state of my child and add a new data to the parents state the component re renders and my child components index is misplaced by child's state.
class List extends React.Component {
constructor() {
super();
this.state = {
checked:false
}
}
onChange = () => {
this.setState({checked:!this.state.checked})
}
render() {
const {data} = this.props;
return(
<tr>
<td>{data.name}</td>
<td>{data.age}</td>
<td><input type='checkbox' onChange={this.onChange} checked={this.state.checked?true:false}/></td>
</tr>
)
}
}
class AddData extends React.Component {
...
}
class Application extends React.Component {
constructor() {
super();
this.state = {
datas: [
{name:'user1',age:'26'},{name:'user2',age:'27'}
]
}
}
addData = (data) => {
let newData = [data];
this.setState((prevState) => ({datas:[...newData,...prevState.datas]}));
}
render() {
const { datas } = this.state;
return (
<div>
<h3>Example</h3>
<table className="table table-bordered table-striped">
{datas.map((data,index) =>
<List data={data} key={index}/>
)}
</table>
<AddData addData={this.addData}/>
</div>
)
}
}
So lets say in the above example,
I checked the checkbox for user1
Then i add a new user and update my state
The new user is stacked above current state data
My component re-renders and now the checkbox will be checked for the new user.
So is there any way to let my child state to know the parent has changed, please find the working example here
Thanks in Advance!

it happens exactly because you are using index for key, react uses keys to remember which component was changed, so in your example if you check very first element its key will be 0 (as its index in array), so now react knows that your element with key 0 was checked, when you re-render new element receives key 0 and react applies changes to this element cause it thinks this was your original element that you checked. use original properties from object and it will work fine, if you don't have such properties try to assign id.. uuid is a great library for it. or you can add new data as last element of the array, opposed to what you are doing now, but its not a recommended solution.

Related

How can I convert this hook based code to class based code? is it possible?

How can I convert this hook-based code to class-based code? Does the code still work?
I'm using a react/ASP.net Core template. My project consists of several components that are siblings.
I'm trying to send a state from a component to another one.
import { useState } from "react";
//the change is reflected in the ImageEditor component
const ImageEditor = ({ yourState }) => (
<p>State in the ImageEditor = {yourState}</p>
);
//ImageTile changes the state through the setYourState method
const ImageTile = ({ yourState, setYourState }) => (
<button onClick={() => setYourState("World!")}>
Change State from the ImageTile
</button>
);
//App is the parent component and contains both image editor and tile
const App = () => {
//the state which holds the image ID is declared in the parent
const [imageId, setImageId] = useState("Hello");
return (
<div>
<ImageTile yourState={imageId} setYourState={setImageId} />
<ImageEditor yourState={imageId} />
</div>
);
};
export default App;
You can see the complete code on:
https://codesandbox.io/s/billowing-brook-9y9y5?file=/src/App.js:0-775
A parent passes it’s state to a child via props. The child is not allowed to change its parents state, if a child wants to change a parents state then the parent passes a callback to the child that the child can call to change the state. This is fundamental to reacts state management. A child does not need to know how a parent stores it’s state (class instance, hook instance or state library).
if your application uses a global state manager like redux, then global state is mapped to props and a store action can be called to update global state. In this case the child does not need to know who else is using the state because it’s global.
class Foo extends Component {
constructor (props) {
super(props);
this.state = { myState: 0 };
this.setMyState = this.setMyState.bind(this);
}
setMyState (value) {
this.setState({
myState: value
});
}
render () {
return (
<MyChildCompoent myStat={this.state.myState} setMyState={this.setMyState} />
);
}
}
you'll need to declare the state in the parent:
state = {
someKey: '',
};
And then in the parent, define some function to update it:
updateSomeKey = (newValue) => {
this.setState({ someKey: newValue });
}
And then pass both of these values as props to your sibling components:
render() {
return (
<div>
<Sib1 someKey={this.state.someKey} updateSomeKey={this.updateSomeKey} />
<Sib2 someKey={this.state.someKey} updateSomeKey={this.updateSomeKey} />
</div>
)
}
You shouldn't need to in order to update the 'shared' state. In the code above, the someKey state can be updated in either component by calling the updateSomeKey function that is also available as a prop.
If either component calls that function (this.props.updateSomeKey('hello!')) the updated state will propagate to both components.

I'm attempting to push JSON objects into an array every time a button is pressed and displayed the results

So i'm attempting to generate a random selection from an array which a user can add to by pressing a button and i'm a bit confused on how to do it.The app is made in react and it's used movieDB's api to make a film search engine. This is what I attempted so far:
import React from 'react';
import App from './App';
import{ Button } from 'reactstrap'
import { AddToSelector } from './Components/AddToSelector.Component'
class MovieRow extends React.Component{
addToFavourites = () =>
{
var arr = [];
const movieRow = <AddToSelector key={this.props.movie.id}
movie={this.props.movie}/>
console.log(this.props.movie.title);
arr = arr.push(this.props.movie.id);
}
pickRandom = (arr) =>
{
console.log(arr);
}
render() {
return (
<div>
<table key = {this.props.movie.id}>
<tbody>
<tr>
<td>
<img alt="poster" src={this.props.movie.poster_src}/>
</td>
<td>
<p>{this.props.movie.title}</p>
<p>{this.props.movie.overview}</p>
<Button color="primary" onClick={this.addToFavourites}>Add</Button>
<Button color="primary" onClick={this.pickRandom}>Random</Button>
</td>
</tr>
</tbody>
</table>
</div>
);
}
}
export default MovieRow;
You're getting there. You've got a few issues:
You're defining arr in addToFavourites, so every time it is called a new arr is created that's only available within that function. Essentially, you're creating the array, adding the item, and then it's tossed in the garbage.
Since your MovieRow represents one movie, you're gonna want to have
a wrapper component that maintains arr as part of its state. You'll
also define addToFavourites there and then pass that down to each
MovieRow component, making sure to pass at least the movie id so you
know which movie to add to the favorites
I'm not sure what movieRow in addToFavorites does because you assign it and then never use it. Essentially here you're defining movieRow on the global window object the first time around and then reassigning that each time you click. This seems unnecessary.
You'll also want to move pickRandom to the parent component that maintains your favorite list.
Hopefully this gets you going in the right direction. Let me know if you have questions
I think state is what you're probably looking for. You need to set initial array state in constructor, then update it in addToFavourites (using this.setState) and use updated state in pickRandom. Please consult the docs.
You should store the list of movies in you component state. Read more about the app state and components lifecycle in the React documentation
class MovieRow extends React.Component {
constructor(props) {
this.state = {
movies: []
}
}
}
Then in the method that is handling the click event in your Add button, you can use setState() to update the state and then changes will be reflected in the template.
addToFavourites() {
const newMovie = getMovie(); // I don't know where are you getting the movie, but you get the idea.
this.setState((state) => {
return {
movies: [...state.movies, newMovie]
}
});
}
Then considering you have stored in the state an array of objects representing your movies. You should map that array to a list of components in the render function.
Example:
render() {
return (
this.state.movies.map((movie) => <MovieComponent key={movie.id} title={movie.title} />)
)
}

React Component's state renders strangely with .unshift(), but normal with .push()

I have an array, I have 2 components(child and parent). I iterate through array within parent component, I render child components, I give them props with data from array.
Child components have their props translated to state and then have increment and decrement that state.
Parent component can add new item into array and re-render. BUT. If i unshift() new item in front of the array, i get last item from array added to the screen instead of new one to the front.
QUESTION: Why it renders good with .push() and bad with .unshift(). Also everything is ok with concat and [newItem, ...oldArray], but bad with same things when i add items in front of the array? Also how to properly .unshift() new items(comments, counters, images, posts, eg anything) into state, so they render first?
PS: Anything i do (concat, slice, ...array, unshift, react's immutability helper) doesn't seem to work properly. Mobx and Redux didn't help.
PS: This also occurs with Mithril, Inferno and Aurelia.
import React from 'react'
import {render} from 'react-dom'
var Component = React.Component
var data = [0, 12, -10, 1, 0, 1]
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
counter: data
}
this.addCounter = this.addCounter.bind(this)
}
addCounter(e){
let newArr = [1, ...this.state.counter]
this.setState({
counter: newArr
})
}
render() {
if(this.state.counter){
return (
<div>
<button onClick={this.addCounter}>add counter</button>
{this.state.counter.map(e=>{
return(
<Counter count={e}/>
)
})}
</div>
)
} else {
return(
<div>loading...</div>
)
}
}
}
class Counter extends Component {
constructor(props) {
super(props)
this.state = {
count: this.props.count
}
this.increment = this.increment.bind(this)
this.decrement = this.decrement.bind(this)
}
increment(e){
this.setState({count: this.state.count + 1})
}
decrement(e){
this.setState({count: this.state.count - 1})
}
render() {
return (
<span>
<b onClick={this.increment}>+</b>
<i>{this.state.count}</i>
<b onClick={this.decrement}>-</b>
</span>
)
}
}
render(<App/>, document.getElementById('root'))
The main problem isn't the way you are prepending items to your array, it's that you are not providing a key when rendering the child component.
What happens when you render the initial array is that the child component gets instantiated once per item in your array. However, React has no way of mapping the values in your array to those instances.
Let's call the first instance A. When you prepend to your list and render again, the first child instance (in the array resulting from your this.state.counter.map) will still be instance A, just with the prop e set to a new value. You can verify this by for example logging this.props.e in your child's render method. After prepending the new item, the first logged value should correspond to the prepended value.
Since your child component is stateful, and does not do anything to handle componentWillReceiveProps, having the e prop changed will not do anything to change each instance's previous state.
The reason why it works when you append is because the already existing instances will still map 1-to-1 with the items in your counter array, and the new item will be rendered as a new instance of Counter.
You would have the same problem if you were to rearrange the order of the items in counter, for example. The resulting child instances would not change order.
So, the solution is to provide a unique key to Counter, for each item. Since your items do not have an intrinsic identity, my suggestion would be to put a
let currentId = 0
above your App component, and have each item in your counter array be an object of {value, id: currentId++}, then pass id as the key to Counter.

React with lists in state, how to set new state?

I ran into an issue with updating part of the state that is a list that's passed on to children of a component.
I pass in a list to a child, but then have trouble to update that list and have the child reflect the new state;
<ItemsClass items={this.state.items1} />
When I change the value of this.state.items1, the component doesn't render with the new value.
this.setState({items1: []}); // this has no effect
However, if I change the already existing array (not replacing it new a new empty one), the component renders as I wish;
this.setState(state => { clearArray(state.items1); return state; });
That means the state updating function isn't pure, which React states it should be.
The HTML;
<div id='app'></div>
The js;
class ItemsClass extends React.Component {
constructor(props){
super(props);
this.state = {items: props.items};
}
render() {
var items = this.state.items.map(it => <div key={it.id}>{it.text}</div>);
return(
<div>{items}</div>
);
}
}
function ItemsFunction(props) {
var items = props.items.map(it => <div key={it.id}>{it.text}</div>);
return(
<div>{items}</div>
);
}
class App extends React.Component {
constructor(props){
super(props);
var items = [{id:1, text: 'item 1'}, {id: 2, text: 'item 2'}];
this.state = {
items1: items.slice(),
items2: items.slice(),
items3: items.slice()
};
this.clearLists = this.clearLists.bind(this);
}
clearLists() {
// for items1 and items2, clear the lists by assigning new empty arrays (pure).
this.setState({items1: [], items2: []});
// for items3, change the already existing array (non-pure).
this.setState(state => {
while (state.items3.length) {
state.items3.pop();
}
})
}
render() {
return (
<div>
<button onClick={this.clearLists}>Clear all lists</button>
<h2>Items rendered by class, set list to new empty array</h2>
<ItemsClass items={this.state.items1} />
<h2>Items rendered by class, empty the already existing array</h2>
<ItemsClass items={this.state.items3} />
<h2>Items rendered by function</h2>
<ItemsFunction items={this.state.items2} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
Try it out on codepen.
It seems that the ItemsClass doesn't update even though it's created with <ItemsClass items={this.state.items1}/> and this.state.items1 in the parent changes.
Is this the expected behavior? How can I update the state in the ItemsClass child from the parent?
I'm I missing something? This behavior seems quite error prone, since it's easy to assume that the child should follow the new state, the way it was passed in when the child was created.
You're copying the props of ItemsClass into the state when the component gets initialized - you don't reset the state when the props change, so your component's updates don't get displayed. To quote the docs:
Beware of this pattern, as state won't be up-to-date with any props update. Instead of syncing props to state, you often want to lift the state up.
If your component has to do something when the props change, you can use the componentWillReceieveProps lifecycle hook to do so (note that it doesn't get run when the component initially mounts, only on subsequent prop updates).
That said, there's zero reason for you to be duplicating the props here (and honestly there's rarely a good reason to do so in general) - just use the props directly, as you're doing with ItemsFunction, and everything will stay in sync:
class ItemsClass extends React.Component {
render() {
var items = this.props.items.map(it => <div key={it.id}>{it.text}</div>);
return(
<div>{items}</div>
);
}
}
Here's a working version of your Codepen: http://codepen.io/anon/pen/JNzBPV

Child component re-render but data never appears

I am using map along with Table, however I cannot get it to work as this is my first ReactJS application.
Here is What I am doing:
<table className="table table-striped">
<tbody>
{this.state.itemList.map(this.addMemberRow)}
</tbody>
</table>
Here is my addMemberRow funtion, I am not sure if it is correct!
addMemberRow(item, i) {
return(
<tr key={i}>
<td> {item} </td>
</tr>
);
}
Here is my constructor
constructor(props) {
super(props);
this.state = {
itemList: [],
};
this.addMemberRow = this.addMemberRow.bind(this);
this.openAll = this.openAll.bind(this);
}
This above code is from child component of
In the App I have
this.setState({itemList: itemList});
So whenever I add something to the array, I call setState so that this child component re-renders. but the data never appears. Nor does anything else even If I put
<h1>bhavya</h1>
in place of
{item}
EDIT :
onUpdate(item){
itemList.push(item);
this.setState({itemList: itemList}, () => {
alert(that.state.itemList.length);
});
}
This is where I set the state. I get the alert everytime with updated length.
EDIT 2 :
my array is empty in the child component ! :( I did console.dir(this.state.itemList) as soon as I enter render in child component. It shows nothing in the console, but in browser console It shows Array which is empty!! :(
I pass the itemList
<AddList itemList={this.state.itemList}/>
and I also have
App.defaultProps = {
itemList: []
};
and
constructor(props) {
super(props);
this.state = {
itemList: [],
};
this.onUpdate = this.onUpdate.bind(this);
}
You are setting the itemList state in App, and rendering itemList from state in AddList - a completely different component.
Thats not how state works. Only one component owns any particular state. If you are doing the update on the itemList state in your App component, App needs to pass the itemList down to the child component, and the child component needs to render from props -- not state. State is not shared between components. It is owned by one single component and then passed down for rendering through props.
Here is an example:
App's render method body:
return <AddList itemList={this.state.itemList} />
The line above passes App's itemList state down to the AddList component as a prop.
AddList's render method body (notice use of props):
render() {
var itemList = this.props.itemList;
return (
<table className="table table-striped">
<tbody>
{itemList && itemList.map(this.addMemberRow)}
</tbody>
</table>
);
}
Here - AddList renders the itemList array that was passed down as a prop.
I believe that this code:
onUpdate(item){
itemList.push(item);
this.setState({itemList: itemList});
alert(that.state.itemList.length);
}
should be more like this:
onUpdate(item){
var itemList = this.state.itemList;
itemList.push(item);
this.setState({itemList: itemList}, () => {
alert(this.state.itemList.length);
});
}
setState in React is not guaranteed to be synchronous; that is, you can't count on the state object to be set immediate after setState returns, which is why there's an optional callback argument to setState so you can be notified when all the state changes have taken effect.

Categories

Resources