react onChange on text field update whole component when state is changed - reactjs

I had a functional component and using react hooks to manage the state.
As soon as I add onChange in textField(input in material UI) the whole components re-render and when I remove the onChange in textField it does not re-render
<TextField
id='outlined-basic'
label='Outlined'
variant='outlined'
fullWidth
onChange={e => {
setChannelUrl(e.target.value);
}}
/>
The above picture is of component is rerender even I am not passing any props.
As soon as I remove setChannelUrl(e.target.value); everythng works like fine.
setChannelUrl is react useState Hooks
const [channelUrl, setChannelUrl] = useState('');
useEffect(() => {
console.log('runnedddd');
return () => {};
}, []);
const classes = useStyle();
return (
<Grid container spacing={2} className={classes.root}>
<Grid item md={6}>
<Typography variant='h6' color='primary' className={classes.button}>
Please Enter Your Channel Url
</Typography>
<TextField
id='outlined-basic'
label='Outlined'
variant='outlined'
fullWidth
onChange={e => {
setChannelUrl(e.target.value);
}}
/>
<Button fullWidth color='primary' variant='contained'>
large
</Button>
</Grid>
<Grid item md={6}>
<Paper elevation={2} className={`flex flexColumn ${classes.w100}`}>
<Avatar alt='Remy Sharp' src=''>
H
</Avatar>
<Typography variant='body' align='center'>
Hitesh Choudary
</Typography>
<Button
fullWidth
color='primary'
variant='contained'
className={classes.button}
>
Save It
</Button>
<Button
fullWidth
color='secondary'
variant='contained'
className={classes.button}
>
No, its not mine
</Button>
<Stats />
</Paper>
</Grid>
</Grid>
);
};
stats component
import React from 'react';
const Stats = props => {
console.log(props);
return <div></div>;
};
export default Stats;
I am using functional component and arrow function

Stats component renders duo to its parent render.
The parent renders when users typing in text field and onChange updates the stated through setChannelUrl.
You can memoize Stats with React.memo which will prevent the undesired renders:
If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.
const Stats = () => {
// Won't log on parent render
console.log('rendered');
return <div></div>;
};
export default React.memo(Stats);

Related

ReactJS - How to utilize useRef for an array generated set of text fields?

I am creating a Poll with title and options. The title is stored using useRef and it works fine.
The options for the poll are generated uisng - state.pollOptions.map
export default function CreatePoll() {
const titleRef = useRef();
const [state, setState] = useState({
pollOptions: ['Yes', 'No'],
published: false,
});
...
const handleSubmit = async () => {
console.log(titleRef.current.value) // shows value entered in text field
...
<Box mb={2} mt={4} pt={4} px={3}>
<TextField
id="standard-multiline-flexible"
label="Poll Title"
name="pollTitle"
multiline
maxRows={4}
value={state.pollTitle}
inputRef={titleRef}
variant="standard"
fullWidth
/>
</Box>
<Box p={3}>
{state.pollOptions.map((option, index) => (
<Grid container spacing={3} mt={index==0?0:2}>
<Grid item xs={3}>
<TextField
size='small'
required
id={`value_field_${index}`}
label="Value"
defaultValue={index}
className="mb-1 mr-2"
fullWidth
/>
</Grid>
<Grid item xs={9}>
<TextField
required
size='small'
id={`value_field_${index}`}
label="Option Title"
defaultValue={option}
className="mb-1"
fullWidth
/>
</Grid>
</Grid>
))}
</Box>
The options for the poll are generated using an array. I am planning to add a button to add and remove options from array.
I am not sure how to track the values of these generated options in a state or a Ref.

How can I fix the following warning: "Can't perform a React state update on an unmounted component"

Edit: I have been trying to implement a similar fix as this in my code, but I am very confused about how this method would translate in my code. I have been trying to apply that fix to my updateDose function, but it isn't working.
I am creating an app using React, Material UI, React Hook Form, and Yup.
I have two dialogs, one for the "edit dose" button and the "delete med" button for each card. On the "edit dose" dialog, the user inputs a new dose into a form.
I am getting the following warning if I try to update a medication's dose more than once (the first update shows no error, and then the second shows this)...
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I have searched other tutorials to try to find a fix based on other examples, but I am not having any luck. Thank you so much in advance and here's my relevant code...
const Medication = ({medication}) => {
const {handleSubmit, control, formState} = useForm({
mode: "onChange",
resolver: yupResolver(validationSchema)
});
// This handles the update medication dialog
const [openUpdate, setOpenUpdate] = useState(false);
const handleClickOpenUpdate = () => {
setOpenUpdate(true);
};
const handleCloseUpdate = () => {
setOpenUpdate(false);
};
// Function for the update dose button
function updateDose(medicationId, parsedMedications, data) {
let medication;
let index;
for (let i = 0; i < parsedMedications.length; i++) {
if (parsedMedications[i].id === medicationId) {
medication = parsedMedications[i];
index = i;
}
}
medication.dose = data.dose;
parsedMedications[index] = medication;
localStorage.setItem("medications", JSON.stringify(parsedMedications));
// This forces the dialog to close
setOpenUpdate(false);
}
return (
<Box>
<Card sx={cardSx}>
<CardContent>
<Typography sx={typographyMedicationSx} variant="h5">
Medication: {medication.medication}
</Typography>
<Typography sx={typographyMedicationSx} variant="h5">
Dose: {medication.dose} mg
</Typography>
</CardContent>
<Box>
<Button onClick={() => handleClickOpenUpdate()} size="large"
sx={buttonSx}
variant="contained">Edit
Dose</Button>
<Button onClick={() => handleClickOpen()} color="error"
size="large"
sx={buttonSx} variant="contained">Delete
Med </Button>
</Box>
</Card>
{/* Update medication dialog */}
<Dialog
open={openUpdate}
onClose={handleCloseUpdate}
TransitionComponent={Transition}
>
<DialogTitle sx={dialogTitleSx}>
{handleCloseUpdate ? (
<IconButton
aria-label="close"
onClick={handleCloseUpdate}
sx={iconButtonSx}
>
<CloseIcon/>
</IconButton>
) : null}
</DialogTitle>
<form
onSubmit={handleSubmit((data) => updateDose(medication.id, parsed, data))}
noValidate>
<Typography sx={updateDoseTypographySx} variant="h4">
Update dose
</Typography>
<Box
sx={boxSx}
>
<Controller
name="dose"
control={control}
defaultValue={""}
render={({field: {ref, ...field}, fieldState: {error}}) => (
<Autocomplete
{...field}
autoHighlight
disableClearable
isOptionEqualToValue={(option, value) => option.id === value.id}
id="dose-autocomplete"
onChange={(event, value) => field.onChange(value.label)}
options={doseSuggestions}
renderInput={(params) => (
<TextField
required
error={!!error}
helperText={error?.message}
id="dose"
label="Dose"
name="dose"
type="numeric"
inputRef={ref}
{...params}
/>
)}
/>
)}
/>
<Button disabled={!formState.isValid} size="large"
sx={formButtonSx} type="submit"
variant="contained">Submit</Button>
</Box>
</form>
</Dialog>
</Box>
)
};
const medications = parsed.map((medication, index) => {
return (<Medication medication={medication} key={"medication" + index}/>)
});
Someone on a slack community helped me figure it out! I needed to add this to the dialog... keepMounted={true}

How to set values in a multi-step Formik form with components that implement useField()

I'm implementing the multi-step wizard example with Material-UI components and it works well with the useField() hook but I cannot figure out how to bring setFieldValue() into scope, so I can use it from a wizard step.
I've seen suggestions to use the connect() higher-order component but I have no idea how to do that.
Here is a snippet of my code: CodeSandbox, and the use case:
A wizard step has some optional fields that can be shown/hidden using a Material-UI Switch. I would like the values in the optional fields to be cleared when the switch is toggled off.
I.e.
Toggle switch on.
Enter data in Comments field.
Toggle switch off.
Comments value is cleared.
Toggle switch on.
Comments field is empty.
Hoping someone can help! Thanks.
I came across this answer the other day but discarded it because I couldn't get it working.
It does actually work but I'm in two minds as to whether it's the right approach.
const handleOptionalChange = (form) => {
setOptional(!optional)
form.setFieldValue('optionalComments', '', false)
}
<FormGroup>
<FormControlLabel
control={
// As this element is not a Formik field, it has no access to the Formik context.
// Wrap with Field to gain access to the context.
<Field>
{({ field, form }) => (
<Switch
checked={optional}
onChange={() => handleOptionalChange(form)}
name="optional"
color="primary"
/>
)}
</Field>
}
label="Optional"
/>
</FormGroup>
CodeSandbox.
I believe this is what you're after: CodeSandbox. I forked your CodeSandbox.
I tried to follow your code as closely as possible and ended up not using WizardStep. The step variable is returning a React component that is a child to Formik. Formik is rendered with props e.g. setFieldValue, which can be passed down to its children. In order to pass the setFieldValue as a prop to step, I had to use cloneElement() (https://reactjs.org/docs/react-api.html#cloneelement), which allows me to clone the step component and add props to it as follows.
// FormikWizard.js
<Formik
initialValues={snapshot}
onSubmit={handleSubmit}
validate={step.props.validate}
>
{(formik) => (
<Form>
<DialogContent className={classes.wizardDialogContent}>
<Stepper
className={classes.wizardDialogStepper}
activeStep={stepNumber}
alternativeLabel
>
{steps.map((step) => (
<Step key={step.props.name}>
<StepLabel>{step.props.name}</StepLabel>
</Step>
))}
</Stepper>
<Box
className={classes.wizardStepContent}
data-cy="wizardStepContent"
>
{React.cloneElement(step, {
setFieldValue: formik.setFieldValue
})}
</Box>
</DialogContent>
<DialogActions
className={classes.wizardDialogActions}
data-cy="wizardDialogActions"
>
<Button onClick={handleCancel} color="primary">
Cancel
</Button>
<Button
disabled={stepNumber <= 0}
onClick={() => handleBack(formik.values)}
color="primary"
>
Back
</Button>
<Button
disabled={formik.isSubmitting}
type="submit"
variant="contained"
color="primary"
>
{isFinalStep ? "Submit" : "Next"}
</Button>
</DialogActions>
</Form>
)}
</Formik>
To access the setFieldValue prop in the child component, in App.js, I created a new component called StepOne and used it to wrap around the inputs, instead of using WizardStep. Now I am able to access setFieldValue and use it in the handleOptionalChange function.
// App.js
import React, { useState } from "react";
import "./styles.css";
import { makeStyles } from "#material-ui/core/styles";
import Box from "#material-ui/core/Box";
import CssBaseline from "#material-ui/core/CssBaseline";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import FormGroup from "#material-ui/core/FormGroup";
import Switch from "#material-ui/core/Switch";
import FormikTextField from "./FormikTextField";
import { Wizard, WizardStep } from "./FormikWizard";
const useStyles = makeStyles((theme) => ({
content: {
display: "flex",
flexFlow: "column nowrap",
alignItems: "center",
width: "100%"
}
}));
const initialValues = {
forename: "",
surname: "",
optionalComments: ""
};
const StepOne = ({ setFieldValue }) => {
const classes = useStyles();
const [optional, setOptional] = useState(false);
const displayOptional = optional ? null : "none";
const handleOptionalChange = () => {
setFieldValue("optionalComments", "");
setOptional(!optional);
};
return (
<Box className={classes.content}>
<FormikTextField
fullWidth
size="small"
variant="outlined"
name="forename"
label="Forename"
type="text"
/>
<FormikTextField
fullWidth
size="small"
variant="outlined"
name="surname"
label="Surname"
type="text"
/>
<FormGroup>
<FormControlLabel
control={
<Switch
checked={optional}
onChange={handleOptionalChange}
name="optional"
color="primary"
/>
}
label="Optional"
/>
</FormGroup>
<FormikTextField
style={{ display: displayOptional }}
fullWidth
size="small"
variant="outlined"
name="optionalComments"
label="Comments"
type="text"
/>
</Box>
);
};
function App(props) {
return (
<>
<CssBaseline />
<Wizard
title="My Wizard"
open={true}
initialValues={initialValues}
onCancel={() => {
return;
}}
onSubmit={async (values) => {
console.log(JSON.stringify(values));
}}
>
<StepOne />
<StepTwo />
</Wizard>
</>
);
}
export default App;
Alternative
To use setFieldValue in Formik, the easiest way would be to have the all input elements within the <Formik></Formik tags. You could conditionally render the input elements based on what step you're on as follows. This gives the inputs a direct access to setFieldValue so you can call setFieldValue("optionalComments", "") on the Switch input which will clear the comments on each toggle. Although this may mean you'll have a longer form, I don't think this is necessarily a bad thing.
<Formik>
<Form>
{step === 1 && <div>
// Insert inputs here
</div>}
{step === 2 && <div>
<TextField
onChange={(event) => setFieldValue("someField", event.target.value)}
/>
<Switch
checked={optional}
onChange={() => {
setFieldValue("optionalComments", "");
setOptional(!optional);
}}
name="optional"
color="primary"
/>
</div>}
</Form>
</Formik>

react router link with material ui button submit

I am using flask on the backend and react, react router and material-ui as a frontend.
I have:
material-ui/core 3.4.0,
react router dom 4.3.1
I am trying to create simple sign up form.
I would like to submit the form's data and then redirect to the index page using react router.
My problem is that material-ui button does not work with the link from router.
Is it possible to combine this two together somehow?
My code:
import:
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { withStyles, Grid, Paper, Avatar, Typography, Button, TextField} from '#material-ui/core'
import LockIcon from '#material-ui/icons/LockOutlined'
and render return:
render() {
const { classes } = this.props
const { user: { username, password } } = this.state
return (
<Grid container className={classes.root}>
<Paper className={classes.paper}>
<Avatar className={classes.avatar}>
<LockIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign up
</Typography>
<form onSubmit={this.saveUser}>
<TextField label="Username" value={username} onChange={this.handleChange('username')} margin="normal" required fullWidth/>
<TextField label="Password" value={password} onChange={this.handleChange('password')} margin="normal" required fullWidth type='password'/>
<Button component={Link} to="/" type="submit" fullWidth variant="contained" color="primary">Sign Up</Button>
</form>
</Paper>
</Grid>
);
}
When I remove from Button component={Link} to="/":
<Button type="submit" fullWidth variant="contained" color="primary">Sign Up</Button>
then submit work and saves data in database. When I remove type="submit":
<Button component={Link} to="/" fullWidth variant="contained" color="primary">Sign Up</Button>
then redirect to index page.
Can I combine somehow this two in one button?
RaisedButton, FlatButton from material-ui not working anymore
Add to state:
doRedirect: false
In render():
<Button type="submit" fullWidth variant="contained" color="primary">Sign Up</Button>
In submit hanler:
saveUser = () => {
// **********
// Your code to update database
// **********
this.setState({ doRedirect: true });
}
In render():
{ this.state.doRedirect && <Redirect to="/" /> }
And of course:
import { Redirect } from 'react-router-dom'
In your save user function just redirect after saving the user.
you can get history from props
saveUser = event => {
event.preventDefault()
//update database with user
this.props.history.location.push('new-route')
// or you can use window.location
window.location.href = 'new-route'
}
<Button
onClick={() => {}}
variant="outlined"
component={Link}
to={{
pathname: `/`,
search: location.search,
}}
>
Submit
</Button>

React Passing data to components

I have a parent stateful component and i pass the state of show dialog to a stateless header component.
When a icon clicked on the header component it opens a stateless dialog component.
In the stateless dialog component i want to be able to enter data into a text-field.
Do i have to completely change my code to make the stateless dialog to a stateful component?
Below is my code. If anyone can recommend the best way of doing this. Thanks.
class Layout extends Component {
state = {
show:false
}
toggleSidenav = (action) =>{
this.setState({
showDialog:action
})
}
render(){
return(
<div>
<Header
showNav={this.state.showDialog}
onHideNav={() => this.toggleSidenav(false)}
onOpenNav={() => this.toggleSidenav(true)}
/>
</div>
)
}
}
export default Layout;
Header component
const Header = (props) => {
console.log(props.onOpenNav)
const navBars = () => (
<div>
<AppBar position="static">
<Toolbar>
<IconButton color="inherit" aria-label="createfolder">
<SvgIcon>
<path d={createfolder}
onClick={props.onOpenNav}
name="firstName" />
</SvgIcon>
</IconButton>
</Toolbar>
</AppBar>
</div>
)
return (
<div>
<SideNav {...props} />
<div>
{navBars()}
</div>
</div>
)
}
Dialog Component
const DialogBox = (props) => {
return (
<div>
<Dialog
open={props.showNav}
aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Add Folder</DialogTitle>
<DialogContent>
<TextField
margin="normal"
/>
</DialogContent>
<DialogActions>
<Button onClick={props.onHideNav} color="primary">
Cancel
</Button>
<Button onClick={props.onHideNav} color="primary"
onChange={this.handleFieldChange}
value={this.value}
>
Create
</Button>
</DialogActions>
</Dialog>
</div>
)
}
Since component Header is readily stateful. You could initialize its state to
state = {
show:false,
formData: {} //later, you may save user input from the child component here
}
and in Header component, you may add a function:
handleInputEntered = (event) => {
const _data = { ...this.state.formData };
_data[event.target.name] = event.target.value;
this.setState({
formData: _data
});
}
and make sure to pass this new function as a prop to like this:
<Header
handleInputEntered = {this.handleInputEntered}
/>
and set onChange to be this new function where you have input field:
<TextField
onChange={this.props.handleInputEntered}
/>
It seems you're using MaterialUI, so just look up how you may supply the onChange property to TextField component.
Is this clear?

Resources