After learning how to pass a state to a child, I am now wondering how to do it between children.
Parent:
const PostTemplate = ({ data }) => {
const [isIndex, setIndex] = useState(0);
return (
<>
<Slider
setIndex={isIndex}
{...data}
/>
<Views
setIndex={setIndex}
{...data}
/>
</>
);
};
Child1 (Views):
const Views = (data) => {
return (
<div>
{data.views.edges.map(({ node: view }, index) => (
<div
onClick={() => {
data.setIndex(index);
}}
>
<p>Hello</p>
/>
</div>
))}
</div>
);
};
Child2 (Slider):
const Slider = (data) => {
return (
<Swiper initialSlide={data.isIndex}>
{data.views.edges.map(({ node: view }) => (
<SwiperSlide>Slide</SwiperSlide>
))}
</Swiper>
);
};
This returns a rather strange error: undefined is not an object (evaluating 'el.classList').
What I would like to do is pass the index of Views to Slider.
Thanks for all the help!
Props:
Props are data which is passed to the component when it is added to ReactDOM
Props are immutable- means component can never change it's own props.
Data Flow:
When two child component have to share/use same data, parent component will pass this data to child component. Data (as Props) flows from Up to Down
Now this data is owned by Parent component. So any change of this data have to handle by this parent component also. For example, if child component wants to change this Data, they have to call parent's component change handler. Change Event flow from child to parent.
In your example PostTemplate is parent and Views & Slider are child.
PostTemplate own and manage index data (state).
PostTemplate will send index data (state) to child components: Views & Slider
Now both components have index value in their Props.
Child component Views need to change the value of Index. So parent component also pass it's own index change handler to Views component
Views component calls the change handler it got from it's parent as props when it needs to change Index value.
Here is a working example from your code in question:
function Slider(props) {
return (
<fieldset>
<legend>Slider Component </legend>
<p>Got Index data as Props: {props.index}</p>
</fieldset>);
}
class PostTemplate extends React.Component {
constructor(props) {
super(props);
this.setIndex = this.setIndex.bind(this);
this.state = {index: 0};
}
setIndex(e) {
this.setState({index: e.target.value});
}
render() {
const index = this.state.index;
return (
<fieldset>
<legend>PostTemplate Component:</legend>
<ol>
<li key={1}> This is parent component which has two child componets: Slider, Views </li>
<li key={2}> As Index data is used by both of it's child components, Index data is initilized and managed by this component.</li>
<li key={3}> When a child component needs to use this data (state:index), Posttemplate (parent) will pass the value as props </li>
<li key={3}> When a child component needs to change this data (state:index), Posttemplate (parent) will pass the changeHandler as props </li>
</ol>
<Views
index={index}
setIndex={this.setIndex}/>
<Slider
index={index} />
</fieldset>
);
}
}
function Views(props) {
return (<fieldset>
<legend>Views Component </legend>
<p>Got Index data as Props: {props.index}</p>
<p>Got index change handler function from Props: {typeof props.setIndex }</p>
<input
value={props.index}
onChange={props.setIndex} />
</fieldset>);
}
ReactDOM.render(
<PostTemplate />,
document.getElementById('root')
);
<script src="https://unpkg.com/react/umd/react.development.js">
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js">
<div id="root">
<!-- This div's content will be managed by React. -->
</div>
Try it on CodePen
Related
I am working on the project in React Typescript.
I have created hierarchy of components as per requirement.
In one scenario I have to pass data from child component to parent component and I am passing function as props and it works.
Issue :
When passing data to parent component child component gets re-render it looks like. Mean to say Dropdown selection is get reset and tree control expanded nodes get collapsed and set to the position as first time rendered.
I have used useState,useEffects hooks.
I have also tried React.memo as a part of my search on internet.
What I need :
I want to pass data to parent component from child without re-render the child component as there is no change in the props of child component.
Try this approach:
Add useCallback hook to memoize your function which lift data to <Parent />.
Then use React.memo for <Child /> to control prop changes and avoid unwanted re-renders.
I prepare an example for you here.
UPD. I have uploaded an example, you can copy it and see how it works!
Here is Child component:
const Child = ({ onChange }) => {
console.log("Child re-render");
return (
<div className="App">
<h1>Child</h1>
<button onClick={() => onChange(Math.random())}>
Lift value to Parant
</button>
</div>
);
};
const areEqual = ({ onChange: prevOnChange }, { onChange }) => {
return prevOnChange === onChange; // if true => this will avoid render
}
export default React.memo(Child, areEqual);
And the Parent:
consn App = () => {
const [value, setValue] = useState("");
const onChange = useCallback((value) => setValue(String(value)), []);
console.log("Parant re-render");
return (
<div className="App">
<h1>Parent</h1>
<div>Value is: {value}</div>
<Child onChange={onChange} />
</div>
);
}
Best regards 🚀
So right now, in my App file I have this:
{items.map(el => (
<Item
prop1={foo}
prop2={bar}
el={baz}
/>
))}
And in the <Item> component, I have this:
<span className={finishedClass ? "finishedItem" : ""}>
{props.el}
</span>
where finishedClass is a state variable.
So my question is, how can I check if finishedClass is true for every single <Item> component that gets generated as a result of the items.map?
So, you basically want to know if all the finishedClass state values in all the Item components are true. This can be simplified in the sense that if any of the Item components have finishedClass as false, you will perform a certain action.
So, what you can do is, pass a function as a prop to the Item component as follows:
{items.map(el => (
<Item
prop1={foo}
prop2={bar}
el={baz}
setFinishedClassToFalse={()=>{/*your statements*/}}
/>
))}
This function setFinishedClassToFalse will be called by the Item component if and only if its state value finishedClass is false. Obviously, there's a little more implementation to this than what I have described. But this should give you a start.
Parent component can communication with child component with parent props that have function in it. You can see code below onFinished property in Parent component has handleFinished() function value in it. That fucntion will be a bridge to help Child component to communicate with Parent component. On the Child component you must run onFinished props to triggering handleFinished function on Parent comoponent. In code below the trigger for props.onFinished is when <span> clicked by user.
import React from "react";
import ReactDOM from "react-dom";
import { Grid } from "react-flexbox-grid";
class App extends React.Component {
constructor(props) {
super(props);
this.state = { data: "test" };
}
render() {
const items = ["red", "green", "blue"];
const handleFinished = (e, index) => {
this.setState({ data: e });
console.log(this.state);
};
return (
<Grid>
{items.map((el, index) => (
<Child
prop1={"test"}
prop2={"test1"}
el={el}
onFinished={(e) => handleFinished(e, index)}
/>
))}
<div>{this.state.data.toString()}</div>
</Grid>
);
}
}
const Child = (props) => {
// example for your finishedClass value state
const finishedClass = true;
return (
<span
onClick={() => props.onFinished(finishedClass)}
className={finishedClass ? "finishedItem" : ""}
>
{props.el}{" "}
</span>
);
};
ReactDOM.render(<App />, document.getElementById("container"));
As finishedClass is a state variable, if you just console.log(finishedClass) inside component it will just do the work
My wrapper component has this signature
const withReplacement = <P extends object>(Component: React.ComponentType<P>) =>
(props: P & WithReplacementProps) => {...}
Btw, full example is here https://codepen.io/xitroff/pen/BaKQNed
It's getting original content from argument component's props
interface WithReplacementProps {
getContent(): string;
}
and then call setContent function on button click.
const { getContent, ...rest } = props;
const [ content, setContent ] = useState(getContent());
I expect that content will be replaced everywhere (1st and 2nd section below).
Here's the part of render function
return (
<>
<div>
<h4>content from child</h4>
<Component
content={content}
ReplaceButton={ReplaceButton}
{...rest as P}
/>
<hr/>
</div>
<div>
<h4>content from wrapper</h4>
<Hello
content={content}
ReplaceButton={ReplaceButton}
/>
<hr/>
</div>
</>
);
Hello component is straightforward
<div>
<p>{content}</p>
<div>
{ReplaceButton}
</div>
</div>
and that's how wrapped is being made
const HelloWithReplacement = withReplacement(Hello);
But the problem is that content is being replaced only in 2nd part. 1st remains untouched.
In the main App component I also replace the content after 20 sec from loading.
const [ content, setContent ] = useState( 'original content');
useEffect(() => {
setTimeout(() => {
setContent('...too late! replaced from main component');
}, 10000);
}, []);
...when I call my wrapped component like this
return (
<div className="App">
<HelloWithReplacement
content={content}
getContent={() => content}
/>
</div>
);
And it also has the issue - 1st part is updating, 2nd part does not.
It looks like you are overriding the withReplacement internal state with the external state of the App
<HelloWithReplacement
content={content} // Remove this to stop overriding it
getContent={() => content}
/>
Anyway it looks weird to use two different states, it is better to manage your app state in only one place
I'm making a contact list where you can add contacts to your favorites. Then filter my favorite contacts.
First all contacts have the state isFavorite: false, then I click on one contact, click on the star that sets isFavorite: true. I close that contact and click on the filter button, to see all my favorite contacts
so in here I add a contact to my favorites:
ContactName.js
state = {
isFavorite: false
}
handleFavorite = () => {
this.setState({
isFavorite: !this.state.isFavorite
})
}
render() {
return (
<React.Fragment>
<li onClick={this.handleClick}>
{this.props.contact.name}
</li>
{
this.state.isOpen ?
<Contact
contact={this.props.contact}
close={this.handleClick}
favorite={this.handleFavorite}
isFavorite={this.state.isFavorite}
/>
: null
}
</React.Fragment>
)
}
Contact.js
<Favorites
id={contact.id}
name={contact.name}
onClick={this.props.favorite}
state={this.props.isFavorite}
/>
Favorites.js
this is just where the favorite component is
<span onClick={this.props.onClick}>
{
!this.props.state
? <StarBorder className="star"/>
: <Star className="star"/>
}
</span>
and here is where I want to be able to get the isFavorite state. This is the parent component where the button for filtering the contacts is.
ContactList.js
<React.Fragment>
<span
className="filter-button"
>Filtera favoriter</span>
<ul className="contacts">
{
this.props.contacts
.filter(this.handleSearchFilter(this.props.search))
.map(contact => (
<ContactName
key={contact.id}
contact={contact}
name={contact.name}
/>
))
}
</ul>
</React.Fragment>
You are doing this in the wrong direction.
In the React, you can pass the data down with props (or by using Context which is no the case here). So if you need a data on the ancestor component, the data should be state/props of that ancestor.
In your case, the favorite data should be inside of the contacts (that is defined as props of the ContactName), and you should pass it to the ContactName just like other props.
<React.Fragment>
<span
className="filter-button"
>Filtera favoriter</span>
<ul className="contacts">
{
this.props.contacts
.filter(this.handleSearchFilter(this.props.search))
.map((contact, index) => (
<ContactName
key={contact.id}
contact={contact}
name={contact.name}
isFavorite={contact.isFavorite}
handleFavorite={() => this.props.handleFavorite(index))}
/>
))
}
</ul>
</React.Fragment>
and inside your ContactName.js
render() {
return (
<React.Fragment>
<li onClick={this.handleClick}>
{this.props.contact.name}
</li>
{
this.state.isOpen ?
<Contact
contact={this.props.contact}
close={this.handleClick}
favorite={this.props.handleFavorite}
isFavorite={this.props.isFavorite}
/>
: null
}
</React.Fragment>
)
}
and toggleFavorite function also should be the same place as the contacts state is.
In React, parent components should not have access to their children's state. Instead, you need to move your isFavorite state up a level to your ContactList component and turn it into a list or map instead of a boolean.
ContactList.js
class ContactList extends React.Component {
state = {
// In this example, `favorites` is a map of contact ids.
// You could also use an array to keep track of the favorites.
favorites: {},
};
render() {
return (
<React.Fragment>
<span className="filter-button">Filter a favorite</span>
<ul className="contacts">
{this.props.contacts
.filter(this.handleSearchFilter(this.props.search))
.map(contact => (
<ContactName
key={contact.id}
contact={contact}
isFavorite={!!this.state.favorites[contact.id]}
name={contact.name}
handleFavorite={() => this.handleFavorite(contact.id)}
/>
))}
</ul>
</React.Fragment>
);
}
handleFavorite = contactId => {
// Use a callback for state here, since you're depending on the previous state.
this.setState(state => {
return {
...state.favorites,
[contactId]: !state.favorites[contactId], // Toggle the value for given contact.
};
});
};
}
Now the handleFavorite and isFavorite props can simply be passed down as needed to your child components.
okay, I've managed to get the childs state in the parent. But now everytime I add a new contact to my favorites, it creates new objects - see codebox https://codesandbox.io/embed/charming-bohr-rwd0r
Is there a way to mash all of those new created objects into one object and set that one big objects equal to a new state called favoriteContacts = []?
Is there any proper way to access a property in the state of a child component and get its value from a parent component?
I have a component called "itemSelection" where I map through an api response to get some items like this
<div className="row">
{this.state.items.map(i => <Item ref="item" id={i.id} name={i.name} quantity={i.quantity} />)}
</div>
In the Item component there a property in the state called "selected" which I want to know its value if it was true or false in the itemSelection component. I know I can pass props from itemSelection to Item but what if I want the opposite? where I can pass data from Item to itemSelection
EDITED
So, I have made a property in the parent component "itemSelection" called "selected" and I have set it to =false= (knowing that I have the same property in the child component which is set to =false= also)
in the child component I have put this line in the event handler function after I have made setState to the property selected to change it to =true=
this.props.getPropsFromChild(this.state.selected);
then in the parent component I have made this function
getPropsFromChild = (selected) => {
this.setState({selected: selected});
}
but still didn't work, I don't know if I have set it right.
Passing props from child to parent component works using callback functions in React. Or you can also use state management library like Redux and store the data in child component and get the data in parent component.
The example below illustrate how to send props from child to parent. I am sharing below example to make you understand how you can send props from child to parent.
ItemSelection: Parent component
//handler function
getPropsFromChild = (id, name) => {
console.log(id, name);
}
//pass down your handler function to child component as a prop
<div className="row">
{this.state.items.map(i => <Item ref="item" id={i.id} name={i.name} getPropsFromChild={this.getPropsFromChild} quantity={i.quantity} />)}
</div>
Item: Child component
componentDidMount(){
//access handler function passed to your item component and access it using this.props and send the values as you want to the function
this.props.getPropsFromChild(“01”, “Hi”);
}
As tried to explain in the comments you can use callbacks for this, but try to avoid to get a value from child component like that. You can keep selected state in your parent component. Your Item component does not need to keep a state at all for this. With proper handlers from the parent, you can update your state easily.
class App extends React.Component {
state = {
items: [
{ id: "1", name: "foo", quantity: 1 },
{ id: "2", name: "bar", quantity: 2 },
{ id: "3", name: "baz", quantity: 3 },
],
selected: "",
}
handleSelect = item => this.setState({ selected: item.id })
render() {
const { items } = this.state;
return (
<div>
Selected item: {this.state.selected}
{
items.map( item =>
<Item key={item.id} item={item} onSelect={this.handleSelect} />
)
}
</div>
);
}
}
const Item = props => {
const { item, onSelect } = props;
const handleSelect = () => onSelect( item );
return (
<div style={{border: "1px solid gray"}} onClick={handleSelect}>
<p><strong>Item id:</strong> {item.id}</p>
<p><strong>Item name</strong>: {item.name}</p>
<p><strong>Item quantity</strong>: {item.quantity}</p>
</div>
)
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>