Updating state from child component to parent component - javascript

I'm struggling with an issue of App.js which has a Checkboxes component. When someone ticks a checkbox in that component, I want the state for that checkbox to be updated in the App.js file. The code for my Checkboxes component is below. I am using material ui if that makes any difference.
import Checkbox from '#material-ui/core/Checkbox';
import FormGroup from '#material-ui/core/FormGroup';
import FormControlLabel from '#material-ui/core/FormControlLabel';
export default function CheckboxLabels() {
return (
<FormGroup row>
<FormControlLabel
control={<Checkbox name="checkedD" />}
label="Remove Duplicates"
/>
</FormGroup>
);
}
The code in App.js that is relevant is:
<Grid>
<Checkboxes />
</Grid>
What should I be doing? How do I modify the above code?

If you want to keep the state in the App component, then you need to pass a function to your child component which will be used to send the state to the parent (App) component.
In your parent component, you would create the state so that each state of checkboxes can be stored and pass the function that will handle the updating of the state. I would store the state in Map to avoid having duplications of the same checkbox on each update. Also, pass an id to each of the Checkbox components so that you know which state refers to which checkbox when updating later on.
import React from "react";
import CheckboxLabels from "./CheckboxLabel";
class App extends React.Component {
state = {
checkboxes: new Map()
};
handleClick = (e, id) => {
const { checked } = e.target;
this.setState((prevState) => ({
checkboxes: prevState.checkboxes.set(id, checked)
}));
};
render() {
return (
<div className="App">
<CheckboxLabels id={1} handleClick={this.handleClick} />
<CheckboxLabels id={2} handleClick={this.handleClick} />
<CheckboxLabels id={3} handleClick={this.handleClick} />
<CheckboxLabels id={4} handleClick={this.handleClick} />
</div>
);
}
}
export default App;
In your child component, you would accept the function and the id and then pass that function with the event and id in onChange method of the checkbox.
import React from "react";
import Checkbox from "#material-ui/core/Checkbox";
import FormGroup from "#material-ui/core/FormGroup";
import FormControlLabel from "#material-ui/core/FormControlLabel";
export default function CheckboxLabels({ id, handleClick }) {
return (
<FormGroup row>
<FormControlLabel
control={<Checkbox name="checkedD" />}
label="Remove Duplicates"
onChange={(e) => handleClick(e, id)}
/>
</FormGroup>
);
}
This way, you can store the state of multiple checkboxes and you can easily track which state is which by an id.

You can use regular state mechanism in parent and just pass it down to CheckboxLabels component. So send setChecked and checked values down:
export default function CheckboxLabels({ checked, onChange }) {
return (
<FormGroup row>
<FormControlLabel
control={
<Checkbox
name="checkedD"
checked={checked}
onChange={(e) => {
if (onChange) {
onChange(e.target.checked);
}
}}
/>
}
label="Remove Duplicates"
/>
</FormGroup>
);
}
export default function App() {
const [checked, setChecked] = useState(false);
return (
<Grid>
<CheckboxLabels checked={checked} onChange={setChecked} />
</Grid>
);
}
I am not sure if your code is good naming-wise, but I added that checkbox component is name CheckboxLabels

Related

How to Make input field empty but have predefined default output value in reactjs

I am trying to traverse form data from child to parent and parent to another child and so on. But I want to have my input field empty initially and have pre-populated data on desired output tag. If I use a value tag on input and use "useState" then the value gets automatically populated with whatever default I have given on "UseState"
Child.js
import React from "react";
const Child = ({ user, setUser }) => {
const handleChange = (e) => {
setUser((prev) => ({
...prev,
[e.target.id]: e.target.value
}));
};
const formOnChageHandler = (e) => {
e.preventDefault();
};
const fromOnSubmitHandler = (e) => {
e.preventDefault();
};
return (
<React.Fragment>
<form onChange={formOnChageHandler} onSubmit={fromOnSubmitHandler}>
<label htmlFor="name">Name:</label>
<input
id="name"
placeholder="Enter Name"
value={user.name}
onChange={handleChange}
/>
<br />
<label htmlFor="age">Age:</label>
<input
id="age"
placeholder="Enter Age"
value={user.age}
onChange={handleChange}
/>
</form>
</React.Fragment>
);
};
export default Child;
App.js
import React, { useState } from "react";
import Child from "./components/Child";
import Child2 from "./components/Child2";
function App() {
const [user, setUser] = useState({
name: "Surojit Manna",
age: "23"
});
return (
<React.Fragment>
<Child user={user} setUser={setUser} />
<h1>Getting From Parent Element</h1>
<h4>Your name is:{user.name}</h4>
<h4>Your age is:{user.age}</h4>
{/* Passing data from parent to another child {Child2} from Child1 */}
<Child2 data={user} />
</React.Fragment>
);
}
export default App;
Codesandbox-
https://codesandbox.io/s/from-traversing-child-to-parent-to-another-child-ynwyqd?file=/src/App.js:0-581
a simple hack is that you can handle it accordingly in the child/parent components like, what should be rendered when there is no data in the input fields.
const Child2 = (props) => {
const {name, age} = props.data
return (
<React.Fragment>
<h1>Child 2</h1>
<h4>Your name is:{name?name:'defaultvalue'}</h4>
<h4>Your age is:{age?age:'defaultvalue'}</h4>
{/* Passing data to another deep child element */}
<Child3 data={props.data} />
</React.Fragment>
);
};
and for the parent,
return (
<React.Fragment>
<Child user={user} setUser={setUser} />
<h1>Getting From Parent Element</h1>
<h4>Your name is:{user.name?user.name:'defaultvalue'}</h4>
<h4>Your age is:{user.age?user.age:'defaultvalue'}</h4>
{/* Passing data from parent to another child {Child2} from Child1 */}
<Child2 data={user} />
</React.Fragment>
);
I hope this helps.

How to clear a material-ui search input using a button

Working on a tutorial atm that involves react material-ui tables that also has a search input textfield. What I am trying to add to it, is a button that will reset the table report but also clear the search input textfield.
It is the clearing of the search textfield that I am having trouble with.
They are using this code as a separate component library called Controls.input:
import React from 'react'
import { TextField } from '#material-ui/core';
export default function Input(props) {
const { name, label, value,error=null, onChange, ...other } = props;
return (
<TextField
variant="outlined"
label={label}
name={name}
value={value}
onChange={onChange}
{...other}
{...(error && {error:true,helperText:error})}
/>
)
}
The main search code is as follows where I have also added a button
<Controls.Input
id="name"
label="Search Name"
className={classes.searchInput}
InputProps={{
startAdornment: (<InputAdornment position="start">
<Search />
</InputAdornment>)
}}
onChange={handleSearch}
/>
<Button
onClick={handleClear}
className="materialBtn"
>
Clear
</Button>
At this point, I am not sure how to reference/target the search input field as part of the handleClear function, in-order to clear it's contents?
const handleClear = () => {
????
}
Do I need to use useState()?
You are right with having to put the value into state. Based on what you have supplied it seems that your state needs to be in your parent component. So something like this should work
import { useState } from 'react'
const ParentComponent = () => {
const [value, setValue] = useState('')
const handleClear = () => {
setValue('')
}
const handleSearch = (event) => {
setValue(event.target.value)
}
return (
<>
<Controls.Input
id="name"
label="Search Name"
className={classes.searchInput}
value={value}
onChange={handleSearch}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Search />
</InputAdornment>
),
}}
/>
<Button onClick={handleClear} className="materialBtn">
Clear
</Button>
</>
)
}

How to sync state in react.js?

I am having difficulty updating my state and my component. After a button is pressed and I change the value of one of the props of the popup component. The value of the props is not updated. I believe that this is one of the side effects of using the setstate. I did some research and saw that there is a way to solve this problem using the useeffect hook but I am unable to receive the result. Here is my code below:
My goal is to get from the form having the prop of Data0 to have a prop of Data1, but the prop does not seem to be updating at all.
I am simulating clicking multiple objects and the result is an update in the value of fromData. Thus, app.js is my parent component. The child component is the popup, whose value should change to Bob and an actual date instead of just string values of the original name and original date.
import React, { useState, useEffect } from 'react';
import './App.css';
import FormDialog from './component/popup'
import Button from '#material-ui/core/Button';
function App() {
const Data0 = { name:'original name', date:'original date' }
const Data1 = { name:'Bob', date:'1939' }
const [formStatus, setformStatus] = React.useState(false);
const [formdata2, setformData2] = useState(Data0)
const [tempform, settempform] = useState(<FormDialog formStatus = {formStatus} handelForm={() => handelForm()} Data0={Data0}/>)
const handelForm = () => {
const tempform = <FormDialog formStatus = {formStatus} handelForm={() => handelForm()} Data0={Data1}/>
settempform(tempform);
};
useEffect(() => {
const tempform = <FormDialog formStatus = {formStatus} handelForm={() => handelForm()} Data0={Data1}/>
settempform(tempform);
setformStatus(!formStatus);
console.log('formdata2 EFFECT', formdata2)
settempform(tempform);
setformStatus(!formStatus);
setformStatus(!formStatus);
}, [formdata2]
);
return (
<div className="App">
<h1 align="center">React-App</h1>
<h4 align='center'>Render Custom Component in Material Table</h4>
<Button variant="outlined" color="primary" onClick={() => handelForm()}>
Vendor row
</Button>
{tempform}
{formdata2.billingVendor}
</div>
);
}
export default App;
export default function FormDialog (props) {
let [Data, setData] = React.useState(props.Data0);
return (
<React.Fragment>
<Dialog
maxWidth='lg'
open={props.formStatus}
aria-labelledby="max-width-dialog-title"
disableBackdropClick= {true}
disableEscapeKeyDown={true}
>
<DialogTitle className={classes.title}>{Data.name}</DialogTitle>
<Divider />
<DialogContent>
<DialogContentText>
Please be cautious when updating the fields below.
</DialogContentText>
<form noValidate>
<FormControl className={classes.formControl} fullWidth= {true}>
<div className={classes.root}>
<TextField
fullWidth
label='Date'
style={{ margin: 8 }}
disabled
value={Data.name}
variant="outlined"
/>
<br/>
<TextField
fullWidth
label='Date'
style={{ margin: 8 }}
disabled
value={Data.name}
variant="outlined"
/>
<br/>
<TextField
fullWidth
style={{ margin: 8 }}
disabled
value={Data.date}
variant="outlined"
/>
<br/>
<TextField
fullWidth
style={{ margin: 8 }}
disabled
value={Data.date}
variant="outlined"
/>
<br/>
</div>
</FormControl>
</form>
</DialogContent>
<DialogActions>
<Button onClick={() => props.handelForm()} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}```
I think the process you are following is not a good one. You shouldn't store a react component in the state rather you should dynamically load the component or pass what prop you need.
import React, { useState, useEffect } from 'react';
import './App.css';
import FormDialog from './component/popup'
import Button from '#material-ui/core/Button';
function App() {
const Data0 = { name:'original name', date:'original date' }
const Data1 = { name:'Bob', date:'1939' }
const [formStatus, setformStatus] = React.useState(false);
const [formdata2, setformData2] = useState(Data0)
const [formData, setFormData] = useState(Data0)
const handelForm = () => {
// here change the state however you want
setFormData(Data0);
};
return (
<div className="App">
<h1 align="center">React-App</h1>
<h4 align='center'>Render Custom Component in Material Table</h4>
<Button variant="outlined" color="primary" onClick={() => handelForm()}>
Vendor row
</Button>
<FormDialog formStatus = {formStatus} handelForm={() => handelForm()} Data0={formData}/>
</div>
);
}
export default App;
In the FormDialog add the useEffect to perform the change
useEffect(() => {
setData(props.Data0)
}, [props.Data0])
This is update the state with the changes
You are creating a new state based on the value of the props in your child component, which is independent to the state in the parent component. So a change in the child cannot be passed back to the parent.
To fix it,
create the state in your parent component by
const [Data0, setData0] = useState({ name:'original name', date:'original date' })
pass the setState function to change the value in the parent component to your children by
const tempform = <FormDialog formStatus = {formStatus} handelForm={() => handelForm()} Data={Data1} setData={setData0}/>
change the value in your child component accordingly
let {Data, setData} = props;
Then the call of setData should be calling the one in your parent component and it should be able to update the value accordingly.

Checkboxes - Getting starting value from state

I have the following form, and the part that works is it sets the state for each option to "true" of "false" as I check and uncheck the boxes as expected.
My problem is that when I first run the app, if I have some states set to true, I want those checkboxes to start off rendered as checked... however they don't. Even those the state is true, they all render unchecked. I believe I'm just not getting the value to the right spot to make it check. But I'm not sure what else to try. Any help would be appreciated.
Parent Component:
class Audit extends Component {
constructor(props) {
super(props);
this.state = {
formRewardsService: true,
formRewardsRetirement: true,
formRewardsPeerRecognition: false,
formRewardsSpot: false
};
this.handleCheck = this.handleCheck.bind(this);
}
handleCheck(e) {
this.setState(({ isChecked }) => (
{
isChecked: !isChecked
}
));
console.log(e.target.name + ': ' + e.target.checked);
}
render() {
return (
<ThemeProvider theme={theme}>
<Container>
<Div>
<Tabs defaultActiveKey="general" id="audit=tabs">
<Tab eventKey="culture" title="Culture">
<Culture handleCheck={this.handleCheck} { />
</Tab>
</Tabs>
</Div>
</Container>
</ThemeProvider>
);
}
}
export default Audit;
My Child Component with the form and checkboxes(The first two should render as checked since they are "true" to start off):
import React, { Component } from 'react';
import {Container, Form, Row, Col} from 'react-bootstrap';
import styled, { ThemeProvider } from 'styled-components';
import theme from "../../../../Config/Theme";
const Div = styled.div`
background-color: white;
color: black;
`
class Culture extends Component {
render() {
return (
<ThemeProvider theme={theme}>
<Div>
<Container >
<Form className="p-3">
<Form.Group name="formRewards1" as={Row} controlId="formRewards1" onChange={this.props.handleCheck}>
<Form.Label column sm={5}>
1.What types of employee recognition programs are utilized within the organization? Check all that apply.
</Form.Label>
<Col>
<Form.Check
type="checkbox"
label="Service Awards"
value={this.props.formRewardsService}
name="formRewardsService"
id="formRewards1-1"
checked={this.props.value}
/>
<Form.Check
type="checkbox"
label="Retirement Awards"
value={this.props.formRewardsRetirement}
name="formRewardsRetirement"
id="formRewards1-2"
checked={this.props.value}
/>
<Form.Check
type="checkbox"
label="Peer Recognition Awards"
value={this.props.formRewardsPeer}
name="formRewardsPeer"
id="formRewards1-3"
checked={this.props.value}
/>
<Form.Check
type="checkbox"
label="Spot Awards"
value={this.props.formRewardsSpot}
name="formRewardsSpot"
id="formRewards1-4"
checked={this.props.value}
/>
</Col>
</Form.Group>
</div>
</Form>
</Container>
</Div>
</ThemeProvider>
);
}
}
export default Culture;
To pass all checkboxes value from state at once, you can grab them in a sub level of state like:
state = { checkboxes : {
formRewardsService: false,
formRewardsRetirement : true,
...
}}
and then pass only checkboxes states to Culture props
<Culture handleCheck={this.handleCheck} {...this.state.checkboxes } />
And rewrite your handleCheck function like this:
handleCheck = (e) => {
const name = e.target.name;
const checked = e.target.checked
this.setState(
{
...this.state,
checkboxes: {
...this.state.checkboxes,
[name]: checked
}
}
));
console.log(e.target.name + ': ' + e.target.checked);
}
You can remove the bind function if you write function like this:
handleCheck = (e) => { ...
Then write this function to set the state properly like this:
handleCheck = (e) => {
const name = e.target.name;
const checked = e.target.checked
this.setState(
{
[name]: checked
}
));
console.log(e.target.name + ': ' + e.target.checked);
}
Then you have to pass checked states to Culture props.
render() {
return (
<ThemeProvider theme={theme}>
<Container>
<Div>
<Tabs defaultActiveKey="general" id="audit=tabs">
<Tab eventKey="culture" title="Culture">
<Culture handleCheck={this.handleCheck} formRewardsService={this.state.formRewardsService} ... />
</Tab>
</Tabs>
</Div>
</Container>
</ThemeProvider>
);
}
and for checkboxes:
<Form.Check
type="checkbox"
label="Service Awards"
name="formRewardsService"
id="formRewards1-1"
checked={this.props.formRewardsService}
/>

ReactDOM.render doesn't update component on props change

I have create a very small app to demonstrate my query.
Below shown code has the functionality where the component is dynamically added to DOM using ReactDOM.render and this component carries a prop called title, but when I update the title of the parent component ( in state ) the DynamicComponent doesn't update.
import React from 'react';
import ReactDOM from 'react-dom';
const DynamicComponent = (props) => {
return (
<div style={{ 'border': '2px dotted green' }} >Dynamic Component : {props.title}</div>
)
}
class App extends React.Component {
state = {
title: 'Iam Title'
}
addBlock = () => {
return ReactDOM.render(<DynamicComponent title={this.state.title} />, document.getElementById('dynamiccomponents'))
}
render() {
return (
<div>
<div>Value in state: <b>{this.state.title}</b></div>
<p><b><DynamicComponent /></b> Added Initially</p>
<DynamicComponent title={this.state.title} />
<br />
<p><b><DynamicComponent /></b> Added By ReactDOM.render will be shown below: </p>
<div id="dynamiccomponents"></div>
<button onClick={this.addBlock} >Click to Dynamic Component</button>
<button onClick={() => this.setState({ title: `Update Title` })} >Update Title</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
The first button is used to added the DynamicComponent, works fine as expected.
The Second button is used to update the title in state, now the title got changed but still DynamicComponent doesn't update.
am I missing anything, how do I solve this issue, any help would be appreciated
Thanks
You could re-render the component after state change using a LifeCycle method componentDidUpdate()
import React from "react";
import ReactDOM from "react-dom";
const DynamicComponent = props => {
return (
<div style={{ border: "2px dotted green" }}>
Dynamic Component : {props.title}
</div>
);
};
class App extends React.Component {
state = {
title: "Iam Title"
};
addBlock = () => {
return ReactDOM.render(
<DynamicComponent title={this.state.title} />,
document.getElementById("dynamiccomponents")
);
};
componentDidUpdate() {
return ReactDOM.render(
<DynamicComponent title={this.state.title} />,
document.getElementById("dynamiccomponents")
);
}
render() {
return (
<div>
<div>
Value in state: <b>{this.state.title}</b>
</div>
<p>
<b><DynamicComponent /></b> Added Initially
</p>
<DynamicComponent title={this.state.title} />
<br />
<p>
<b><DynamicComponent /></b> Added By ReactDOM.render will be
shown below:{" "}
</p>
<div id='dynamiccomponents'></div>
<button onClick={this.addBlock}>Click to Dynamic Component</button>
<button onClick={() => this.setState({ title: `Update Title` })}>
Update Title
</button>
</div>
);
}
}
export default App;
This is because when you call addBlock, you are only rendering <DynamicComponent title={this.state.title} /> once to the <div id="dynamiccomopnents"></div>.
When you update the state of title by clicking the button, it re-runs your App's render function, but this.addBlock does not get run again in your render function and therefore your title does not get updated. You can verify this by clicking the button that calls this.addBlock again. It will render your component again, with the updated title.
I'd suggest you introduce some state to conditionally render your component instead of using ReactDOM.render. That way, your component gets re-rendered everytime your render method is run. Here's an example:
import React from 'react';
import ReactDOM from 'react-dom';
const DynamicComponent = (props) => {
return (
<div style={{ 'border': '2px dotted green' }} >Dynamic Component : {props.title}</div>
)
}
class App extends React.Component {
state = {
title: 'Iam Title',
showBlock: false,
}
addBlock = () => {
// this method now sets `this.state.showBlock` to true
this.setState({ showBlock: true });
}
renderBlock = () => {
// return any component you want here, you can introduce some conditional
// logic or even return nested elements, for example:
return (
<div>
<p>Dynamic Component!</p>
<DynamicComponent title={this.state.title} />
</div>
);
}
render() {
return (
<div>
<div>Value in state: <b>{this.state.title}</b></div>
<p><b><DynamicComponent /></b> Added Initially</p>
<DynamicComponent title={this.state.title} />
<br />
<p><b><DynamicComponent /></b> Added By ReactDOM.render will be shown below: </p>
{/* This will run `this.renderBlock` only if `this.state.showBlock` is true */}
{this.state.showBlock && this.renderBlock()}
<button onClick={this.addBlock} >Click to Dynamic Component</button>
<button onClick={() => this.setState({ title: `Update Title` })} >Update Title</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
ReactDOM.render renders element only once. It creates a different tree that is not connected to your first tree. That is, React doesn't keep track of all ReactDOM.renders you might have ever called and doesn't update them with data that was used to create them
If you need to render element somewhere in the DOM tree outside of your App component but you want it to be connected with your App component (so that it reacts to state changes), use ReactDOM.createPortal

Categories

Resources