How to pass state from child to parent with Hooks - javascript

I am new to react and I am trying to build a website where the user can write a query in a searchbar and get some results back.
I created two components: Searchform (parent) and Searchbar (child).
I know how to update the state of the child:
function Searchbar() {
const [query, setQuery] = useState("");
return(
<TextField
defaultValue=""
placeholder="Search"
label="Search bar"
fullWidth
onChange={(event)=>setQuery(event.target.value)}
/>
)
}
However, I am not sure how to send this new state to the parent.
function Searchform() {
function handleSubmit() {
console.log(query)
}
const [query, setQuery] = useState("");
return(
<form onSubmit={handleSubmit}>
<div className="searchbar">
<Searchbar /> # I think I have to do something here but not sure what.
</div>
<Button variant="contained" color="primary" type="submit">
Submit your search
</Button>
</form>
)
}

You need to maintain the query in the parent and pass value and onChange to child:
function Searchbar({ value, onChange }) {
return (
<TextField
placeholder="Search"
label="Search bar"
fullWidth
value={value}
onChange={onChange}
/>
);
}
function Searchform() {
function handleSubmit() {
console.log(query);
}
const [query, setQuery] = useState('');
//create onChange function that will never
// change during component life cycle
const onChange = useCallback(
e => setQuery(e.target.value),
[]
);
return (
<form onSubmit={handleSubmit}>
<div className="searchbar">
{/* pass value and onChange */}
<Searchbar value={query} onChange={onChange} />
but not sure what.
</div>
<Button
variant="contained"
color="primary"
type="submit"
>
Submit your search
</Button>
</form>
);
}

Hope this helps,
function Searchform() {
function handleSubmit() {
console.log(query)
}
const [query, setQuery] = useState("");
return(
<form onSubmit={handleSubmit}>
<div className="searchbar">
<Searchbar setQuery={setQuery} />
</div>
<Button variant="contained" color="primary" type="submit">
Submit your search
</Button>
</form>
)
}
function Searchbar({setQuery}) {
// const [query, setQuery] = useState("");
return(
<TextField
defaultValue=""
placeholder="Search"
label="Search bar"
fullWidth
onChange={(event)=>setQuery(event.target.value)}
/>
)
}

State should be hoisted to the top-most container that deals with the state, and then passed down through props (or providers for contexts).
To update the state, you must pass down a callback function (through props) from the parent to child components that allow the child to update the parent's state. The change will then propagate to all children during a re-render.
This is a universal React concept, whether using hooks or classes, see the React documentation
A basic example:
const Parent = () => {
const [query, setQuery] = useState(null);
return <Child onQuery={setQuery} />;
}
const Child = ({ onQuery }) => (
<TextField onChange={event => onQuery(event.target.value)} />
);

Related

Unfocus/blur a Material UI input

I have an input field that can be reset/cleared by a button click:
https://codesandbox.io/s/basictextfields-material-demo-forked-m1z5l?file=/demo.js:127-482
const searchInput = useRef(null);
const clearInput = () => {
searchInput.current.value = '';
searchInput.current.blur();
}
return (
<div>
<Input
inputRef={searchInput}
id="outlined-basic"
label="Outlined"
variant="outlined" />
<button onClick={clearInput}>Clear</button>
</div>
);
The issue is that the label does not reset to it's original position after clearing the input. I'm guessing because the input field still thinks it has the focus. I tried adding a blur() but that doesn't seem to do anything.
You can use inputProps for catch onBlur event.
<Input
id="outlined-basic"
label="Outlined"
variant="outlined"
inputProps={{
onBlur: () => {
console.log('foo bar')
}
}}/>
<button onClick={clearInput}>Clear</button>
This is in no way an elegant solution. But it's what I got to make it work as you wanted
import React, {useRef, useState, } from 'react';
import Input from '#mui/material/TextField';
export default function BasicTextFields() {
const searchInput = useRef(null);
const [isFocused, setFocus] = useState(false);
const clearInput = () => {
searchInput.current.value = '';
searchInput.current.blur();
setInputFocus(false)
}
const setInputFocus = (state)=>{
const notEmpty = !!searchInput.current?.value;
if(notEmpty) return setFocus(true)
setFocus(state)
}
return (
<div>
<Input
inputRef={searchInput}
id="outlined-basic"
label="Outlined"
InputLabelProps={{shrink: isFocused}}
onFocus={()=>setInputFocus(true)}
onBlur={()=>setInputFocus(false)}
variant="outlined" />
<button onClick={clearInput}>Clear</button>
</div>
);
}
If what you really want is clearing the TextField programmatically, then just use controlled mode and reset the value state when needed. See uncontrolled-vs-controlled:
export default function BasicTextFields() {
const [value, setValue] = useState("");
const clearInput = () => setValue("");
return (
<>
<Input
value={value}
onChange={(e) => setValue(e.target.value)}
label="Outlined"
variant="outlined"
/>
<button onClick={clearInput}>Clear</button>
</>
);
}
Live Demo
If you are using TextField, then you just have to use onBlur property:
<TextField
onBlur={your_method_handleOnBlur}
...other props
/>

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.

Enable-Disable button with ReactJS

I am trying to enable or disable a button based on whether or not there is text in my input but cant seem to achieve it. When I manually set {true OR false} in the disabled property of Button function it works fine but I am really confused on how to set that dynamically based on the content of the input.
Any guidance is super welcome!
This is my app code
import { useState } from "react";
function Input (props){
const { onChange, value } = props
return (<input value={value} onChange={onChange} type="text" placeholder="Add a ToDo" maxLength="50"/>)
}
function Button (props) {
const {onChange, state, text} = props
return (<button disabled={false} onChange={onChange}>{text}</button>)
}
function App() {
const [text, setText] = useState("");
const [state, setSate] = useState(true);
const handleChange = (event) => {
if (!setText(event.target.value)) {
setSate(false);
} else {
setSate(true);
}
};
return (
<div className="App">
<div className="container">
<Input value={text} onChange={handleChange} />
<Button onChange={() => handleChange(state)} text="Add" />
<Button onChange={() => handleChange(state)} text="Clean" />
</div>
);
}
export default App;
Button element should change to:
function Button (props) {
const {disabled, onChange, state, text} = props
return (<button disabled={disabled} onChange={onChange}>{text}</button>)
}
Rendering of it should change to:
...
<Button disabled={!text} onChange={() => handleBtn()} text="Add" />
...
Sandbox: https://codesandbox.io/s/zen-hawking-qqzkw?file=/src/App.js
The idea is to send down disabled prop which would be true if the there is no text in the field.
ALSO, handleChange should look like this:
const handleChange = (event) => {
setText(event.target.value);
};
because the rest of your code in that function does not do anything.
Buttons should have their own handler functions .e.g. const handleBtn = () => {};
So you should pass the state value you are using to store whatever the users write in the input to the button so that the button knows when the input has text on it or not. And then your second state value can be used to store your todo list, so something like this
import { useState } from "react";
function Input({ handleChange, value }) {
return (
<input
value={value}
onChange={handleChange}
type="text"
placeholder="Add a Todo"
maxLength="50"
/>
);
}
function Button({ handleClick, text, disabled }) {
return (
<button disabled={disabled} onClick={handleClick}>
{text}
</button>
);
}
function App() {
const [value, setValue] = useState("");
const [todoList, setTodoList] = useState([]);
const handleChange = (event) => {
setValue(event.target.value);
};
const handleAdd = () => {
setTodoList([...todoList, value]);
handleClear();
};
const handleClear = () => {
setValue("");
};
return (
<div className="App">
<div className="container">
<Input value={value} handleChange={handleChange} />
<Button handleClick={handleAdd} disabled={!value} text="Add" />
<Button handleClick={handleClear} disabled={!value} text="Clear" />
</div>
</div>
);
}
export default App;

Updating state and backend

I'm wondering on the best way to do a HTTP PUT with my react application. I have a Post component that fetches data from https://jsonplaceholder.typicode.com/posts/1 and displays the data.
I have another component EditPost that when clicked on button "Edit" displays a dialog where the user can edit the post. The current data is sent to EditPost with a prop.
Questions
Is it best to hold the state in the Post component?
If so, should the update of state and HTTP PUT call be placed in the Post component
How can I update eg. title and not the other attributes of post?
Post component
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import Hello from './Hello';
import axios from "axios";
function Post() {
const [post, setPost] = useState();
useEffect(() => {
const fetchData = async () => {
try {
const result = await axios(
"https://jsonplaceholder.typicode.com/posts/1"
);
setPost(result.data);
} catch (error) {console.log(error)}
};
fetchData();
}, []);
return(
<div>
<h1>{post? post.id: ""}</h1>
<h1>{post? post.title: ""}</h1>
<h1>{post? post.body: ""}</h1>
<EditPost value={post}/>
</div>)
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Post/>, rootElement);
}
Edit Post component:
import React from "react";
import Button from "#material-ui/core/Button";
import TextField from "#material-ui/core/TextField";
import Dialog from "#material-ui/core/Dialog";
import DialogActions from "#material-ui/core/DialogActions";
import DialogContent from "#material-ui/core/DialogContent";
import DialogContentText from "#material-ui/core/DialogContentText";
import DialogTitle from "#material-ui/core/DialogTitle";
export default function EditPost(props) {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
function handleSubmit() {
//Do something
}
return (
<form onSubmit={handleSubmit}>
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Update Post
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Update Post</DialogTitle>
<DialogContent>
<DialogContentText>Update Post</DialogContentText>
<TextField
autoFocus
margin="dense"
id="title"
label="title"
value={props.value.title}
fullWidth
/>
<TextField
autoFocus
margin="dense"
id="body"
label="body"
value={props.value.body}
fullWidth
/>
<TextField
autoFocus
margin="dense"
id="id"
label="id"
value={props.value.id}
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button color="primary" type="submit">
OK
</Button>
</DialogActions>
</Dialog>
</div>
</form>
);
}
Yes it is a good practice to handle the state of your form in the component so to do it that way you will need minor modifications on your code.
You are missing a function to handle the values that you want to modify, so you can update it with your service and have the same state on the backend and on the ui.
First you need a the variables to store your title (and the other props) state, lets make an example with the title.
const [title, setTitle] = useState('');
You also can set the title as the prop of your data that the parent component is requesting:
const [title, setTitle] = useState(props.value.title);
Then you have to create a function that handles the title state.
const handleTitle = ( e ) => {
setTitle(e.target.value);
}
You have to add this function to you TextField component.
<TextField
autoFocus
margin="dense"
id="title"
label="title"
value={title}
onChange={handleTitle}
fullWidth
/>
When you have all yoiur methods that handle the props of your object, in this case is : title, body and id, you will need a method to submit all this data to your service.
const handleSubmit = () => {
const newData = {title: title, id: id, body: body };
//So here you will submit your data , and when the data is successfully submited you will have to update you Parent state to have the same post data in both components, so you will have to pass your 'setPost' method to EditPost Component to be able to do this:
props.setPost(newData)
}
So to pass the method to update the current post you have to do something like this in your EditPost declaration:
<EditPost setPost={setPost} value={post}/>
I think It's best if you had parent component which holds the state of post and http callbacks and 2 child component for view and edit.
const App = props => {
// Post state here
// http callbacks, put get etc.
return(
<PostView {...viewProps}/>
<PostEdit {...editProps}/>
)
}
As for your last question you can use 3rd party form manager such as Formik or react-hooks-form which is helpful mostly because of form validation imo. or you can do it yourself.
If you do it yourself you need to save the inputs state by supplying onChange callback. here's an example:
export default function EditPost(props) {
const [open, setOpen] = React.useStat(false);
const [formState, setFormState] = React.useState({...props.initialValues})
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
function handleSubmit(e) {
e.preventDefault();
// lift formState to <App/>
props.onSubmit(formState.title);
}
const handleChange = e => {
const {name, value} = e.target;
// can do some validation here before saving the state
setFormState(prevState => ({...prevState, [name]: value}))
}
return (
<form onSubmit={handleSubmit}>
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Update Post
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Update Post</DialogTitle>
<DialogContent>
<DialogContentText>Update Post</DialogContentText>
<TextField
autoFocus
margin="dense"
id="title"
label="title"
value={formState.title}
name="title"
onChange={handleChange}
fullWidth
/>
<TextField
autoFocus
margin="dense"
id="body"
label="body"
name="body"
value={formState.body}
fullWidth
/>
<TextField
autoFocus
margin="dense"
id="id"
label="id"
name="id"
value={formState.id}
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button color="primary" type="submit">
OK
</Button>
</DialogActions>
</Dialog>
</div>
</form>
);
}

Categories

Resources