React conditionally rendering - javascript

I have a simple form build from React. Upon submission, if the error comes back, I wish to render an extra div to show error on the form.
Right now I got it to work but I do not like the solution. My solution is based on the knowledge that the component will only re-render if the state changes inside the render function (In this case this.state.errorMessages). So I had to explicitly put the if condition inside the render function like so
renderError() {
var errArr = [];
for (var key in this.state.errorMessages) {
errArr = [...errArr, ...this.state.errorMessages[key]];
}
return (
<div className="alert alert-danger">
{errArr.map((err) => {
return <p>{err}</p>
})}
</div>
)
}
renderForm() {
return (
<form onSubmit={this.handleRegisterFormSubmit}>
<div className="form-group">
<label>First Name</label>
<input type="text" className="form-control" name="name" placeholder="Name" value={this.state.firstName} onChange={this.handleFirstNameChange} required/>
</div>
<div className="form-group">
<label>Last Name</label>
<input type="text" className="form-control" name="lastName" placeholder="Last Name" value={this.state.lastName} onChange={this.handleLastNameChange} required/>
</div>
<div className="form-group">
<label>Email address</label>
<input type="email" className="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email" value={this.state.email} onChange={this.handleEmailChange} />
<small id="emailHelp" className="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div className="form-group">
<label>Password</label>
<input type="password" className="form-control" name="password" placeholder="Password" value={this.state.password} onChange={this.handlePasswordChange}/>
</div>
<div className="form-group">
<label>Password Confirmation</label>
<input type="password" className="form-control" name="password_confirmation" placeholder="Password Confirmation" value={this.state.passwordConfirmation} onChange={this.handlePasswordConfirmationChange}/>
</div>
<div>
<button type="submit" className="btn btn-primary">Submit</button>
<button type="button" className="btn btn-danger" onClick={this.handleCancelClick}>Cancel</button>
</div>
</form>
)
}
render () {
if (!this.state.errorMessages) {
return (
<div>
{this.renderForm()}
</div>
)
} else {
return (
<div>
{this.renderForm()}
{this.renderError()}
</div>
)
}
}
I don't really like this approach as this could get nasty if I have more condition to re-render. I'm hoping there is a solution along the line of not having much logic in the actual render function and have that extracted out. For example...
renderError() {
if (!this.state.errorMessages) {
return;
}
var errArr = [];
for (var key in this.state.errorMessages) {
errArr = [...errArr, ...this.state.errorMessages[key]];
}
return (
<div className="alert alert-danger">
{errArr.map((err) => {
return <p>{err}</p>
})}
</div>
)
}
render () {
<form onSubmit={this.handleRegisterFormSubmit}>
<div className="form-group">
<label>First Name</label>
<input type="text" className="form-control" name="name" placeholder="Name" value={this.state.firstName} onChange={this.handleFirstNameChange} required/>
</div>
<div className="form-group">
<label>Last Name</label>
<input type="text" className="form-control" name="lastName" placeholder="Last Name" value={this.state.lastName} onChange={this.handleLastNameChange} required/>
</div>
<div className="form-group">
<label>Email address</label>
<input type="email" className="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email" value={this.state.email} onChange={this.handleEmailChange} />
<small id="emailHelp" className="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div className="form-group">
<label>Password</label>
<input type="password" className="form-control" name="password" placeholder="Password" value={this.state.password} onChange={this.handlePasswordChange}/>
</div>
<div className="form-group">
<label>Password Confirmation</label>
<input type="password" className="form-control" name="password_confirmation" placeholder="Password Confirmation" value={this.state.passwordConfirmation} onChange={this.handlePasswordConfirmationChange}/>
</div>
<div>
<button type="submit" className="btn btn-primary">Submit</button>
<button type="button" className="btn btn-danger" onClick={this.handleCancelClick}>Cancel</button>
</div>
{this.renderError}
</form>
}
This throws an error because it complains that this.renderError should be called as a function. But when I put it as
this.renderError()
The error will never render because it does not get automatically called when errors come back.
-----------Update----------
Alternatively, why can't I do something like the following
render () {
<div>
<form onSubmit={this.handleRegisterFormSubmit}>
...
</form>
{if (this.state.errorMessages) {this.renderError()}}
</div>
}
This throws console error
Uncaught Error: Module build failed: SyntaxError: Unexpected token
(117:13)
----------Update 2-----------
Essentially, I'm looking for a solution where inside the render function, I can easily show a whole block of code when the state changes. In Vue, I can do something like
<form>
<input type="text">
</form>
<div v-if="hasError">
<div class="alert alert-danger">{{something}}</div>
</div>
Can I do something as easy as this in React?

You can just use map in order to extract the error message from your object.
Here below a minimal example of form validations and errors in React. It's good to understand how it works, but for my part, I use Formik which simplifies this process.
class Test extends React.Component {
constructor(props) {
super(props);
this.state = { errorMessages: {} };
}
handleRegisterFormSubmit = e => {
e.preventDefault(); // don't submit the form until we run what's below
let errorMessages = {};
if (!this.state.lastName) errorMessages.lastName = 'You need to enter your last name';
// And so on for each field validation
// Do we have errors ?
if (Object.keys(errorMessages).length > 0) {
this.setState(errorMessages);
} else {
// Submit to server
}
};
handleChange = e => {
this.setState({
[e.target.name]: e.target.value,
errorMessages: {
...this.state.errorMessages,
[e.target.name]: null // remove the error of this field if it's being edited
}
});
};
render() {
const errArr = Object.keys(this.state.errorMessages).map(key => this.state.errorMessages[key]);
return (
<form onSubmit={this.handleRegisterFormSubmit}>
<div className="form-group">
<label>Last Name</label>
<input type="text" className="form-control" name="lastName" placeholder="Last Name" value={this.state.lastName} onChange={this.handleChange} />
</div>
{/* ... your dom */}
<div>
<button type="submit" className="btn btn-primary">
Submit
</button>
<button type="button" className="btn btn-danger" onClick={this.handleCancelClick}>
Cancel
</button>
</div>
{errArr.length > 0 && (
<div className="alert alert-danger">
{errArr.map(err => {
return <p>{err}</p>;
})}
</div>
)}
</form>
);
}
}
One more way to not display your alert div is with a ternary operator for your className and using bootstrap's d-none
<div className={errArr.length ? "alert alert-danger" : "d-none"}>
{errArr.map(err => {
return <p>{err}</p>;
})}
</div>

I believe this is an architectural question.
Try to follow those practices:
1- Inject conditional statement inside JSX directly
2- Use functional components to render JSX not object methods
1- Inject conditional statement inside JSX directly
Bad:
if (!this.state.errorMessages) {
return (
<div>
{this.renderForm()}
</div>
)
} else {
return (
<div>
{this.renderForm()}
{this.renderError()}
</div>
)
}
Good:
return <div>
{this.renderForm()}
{this.state.errorMessages && this.renderError()}
</div>
2- Use functional components to render JSX not object methods
Bad:
class FormComponent {
// ....
renderError() {
// Blah blah blah
return (
<div className="alert alert-danger">
{errArr.map((err) => {
return <p>{err}</p>
})}
</div>
)
}
render() {
return (
<div>
<AnotherComponent />
{this.renderError()}
<div/>
)
}
}
then {this.renderError()}
Good
class FormComponent {
// ....
render() {
return (
<div>
<AnotherComponent />
{<Error />} {/* render it as componet ⚠️*/}
<div/>
)
}
}
// Build it as component outside the main component (FormComponent) ⚠️
function Error(props) {
return (
<div className="alert alert-danger">
{props.errArr.map((err) => {
return <p>{err}</p>
})}
</div>
)
}
I spent many 2 years in React development of enterprise apps also graduating from Udacity and I am sharing my experience here. Congrats!

One would usually render items in an element representing the list with some styling applied. We also sometimes don't want this wrapping element if we don't have any items.
I've written the following component which allows conditional rendering. When the condition is false, no elements are rendered keeping the DOM clean.
export default class If extends Component<{ condition: any }, {}> {
render() {
return this.props.condition ? this.props.children : <Fragment></Fragment>;
}
}
One can now simply use the component as follow:
<If condition={items.length}>
<-- item list -->
</If>

Related

Can't convert input element to re-usable component in react js

I created a login form and now I want to convert my input fields to re- usable component. I created separate common input.jsx file. This is input.jsx file's code.
import React from "react";
const Input = ({ name, label, value, onChange }) => {
return (
<div className="form-group">
<label htmlFor={name}>{label}</label>
<input
value={value}
onChange={onChange}
id={name}
name={name}
type="text"
className="form-control"
/>
</div>
);
};
export default Input;
and imported it to my loginForm.jsx. Here is my loginForm.jsx render method
handleChange = ({ currentTarget: input }) => {
const account = { ...this.state.account };
account[input.name] = input.value;
this.setState({ account });
};
render() {
const { account } = this.state;
return (
<div>
<h1>Login</h1>
<form onSubmit={this.handleSubmit}>
<Input
name="username"
value={account.username}
label="Username"
onChange={this.handleChange}
/>
<Input
name="password"
value={account.password}
label="Password"
onChange={this.handleChange}
/>
<button className="btn btn-primary">Login</button>
</form>
</div>
);
}
But after adding below code to my loginForm.jsx,
<Input
name="username"
value={account.username}
label="Username"
onChange={this.handleChange}
/>
code and deleted previous code ,
<div className="form-group">
<label htmlFor="username">Username</label>
<input
value={account.username}
name="username"
onChange={this.handleChange}
ref={this.username}
id="username"
type="text"
className="form-control"
/>
</div>
suddenly my login page not loading.(Empty page).
My login page's console showing below error.
The above error occurred in the <LoginForm> component:
at LoginForm (http://localhost:3000/main.5d4e82bfe117bc198b43.hot-update.js:27:5)
at Route (http://localhost:3000/static/js/bundle.js:54444:5)
at Switch (http://localhost:3000/static/js/bundle.js:54739:5)
at main
at App
at Router (http://localhost:3000/static/js/bundle.js:54612:5)
at BrowserRouter (http://localhost:3000/static/js/bundle.js:53870:5)
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.

setting the value of more than one input

I am trying to build a login form. I am trying to set up the value of the email & password field individually. But as soon as I try to enter the text in the email text field, the same appears in the password field too. Can I have a solution to this?
Below is the code.
I guess the error is in OnChange fn where I am assigning the same value e.target.value to both the {email, passwaord}.
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
const LoginPage = () => {
let navigate = useNavigate();
const [credentials, setCredentials] = useState({email:"",password:""});
const onChange = (e) => {
setCredentials({email: e.target.value ,password: e.target.value})
console.log(credentials.email, credentials.password)
}
const goToSignUp = () => {
navigate("/signup");
}
return (
<>
<div className="container my-5">
<div id="loginbody">
<div className="mt-3">
<h2 className="my-3 display-3">Login Here</h2>
<form className="login-form p-5">
<div className="mb-3">
<label for="exampleInputEmail1" className="form-label">
Email address
</label>
<input
type="email"
className="form-control"
id="email"
name="email"
value={credentials.email}
aria-describedby="emailHelp"
onChange={onChange}
/>
<div id="emailHelp" className="form-text">
We'll never share your email with anyone else.
</div>
</div>
<div className="mb-3">
<label for="exampleInputPassword1" className="form-label">
Password
</label>
<input
type="password"
className="form-control"
id="password"
name="password"
value={credentials.password}
onChange={onChange}
/>
</div>
<div className="d-grid gap-2 my-4 col-6 mx-auto">
<button type="submit" className="btn btn-success">
Submit
</button>
</div>
<hr />
<div className="mb-3 text-center">
<div id="emailHelp" className="form-text center my-3">
Didn't have an account ?
</div>
<div className="d-grid gap-2 my-3 col-6 mx-auto">
<button onClick={goToSignUp} className="btn btn-success ">
SignUp Here !
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</>
);
};
export default LoginPage;
You have identified the problem. You need to pass the key to change as well.
Here passing a callback to setState which provides the current state as a parameter, cloning the state object using spread syntax, and then updating the relevant property in the copied object using the passed key as a computed property name.
const LoginPage = () => {
const [credentials, setCredentials] = useState({email:"",password:""});
const onChange = (e, key) => {
setCredentials(prevCredentials => ({...prevCredentials, [key]: e.target.value}))
}
return (
<>
//...
<input
type="email"
className="form-control"
id="email"
name="email"
value={credentials.email}
aria-describedby="emailHelp"
onChange={(e) => onChange(e, 'email')}
/>
//...
<input
type="password"
className="form-control"
id="password"
name="password"
value={credentials.password}
onChange={(e) => onChange(e, 'password')}
/>
//...
</>
);
};
Note: Calling console.log() right after setting state will not log the updated state, the new state values won't be available until the next render cycle. see: useState set method not reflecting change immediately
Use the proper key to the respective fields
const onChange = (e) => {
setCredentials({ ...credentials, [e.target.name]: e.target.value})
console.log(credentials);
}

How can Prompt be used to stop navigation on formik dirty state

I'm using formik in a reactjs project, and I want to use Prompt from react-router to open a notification before a user leaves and loses changes made to their submission.
I'd expected something like this to work:
<Prompt
when={formik.dirty}
message="You have unsaved changes. Are you sure you want to leave?"
/>
My formik block looks like this:
const formik = useFormik({
initialValues: {
<values>
},
enableReinitialize: true,
validate,
onSubmit: values => {
<submit functional stuff>
}
});
And my form is something like this:
<form id="myForm" onSubmit={formik.handleSubmit}>
<div className="row">
<div className="form-group">
<label htmlFor="name">Name</label>
<input
id="name"
type="text"
onChange={formik.handleChange}
value={formik.values.name}
className="form-control"
placeholder="Enter name"
disabled={isDisabled}
/>
{formik.errors.name ? <div className="text-danger">{formik.errors.name}</div> : null}
</div>
<div className="form-group">
<label htmlFor="subject">Subject</label>
<input
id="subject"
type="text"
onChange={formik.handleChange}
value={formik.values.subject}
className="form-control"
placeholder="Email subject"
disabled={isDisabled}
/>
{formik.errors.subject ? <div className="text-danger">{formik.errors.subject}</div> : null}
</div>
</div>
</form>
but it appears that formik.dirty is either not defined or it's not seen as true (despite making changes to the form).
How would I properly use the dirty prop to trigger the Prompt?
I am not sure what kind of setup you have, but I created a PoC with routing which has two tabs (links) for navigation and I am using prompt on tab with formik form component.
import React from "react";
import { Prompt } from "react-router-dom";
import { useFormik } from "formik";
const MyForm = () => {
const formik = useFormik({
initialValues: {
name: "",
subject: ""
},
enableReinitialize: true,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
onChange: (e) => {
console.log(e);
}
});
return (
<div>
<Prompt
when={!!formik.dirty}
message={(location) =>
`Are you sure you want to go to ${location.pathname}`
}
/>
<form id="myForm" onSubmit={formik.handleSubmit}>
<div className="row">
<div className="form-group">
<label htmlFor="name">Name</label>
<input
id="name"
type="text"
onChange={formik.handleChange}
value={formik.values.name}
className="form-control"
placeholder="Enter name"
/>
{formik.errors.name ? (
<div className="text-danger">{formik.errors.name}</div>
) : null}
</div>
<div className="form-group">
<label htmlFor="subject">Subject</label>
<input
id="subject"
type="text"
onChange={formik.handleChange}
value={formik.values.subject}
className="form-control"
placeholder="Email subject"
/>
{formik.errors.subject ? (
<div className="text-danger">{formik.errors.subject}</div>
) : null}
</div>
{formik.dirty && <button tye="submit">Save</button>}
</div>
</form>
</div>
);
};
export default MyForm;
take a look the this codesandbox.

Why this onSubmit not calling its function?

Whats wrong with this Form , when i click submit button the signInSubmitHandler() function is not called , i tested it with a simple Button with onClick and it works but if i use Form with submit button it doesnt work. (im using reactjs)
in SignInForm.jsx file:
const SignInForm = (props) => {
return (
<React.Fragment>
<h1>Welcome to ToDo</h1>
<form onSubmit={props.signInSubmitHandler} className={style.signInForm}>
<div className={style.signInFormImportantElements}>
<span className={style.userFormsErrors}>{props.userEmailError}</span>
<input
name="userEmail"
type="email"
placeholder="email"
value={props.currentUserEmailText}
className={style.signInText}
onChange={(e) => {
props.signInOnChangeHandler(e);
}}
onBlur={(e) => props.signInOnBlurHandler(e)}
/>
<span className={style.userFormsErrors}>{props.userPasswordError}</span>
<input
name="userPassword"
type="password"
placeholder="password"
value={props.currentUserPasswordText}
className={style.signInText}
onChange={(e) => {
props.signInOnChangeHandler(e);
}}
onBlur={(e) => props.signInOnBlurHandler(e)}
/>
<input type="submit" value="Submit" className={style.signInSubmit} />
</div>
<div className={style.signInLinks}>
Forget Password
Create Account
</div>
</form>
</React.Fragment>
);
};
in app.jsx file :
signInSubmitHandler() {
console.log('waaaat');
}
Form Code image
props
signInSubmitHandler binding
signInSubmitHandler function
your function has one param
signInSubmitHandler(e) {
console.log('waaaat');
}
so try this
<form onSubmit={(e) => props.signInSubmitHandler(e)} className={style.signInForm}>

Where does redux-form's `meta : { touched, error }` live? Could I access it when not contained by renderField scope?

The redux-form documentation advises me to render my input and submit-validation errors like this.
const renderField = ({ input, placeholder, className, type, meta: { touched, error } }) => (
<div>
<input {...input} className={className} placeholder={placeholder} type={type}/>
{touched && error && <span><font color="red">{error}</font></span>}
</div>
)
Inside render(){return(<form> </form>)} you are then supposed to create your inputs like this (note component={renderField} in the next code line):
<Field type="password" placeholder="password" className="form-control" component={renderField} name="password"/>
I wanted to customize this in order to fit it better into my own work. But I cannot seem to find a way to target touched and error unless I place the component in renderField, I guess I am still missing some vital knowledge. Where are these meta: {touched, error} properties going exactly and if I can access them somewhere?
Below is my entire container file for your reference.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Field, reduxForm } from "redux-form"
import Title from "../components/Title.js"
const renderField = ({ input, placeholder, className, type, meta: { touched, error } }) => (
<div className={"" + touched && error && "input_error_border"}>
<input {...input} className={className} placeholder={placeholder} type={type}/>
{touched && error && <span><font color="red">{error}</font></span>}
</div>
)
class RegisterForm extends React.Component {
constructor(props) {
super(props);
this.props = props;
}
is_registered(){
if(this.props.user.server && this.props.user.insert){
return (<div>
<p>Thank you for registering {this.props.user.first_name}</p>
<p>You will recieve an email with further instructions shortly.</p>
</div>)
}else{
return <div></div>
}
}
render() {
const { handleSubmit } = this.props
console.log(this.props)
return (
<form onSubmit={ handleSubmit } className="box-sizing mx-auto max_vertical_form_400">
<Title innerH="Register New User"/>
<div className="input-group-btn">
{this.is_registered()}
</div>
<div className="form-group">
<Field type="text" placeholder="first name" className="form-control" component={renderField} name="first_name" />
<Field type="text" placeholder="last name" className="form-control" component={renderField} name="last_name" />
</div>
<div className="form-group">
<Field type="text" placeholder="email" className="form-control" component={renderField} name="email"/>
</div>
<div className="form-group">
<Field type="text" placeholder="company" className="form-control" component={renderField} name="company"/>
<Field type="text" placeholder="department" className="form-control" component={renderField} name="department"/>
</div>
<div className="form-group">
<Field type="password" placeholder="password" className="form-control" component={renderField} name="password"/>
<Field type="password" placeholder="repeat password" className="form-control" component={renderField} name="password_repeated"/>
</div>
<div className="input-group-btn">
<button type="submit" className="btn btn-primary">Submit</button>
</div>
{/* <RegisterFormContainer />
<ThemeContainer /> */}
</form>
);
}
}
function validate(values){
const errors= {};
if(!values.password) errors.password = "missing password";
if (!values.email) {
errors.email = 'Required'
} else if (!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = 'Invalid email address'
}
return errors;
}
RegisterForm = reduxForm({
form: "register_user_form",
validate
})(RegisterForm)
function mapStateToProps({ user }) {
return { user };
}
export default RegisterForm = connect(mapStateToProps, null)(RegisterForm)
You can use redux-form selectors, specifically getFormMeta to know which fields are dirty or touched and getFormSyncErrors to know the fields having errors.
In your code, you need to change to import the selectors
import { getFormMeta, getFormSyncErrors } from 'redux-form';
add it to your mapStateToProps which might look like this:
function mapStateToProps(state) {
return {
user: state.user,
metaForm: getFormMeta('register_user_form')(state),
formSyncErrors: getFormSyncErrors('register_user_form')(state),
};
}
and then you can reference in your component with this.props.metaForm and this.props.formSyncErrors

Categories

Resources