How to get updated state from react parent component - javascript

I have the following structure
=Parent Component (with a list as a state)
== Sub Comp list={list})
=== Sub Comp2 list={list}
==== Node list={list}
export const Node = (props) => {
// some state setup
const [checked, setChecked] = useState(false)
const handleCheckbox = () => {
if(!checked){
//Checkbox checked, add this Node to list in Parent Component
props.updateList(someLabel)
}
else{
props.removeFromList(someLabel)
}
}
return(
<TreeItem
icon = {<Checkbox checked={checked} onChange={handleCheckbox}}
>
{expanded && !fetching ? childNodes : <TreeItem label="reached end" />
</TreeItem>
)
}
Now this work the way that I intended, but the problem is since it's a TreeView if I collapse and expand one of the parent nodes, I lose the checked value.
To fix this I put
useEffect(() => {
var inList = props.list.find(function(item) { return item.name === label} ) !=== undefined
if(inList){ setChecked(true)} else{setChecked(false)}
}, [])
Now the above works as intended, but only if I go to the next page on my form and then come back. The problem is that when the list is updated, the useEffect is still using the old state of the list.
How do I force useEffect to use the most updated state, or force the state to update since it's asynchronous?

You have a few options depending on your circumstance. If you just want the latest parent state in the child component you can pass the parent's state as well as the updater as a prop to the child component as below.
Parent:
const ParentComponent = () => {
const [checked, setChecked] = React.useState(false);
return (
<ChildComponent checked={checked} setChecked={setChecked} />
)
}
Child:
const ChildComponent = ({checked, setChecked}) => {
return (
<>
<p>{checked}</p>
<button onClick={() =>setChecked(!checked)}>Button!</button>
</>
)
}
If you are trying to use the state information in parallel components or if you are passing state more than one or two levels down, consider using a context.

Related

Problems using useRef / useImperativeHandle in mapping components

I have a dashboard with different components. Everything is working with a separate start-button on each component, now I need to have a common start-button, and for accessing the children's subfunctions from a parent, I understand that in React you should use the useRef.(but its perhaps not correct, but I'm struggling to see another way). I would like to have the flexibility to choose which component to start from this "overall start-button"
I have a component list that i map through shown below.
return(
{ComponentsList.map((item) => {
return (
<Showcomponents
{...item}
key={item.name}
/>
)
This works fine, but I would like, as mentioned, to access a function called something like "buttonclick" in each of the children, so I tested this with a pressure-gauge component
The function "exposed" via the forwardRef and the useImparativeHandle
const ShowRadialGauge = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
buttonclick() {
setStart(!start);
},
}));
)
then in my dashboard I changed to :
const gaugepressure = useRef();
return(
<div>
<Button onClick={() => gaugepressure.current.buttonclick()}>
Start processing
</Button>
<ShowRadialGauge ref={gaugepressure} />
<div>
)
This works fine if I use the useRef from the dashboard and instead of mapping over the components, I add them manually.
I understand the useRef is not a props, but its almost what I want. I want to do something like this:
return(
{ComponentsList.map((item) => {
return (
<Showcomponents
{...item}
key={item.name}
**ref={item.ref}**
/>
)
where the ref could be a part of my component array (as below) or a separate array.
export const ComponentsList = [
{
name: "Radial gauge",
text: "showradialgauge",
component: ShowRadialGauge,
ref: "gaugepressure",
},
{
name: "Heatmap",
text: "heatmap",
component: Heatmap,
ref: "heatmapstart",
},
]
Anyone have any suggestions, or perhaps do it another way?
You are on the right track with a React ref in the parent to attach to a single child component. If you are mapping to multiple children though you'll need an array of React refs, one for each mapped child, and in the button handler in the parent you will iterate the array of refs to call the exposed imperative handle from each.
Example:
Parent
// Ref to hold all the component refs
const gaugesRef = React.useRef([]);
// set the ref's current value to be an array of mapped refs
// new refs to be created as needed
gaugesRef.current = componentsList.map(
(_, i) => gaugesRef.current[i] ?? React.createRef()
);
const toggleAll = () => {
// Iterate the array of refs and invoke the exposed handle
gaugesRef.current.forEach((gauge) => gauge.current.toggleStart());
};
return (
<div className="App">
<button type="button" onClick={toggleAll}>
Toggle All Gauges
</button>
{componentsList.map(({ name, component: Component, ...props }, i) => (
<Component
key={name}
ref={gaugesRef.current[i]}
name={name}
{...props}
/>
))}
</div>
);
Child
const ShowRadialGauge = React.forwardRef(({ name }, ref) => {
const [start, setStart] = React.useState(false);
const toggleStart = () => setStart((start) => !start);
React.useImperativeHandle(ref, () => ({
toggleStart
}));
return (....);
});
The more correct/React way to accomplish this however is to lift the state up to the parent component and pass the state and handlers down to these components.
Parent
const [gaugeStarts, setGaugeStarts] = React.useState(
componentsList.map(() => false)
);
const toggleAll = () => {
setGaugeStarts((gaugeStarts) => gaugeStarts.map((start) => !start));
};
const toggleStart = (index) => {
setGaugeStarts((gaugeStarts) =>
gaugeStarts.map((start, i) => (i === index ? !start : start))
);
};
return (
<div className="App">
<button type="button" onClick={toggleAll}>
Toggle All Guages
</button>
{componentsList.map(({ name, component: Component, ...props },, i) => (
<Component
key={name}
start={gaugeStarts[i]}
toggleStart={() => toggleStart(i)}
name={name}
{...props}
/>
))}
</div>
);
Child
const ShowRadialGauge = ({ name, start, toggleStart }) => {
return (
<>
...
<button type="button" onClick={toggleStart}>
Toggle Start
</button>
</>
);
};
#Drew Reese
Thx Drew,
you are off course correct. I'm new to React, and I'm trying to wrap my head around this "state handling".
I tested your suggestion, but as you say, its not very "React'ish", so I lifted the state from the children up to the parent.
In the parent:
const [componentstate, setComponentstate] = useState([
{ id:1, name: "pressuregauge", start: false},
{ id:2, name: "motormap", start: false },
{ id:3, name: "heatmapstart", start: false},
]);
then in the component ShowRadialGauge, I did like this:
const ShowRadialGauge = ({ props, componentstate })
and if we need to keep the button in each component, I have the id in the componentstate object that is desctructured, so I can send that back.
.
First of all, why do you need refs to handle click when you can access it via onClick. The most common use case for refs in React is to reference a DOM element or store value that is persist between renders
My suggestion are these
First, try to make it simple by passing a function and then trigger it via onClick
Second if you really want to learn how to use imperativeHandle you can reference this video https://www.youtube.com/watch?v=zpEyAOkytkU.

Creating tab component in React and handling click events for child components

New to React and trying to build a tabular component. I know I'm reinventing the wheel but I'm trying to take this as a learning experience.
Here is how I intend to use the component:
<Tabs>
<Tabs.MenuItems>
<Tabs.MenuItem>Tab item 1</Tabs.MenuItem>
<Tabs.MenuItem>Tab item 2</Tabs.MenuItem>
<Tabs.MenuItem>Tab item 3</Tabs.MenuItem>
<Tabs.MenuItem>Tab item 4</Tabs.MenuItem>
</Tabs.MenuItems>
<Tabs.Panes>
<Tabs.Pane><div>placeholder></div></Tabs.Pane>
<Tabs.Pane><div>placeholder></div></Tabs.Pane>
<Tabs.Pane><div>placeholder></div></Tabs.Pane>
<Tabs.Pane><div>placeholder></div></Tabs.Pane>
</Tabs.Panes>
</Tabs>
My current implementation works in displaying the items properly. But the one challenge I am facing is being able to handle the onClick event for the Tabs.MenuItem. I understand that I should not be handling the onClick in the Tabs.MenuItem child component, and rather should be handled in the upmost parent Tabs component.
I tried using forwardedRef but that posed some limitations in accessing the props.children. Even if I managed to get it working syntactically, I am not even sure how the Tabs component is suppose to access that ref.
The idea here is that depending on what Tabs.MenuItem is in an active state, it will correspond to the same child Tabs.Pane component index to render that pane.
import React, { forwardRef, useState } from "react";
const Tabs = (props, { activePane }) => {
return (
props.children
);
}
const MenuItems = (props) => {
React.Children.forEach(props.children, child => {
console.log(child);
})
return (
<div className="ui secondary menu" style={props.style}>
{props.children}
</div>
)
}
const MenuItem = (props) => {
const [isActive, setActive] = useState(false);
return (
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<a className={isActive ? "item active" : "item"} onClick={() => setActive(!isActive)}>{props.children}</a>
)
}
// const MenuItem = forwardRef((props, ref) => (
// // eslint-disable-next-line jsx-a11y/anchor-is-valid
// <a ref={ref} className="item">{props.children}</a> // error accessing props.children
// ))
const Panes = (props) => {
return (
props.children
)
}
const Pane = (props) => {
return (
props.children
)
}
Tabs.MenuItems = MenuItems;
Tabs.MenuItem = MenuItem;
Tabs.Panes = Panes;
Tabs.Pane = Pane;
export default Tabs;
I am not looking for someone to complete the entire tabular functionality, just an example of how I can forward the children references to the topmost parent so that I can handle click events properly.

What is going wrong when I pass a boolean from a child to its parent component using React Hooks?

I'm currently trying to revise the dropdown menu component on my Gatsby site so that it reports a boolean to its parent component, a navbar. I plan on using that boolean to trigger some conditional CSS in Emotion.
The boolean isOpen reports if the dropdown menu is open or not, so true means it's open, and false means it's not.
As of now, I'm using React Hooks to pass that data from the child to the parent component. It seems like I'm successfully passing data, but when I click the dropdown menu, it sends both a true and a false boolean value in rapid succession, even as the menu remains open.
How do I revise this code so that isOpen in the child component is correctly reported to the parent component?
import React, { useState, useEffect } from "react"
const Child = ({ isExpanded }) => {
const [expandState, setExpandState] = useState(false)
useEffect(() => {
setExpandState(isOpen)
isExpanded(expandState)
})
return(
<dropdownWrapper>
<button
{...isExpanded}
/>
{isOpen && (
<Menu>
//menu items go here
</Menu>
)}
</dropdownWrapper>
)
}
const Parent = () => {
const [expandState, setExpandState] = useState(false)
const onExpand = (checkExpand) => {
setExpandState(checkExpand)
}
return(
<Dropdown
isExpanded={onExpand}
onClick={console.log(expandState)}
/>
)
}
Figured this one out myself. Parent needed a useEffect to register the incoming boolean.
Fixed code for the parent:
const Parent = () => {
const [expandState, setExpandState] = useState(false)
const onExpand = (checkExpand) => {
setExpandState(checkExpand)
}
useEffect(() => {
onExpand(expandState)
})
return(
<Dropdown
isExpanded={onExpand}
onClick={console.log(expandState)}
/>
)
}

React on click event order array of data passing in the component

I'm new to React and I'd like some help please. I'm having a button and a component inside my app.js which is the main file
import React from 'react'
const App = () => {
const {data, loading, error} = useQuery(GET_DATA, {
variables: {...}
})
console.log(data)
state = {
clickSort: false
}
let clickSort = () => {
this.setState({
clickSort: true
})
}
return (
<div className="myApp">
<button onClick="{this.clickSort}">Click Me</button>
<div className="myClass">
<FooComponent fooData={data} clickSort={this.state.clickSort} />
</div>
</div>
)
}
What I want to do is when I click the button to sort the array of data I'm rendering in my component in a desc order. I was thinking of passing another parameter like a flag in the component, but I'm not sure how can I do this
If both of your components (<Button /> and <List />) are wrapped within common parent (<Parent />) you may employ the concept, known as lifting state up
Essentially, it is binding event handler within one of the child component's props (onSort() of <Button />) to the callback within parent (handleSort() of <Parent />), as well as binding dependent child prop (isSorted of <List />) to the state variable of common parent (sorted of <Parent />).
With that, you simply keep track of sorted flag within parent state (using useState() hook) and once handleSort() is triggered, it modifies that flag and consequent re-render of dependent components (<List />) takes place:
const { render } = ReactDOM,
{ useState } = React
const sampleData = ['itemC', 'itemA', 'itemD', 'itemB']
const Button = ({onSort}) => <button onClick={onSort}>Sort it</button>
const List = ({listData, isSorted}) => {
const listToRender = isSorted ? listData.sort((a,b) => b > a ? 1 : -1) : listData
return (
<ul>
{listToRender.map((li,key) => <li {...{key}}>{li}</li>)}
</ul>
)
}
const Parent = () => {
const [sorted, setSorted] = useState(false),
handleSort = () => setSorted(true)
return (
<div>
<Button onSort={handleSort} />
<List listData={sampleData} isSorted={sorted} />
</div>
)
}
render (
<Parent />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
It looks from your question that you want to let a child component (FooComponent) know that the button has been clicked so that it can process (sort) the data it has received.
There are a lot of approaches to this. For instance, you could pass a boolean property to the child component that is a flag for it to do the sorting. So the parent component tracks when the button has been clicked, and the child component just observes this (perhaps in componentDidUpdate).
This would change slightly if you are using functional components, rather than class based components, but it gives you an idea.
state = {
requestSort: false
}
requestSort = () => {
this.setState({
requestSort: true
}
}
render() {
return (
<>
<button id="myBtn" onClick={this.requestSort}>Click Me</button>
<div className="myClass">
<FooComponent requestSort={this.state.requestSort} fooData={data} />
</div>
</>
)
}
Alternatively, since the data is being passed to the child component as well, you could have the parent sort it when it is clicked. It depends on if you are doing anything else with the data (i.e. is only FooComponent the one that should have the sorted copy of the data or not).
Pass the data from the state into FooComponent and write a function that sorts the data in that state. The data will instantly be updated in the child component once the state has updated in the parent component because the child component will rerender once it's noticed that the data in the parent component doesn't match the data that it previously received. Below is an example.
import React from 'react'
const FooComponent = ({ fooData }) => (
<div>
{fooData}
</div>
)
export default class Home extends React.Component {
constructor(props){
super(props);
this.state = {
data: [1, 4, 2, 3]
}
}
sortData() {
const { data } = this.state;
this.setState({
data: data.sort((a, b) => b - a),
})
}
render(){
const { data } = this.state;
return (
<div>
<button id="myBtn" onClick={() => this.sortData()}>Click Me</button>
<div className="myClass">
<FooComponent fooData={data} />
</div>
</div>
)
}
}

How to access a child's state in React

I have the following structure:
FormEditor - holds multiple instances of FieldEditor
FieldEditor - edits a field of the form and saving various values about it in its state
When a button is clicked within FormEditor, I want to be able to collect information about the fields from all FieldEditor components, information that's in their state, and have it all within FormEditor.
I considered storing the information about the fields outside of FieldEditor's state and put it in FormEditor's state instead. However, that would require FormEditor to listen to each of its FieldEditor components as they change and store their information in its state.
Can't I just access the children's state instead? Is it ideal?
Just before I go into detail about how you can access the state of a child component, please make sure to read Markus-ipse's answer regarding a better solution to handle this particular scenario.
If you do indeed wish to access the state of a component's children, you can assign a property called ref to each child. There are now two ways to implement references: Using React.createRef() and callback refs.
Using React.createRef()
This is currently the recommended way to use references as of React 16.3 (See the documentation for more information). If you're using an earlier version then see below regarding callback references.
You'll need to create a new reference in the constructor of your parent component and then assign it to a child via the ref attribute.
class FormEditor extends React.Component {
constructor(props) {
super(props);
this.FieldEditor1 = React.createRef();
}
render() {
return <FieldEditor ref={this.FieldEditor1} />;
}
}
In order to access this kind of ref, you'll need to use:
const currentFieldEditor1 = this.FieldEditor1.current;
This will return an instance of the mounted component so you can then use currentFieldEditor1.state to access the state.
Just a quick note to say that if you use these references on a DOM node instead of a component (e.g. <div ref={this.divRef} />) then this.divRef.current will return the underlying DOM element instead of a component instance.
Callback Refs
This property takes a callback function that is passed a reference to the attached component. This callback is executed immediately after the component is mounted or unmounted.
For example:
<FieldEditor
ref={(fieldEditor1) => {this.fieldEditor1 = fieldEditor1;}
{...props}
/>
In these examples the reference is stored on the parent component. To call this component in your code, you can use:
this.fieldEditor1
and then use this.fieldEditor1.state to get the state.
One thing to note, make sure your child component has rendered before you try to access it ^_^
As above, if you use these references on a DOM node instead of a component (e.g. <div ref={(divRef) => {this.myDiv = divRef;}} />) then this.divRef will return the underlying DOM element instead of a component instance.
Further Information
If you want to read more about React's ref property, check out this page from Facebook.
Make sure you read the "Don't Overuse Refs" section that says that you shouldn't use the child's state to "make things happen".
If you already have an onChange handler for the individual FieldEditors I don't see why you couldn't just move the state up to the FormEditor component and just pass down a callback from there to the FieldEditors that will update the parent state. That seems like a more React-y way to do it, to me.
Something along the line of this perhaps:
const FieldEditor = ({ value, onChange, id }) => {
const handleChange = event => {
const text = event.target.value;
onChange(id, text);
};
return (
<div className="field-editor">
<input onChange={handleChange} value={value} />
</div>
);
};
const FormEditor = props => {
const [values, setValues] = useState({});
const handleFieldChange = (fieldId, value) => {
setValues({ ...values, [fieldId]: value });
};
const fields = props.fields.map(field => (
<FieldEditor
key={field}
id={field}
onChange={handleFieldChange}
value={values[field]}
/>
));
return (
<div>
{fields}
<pre>{JSON.stringify(values, null, 2)}</pre>
</div>
);
};
// To add the ability to dynamically add/remove fields, keep the list in state
const App = () => {
const fields = ["field1", "field2", "anotherField"];
return <FormEditor fields={fields} />;
};
Original - pre-hooks version:
class FieldEditor extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const text = event.target.value;
this.props.onChange(this.props.id, text);
}
render() {
return (
<div className="field-editor">
<input onChange={this.handleChange} value={this.props.value} />
</div>
);
}
}
class FormEditor extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleFieldChange = this.handleFieldChange.bind(this);
}
handleFieldChange(fieldId, value) {
this.setState({ [fieldId]: value });
}
render() {
const fields = this.props.fields.map(field => (
<FieldEditor
key={field}
id={field}
onChange={this.handleFieldChange}
value={this.state[field]}
/>
));
return (
<div>
{fields}
<div>{JSON.stringify(this.state)}</div>
</div>
);
}
}
// Convert to a class component and add the ability to dynamically add/remove fields by having it in state
const App = () => {
const fields = ["field1", "field2", "anotherField"];
return <FormEditor fields={fields} />;
};
ReactDOM.render(<App />, document.body);
As the previous answers said, try to move the state to a top component and modify the state through callbacks passed to its children.
In case that you really need to access to a child state that is declared as a functional component (hooks) you can declare a ref in the parent component, and then pass it as a ref attribute to the child, but you need to use React.forwardRef and then the hook useImperativeHandle to declare a function you can call in the parent component.
Take a look at the following example:
const Parent = () => {
const myRef = useRef();
return <Child ref={myRef} />;
}
const Child = React.forwardRef((props, ref) => {
const [myState, setMyState] = useState('This is my state!');
useImperativeHandle(ref, () => ({getMyState: () => {return myState}}), [myState]);
})
Then you should be able to get myState in the Parent component by calling:
myRef.current.getMyState();
It's 2020 and lots of you will come here looking for a similar solution but with Hooks (they are great!) and with the latest approaches in terms of code cleanliness and syntax.
So as previous answers had stated, the best approach to this kind of problem is to hold the state outside of child component fieldEditor. You could do that in multiple ways.
The most "complex" is with a global context (state) that both parent and children could access and modify. It's a great solution when components are very deep in the tree hierarchy and so it's costly to send props in each level.
In this case I think it's not worth it, and a more simple approach will bring us the results we want, just using the powerful React.useState().
An approach with a React.useState() hook - way simpler than with Class components
As said, we will deal with changes and store the data of our child component fieldEditor in our parent fieldForm. To do that we will send a reference to the function that will deal and apply the changes to the fieldForm state, you could do that with:
function FieldForm({ fields }) {
const [fieldsValues, setFieldsValues] = React.useState({});
const handleChange = (event, fieldId) => {
let newFields = { ...fieldsValues };
newFields[fieldId] = event.target.value;
setFieldsValues(newFields);
};
return (
<div>
{fields.map(field => (
<FieldEditor
key={field}
id={field}
handleChange={handleChange}
value={fieldsValues[field]}
/>
))}
<div>{JSON.stringify(fieldsValues)}</div>
</div>
);
}
Note that React.useState({}) will return an array with position 0 being the value specified on call (Empty object in this case), and position 1 being the reference to the function
that modifies the value.
Now with the child component, FieldEditor, you don't even need to create a function with a return statement. A lean constant with an arrow function will do!
const FieldEditor = ({ id, value, handleChange }) => (
<div className="field-editor">
<input onChange={event => handleChange(event, id)} value={value} />
</div>
);
Aaaaand we are done, nothing more. With just these two slim functional components we have our end goal "access" our child FieldEditor value and show it off in our parent.
You could check the accepted answer from 5 years ago and see how Hooks made React code leaner (by a lot!).
Hope my answer helps you learn and understand more about Hooks, and if you want to check a working example here it is.
Now you can access the InputField's state which is the child of FormEditor.
Basically, whenever there is a change in the state of the input field (child), we are getting the value from the event object and then passing this value to the Parent where in the state in the Parent is set.
On a button click, we are just printing the state of the input fields.
The key point here is that we are using the props to get the input field's id/value and also to call the functions which are set as attributes on the input field while we generate the reusable child input fields.
class InputField extends React.Component{
handleChange = (event)=> {
const val = event.target.value;
this.props.onChange(this.props.id , val);
}
render() {
return(
<div>
<input type="text" onChange={this.handleChange} value={this.props.value}/>
<br/><br/>
</div>
);
}
}
class FormEditorParent extends React.Component {
state = {};
handleFieldChange = (inputFieldId , inputFieldValue) => {
this.setState({[inputFieldId]:inputFieldValue});
}
// On a button click, simply get the state of the input field
handleClick = ()=>{
console.log(JSON.stringify(this.state));
}
render() {
const fields = this.props.fields.map(field => (
<InputField
key={field}
id={field}
onChange={this.handleFieldChange}
value={this.state[field]}
/>
));
return (
<div>
<div>
<button onClick={this.handleClick}>Click Me</button>
</div>
<div>
{fields}
</div>
</div>
);
}
}
const App = () => {
const fields = ["field1", "field2", "anotherField"];
return <FormEditorParent fields={fields} />;
};
ReactDOM.render(<App/>, mountNode);
You may access the child state by passing a callback to the child component.
const Parent = () => {
return (
<Child onSubmit={(arg) => {
console.log('accessing child state from parent callback: ', arg)
}}
/>
)
}
const Child = ({onSubmit}) => {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={setText}>
<button onClick={() => onSubmit(text)} />
</>
)
}
Now if you click the button in the child component, you will execute the function passed from the parent and have access to the child component's state variables.

Categories

Resources