How to update state of a component which uses two context consumers - reactjs

I have a class component which uses two contexts with the value generated from API calls to a REST API.
What I want to do is get the context values and use them to update my component state.
I'm passing the context values like so
<TextContext.Consumer>
{(textContext) => (
<UserContext.Consumer>
{(userConsumer) => {
const text = textContext.text;
const user = userConsumer.user;
if(text != null && user != null){
return (
<div className="md:flex max-w-2xl">
<div className="flex flex-col flex-1 md:pr-32">
<FuseAnimateGroup
enter={{
animation: "transition.slideUpBigIn"
}}
>
<div style={{paddingRight:"8px"}}>
<Typography variant="h4" >{text.TITLE_PAGE_PROFILE}</Typography>
<TextField
id="outlined-full-width"
style={{ margin: 8 }}
placeholder={text.PROFILE_EMAIL_PLACEHOLDER}
value = {user.email}
disabled
fullWidth
margin="normal"
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment>
<IconButton>
<EmailIcon/>
</IconButton>
</InputAdornment>
)
}}
/>
</div>
<div style={{paddingRight:"8px"}}>
<form className={classes.container} noValidate autoComplete="off">
<TextField
id="outlined-full-width"
style={{ margin: 8 }}
placeholder={text.PROFILE_NAME}
value={user.name_user}
fullWidth
margin="normal"
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment position="start">
<AccountCircle />
</InputAdornment>
)
}}
/>
</form>
</div>
<div style={{paddingRight:"8px"}}>
<TextField
id="outlined-full-width"
style={{ margin: 8 }}
value={user.address_user}
placeholder={text.PROFILE_ADDRESS_PLACEHOLDER}
fullWidth
margin="normal"
variant="outlined"
InputLabelProps={{
shrink: true,
}}
/>
</div>
<div style={{paddingRight:"8px"}}>
<form className={classes.container} noValidate autoComplete="off">
<TextField
id="outlined-full-width"
style={{ margin: 8 }}
value={user.city_user}
label={text.PROFILE_CITY_PLACEHOLDER}
className={classes.textField}
fullWidth
margin="normal"
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment position="start">
<LocationCityIcon/>
</InputAdornment>
)
}}
/>
</form>
</div>
<div>
<TextField
id="outlined-select-currency"
select
value={user.country_user}
label={text.PROFILE_COUNTRY_PLACEHOLDER}
InputProps={{
endAdornment: (
<InputAdornment>
<IconButton>
<FlagIcon/>
</IconButton>
</InputAdornment>
)
}}
fullWidth
style={{ margin: 8, paddingRight: 8}}
SelectProps={{
MenuProps: {
className: classes.menu,
},
}}
margin="normal"
variant="outlined"
/>
</div>
<div style={{padding:"10px"}}>
<Fab variant="contained" aria-label="delete" className={classes.fab}>
{text.PROFILE_CHANGE_PASSWORD_BUTTON_PLACEHOLDER}
</Fab>
</div>
<div style={{paddingRight:"8px"}}>
<Typography variant="h4" > {text.COMPANY_INFORMATION_TITLE}</Typography>
<TextField
id="outlined-full-width"
style={{ margin: 8 }}
placeholder={text.COMPANY_NAME_PLACEHOLDER}
value={user.name_company}
fullWidth
margin="normal"
variant="outlined"
InputLabelProps={{
shrink: true,
}}
/>
</div>
<div style={{paddingLeft:"10px"}}>
<form className={classes.container} noValidate autoComplete="off">
<TextField
style={divStyle}
id="outlined"
label={text.COMPANY_EU_VAT_PLACEHOLDER}
value={user.vat_company}
className={classes.textField}
margin="normal"
variant="outlined"
/>
<TextField
style={div2Style}
id="outlined"
label={text.COMPANY_NUMBER_PLACEHOLDER}
value={user.registration_number_company}
className={classes.textField}
margin="normal"
variant="outlined"
/>
</form>
</div>
<div style={{paddingRight:"8px"}}>
<TextField
id="outlined-full-width"
style={{ margin: 8 }}
value={user.address_company}
placeholder={text.COMPANY_ADDRESS_PLACEHOLDER}
fullWidth
margin="normal"
variant="outlined"
InputLabelProps={{
shrink: true,
}}
/>
</div>
<div style={{paddingRight:"8px"}}>
<form className={classes.container} noValidate autoComplete="off">
<TextField
id="outlined-full-width"
style={{ margin: 8 }}
label={text.COMPANY_CITY_PLACEHOLDER}
value={user.city_company}
className={classes.textField}
fullWidth
margin="normal"
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment position="start">
<LocationCityIcon/>
</InputAdornment>
)
}}
/>
</form>
</div>
<div>
<TextField
id="outlined-select-currency"
select
label={text.COMPANY_COUNTRY_PLACEHOLDER}
fullWidth
style={{ margin: 8, paddingRight: 8}}
SelectProps={{
MenuProps: {
className: classes.menu,
},
}}
InputProps={{
endAdornment: (
<InputAdornment>
<IconButton>
<FlagIcon/>
</IconButton>
</InputAdornment>
)
}}
margin="normal"
variant="outlined"
/>
</div>
</FuseAnimateGroup>
</div>
<div className="flex flex-col md:w-320">
<FuseAnimateGroup
enter={{
animation: "transition.slideUpBigIn"
}}
>
<Button variant="contained" size="large" color="default" className={classes.button}>
{text.UPDATE_BUTTON_TEXT}
</Button>
</FuseAnimateGroup>
</div>
</div>
);
} else return <div>Loading...</div>
}
}
</UserContext.Consumer>
)}
</TextContext.Consumer>
I've tried to update the state inside the render by doing something like this
<TextContext.Consumer>
{(textContext) => (
<UserContext.Consumer>
{(userConsumer) => {
const text = textContext.text;
const user = userConsumer.user;
this.setState({
user:user,
text: text,
})
</UserContext.Consumer>
)}
</TextContext.Consumer>
The problem with this approach is that it throws the "Maximum update depth exceeded." error.
How should I go about this?

"Maximum update depth exceeded." error.
Do not setState() inside render().
How should I go about this?
Simply extract a component from it.
const User = (props) => {
return (
<>
<span>{props.user}</span>
<span>{props.text}</span>
</>
);
}
// in render
<TextContext.Consumer>
{(textContext) => (
<UserContext.Consumer>
{(userConsumer) => (
<User
text={textContext.text}
user={userConsumer.user}
/>
))}
</UserContext.Consumer>
)}
</TextContext.Consumer>
<User /> will still re-render every time the props (user, text) changes.

you can't update the state inside the render function.
Like that, you will be in the infinity loop of renders. Whenever you change the state that triggers the render function then you change the state again and so on.
anyway, you don't need to store this state inside the local state to use it, you can use it directly from the context.

First of all - are you sure you really need to store context in state? I don't see any reason to copy context (which always available) to state. Just use values from context, not from state.
But if you really need it, you can't update state in render function, because it will cause the infinite update loop. There some options to do so:
Extract component:
return (
<TextContext.Consumer>
{({ text }) => (
<UserContext.Consumer>
({user}) => <ExtractedComponent text={text} user={user} />
</UserContext.Consumer>
)}
</TextContext.Consumer>
);
Then you just need to overrider getDerrivedStateFromProps() for ExtractedComponent to get new state when props changed.
[ugly way] perform conditional update in render function to prevent infinite loop:
if (state.user !== user || state.text !== text) {
this.setState({ user, text });
}
Probably you can switch to functional components with hooks:
const YourComponent = () => {
const { user } = useContext(UserContext);
const { text } = useContext(TextContext);
const [ state, setState ] = useState({});
useEffect(() => {
setState({ user, text });
}, [ user, text ]);
}

Related

React and LocalStorage

I am trying this code where I can send some data and save it in the localstorage. I tied the below code.
function Login () {
const uname=useRef()
const pass = useRef()
const getEmail = localStorage.getItem("emailData")
const getPassword = localStorage.getItem("passwordData")
const handleSubmit=()=>{
if(uname.current.value==="admin"&&pass.current.value==="admin"){
localStorage.setItem("emailData","admin")
localStorage.setItem("passwordData","admin")
}
}
const [values, setValues] = useState({
email: "",
pass: "",
showPass: false,
});
const handlePassVisibilty = () => {
setValues({
...values,
showPass: !values.showPass,
});
};
return (
<div>
{
getEmail&&getPassword?<Home/>:
<Container maxWidth="sm">
<Grid
container
spacing={2}
direction="column"
justifyContent="center"
style={{ minHeight: "100vh" }}
>
<Paper elelvation={2} sx={{ padding: 10 }}>
<h2>Welcome to Employee Management System</h2>
<form onSubmit={handleSubmit}>
<Grid container direction="column" spacing={2}>
<Grid item>
<TextField
type="text"
fullWidth
label="Enter your Username"
placeholder="Username"
variant="outlined"
required
ref={uname}
/>
</Grid>
<Grid item>
<TextField
type={values.showPass ? "text" : "password"}
fullWidth
label="Enter your Password"
placeholder="Password"
variant="outlined"
required
ref={pass}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={handlePassVisibilty}
aria-label="toggle password"
edge="end"
>
{values.showPass ? (
<VisibilityOffIcon />
) : (
<VisibilityIcon />
)}
</IconButton>
</InputAdornment>
),
}}
/>
</Grid>
<Grid item>
<Button type="submit" fullWidth variant="contained" >
Sign In
</Button>
</Grid>
</Grid>
</form>
</Paper>
</Grid>
</Container>
}
</div>
);
};
export default Login;
What I am trying to do is that if the localstorage has the correct username and password the login page will redirect to the home page. The problem I am facing is that the data is not stored in the localstorage. Can someone please explain to me what I am doing wrong? Any help is appreciated Thank you.
you need to give type="submit" to button of your form in order to submit form
<Button fullWidth type="submit" variant="contained">
Sign In
</Button>
if it still not working, use state instead
here what I did with your code :
const getEmail = localStorage.getItem("emailData")
const getPassword = localStorage.getItem("passwordData")
const [values, setValues] = useState({
email: "",
pass: "",
showPass: false,
});
const handleSubmit=()=>{
if(values.email ==="admin" && values.pass ==="admin"){
localStorage.setItem("emailData","admin")
localStorage.setItem("passwordData","admin")
}
}
const handlePassVisibilty = () => {
setValues({
...values,
showPass: !values.showPass,
});
};
return (
<div>
{
getEmail&&getPassword?<Home/>:
<Container maxWidth="sm">
<Grid
container
spacing={2}
direction="column"
justifyContent="center"
style={{ minHeight: "100vh" }}
>
<Paper elelvation={2} sx={{ padding: 10 }}>
<h2>Welcome to Employee Management System</h2>
<form onSubmit={handleSubmit}>
<Grid container direction="column" spacing={2}>
<Grid item>
<TextField
type="text"
fullWidth
label="Enter your Username"
placeholder="Username"
variant="outlined"
required
value={values.email}
onChange={(e)=>{
setValues(prevState=>({...prevState,email:e.target.value}))
}}
/>
</Grid>
<Grid item>
<TextField
type={values.showPass ? "text" : "password"}
fullWidth
label="Enter your Password"
placeholder="Password"
variant="outlined"
required
value={values.pass}
onChange={(e)=>{
setValues(prevState=>({...prevState,pass:e.target.value}))
}}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={handlePassVisibilty}
aria-label="toggle password"
edge="end"
>
{values.showPass ? (
<VisibilityOffIcon />
) : (
<VisibilityIcon />
)}
</IconButton>
</InputAdornment>
),
}}
/>
</Grid>
<Grid item>
<Button type="submit" fullWidth variant="contained" >
Sign In
</Button>
</Grid>
</Grid>
</form>
</Paper>
</Grid>
</Container>
}
</div>
);

How do I place components on new line while contained in a <Box>?

I have all my components contained in a <Box> tag and would like to separate the submit button from the 3 text fields. I tried using a <br /> tag and that did not work.
Here is the website
And here is the code I have:
<Box
component="form"
noValidate
autoComplete="off"
display="flex"
alignItems="center"
justifyContent="center">
<TextField
label="Height (Feet)"
id="outlined-start-adornment"
type='number'
sx={{ marginX: 2 }}
InputProps={{
startAdornment: <InputAdornment position="start">ft:</InputAdornment>,
}}
/>
<TextField
label="Height (Feet)"
id="outlined-start-adornment"
type='number'
sx={{ marginX: 2 }}
InputProps={{
startAdornment: <InputAdornment position="start">in:</InputAdornment>,
}}
/>
<TextField
label="Weight"
id="outlined-start-adornment"
type='number'
sx={{ marginX: 2 }}
InputProps={{
startAdornment: <InputAdornment position="start">lbs:</InputAdornment>,
}}
/>
<Button variant="contained" type='submit'>Submit</Button>
</Box>
You could simply move your Button outside of Box.
<Box>
// Your inputs...
</Box>
<Button />
You might need to wrap everything inside a React fragment <> // Your code... </>.

MUI Autocomplete endAdornment blowing away default clearIcon

I am using an MUI Autocomplete and have added an endAdornment loader to the Textfield while the options are being fetched (from an external source), however this is overriding MUI's clearIcon (presumably because it is also an endAdornment). How do ensure that the default clearIcon remains? Here is my code -
<Autocomplete
style={{ margin: 'auto' }}
options={itemOptions}
getOptionLabel={(option: Item) => option? option.name:''}
value={selectedItem}
inputValue={filterInput}
onInputChange={(e, v) => handleInputChange(v)}
onChange={(e, val) => {
if (val) {
handleItemSelect(val)
}
}}
renderInput={(params) => (
<TextField
{...params}
label="Items"
variant="outlined"
style={{ width: '300px' }}
InputProps={{
...params.InputProps,
endAdornment: (
<>
{loading ? <CircularProgress color="inherit" size={25} /> : null}
</>
),
}}
type="text"
/>
)}
/>
I figured it out, just need to add:
{params.InputProps.endAdornment}
to my endAdornment like so:
endAdornment: (
<>
{loading ? <CircularProgress color="inherit" size={25} /> : null}
{params.InputProps.endAdornment}
</>
),

How can I transform this DataPicker into DataRangePicker of Material UI?

I've tried some imports but doesn't have much idea how to implement
<form className={classes.formContainer} noValidate autoComplete="off">
<div className={classes.formContent}>
<div className={classes.fieldsContent}>
<TextField
id="search-name-enrollment"
placeholder="Buscar por nome ou número de matrícula"
className={clsx(classes.field, classes.fieldNameEnroll)}
value={nameEnrollment}
onChange={(event) => handleNameEnrollmentChange(event.target.value)}
InputProps={{
disableUnderline: true,
classes: { input: classes.placeholder },
startAdornment: (
<InputAdornment position="start">
<SearchIcon color="primary" />
</InputAdornment>
)
}}
/>
</div>
<Link
component="button"
type="button"
variant="body2"
color="primary"
underline="none"
className={classes.linkFiltros}
onClick={() => {
setOpenFilters(!openFilters)
}}
>
Mais filtros
{openFilters ? (
<KeyboardArrowUpIcon fontSize="small" />
) : (
<KeyboardArrowDownIcon fontSize="small" />
)}
</Link>
<Collapse
in={openFilters}
classes={{
wrapper: classes.wrapper
}}
className={classes.fieldsContent}
timeout="auto"
unmountOnExit
>
<div className={classes.secundaryFieldContent}>
<div className={classes.secundaryField}>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<KeyboardDatePicker
disableToolbar
variant="inline"
format="dd/MM/yyyy"
margin="normal"
id="date-picker-inline"
placeholder="Data de matrícula"
value={dateEnrollment}
onChange={handleDateEnrollmentChange}
className={clsx(classes.field, classes.dates)}
invalidDateMessage=""
InputProps={{
disableUnderline: true,
classes: { input: classes.placeholder }
}}
KeyboardButtonProps={{
'aria-label': 'change date'
}}
/>
</MuiPickersUtilsProvider>
</div>
<div
className={clsx(
classes.secundaryField,
classes.secundaryFieldMarginL
)}
>
<Autocomplete
multiple
limitTags={1}
id="checkboxes-tags-demo"
options={options}
disableCloseOnSelect
value={status}
onChange={handleStatusChange}
getOptionLabel={(option) => option.label}
renderOption={(option, { selected }) => (
<React.Fragment>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.label}
</React.Fragment>
)}
renderInput={(params) => (
<TextField
{...params}
className={clsx(classes.field, classes.status)}
placeholder="Status de matrícula"
classes={{ root: classes.underline }}
/>
)}
/>
</div>
</div>
</Collapse>
</div>
</form>
I would like to know how to transform into to DataRangePicker, you guys can help me?
I've tried already some imports and similiar ones, but at all not having a full idea how to implement it. This model that I have already developed just is one date, I would like to have a calendar that I can choose a range as well.

Formik FieldArray input value for nested form input

I have Formik FieldArray integration in my React Web app as below, but I am not getting values of input field when I enter value in textfield, handleChange event is not working I think. How can I get input values in below case.
From WrapperForm also I am calling different sub form or children. I am not sure how to pass props to children.
return (
<div className={classes.container}>
<Formik
initialValues={{
profile: [
{
id: Math.random(),
firstName: "Test",
lastName: "",
email: ""
}
]
}}
validationSchema={validationSchema}
onSubmit={values => {
console.log("onSubmit", JSON.stringify(values, null, 2));
}}
>
{({ values, touched, errors, handleChange, handleBlur, isValid }) => (
<Form noValidate autoComplete="off">
<FieldArray name="profile">
{({ push, remove }) => (
<div>
{values.profile.map((p, index) => {
return (
<div key={p.id}>
<Grid container spacing={3} >
<Grid item xs={12} sm={6}>
<WrapperForm index = {index} remove={remove} touched={touched} errors={errors} handleChangeOnText={handleChange} handleBlur={handleBlur} />
</Grid>
</Grid>
</div>
);
})}
<Button
className={classes.button}
variant="contained"
color="primary"
startIcon={<AddCircleIcon />}
onClick={() =>
push({ id: Math.random(), firstName: "", lastName: "" })
}
>
Add More
</Button>
</div>
)}
</FieldArray>
<Divider style={{ marginTop: 20, marginBottom: 20 }} />
<Button
className={classes.button}
type="submit"
color="primary"
variant="contained"
// disabled={!isValid || values.people.length === 0}
>
submit
</Button>
<Divider style={{ marginTop: 20, marginBottom: 20 }} />
{debug && (
<>
<pre style={{ textAlign: "left" }}>
<strong>Values</strong>
<br />
{JSON.stringify(values, null, 2)}
</pre>
<pre style={{ textAlign: "left" }}>
<strong>Errors</strong>
<br />
{JSON.stringify(errors, null, 2)}
</pre>
</>
)}
</Form>
)}
</Formik>
WrapperForm.js
export default function HorizontalLinearStepper({index, remove, touched, errors, handleChangeOnText, handleBlur}) {
....
....
function getStepContent(step, index, touched, errors, handleChangeOnText, handleBlur) {
switch (step) {
case 0:
return <Profile index={index} touched={touched} errors={errors} handleChangeOnText=. {handleChangeOnText} handleBlur={handleBlur} />;
case 1:
return <Offer index={index} touched={touched} errors={errors} handleChangeOnText=. {handleChangeOnText} handleBlur={handleBlur} />;
case 2:
return 'This is the bit I really care about!';
default:
return 'Unknown step';
}
}
return (
<div className={classes.layout}>
<Paper variant="outlined" className={classes.paper}>
<Grid container spacing={3}>
<Grid item xs={12} sm={6}>
<TextField
required
id="email"
//name={`${member}.email`}
label="Email"
fullWidth
autoComplete="email"
/>
</Grid>
<Grid item xs={12} sm={4}>
<InputLabel htmlFor="brand-native-simple">Brand</InputLabel>
<Select
native
value={state.brand}
onChange={handleChange}
inputProps={{
name: 'brand',
id: 'brand-native-simple',
}}
>
<option aria-label="None" value="" />
<option value={'gp'}>GAP</option>
<option value={'br'}>BANANA REP</option>
<option value={'on'}>OLDNAVY</option>
</Select>
</Grid>
</Grid>
<Stepper activeStep={activeStep} className={classes.stepper} >
{steps.map((label, index) => {
const stepProps = {};
const labelProps = {};
if (isStepOptional(index)) {
labelProps.optional = <Typography variant="caption">Optional</Typography>;
}
if (isStepSkipped(index)) {
stepProps.completed = false;
}
return (
<Step key={label} {...stepProps}>
<StepLabel {...labelProps}>{label}</StepLabel>
</Step>
);
})}
</Stepper>
<div>
{activeStep === steps.length ? (
<div>
<Typography className={classes.instructions}>
All steps completed - you&apos;re finished
</Typography>
<Button onClick={handleReset} className={classes.button}>
Reset
</Button>
</div>
) : (
<div>
{getStepContent(activeStep, index, touched, errors, handleChangeOnText, handleBlur)}
<div>
<Button disabled={activeStep === 0} onClick={handleBack} className={classes.button}>
Back
</Button>
{isStepOptional(activeStep) && (
<Button
variant="contained"
color="primary"
onClick={handleSkip}
className={classes.button}
>
Skip
</Button>
)}
<Button
variant="contained"
color="primary"
onClick={handleNext}
className={classes.button}
>
{activeStep === steps.length - 1 ? 'Finish' : 'Next'}
</Button>
<Button
variant="contained"
color="secondary"
className={classes.button}
startIcon={<DeleteIcon />}
onClick={() => remove(index)}
>
Remove
</Button>
</div>
</div>
)}
I see some problems with your code here, first thing is you have to pass formik values as props to WrapperForm,
<WrapperForm values={values} {...otherPropsYouArePassing} />
Next you need to iterate over these values in the WrapperForm:
<div>
{
// You are getting these values from props in WrapperForm
values.profile &&
values.profile.length > 0 &&
values.profile.map((x, idx) => {
// Return you JSX here
return (...);
})
}
</div>
And lastly you need to name your inputs right like these:
// I'm assuming your data structure to be like this
/*
{
profile: [{ email: '', ... }, ...]
}
*/
<TextField
required
id="email"
name={`profile[${idx}].email`} // We're getting idx from the map above
label="Email"
fullWidth
autoComplete="email"
/>

Resources