Reputation: 707
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;
Upvotes: 1
Views: 7894
Reputation: 2824
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
Upvotes: 0
Reputation: 503
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..
Upvotes: 0
Reputation: 39432
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.
Upvotes: 2