I'm having a problem with a react component. It's supposed to test, whenever the field's value is changed, if the value on the field is different from the prop value, but once changed it seems to change the prop too.
handleChange = (id, event) => {
this.props.test(id, event.target.value)
if (this.validate(event.target.value) || (event.target.value === "" && !this.props.required)) {
this.setState({ valid: true })
this.props.canSubmit(true)
}
else {
this.setState({ valid: false })
this.props.canSubmit(false)
}
if (event.target.value !== this.props.field_value) {
this.setState({ changed: true })
}
else {
this.setState({ changed: false })
}
render() {
return (
<div className="mb-3" >
<link
rel="stylesheet"
href="https://fonts.googleapis.com/icon?family=Material+Icons"
/>
<span>
<TextField id="outlined-basic" variant="standard" type="text" aria-describedby="emailHelp"
value={this.props.field_value || ""}
onChange={event => this.handleChange(this.props.field_id, event)}
label={this.props.field_label}
error={!this.state.valid}
/>
<Icon onClick={this.handleDescription} className="description">help_outlined</Icon>
{this.state.changed ? <Icon>edit</Icon> : null}
</span>
</div>
I've also tried saving the prop value in a state, which also doesn't work. Does anyone have any idea?
Edit:
For more clarification, the problem I'm having is that the prop value is changed whenever the field value is changed, which means that once I change the value in the field, it's always going to think it's different from the original value. For exemple: let's say an email field has a default value of "a#a.com", then I change it to "b#a.com". Now it says it's different, and it's correct. The problem
is when I change it back to "a#a.com", now it considers the prop value as the former "b#a.com" and therefore thinks its different even tho it's the same.
You have set flag which is set to true/false in your code, which itself tells you whether or not you have anything in your text field or not. The flag does not tell you what value you have in the text field.
So, it will be something like code below if its what your searching for:
import React, { Component } from "react";
import Grid from "#material-ui/core/Grid";
import Button from "#material-ui/core/Button";
import TextField from "#material-ui/core/TextField";
class MainPage extends Component {
constructor() {
super();
this.state = {
title: "",
subtitle: "",
flag: false,
};
}
handleFilterChange = (event) => {
this.setState({ [event.target.name]: event.target.value });
if (this.state.title || this.state.subtitle) {
this.setState({ flag: true });
} else {
this.setState({ flag: false });
}
};
fetchOrdersByQuery = async () => {
//code for search
}
resetFilter = async () => {
this.setState({
head: "",
subhead: "",
flag: false,
});
};
render() {
const { classes } = this.props;
return (
<Grid>
<Grid item xs={4}>
<TextField
fullWidth
margin="dense"
name="title"
label="Title"
value={this.state.title}
variant="outlined"
onChange={this.handleFilterChange}
/>
</Grid>
<Grid item xs={4}>
<TextField
fullWidth
margin="dense"
name="subtitle"
label="Sub-Title"
value={this.state.subtitle}
variant="outlined"
onChange={this.handleFilterChange}
/>
</Grid>
<Grid item xs={2}>
<Button
type="submit"
fullWidth
variant="contained"
margin="normal"
onClick={this.resetFilter}
>
{" "}
Reset
</Button>
</Grid>
<Grid item xs={2}>
<Button
type="submit"
fullWidth
variant="contained"
margin="normal"
color="primary"
onClick={this.fetchOrdersByQuery}
>
Search
</Button>
</Grid>
</Grid>
);
}
}
export default (MainPage);
Related
I want to set all TextField to the initial state after adding data to the table by clicking ADD button
I am able to set that to the initial state by using AssignSearchesForm.resetForm(); but what happens here if I set this in add button click it will reset data but my table data also get removed because it's updated Formik values setTeamData([values, ...teamdata]);
I am adding Formik value in this state after onClick it will get added but if I reset form and set initial this state value also getting set empty so what I want after adding data into table TextField get reset or initial but table data will stay the same either if we do not update that
This way I am trying
<Button
onClick={() => {
AssignSearchesForm.handleSubmit();
// I tried this two way
//first way
// AssignSearchesForm.resetForm();
//second way
// AssignSearchesForm.setFieldValue("selectName","")
// AssignSearchesForm.setFieldValue("selectAge","")
// AssignSearchesForm.setFieldValue("location","")
}}
variant="contained"
>
Add
</Button>
export default function App() {
const [teamdata, setTeamData] = React.useState([]);
const AssignSearchesForm = useFormik({
initialValues: {
selectName: "",
selectAge: "",
location: ""
},
validationSchema,
onSubmit: (values) => {
setTeamData([values, ...teamdata]);
}
});
let filteredArray = nameList.filter(
(e) => !teamdata.some((data) => data.selectName === e.selectName)
);
const handleChange = (e) => {
const selectedName = e.target.value;
const name = nameList.find((data) => data.selectName === selectedName);
const newOptions = Object.values(name).reduce((optionList, key) => {
optionList.push({ value: key, label: key });
return optionList;
}, []);
AssignSearchesForm.setFieldValue("selectName", selectedName);
AssignSearchesForm.setFieldValue("selectAge", newOptions[1]?.value || "");
AssignSearchesForm.setFieldValue("location", newOptions[2]?.value || "");
};
return (
<div className="App">
<Grid container direction="row" spacing={1}>
<Grid item xs={4}>
<TextField
sx={{ minWidth: 150 }}
select
id="outlined-basic"
label="Select Name"
name="selectName"
size="small"
onChange={handleChange}
value={AssignSearchesForm.values.selectName}
error={
AssignSearchesForm.errors.selectName &&
AssignSearchesForm.touched.selectName
}
>
{filteredArray?.map((option) => (
<MenuItem key={option.selectName} value={option.selectName}>
{option.selectName}
</MenuItem>
))}
</TextField>
</Grid>
<Grid item xs={4}>
<TextField
id="outlined-basic"
label="location"
name="location"
size="small"
{...AssignSearchesForm.getFieldProps("location")}
/>
</Grid>
<Grid item xs={4}>
<TextField
id="outlined-basic"
label="Select Age"
name="selectAge"
size="small"
{...AssignSearchesForm.getFieldProps("selectAge")}
/>
</Grid>
<Grid item xs={4}>
<Button
onClick={() => {
AssignSearchesForm.handleSubmit();
// AssignSearchesForm.resetForm();
// AssignSearchesForm.setFieldValue("selectName","")
// AssignSearchesForm.setFieldValue("selectAge","")
// AssignSearchesForm.setFieldValue("location","")
}}
variant="contained"
>
Add
</Button>
</Grid>
</Grid>
<Table teamdata={teamdata} />
</div>
);
}
You can leverage the second parameter formikHelper in onSubmit function of formik.
Better approach pointed out by dev-developer in comments:
onSubmit: (values, formikHelper) => {
setTeamData([values, ...teamdata]);
formikHelper.resetForm();
}
Another alternative approach if resetForm() is not useful.
onSubmit: (values, formikHelper) => {
setTeamData([values, ...teamdata]);
formikHelper.setFieldValue("selectName", "");
formikHelper.setFieldValue("selectAge", "");
formikHelper.setFieldValue("location", "");
}
Here is the modified code sandbox
Objects are not valid as a React child
(found: object with keys {$$typeof, render, propTypes, Naked, options, useStyles}).
If you meant to render a collection of children, use an array instead.
Code: codesandbox
const Form = () => {
//Input
const [project, setProject] = useState({
id: "",
name: "",
isPublic: false
});
const handleChange = (prop) => (event) => {
setProject({ ...project, [prop]: event.target.value });
};
const handlePermisson = (prop) => (event) => {
setProject({ ...project, [prop]: event.target.checked });
console.log(project);
};
const WithStyles = ({ classes }) => {
return (
<div>
<Grid container>
<Grid item md={6}>
<FormControl
className="classes.bottom-gap"
fullWidth
value={project.id}
onChange={handleChange("id")}
>
<TextField
id="project_id"
label="Project id"
variant="outlined"
/>
</FormControl>
<FormControl
fullWidth
value={project.name}
onChange={handleChange("name")}
>
<TextField
id="project_name"
label="Project name"
variant="outlined"
/>
</FormControl>
<FormControl
fullWidth
value={project.id}
onChange={handlePermisson("isPublic")}
>
<FormControlLabel control={<Switch />} label="Is Public" />
</FormControl>
</Grid>
<Grid item md={6}></Grid>
</Grid>
</div>
);
};
return withStyles(styles)(WithStyles);
};
const styles = {
bottomgap: {
marginBottom: "10px"
}
};
export default Form;
You are attempting to use a higher-order component (HOC) inside of your component. You need to use the HOC outside of the component.
const Form = ({ classes }) => { ...
export default withStyles(styles)(Form);
You are also applying the class name as a literal string className="classes.bottom-gap" rather than applying the actual class name from the classes object. It should be
className={classes.bottomgap}
import React, { useState } from "react";
import {
Grid,
FormControl,
FormControlLabel,
Switch,
TextField
} from "#material-ui/core";
import { withStyles } from "#material-ui/core/styles";
const Form = ({ classes }) => {
//Input
const [project, setProject] = useState({
id: "",
name: "",
isPublic: false
});
const handleChange = (prop) => (event) => {
setProject({ ...project, [prop]: event.target.value });
};
const handlePermisson = (prop) => (event) => {
setProject({ ...project, [prop]: event.target.checked });
console.log(project);
};
return (
<div>
<Grid container>
<Grid item md={6}>
<FormControl
className={classes.bottomgap}
fullWidth
value={project.id}
onChange={handleChange("id")}
>
<TextField
id="project_id"
label="Project id"
variant="outlined"
/>
</FormControl>
<FormControl
fullWidth
value={project.name}
onChange={handleChange("name")}
>
<TextField
id="project_name"
label="Project name"
variant="outlined"
/>
</FormControl>
<FormControl
fullWidth
value={project.id}
onChange={handlePermisson("isPublic")}
>
<FormControlLabel control={<Switch />} label="Is Public" />
</FormControl>
</Grid>
<Grid item md={6}></Grid>
</Grid>
</div>
);
};
const styles = {
bottomgap: {
marginBottom: "10px"
}
};
export default withStyles(styles)(Form);
I am using react-hook-form V7 with Material UI to create a simple form however, there is a weird behavior when setting the mode in useForm({mode: ".."})
Here is my code snippet:
import { yupResolver } from "#hookform/resolvers/yup";
import { Button, Grid, Link, TextField, Typography } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
import Alert from "#material-ui/lab/Alert";
import { Controller, useForm } from "react-hook-form";
import { _MESSAGES } from "src/constants/messages";
import * as yup from "yup";
import AuthContainer from "./AuthContainer";
// ##################### Helpers ######################
/**
* #param {object} error
* #param {string} msg
*/
const renderError = (error, msg) =>
error ? <Alert severity="error">{msg}</Alert> : "";
// ############## Schema & Default Values ##############
const schema = yup.object().shape({
email: yup.string().required(),
password: yup.string().required(),
});
const defaultValues = {
email: "",
password: "",
};
// ###################### Styles ######################
const useStyles = makeStyles((theme) => ({
form: {
width: "100%", // Fix IE 11 issue.
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
// ################# Main Component ###################
const SignInPage = () => {
const classes = useStyles();
const {
handleSubmit,
formState: { errors },
reset,
control,
} = useForm({
mode: "all",
resolver: yupResolver(schema),
defaultValues,
});
const onSubmit = (data) => {
console.log(data);
// Reset form fields
reset();
};
console.log(errors);
const Form = () => (
<>
<Typography component="h1" variant="h5">
Sign In
</Typography>
<form className={classes.form} onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="email"
render={({ field }) => (
<TextField
variant="outlined"
margin="normal"
fullWidth
autoComplete="email"
label="Email Address"
{...field}
error={errors.email?.message ? true : false}
/>
)}
/>
{renderError(errors.email?.message, _MESSAGES.required)}
<Controller
control={control}
defaultValue=""
name="password"
render={({ field }) => (
<TextField
variant="outlined"
margin="normal"
fullWidth
autoComplete="current-password"
type="password"
label="Password"
{...field}
error={errors.password?.message ? true : false}
/>
)}
/>
{renderError(errors.password?.message, _MESSAGES.required)}
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Sign In
</Button>
<Grid container>
<Grid item xs={12}>
<Link href="#" variant="body2">
Forgot password?
</Link>
</Grid>
<Grid item>
<Link href="#" variant="body2" color="secondary">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
</>
);
return <AuthContainer Form={<Form />} />;
};
export default SignInPage;
What might have gone wrong?
Here is a Gif of what happened:
selected the input field
started typing
after one char it leaves the input field so, I need to select it to type again.
Move your Form component to its own Component, otherwise, you are mount and unmount your Form Component each re-render.
const App = () => {
const Form = () => {} // mount and unmount with each re-render
return <div><Form /></div>
}
to
const Form = () => {}
const App = () => {
return <div><Form /></div>
}
You have to pass down the ref. For MUI TextField should do the following:
<Controller
control={methods.control}
name="fieldName"
render={({ field: { ref, ...field } }) => (
<TextField {...field} inputRef={ref} /> // ⬅️ The ref
)}
/>
A working demo: https://codesandbox.io/s/broken-microservice-xpuru
Does it work now ?
I have a component which gets a list of employees as a prop. I've also created an input element for filtering the list by string.
I moved filtering logic into a function which expects a list of data and a search value so it could return filtered list.
I want to add lodash debounce to search input, so whenever user types something, it would wait 1 second and filter the list out.
import React from 'react';
import _ from "lodash"
import { IEmployee } from '../../../store/EmployeesStore/reducer'
import AddEmployee from '../AddEmployee/AddEmployee';
import EmployeeItem from './EmployeeItem/EmployeeItem';
import { TextField } from '#material-ui/core';
import InputAdornment from '#material-ui/core/InputAdornment';
import SearchIcon from '#material-ui/icons/Search';
export interface EmployeeProps {
employees: IEmployee[];
}
class EmployeeList extends React.Component<EmployeeProps> {
state = {
searchValue: ''
};
//function which returns filtered list
filterList = (employeesList: IEmployee[], searchValue: string) => {
return employeesList.filter((item: any) => {
const fullName = `${item.firstName}${item.lastName}`.toLowerCase();
const reversedFullName = `${item.lastName}${item.firstName}`.toLowerCase();
const trimmedSearchValue = searchValue
.replace(/\s+/g, '')
.toLowerCase();
return (
fullName.includes(trimmedSearchValue) ||
reversedFullName.includes(trimmedSearchValue)
);
});
};
render() {
// saving filtered list data in filteredList variable
let filteredList = this.filterList(this.props.employees, this.state.searchValue)
return (
<>
<AddEmployee />
<TextField
style={{ marginLeft: '20px' }}
size="medium"
id="input-with-icon-textfield"
variant="outlined"
value={this.state.searchValue}
onChange={(e) => this.setState({ searchValue: e.target.value })}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<SearchIcon />
</InputAdornment>
),
}}
InputLabelProps={{
shrink: true,
}}
/>
<div>
<ul
style={{
margin: '0px',
padding: '0px',
listStyle: 'none',
display: 'flex',
flexWrap: 'wrap',
}}
>
{filteredList.map((employee) => {
return <EmployeeItem key={employee.id} {...employee} />;
})}
</ul>
</div>
</>
);
}
}
export default EmployeeList;
Where should I add the _.debounce function and how?
Only showing the relevant changes :-
constructor (props) {
super(props)
this.state = {
searchValue: ''
};
this.debouncedHandler = _.debounce(this.handleChange.bind(this),1000);
}
handleChange = (e) => {
this.setState({ searchValue: e.target.value })};
}
<TextField
style={{ marginLeft: '20px' }}
size="medium"
id="input-with-icon-textfield"
variant="outlined"
value={this.state.searchValue}
onChange={this.debouncedHandler}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<SearchIcon />
</InputAdornment>
),
}}
InputLabelProps={{
shrink: true,
}}
/>
Explanation : We are calling the debouncedHandler repeatedly via onChange so we need to ensure that handleChange only gets triggered once that burst of 1000ms is over and no call to debouncedHandler happened within this duration. If another call to debouncedHandler happens within that burst interval a new burst interval starts.
From your component's perspective, we are delaying the execution of our main logic which is inside handleChange by 1000ms everytime unless the user doesn't enter any other character in the TextField component within that 1000ms, once that 1000ms are over, the setState will get triggered to state the state ---> meaning a re-render -----> meaning new filteredList and it's display to the user.
You should not be calling your filterList function in return statement, instead of that it must be called on onChange of TextField.
Something like this:
handleChange = (e) => {
this.setState({ searchValue: e.target.value })};
const debouncedCall = _.debounce(() => this.filterList(this.props.employees, e.target.value), 1000);
debouncedCall();
}
//Rest of your code
render() {
<TextField
onChange={(e) => handleChange(e)}
...other attributes
/>
}
I have created a form with material ui, and I would like to clear it. I have searched for answers/solutions across Stackoverflow but all of them are not working for me.
I have tried by using setstate but it not result to clear the form.
I want to know how to do this using onclick event of the button.
import React, { Component } from "react";
import {
Grid,
TextField,
AppBar,
Typography,
Toolbar,
Button
} from "#material-ui/core";
export class MyInput extends Component {
constructor(props) {
super(props);
this.state = {
first_name: "",
last_name: ""
};
}
handle_input = (name, value) => {
let cur_state = this.state;
cur_state[name] = value;
this.setState(cur_state);
};
render() {
return (
<Grid container justify="center">
<Grid item md={4}>
<TextField
defaultValue={this.state.first_name}
variant="outlined"
label="First Name"
onKeyUp={(e) => {
this.handle_input("first_name", e.target.value);
}}
/>
</Grid>
<Grid item md={4}>
<TextField
defaultValue={this.state.last_name}
variant="outlined"
label="Last Name"
onKeyUp={(e) => {
this.handle_input("last_name", e
.target.value);
}}
/>
</Grid>
<Grid item md={4}>
<div
style={{
width: "100%",
padding: "10px",
display: "flex",
justifyContent: "space-between"
}}
>
<Button color="primary" variant="contained">
save
</Button>
<Button
color="secondary"
variant="contained"
onClick={() => {
this.setState({
first_name: "",
last_name: ""
});
}}
>
cancel
</Button>
</div>
</Grid>
</Grid>
);
}
}
export default MyInput;
Inside the TextField set a value,
<TextField
value={this.state.first_name}
defaultValue={this.state.first_name}
variant="outlined"
label="First Name"
onKeyUp={(e) => {
this.handle_input("first_name", e.target.value);
}}
/>
Then when you want to clear it, just use setState
this.setState({ first_name: '' })
Edit:
In fact, you can delete defaultValue as the default is an empty string anyways, which is what you're using.
<TextField
value={this.state.first_name}
variant="outlined"
label="First Name"
onKeyUp={(e) => {
this.handle_input("first_name", e.target.value);
}}
/>
It may be worth reading the react docs, basically you're wanting to override the form's internal state, which you do by using the value prop
I dont know if this will help as I am pretty new to react myself but you could try making a functional component instead of a class to help reduce the amount of code you need, i would do something like this
import React, {useState} from "react";
export default function MyInput(props) {
const [myState, setMyState] = useState(props);
function clearState() {
setMyState.first_name("")
setMyState.last_name("")
}
return (
//Your grid code here
)
//set your button on click to this
onClick = {() => {clearState()}}
Look into writing components like this its quite nice. Again this will need tweaking but may be a point in the right direction.