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

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>
</>
)
}

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 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.

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>
);
}

Storing a value and duplicating a field onClick

I have a field where a user can input there email address, however i also have a button underneath that field that says 'Add more', I am struggling to find a way to store the email value and have the user input another email, I am using React hooks for this process, for instance I understand that i would need to store the value of the email inside a
const [email, setEmail] = useState=();
below is my code relating to the problem
<Grid item xs={12} sm={6}>
<TextField
className={classes.field}
id="contactEmails"
name="contactEmails"
label="Contact Email(s)"
fullWidth
autoComplete="lname"
inputProps={{
maxLength: 250
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Button className={classes.addButton} variant='contained' color='primary'> Add Another Email</Button>
</Grid>
You don't want to have one stateful email but rather emails (an array of emails):
const [emails, setEmails] = useState([]);
To then add an email, take the previous emails and add one:
setEmails([...emails, "new.email#example.com"]);
Replacing an email works the same way:
setEmails(emails.map(prev => prev === "previous#example.com" ? "new#example.com" : prev));
Now you have to render all those emails too, for that you can map the array of emails into an array of components and return that from your component:
return emails.map(email => <p> {email} </p>);
You might want to have a look at useReducer for this instead though, that'll be far more elegant...
Here you have another option already working: https://jsfiddle.net/hpuey7zn/
Explanation:
In the state you store the current input value (see function handleChange) and an array of emails
onClick button adds the current email to the array if it is not included (see handleClick).
class App extends React.Component {
state = {
input: '',
emails: []
}
handleChange = e => {
this.setState({ input: e.target.value });
}
handleClick = ev => {
ev.preventDefault();
const { emails, input } = this.state;
if (!emails.includes(input))
this.setState({ emails: [...emails, input]});
}
render() {
return (
<div>
<input
name="email" label="email" type="email"
onChange={ this.handleChange }
/>
<button onClick={ev => { this.handleClick(ev)}}>
Add Another Email
</button>
<div>EMAILS:</div>
<ul>{this.state.emails.map(email => <li>{email}</li>)}</ul>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById("root"))
And here you have a version with hooks:
import React, { useState } from "react";
import ReactDOM from "react-dom";
const App = () => {
const [input, setInput] = useState('');
const [emails, setEmails] = useState([]);
return (
<div>
<input name="email" label="email" type="email"
onChange={ ev => {setInput(ev.target.value)} }
/>
<button onClick={ev => {
if (!emails.includes(input)) setEmails([...emails, input])
}}>
Add Another Email
</button>
<div>EMAILS:</div>
<ul>{emails.map(e => <li>{e}</li>)}</ul>
</div>
)
}
const rootElement = document.getElementById("root");
if (rootElement) ReactDOM.render(<App />, rootElement);

Why react-validation hasErrors issue

I am using react-validation to validate a login form. After submitting the form I want to know has an error or not. I can get the error state inside my custom button object in below way.
const customSubmit = ({ hasErrors, ...props }) => {
return <Button disabled={hasErrors} >Login</Button>
{hasErrors.toString()}/>
};
const SubmitButton = button(customSubmit);
Is there any way to get hasError state when submit a from?
I can share a similar code when I use State and handleChange to get the errors in the button.
First you need a function were you declare the useState.
import React from "react";
import { Box, Button, Collapse, Grid, TextField } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
function FormVal() {
const [phone, setPhone] = React.useState<string>();
const [errors, setErrors] = React.useState<{ phone: string }>();
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const {
target: { value },
} = event;
setErrors({ phone: "" });
setPhone(value);
let reg = new RegExp(/^\d*$/).test(value);
if (!reg) {
setErrors({ phone: "Only numbers" });
}
};
Now in the return section you need to declare the handleChange. There may be some elements that you don't know such as and that is because I am using Material-ui.
return (
<Box className={classes.root} width="100%" padding="5px">
<Grid container spacing={3}>
<Grid item xs={12}>
<TextField
id="outlined-basic"
autoComplete="off"
value={phone}
label="phone number"
inputProps={{ maxLength: 255 }}
onChange={handleChange}
required
error={Boolean(errors?.phone)}
helperText={errors?.phone}
variant="outlined"
/>
</Grid>
<Collapse in={phone?.length! > 0}>
<div style={{ width: "100%" }}>
<Button variant="contained">Save</Button>
</div>
</Collapse>
</Grid>
</Box>
);
}
export default FormVal;

Categories

Resources