Passing state from a child to parent component - reactjs

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>

Related

Component Re-rendering issue

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 🚀

React: get a list of a child's components

I can get a list of a component's children and the props on each child:
React.Children.map(children, child => {
if (React.isValidElement(child)) {
console.log(child.props);
}
}
What if I wanted to grab the props off a component used within the child? So for example:
<Table>
<CustomColumnA />
<CustomColumnB />
</Table>
const CustomColumnA = () => {
return (
<Column
prop1={true}
prop2={"hello"} />
);
}
From within Table, I want to grab prop1 and prop2. Is this possible? the Children.map code above will print empty objects, because CustomColumnA is being called with no props. But the component it wraps has props and I need access to that.
Thanks

Pass state(index) to several children

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

How to check if all instances of a React Component have the same value for their state?

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

React how to set all remaining sibling's state to false whenever one sibling's state is true

Problem Description
I am trying to find a way to, whenever an arrow is set to rotate, reset to the initial position the remaining arrows.
Given :
const dropDownImage = 'https://i.postimg.cc/k57Q5cNL/down-arrow.png';
function App() {
const information = ['a' ,'b', 'c'];
return (
<p>
{information.map((value, index) => (
<Child index={index} key={index}/>
))}
</p>
)
}
const Child = ({information, index, key}) => {
const [dropDown, setDropDown] = React.useState(false);
return (
<img className={dropDown ? 'dropArrow doRotate' : 'dropArrow'} src={dropDownImage} width="50" width="50"
onClick={
()=>{setDropDown(!dropDown)}
}
/>
)
}
ReactDOM.render(<App />, document.querySelector("#app"));
.dropArrow {
position: relative;
transition: .25s;
}
.doRotate {
position:relative;
transition: 1s;
animation-fill-mode: forwards !important;
animation: rotate 1s;
}
#keyframes rotate {
100% {transform: rotate(180deg);}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
with JSFiddle Link Here
Restrictions
This is the structure of my larger project, so I can't change the modularization presented here.
What I have tried
I tried setting a hook in the parent component const [resetOthers, setResetOthers] = useState(null), pass the setter function as a prop to the child and trigger setResetOthers(index) from the child whenever setDropDown(!dropDown) is called to rotate the arrow. I then added the resetOthers as a key and made it such that an arrow animates only if it corresponds to the resetOthers index, but that didn't work. My arrows just multiplied on every click.
I tried migrating my setDropDown hook to the parent and handling the logic from there, but then I would need 3 different hooks and it would defeat the purpose of making a child in the first place.
Does anyone know how I can solve this problem ?
Manage the state of the children in the parent by using useState() to hold the currently selected item. Whenever an item is clicked, assign it's value to the state, or remove it if it's already selected:
const dropDownImage = 'https://i.postimg.cc/k57Q5cNL/down-arrow.png';
const App = ({ items }) => {
const [selected, setSelected] = React.useState(null);
return (
<p>
{information.map(value => (
<Child
key={value}
onClick={() => setSelected(s => s === value ? null : value)}
selected={selected === value} />
))}
</p>
)
}
const Child = ({ selected, onClick }) => (
<img className={selected ? 'dropArrow doRotate' : 'dropArrow'} src={dropDownImage}
width="50"
width="50"
onClick={onClick}
/>
)
const information = ['a' ,'b', 'c'];
ReactDOM.render(<App items={information} />, document.querySelector("#app"));
.dropArrow {
position: relative;
transition: transform 1s;
}
.doRotate {
position: relative;
transform: rotate(180deg);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
A classic case of needing to "lift up state". In React, state should live at the lowest common denominator above which all components will need access to it. In other words, if multiple components will need to read (or write!) to a given piece of state, it should live one level (or more, but as few as possible) above those components that will need access.
In your case you should be tracking your "dropdown" state not in each child component, but in the App component. Then, the App component can pass that state to each child and provide a mechanism to modify it.
First let's refactor so that dropdown active state is kept in App instead of each child:
const dropDownImage = 'https://i.postimg.cc/k57Q5cNL/down-arrow.png';
function App() {
const information = ['a' ,'b', 'c'];
// Use an array, with each value representing one "dropdown" active state
const [dropDowns, setDropDowns] = React.useState([false, false, false]);
return (
<p>
{information.map((value, index) => (
<Child
key={index}
index={index}
// isActive is passed in as a prop to control active state of each child
isActive={dropDowns[index]}
toggle={
() => {
// the toggle function gets provided to each child,
// and gives a way to update dropDowns state
setDropDowns(oldDropDowns => {
const newDropDowns = [...oldDropDowns];
newDropDowns[index] = !newDropDowns[index];
return newDropDowns;
});
}
} />
))}
</p>
)
}
// now <Child> is completely stateless, its active state is controlled by App
const Child = ({information, index, toggle, isActive}) => {
return (
<img className={isActive ? 'dropArrow doRotate' : 'dropArrow'} src={dropDownImage} width="50" width="50"
onClick={toggle}
/>
)
}
From there it is trivial to modify the toggle callback to update state differently -- say, by resetting all dropdown isActive statuses to false except for the clicked one:
toggle={
() => {
setDropDowns(oldDropDowns => {
const newDropDowns = oldDropDowns.map(ea => false);
newDropDowns[index] = true;
return newDropDowns;
});
}
}
I modified your code, you need to manage the state in the parent component, and update it on each click. In order to be able to switch the state from the child component, you can pass a method from the parent component into the children as a prop. In this case I use that prop alongside the index to properly update the state.
In addition, I also passed the value (true or false), as a prop to orient the chevron correctly.
import React, { Component } from "react";
import "./styles.css";
const dropDownImage = "https://i.postimg.cc/k57Q5cNL/down-arrow.png";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
information: [false, false, false]
};
this.updateInformation = this.updateInformation.bind(this);
}
updateInformation(index) {
let newInformation = this.state.information;
for (let i = 0; i < newInformation.length; ++i)
newInformation[i] = i == index ? true : false;
this.setState({ information: newInformation });
}
render() {
return (
<p>
{this.state.information.map((value, index) => (
<Child
index={index}
key={index}
value={value}
updateInformation={this.updateInformation}
/>
))}
</p>
);
}
}
class Child extends Component {
constructor(props) {
super(props);
}
render() {
return (
<img
className={this.props.value ? "dropArrow doRotate" : "dropArrow"}
src={dropDownImage}
width="50"
width="50"
onClick={() => {
this.props.updateInformation(this.props.index);
}}
/>
);
}
}
I attached you a link to the code sandbox so you can play with it: https://codesandbox.io/s/heuristic-wozniak-q5gq7?file=/src/App.js

Resources