Reputation: 83
I'm not sure whether or not it is best to keep state within the individual child component or the parent.
I have a parent component which will hold a child component which needs to be able to be duplicated on demand.
I have a few questions:
Where do i store the state for the individual component is it in the component itself or is it in the parent?
If it is in the child component how do I tell the parent to update the other children.
If it's in the parent how do I pass a function to the child which will update ITS state and not the parents state?
How do I access each of the components state and tell it to change based on another child state changing?
Currently I'm pushing a new "card" Component into an array which keeps track of all the Components I need to render on the "board".
I can't conceptualise the best way to manage the state of everything and how to access each child. Do they have an individual ID? how can I change all their states.
import React from "react";
import Card from "./Card";
export default class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
cards: [<Card />]
};
}
componentDidMount() {}
createCard() {
this.state.cards.push(<Card />);
this.forceUpdate();
}
render() {
return (
<div id="Board">
<button onClick={() => this.createCard()}>Create Card</button>
{this.state.cards.map(card => (
<Card />
))}
</div>
);
}
}
export default class Card extends React.Component {
constructor(props) {
super(props);
this.state = {
active: false
};
}
cardClick = () => {
this.setState({
active: !this.state.active
});
};
render(cardClick) {
return (
<div>
{this.state.active ? (
<div
className="activeCard"
id="card"
onClick={() => this.cardClick()}
>
Active
</div>
) : (
<div
className="inactiveCard"
id="card"
onClick={() => this.cardClick()}
>
Inactive
</div>
)}
</div>
);
}
} ```
Upvotes: 2
Views: 4113
Reputation: 1400
Alrighty, let's take it from the top. I imagine you have some data you want to render as <Card />
, one for each data item. You don't hold the components in the state, but rather, data. For instance:
this.state = {
data: [
{value: 'Cat 1'},
{value: 'Cat 2'},
{value: 'Cat 3'}
]
}
In this case, it's correct for the parent to hold the data. However, the whole concept of lifting the state up (React docs link) is not all that hard. Mostly, it's the question of: does more than one component care/depend on the same state? If the answer is yes, usually the parent should hold it. You're correct for having each card holding its own active state. However, the the parent could do it as well. Let's do that then shall we:
Board.js
import React from 'react';
import {Card} from './Card';
class Board extends React.Component{
state = {
data: [
{value: 'Cat 1'},
{value: 'Cat 2'},
{value: 'Cat 3'}
],
activeCard: null,
}
cardClick = id => {
this.setState({ activeCard: id })
}
addCard = () => {
const newCard = { value: 'Some val here' };
this.setState({ data: [...this.state.data, newCard] });
}
render(){
return(
<div id="Board">
<button onClick={this.addCard}>Add a card</button>
{this.state.data.map((item, index) =>
<Card key={index}
value={item.value}
active={this.state.activeCard === index}
cardClick={this.cardClick}
index={index}
/>
)}
</div>
)
}
}
export default Board;
By doing this, our parent manages everything. Not to say it's the only right way, but this lets us have the Card
component as a 'dumb', functional component. Be careful - this specific approach relies on data never changing order, since it relies on the .map
index, which is from 0 to length of data - 1.
Card.js
import React from 'react';
const Card = props => (
<div
className={props.active ? 'activeCard' : 'inactiveCard'}
onClick={() => props.cardClick(props.index)}
>
Active
</div>
);
export { Card };
There are a few key differences between what you posted and what I've done.
1) You were mapping cards with the same id="card"
which is bad, id should be unique. Also, unless you really need it, you can omit it.
2) I'm toggling the className
based off of the index of the active card. If the current card, let's say 2, is also the active card, it'll have activeCard
className.
3) Finally, I'm passing in a function to the child that updates the parents state. By doing this, I have the state contained to the parent, and every time I update it, it'll reflect on the children as well. That's not to say your approach of having class based components for Cards is wrong, but this is simpler I think.
4) Just to throw it out there, WAI-ARIA doesn't really agree with a div having onClick
events. To make the Internet a cool, accessible place, you can put a role="button"
on the div, to signal it's a button. That also requires it be focusable, as well as a keyboard event listener, in which case, you're probably better of just using a <button>
if the element should be clickable.
To answer your other questions:
The parent automatically propagates all state changes to all children that care for it
All child components are independent of the parent, eg if they have their own states, the parent doesn't care. It's only when they share a state AND a state update function that this becomes relevant, so only if you specifically pass such function to the children
If the children share a state, then it should be lift up, which is explained in the doc linked!
EDIT: for the given example, I assumed you only want one active card at a time. If that's not the case, either have each card hold their active states, like Keno Clayton suggested, or change activeCard
into an array, checking index of each card against array of active cards
Upvotes: 4
Reputation: 10997
Where do i store the state for the individual component is it in the component itself or is it in the parent?
State of the individual component should be inside the component if that state is only controlled and needed within that component. For eg: active(boolean) in Card. Since a click on a card should make it selected, as well as make current active Card inactive we can conclude that it is better stored outside Card component in the Parent.
If it's in the parent how do I pass a function to the child which will update ITS state and not the parents state?
You can pass that function as a prop(after binding to the parent)
How do I access each of the components state and tell it to change based on another child state changing?
Since you keep list of cards and active boolean in parent Board component, you dont need to do this.
Keep a state like this in Board.
this.state = {
cards: [
{id: 1, name: 'Card1'},
{id: 1, name: 'Card2'},
{id: 1, name: 'Card3'},
],
activeId: 2
}
Upvotes: 0
Reputation: 2098
To answer your questions:
Where do i store the state for the individual component is it in the component itself or is it in the parent?
Best practice is to store the state in the component that needs it. If two sibling components need access to the same state then raise it to the parent.
You can then pass down individual props or pass the entire state down with the Context Api. You can use this to pass down functions to update the parent state if needed, and the children will automatically receive the props as they are updated.
For your specific scenario, each Card should have its own state to determine whether it is active or not. It would be possible to keep an array of active states in the parent as well, but that's not as intuitive and a bit more complex.
How do I access each of the components state and tell it to change based on another child state changing?
You shouldn't do that. You should maintain any information that you want to share with other components in the parent.
Upvotes: 1