React mui 5 checkbox don't change value under function - reactjs

When Checkbox is under a FormControlLabel and function, the value don't change when useState change another variable.
If I comment the useState, checkbox works.
If I move the same code (checkbox and formcontrolLabel) on a render, not in function, checkbox work.
The sample code :
import * as React from "react";
import {
Theme,
useTheme,
Box,
Stack,
Checkbox,
FormControlLabel,
} from "#mui/material";
export const RunnerRaces2: React.FC<{}> = (props) => {
const theme = useTheme();
const [dataSource, setDataSource] = React.useState<[]>(
);
const FrmCheckBox: React.FC<{ label: string }> = (props) => {
const [checked, setChecked] = React.useState(true);
return (<FormControlLabel
label={props.label}
control={
<Checkbox
// checked={checked}
// defaultChecked
size="small"
onChange={(
event: React.ChangeEvent<HTMLInputElement>
) => {
const value = event.target.checked;
setDataSource([]);
}}
/>
}
/>);
};
return (
<Box>
<Stack
direction="row"
>
<FrmCheckBox
label={"t1"} />
<FrmCheckBox
label={"t2"} />
<FrmCheckBox
label={"t3"} />
<FrmCheckBox
label={"t4"} />
</Stack>
</Box>
);
};
I don't understand, it's mui 5 bug ? react bug ? or a wrong code from me ?
sample on codesandbox.io

Problem is caused by setDataSource([]), I fixed it by removing it.
You can change the dataSource by using a useEffect and passing checked state as a dependency to that.
The fixed codesandbox demo

Related

React Component hook variable will not update

I have a React component that gets data from a parent page/component. I use this data object (jobData) to populate a hook variable value when the component (a modal) is fired. The jobData looks like this: [{id: '17003', file_name: 'My_File', type: 'Medium', state: 'Arkansas'}]. In the browser debugger, I can see the jobData getting passed into the component, but when it gets to return ....<TextField .... value={productID} the productID says undefined! Any suggestions as to what I am doing wrong? I want the TextField to display the value of jobData[0]['id'] when it fires and then store the value of productID when it cahnges.
import React, { useState, useEffect } from 'react';
import { Button, TextField, Dialog, DialogActions, DialogContent, DialogTitle, Modal,
FormControl, Select, InputLabel } from '#mui/material';
import { createTheme, ThemeProvider } from '#mui/material/styles';
export default function ScheduleProdsModal({jobData}) {
const [open, setOpen] = useState(false);
const handleClose = () => setOpen(false);
const handleOpen = () => setOpen(true);
// Below is the hook with problems
let [productID, setProductID] = useState(jobData[0]["id"]);
return (
<div>
<ThemeProvider theme={theme}>
<Button color="neutral" variant="contained" cursor="pointer" onClick={handleOpen}>Schedule Products</Button>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>Schedule Products</DialogTitle>
<DialogContent >
<FormControl fullWidth style={{marginTop: '5px', marginBottom: '5px'}}>
<TextField
autoFocus
margin="dense"
width="100%"
id="my_id"
label="My ID"
type="text"
value={productID}
variant="outlined"
onChange={(e) => {
setProductID(e.target.value);
}}
/>
</FormControl>
</DialogContent>
<DialogActions style={{marginRight: 'inherit'}}>
<Button color="neutral" variant="contained" cursor="pointer" onClick={handleClose}>Close</Button>
<Button color="neutral" variant="contained" cursor="pointer" onClick={handleScheduler}>Schedule</Button>
</DialogActions>
</Dialog>
</ThemeProvider>
</div>
);
}
It seems like you have set jobData array when initialising the projectId, which will be undefined in the first load unless it has been handled in the parent component. Therefore as a workaround we normally use useEffect hook to get the upcoming props changes. So, when the jobData array is not null, you can use setProductID to get the relevant product id inside the useEffect hook. For the time period that you are not getting the data, you can use your favourite loading mechanism to show a loading screen. Find the below code snippet.
const [open, setOpen] = useState(false);
const handleClose = () => setOpen(false);
const handleOpen = () => setOpen(true);
// Below is the hook with problems
let [productID, setProductID] = useState(0);
useEffect(() => {
if(jobData && jobData.length > 0){
setProductID(Number(jobData[0]["id"]))
}
}, [jobData])
// You can put more conditions like undefined and null checking if you want
if(productID === 0){
// Use your fav loading page or loader here
return <>Loading...</>
}
return (
<div>
<ThemeProvider theme={theme}>
Hope this would help.

Material-UI Autocomplete, React Hook Form - Changing InputValue in Material UI Autocomplete with Use State in an OnChange?

I've been customising a Material UI Autocomplete within a Controller from React Hook Form, as part of a much larger form, with some difficulty.
The dropdown lists suggestions drawn from the database (props.items, represented here as objects) and if the suggestion is not there, there's the option to add a new one in a separate form with a button from the dropdown. This 'secondComponent' is opened with conditional rendering.
As it gets passed to the second form, the data is stored in state (heldData) and then passed back into the form via React Hook Form's reset, here as reset(heldData).
This updates the value of the form perfectly, as I have an onChange event that sets the value according to what was passed in. React Hook Form handles that logic with the reset and gives the full object to the onChange.
However, I also want to set the InputValue so that the TextField is populated.
In order to create a dynamic button when there are no options ('Add ....(input)... as a guest'), I store what is typed into state as 'texts'. I thought that I could then use the OnChange event to use the same state to update the inputValue, as below. However, when I setTexts from the onChange, the change isn't reflected in the inputValue.
Perhaps this is because the useState is async and so it doesn't update the state, before something else prevents it altogether. If so, it's much simpler than the other code that I have included, but wasn't certain. I have excluded most of the form (over 500 lines of code) but have tried to keep any parts that may be appropriate. I hope that I have not deleted anything that would be relevant, but can update if necessary.
Apologies. This is my first question on Stack Overflow and I'm quite new to React (and coding) and the code's probably a mess. Thank you
**Form**
import React, { useState, useEffect} from "react";
import AutoCompleteSuggestion from "../general/form/AutoCompleteSuggestion";
import SecondComponent from './SecondComponent'
import { useForm } from "react-hook-form";
const items = {
id: 2,
name: "Mr Anderson"
}
const items2 = {
id: 4,
name: "Mr Frog"
}
const defaultValues = {
guest: 'null',
contact: 'null',
}
const AddBooking = () => {
const { handleSubmit, register, control, reset, getValues} = useForm({
defaultValues: defaultValues,
});
const [secondComponent, setSecondComponent] = useState(false);
const [heldData, setHeldData] = useState(null)
const openSecondComponent = (name) => {
setSecondComponent(true)
const data = getValues();
setHeldData(data);
}
useEffect(() => {
!secondComponent.open?
reset(heldData):''
}, [heldData]);
const onSubmit = (data) => {
console.log(data)
};
return (
<>
{!secondComponent.open &&
<form onSubmit={handleSubmit(onSubmit)}
<AutoCompleteSuggestion
control={control}
name="guest"
selection="id"
label="name"
items={items}
openSecondComponent={openSecondComponent}
/>
<AutoCompleteSuggestion
control={control}
name="contact"
selection="id"
label="name"
items={items2}
openSecondComponent={openSecondComponent}
/>
</form>
};
{secondComponent.open?
<SecondComponent/>: ''
};
</>
);
};
And this is the customised AutoComplete:
**AutoComplete**
import React, { useState } from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete, from "#material-ui/lab/Autocomplete";
import parse from "autosuggest-highlight/parse";
import match from "autosuggest-highlight/match";
import { Controller } from "react-hook-form";
import Button from "#material-ui/core/Button";
const AutoCompleteSuggestion = (props) => {
const [texts, setTexts] = useState('');
return (
<>
<Controller
name={props.name}
control={props.control}
render={({ onChange }) => (
<Autocomplete
options={props.items}
inputValue={texts} //NOT GETTING UPDATED BY STATE
debug={true}
getOptionLabel={(value) => value[props.label]}
noOptionsText = {
<Button onClick={()=> props.opensSecondComponent()}>
Add {texts} as a {props.implementation}
</Button>}
onChange={(e, data) => {
if (data==null){
onChange(null)
} else {
onChange(data[props.selection]); //THIS ONCHANGE WORKS
setTexts(data[props.label]) //THIS DOESN'T UPDATE STATE
}}
renderInput={(params) => (
<TextField
{...params}
onChange = { e=> setTexts(e.target.value)}
/>
)}
renderOption={(option, { inputValue }) => {
const matches = match(option[props.label1, inputValue);
const parts = parse(option[props.label], matches);
return (
<div>
{parts.map((part, index) => (
<span
key={index}
style={{ fontWeight: part.highlight ? 700 : 400 }}
>
{part.text}
</span>
))}
</div>
);
}}
/>
)}
/>
</>
);
};
export default AutoCompleteSuggestion;

Hooks Input elements should not switch from controlled to uncontrolled (or vice versa)

I'm working in a project, using react hooks. I'm getting an error message when I am trying to use useState to handle the update from a field.
I am defining the state just like:
const [searchText, setSearchText] = useState('');
And I am trying to define a method which is in charge of change the value of a textField:
const handleSearchTextChange = event => {
console.log("debug :" + event.target.value + " ")
setSearchText(event.target.vale)
}
The issue that I am getting is:
Warning: A component is changing a controlled input of type text to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
I tried to solve this issue defining the state constant as:
const [searchText, setSearchText] = useState({
value: ''
});
But the issue is the same.
The code of my Component is:
import React, { useState } from 'react';
import { Typography, Container, Grid, Card, TextField, Button } from '#material-ui/core';
import styles from './styles'
export default () => {
const [searchText, setSearchText] = useState('');
const classes = styles()
const handleSearchTextChange = event => {
console.log("debug :" + event.target.value + " ")
setSearchText(event.target.vale)
}
const handleCleanTextClick = event => {
console.log("working 1")
}
const handleSearchTetxClick = event => {
console.log("working 2")
}
return (
<Container className={classes.container}>
<Card className ={classes.cardContainer}>
<Grid container className={classes.titleGridContainer}>
<Grid>
<Typography className={classes.title}>Welcome</Typography>
</Grid>
<Grid>
<label>Icon</label>
</Grid>
</Grid>
<TextField
value={searchText}
placeholder="Buscar..."
onChange={handleSearchTextChange}>
className={classes.textFieldSearch}
</TextField>
<Grid className={classes.buttonsContainer}>
<Button variant='contained' onClick={handleCleanTextClick}>Clean</Button>
<Button className={classes.searchButton} variant='contained' color='primary' size='large' onClick={handleSearchTetxClick}>Search</Button>
</Grid>
</Card>
</Container>)
}

How do I pass state between functional components?

I am currently writing a sign-up page with react-js with react-hooks and I am still learning so please excuse me if this is a very simple question.
I have a signup.js written in functional component with hooks. signup.js imports 'EmailTextField', 'PasswordTextField', 'NameTextField', 'CellPhoneTextField' ... components of which are also written in functional components with hooks.
I made all these textfields as separate components to simplify the code as I have a requirement to have many different checks on each text fields. (and having all these fields in signup.js page makes very long code)
At the end of the process in signup.js, I would like to get state of all it's sub-components (all those textfields) status (whether the user is good to sign in or not.) but I am not sure how to pass a state (or variable) from these textfields up to signup.js.
I know redux can manage state but is there anyway to achieve this without redux?
Thank you.
I've created a CodeSandbox Sample with a minimal sample code to work with.
In here, I use EmailTextfield component in apptest.js. I would like to get isValid state on EmailTextfield from apptest.js so that I can make sure all fields are validated before the user signs up.
'./components/UI/Textfield/EmailTextField.js'
import React, { useState } from "react";
import TextField from "#material-ui/core/TextField";
import Grid from "#material-ui/core/Grid";
export const EmailTextField = props => {
const [value, setValue] = useState("");
const [helperText, setHelperText] = useState(
"Email address will be used as your username."
);
const [isValid, setIsValid] = useState("true");
const handleOnChangeEmailAddress = event => {
// Email Validation logic
if (true) {
setIsValid(true);
} else {
setIsValid(false);
}
};
return (
<Grid item xs={12}>
<TextField
variant="outlined"
required
fullWidth
id="email"
label="email address"
error={!isValid}
helperText={helperText}
name="email"
autoComplete="email"
margin="dense"
onBlur={handleOnChangeEmailAddress}
/>
</Grid>
);
};
export default EmailTextField;
'aptest.js'
import React from "react";
import CssBaseline from "#material-ui/core/CssBaseline";
import Grid from "#material-ui/core/Grid";
import { makeStyles } from "#material-ui/core/styles";
import Container from "#material-ui/core/Container";
import { EmailTextField } from "./components/UI/Textfield/EmailTextField";
const useStyles = makeStyles(theme => ({
"#global": {
body: {
backgroundColor: theme.palette.common.white
}
},
paper: {
marginTop: theme.spacing(8),
display: "flex",
flexDirection: "column",
alignItems: "center"
},
mainBox: {
// margin: '200px',
width: "550px",
textAlign: "left",
boxShadow: "0 2px 3px #ccc",
border: "1px solid #eee",
padding: "40px 70px 50px 70px",
boxSizing: "border-box"
},
form: {
width: "100%", // Fix IE 11 issue.
marginTop: theme.spacing(3)
}
}));
const Apptest = props => {
const classes = useStyles();
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<div className={classes.mainBox}>
<form className={classes.form} noValidate>
<Grid container spacing={2}>
<EmailTextField />
</Grid>
</form>
</div>
</div>
</Container>
);
};
export default Apptest;
I have a very crude implementation in mind.
There should be a consistent data-model around the Input fields. This data model should be a single source of truth for the that particular Input field. It should be able to tell whether that particular field is touched, has errors, is pristine, what is it's value and all that stuff.
So let's say you have it like this:
errors: [],
onChange: false,
pristine: true,
touched: false,
value,
Let's call it a StateChangeEvent.
Now each Input field will have a handler for events like change and blur. Here that individual component will update the StateChangeEvent. And these methods will eventually call a callback function with StateChangeEvent as an argument.
That way, the parent will know that there was a change in one of the fields and it can respond accordingly.
In the parent component, to make the Submit Button on the form enabled, we can also have a side effect that will update the overall state of the form. Something like this:
useEffect(() => {
const isValid = !fieldOne.onChange &&
fieldOne.errors.length === 0 &&
fieldOne.value.length !== 0 &&
!fieldTwo.onChange &&
fieldTwo.errors.length === 0 &&
fieldTwo.value.length !== 0 &&
...;
setIsFormValid(isValid);
}, [fieldOne, fieldTwo, ...]);
I'm sure this isn't a complete solution. But I'm sure it would get you started.
UPDATE:
Based on the CodeSandbox that you provided, here's what you can do to make this work:
import ...
const useStyles = makeStyles(theme => ({ ... }));
const Apptest = props => {
const classes = useStyles();
const [isInvalid, setIsInvalid] = useState(true);
const handleStateChange = updatedState => {
console.log("updatedState: ", updatedState);
updatedState.errors.length === 0 ? setIsInvalid(false) : setIsInvalid(true);
};
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<div className={classes.mainBox}>
<form className={classes.form} noValidate>
<Grid container spacing={2}>
<EmailTextField onStateChange={handleStateChange} />
</Grid>
<Button
variant="contained"
color="primary"
disabled={isInvalid}
className={classes.button}
>
Submit
</Button>
</form>
</div>
</div>
</Container>
);
};
export default Apptest;
And in the EmailTextField component:
import React, { useState } from "react";
import TextField from "#material-ui/core/TextField";
import Grid from "#material-ui/core/Grid";
export const EmailTextField = props => {
const { onStateChange } = props;
const [state, setState] = useState({
errors: [],
onChange: false,
pristine: true,
touched: false,
value: null
});
const helperText = "Email address will be used as your username.";
const handleBlur = event => {
// Email Validation logic
const matches = event.target.value.match(
`[a-z0-9._%+-]+#[a-z0-9.-]+.[a-z]{2,3}`
);
if (matches) {
const updatedState = {
...state,
touched: true,
value: event.target.value,
errors: []
};
setState(updatedState);
onStateChange(updatedState);
} else {
const updatedState = {
...state,
touched: true,
value: event.target.value,
errors: ["Please enter a valid email"]
};
setState(updatedState);
onStateChange(updatedState);
}
};
return (
<Grid item xs={12}>
<TextField
variant="outlined"
required
fullWidth
id="email"
label="email address"
error={state.errors.length > 0}
helperText={state.errors.length > 0 ? state.errors[0] : helperText}
name="email"
autoComplete="email"
margin="dense"
onBlur={handleBlur}
/>
</Grid>
);
};
export default EmailTextField;
Here's a Working CodeSandbox Sample for your ref.
I figured it out, sorry for the late reply. I was asleep.Basically an onBlur() takes a callback, now in this case you need to pass the value in the input box to the callback so you can have access to the value of the user input. The other way is to use an onChange() to track the change and set it so that when the onblur is called you can check the value and then you can perform your validations.
So you just have to pass the target value of the event to the callback like so onBlur={(e) => handleOnChangeEmailAddress(e.target.value)} and then you can have access to the value in method. I have refactored the code you shared in the sandbox. Find below a snippet of what I did.
import React, { useState } from "react";
import TextField from "#material-ui/core/TextField";
import Grid from "#material-ui/core/Grid";
export const EmailTextField = props => {
const [value, setValue] = useState("");
const [helperText, setHelperText] = useState(
"Email address will be used as your username."
);
const [isValid, setIsValid] = useState("true");
const handleOnChangeEmailAddress = value => {
// Email Validation logic
if (!value) {
setIsValid(true);
} else {
setIsValid(false);
}
console.log(isValid)
};
return (
<Grid item xs={12}>
<TextField
variant="outlined"
required
fullWidth
id="email"
label="email address"
error={!isValid}
helperText={helperText}
name="email"
autoComplete="email"
margin="dense"
onBlur={(e) => handleOnChangeEmailAddress(e.target.value)}
/>
</Grid>
);
};
export default EmailTextField;
I hope it helps.. if you have any problems don't hesitate to ask questions..
From your codesandbox example it looks like you were almost there you just needed to pass your onStateChange function as a prop:
<EmailTextField onStateChange={onStateChange} />
Then implement the onStateChange function in your apptest.js file which will get the updated object.
Check out my example below and open the console, you will see console logs for errors and an "isValid" response if the email is valid.
https://codesandbox.io/s/loving-blackwell-nylpy?fontsize=14

How to set the height knowing that under input will helper text appear in React MaterialUI v3.7?

As you see below, there is helper text appears under the input if invalid value in input.
But when is valid, there are no helper text.
In process of typing with validation inputs jump. How can I solve this problem?
Rudolf's answer is close to what you need, but the minHeight needs to be applied to the TextField (FormControl if using lower-level components directly) rather than FormHelperText because when the helper text is nil the FormHelperText component isn't displayed at all so the minHeight has no effect.
Here's a working example (I'm using hooks for managing state for my convenience, so this currently only works with the react alpha, but the styling approach is independent of that):
import React, { useState } from "react";
import ReactDOM from "react-dom";
import TextField from "#material-ui/core/TextField";
function App(props) {
const [value, setValue] = useState("");
const errorMessage = value.length === 0 ? "Please enter something" : null;
const helperTextProps = {
error: value.length === 0 ? true : false
};
const textFieldStyle = { minHeight: "5rem" };
return (
<div>
<TextField label="name" style={textFieldStyle} />
<br />
<TextField
label="email"
helperText={errorMessage}
FormHelperTextProps={helperTextProps}
value={value}
onChange={event => setValue(event.target.value)}
style={textFieldStyle}
/>
<br />
<TextField label="other" style={textFieldStyle} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
And here it is in a code sandbox.
If you are using TextField you can pass in extra properties for the helper text:
render() {
const errorMessage = this.state.error ? "error happened" : null;
const helperTextProps = {
error: this.state.error ? true : false,
style: { minHeight: "1rem" }
};
return (
<TextField label="email" helperText={errorMessage} FormHelperTextProps={helperTextProps} />
);
}
Note: I have not tested this code out, it's just to get the gist of it.
It's been a few years, but I stumbled upon this issue myself. I also wanted the minHeight to adjust dynamically based upon the margin prop passed to the TextField component (Here for more info). This is what I came up with - hope it helps some other folks out there.
import React from 'react';
import { TextField, makeStyles } from '#material-ui/core';
const findMinHeight = ({ margin }) =>
(margin && margin.toLowerCase() === 'dense') ? '4em' : '5em';
const inputBoxMinHeight = makeStyles({
minHeightBox: {
minHeight: findMinHeight
}
});
const InputExample = ({ styleVariants }) => {
const { minHeightBox } = inputBoxMinHeight(styleVariants);
return (
<TextField className={ minHeightBox } margin={ styleVariants.margin } />
);
}
export default InputExample;

Resources