Formik FieldArray input value for nested form input - reactjs

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"
/>

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

React picky not auto closing while selecting one value,?

If using single select, is it possible to close the dropdown after selection? I tried passing keepOpen props but cant able to fix, whiling passing keepOpen the drop down not working as it would, How can fix this issue?
console log while clicking drop down
console log
Parent Component Input Feild
<Grid item xs={12} sm={12} md={12} className={classes.item}>
<Field name='gender' label={I18n.t('gender')} filterable={false} component={renderSelect} >
{
_.get(getGender, 'data', genderArray)
}
</Field>
</Grid>
<Grid item xs={12} sm={12} md={12} className={classes.item}>
<Field name='userTypeResponse' label={I18n.t('user_type')} component={renderSelect} filterable={false} >
{
data?.map(item => ({ id: item.id, name: item.name }))
}
</Field>
</Grid>
Selector Component
const renderSelect = ({ input: { value, name, ...inputProps }, children, selectAll, selectAllText = 'Select All', filterable = true, multiple, label, disabled = false, placeholder = '', showLoader = false, spinnerProps = 'selectTagProp', meta: { touched, error } }) => {
return < LoadingOverlay active={showLoader} spinnerProps={spinnerProps} >
<FormGroup>
{label && <div className='customPicky'>
<InputLabel htmlFor={name}>{label}</InputLabel>
</div>}
<MultiSelect
value={multiple ? value || [] : value || null} {...inputProps}
options={children}
open={false}
multiple={multiple}
keepOpen={false}
includeSelectAll={selectAll}
selectAllText={selectAllText}
includeFilter={filterable}
labelKey='name'
valueKey='id'
placeholder={placeholder}
dropdownHeight={150}
clearFilterOnClose={true}
defaultFocusFilter={true}
disabled={disabled}
className={disabled === true ? 'pickySelect grey' : null}
numberDisplayed={1}
renderList={({ items, selectValue, getIsSelected }) => {
return <div >
{items.length < 1 && <div style={{ textAlign: 'center', padding: '5px' }}>{I18n.t('no_data_available')}</div>}
{items.map(item => (
<li key={item.id} onClick={() => selectValue(item)} style={{ display: 'flex', alignItems: 'center' }}>
<input type={multiple ? 'checkbox' : 'radio'} id={item.id} checked={getIsSelected(item)} />
<div style={{ marginLeft: '5px' }} >
{item.name}
</div>
</li>
))}
</div>;
}
}
/>
{touched && error && <Typography color="error" variant="caption">{error}</Typography>}
</FormGroup>
</LoadingOverlay >;
};

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.

TypeError: Cannot read property 'tipodecontato' of undefined

Good afternoon folks I'm having problems executing the action of displaying my modal follows the error
https://ibb.co/WtdvbCC
Below is my code
const of modal
const [contatoAtualizado, setContatoAtualizado] = useState<IContatos>(dados as IContatos);
useEffect(() => {
if(dados !== undefined){
setContatoAtualizado(dados)
}
}, [dados, contatoAtualizado]);
const para abrir a modal
const handleOpen = () => {
setOpen(true);
};
button open a modal
<Button
color="primary"
startIcon={<Icon fontSize="small">add_circle</Icon>}
onClick={handleOpen}
>
Adicionar registro
</Button>
modal
<Dialog fullWidth open={true}>
<DialogTitle>Adicionar profissão</DialogTitle>
<DialogContent>
<Grid container spacing={2}>
<Grid item lg={6}>
<Box my={1}>
<FormLabel
htmlFor="tipodecontato"
children={"Tipo de contato"}
required={true}
/>
<Select
fullWidth
id="tipodecontato"
name="tipodecontato"
variant="outlined"
value={contatoAtualizado.tipodecontato}
onChange={(e) =>
setContatoAtualizado({
...contatoAtualizado,
tipodecontato: e.target.value as string,
})
}
>
{tipodeContato.map((item, index) => (
<MenuItem key={index} value={item.name}>
{item.name}
</MenuItem>
))}
</Select>
</Box>
</Grid>
<Grid item lg={6}>
<Box my={1}>
<FormLabel
htmlFor="informacaoProfissional"
children={"Informação profissional"}
/>
<Select
fullWidth
id="informacaoProfissional"
name="informacaoProfissional"
variant="outlined"
value={contatoAtualizado.informacaoProfissional}
onChange={(e) =>
setContatoAtualizado({
...contatoAtualizado,
informacaoProfissional: e.target.value as string,
})
}
>
{informacaoProfissional.map((item, index) => (
<MenuItem key={index} value={item.name}>
{item.name}
</MenuItem>
))}
</Select>
</Box>
</Grid>
</Grid>
<Grid container>
<Grid item lg={12}>
<Box my={1}>
<FormLabel
htmlFor="contato"
children={"Contato"}
required={true}
/>
<TextField
required
fullWidth
id="contato"
name="contato"
placeholder="Digite aqui"
variant="outlined"
margin="none"
value={contatoAtualizado.contato}
onChange={(e) => {
setContatoAtualizado({
...contatoAtualizado,
contato: e.target.value,
});
}}
/>
</Box>
</Grid>
</Grid>
<Grid container>
<Grid item lg={12}>
<Box my={1}>
<FormLabel htmlFor="observacao" children={"Observação"} />
<TextField
required
fullWidth
placeholder="Digite aqui"
id="observacao"
name="observacao"
variant="outlined"
margin="none"
value={contatoAtualizado.observacao}
onChange={(e) => {
setContatoAtualizado({
...contatoAtualizado,
observacao: e.target.value,
});
}}
/>
</Box>
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button variant="outlined" color="primary" onClick={onClose}>
Cancelar
</Button>
<Button
variant="contained"
color="primary"
onClick={() => {
onSave(contatoAtualizado);
//console.log(contatoAtualizado)
}}
>
Confirmar
</Button>
</DialogActions>
</Dialog>
in this case I'm importing the modal to another component and opening it through the other component

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

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

Resources