Elements losing className - reactjs

I'm trying to display an error in a form field by adding a className.
This is the render function:
render() {
return (
<div className="row row--no-margin">
<button onClick={this.validate}>Test validation</button>
{
this.props.model.map( (field, index) => {
return this.renderTextField(field);
});
}
</div>
);
}
This is the renderTextField function:
renderTextField(field, index) {
let inputClassNames = 'form-control';
if (this.state.errors.indexOf(field.name) !== -1) {
inputClassNames += ' error-required';
}
return (
<div className={field.wrapperClassName} key={field.key}>
<label className="field-label">{field.label}</label>
<input
type="text"
name={field.name}
ref={field.name}
className={inputClassNames}
onChange={this.handleChange}
value={this.state[field.name]}
/>
</div>
);
}
When i click the button to test validation, the class "error-required" is added to the input, but as soon as i type anything, it loses the class.
This is the onChange function:
handleChange(event) {
this.setState({
[event.target.name] : event.target.value
});
}
The field gets its data from an object:
{
key : 'name',
name : 'name',
type : 'text',
label : 'Full Name',
wrapperClassName: 'col-md-6',
},
Am i missing something?
EDIT:
validate function:
validate() {
let errors = [];
this.props.model.map((m, index) => {
if(!this.state[m.name]){
errors.push(m.name);
}
});
this.setState({
errors: errors
})
}

I would suggest separating the form's "field state", from your "validation state", to avoid potential conflicts in the case that you have a field with name "error".
If your form has a field with name "error", changing it's value will cause your validation state to be replaced, and will produce errors/unexpected results.
Consider making the following adjustments:
// in renderTextField() use this.state.form[field.name]
<input
type="text"
name={field.name}
ref={field.name}
className={inputClassNames}
onChange={this.handleChange}
value={this.state.form[field.name]}
/>
And in handleChange(event) consider revising it to:
handleChange(event) {
const form = { ...this.state.form, [event.target.name] : event.target.value }
this.setState({
form : form
})
}
Note, you will also need to initialise your component state to include/define the form object to track the state of fields.

Related

React login form loop isn't re-rendering DOM

I'm trying to make a login component and I think my issue is with React not re-rendering the DOM in my browser but I'm not sure why
If I leave the password field blank when I press the main 'Login' button in my form it will render the alert / warning message .. I can then click this message to dismiss it which is exactly what I want
If I were to repeat the process I would expect the message to be re-rendered and the DOM element reintroduced, however this is not the case - I can see that the loop is being run, I am getting all of the console logs with the correct values, however the loop does not seem to run the 'return' part of my if statement on the second try (in the code below I've added 'this return doesn't re-render' to the console log before that return) - here's my code
Apologies for the large code snippet but I felt it was all relevant for this question
class LoginForm extends Component {
constructor() {
super();
this.state = {
email: "",
password: "",
errors: [],
};
this.onLoginClick = this.onLoginClick.bind(this);
}
onLoginClick() {
const username = this.state.email.trim();
const password = this.state.password.trim();
let errors = [];
console.log("Login press")
if (!EMAIL_REGEX.test(username)) {
errors.push(error_user);
console.log("Username error")
}
if (password === "") {
errors.push(error_pass);
console.log("Password is blank")
}
if (errors.length === 0) {
this.props.onLoginClick(username, password);
if (this.props.loginStatus === login_f) {
errors.push(error_cred);
}
}
this.setState({
errors: errors,
});
console.log("Here are the errors", errors)
}
handleEmailChange = (e) => {
this.setState({ email: e.target.value });
};
handlePasswordChange = (e) => {
this.setState({ password: e.target.value });
};
clearAlertsHandler() {
console.log("Clear alerts")
document.getElementById("misMatch").remove()
}
render() {
let updatedErrors = [...this.state.errors];
return (
<fieldset>
{updatedErrors.map((errorMessage, index) => {
if (errorMessage === error_cred) {
console.log("error_cred match", error_cred, errorMessage)
return (
<button key={index} id={"match"}>{errorMessage} - click to clear</button>
);
} else {
console.log("error_cred mismatch - this return doesn't re-render", error_cred, errorMessage)
return (
<button key={index} id={"misMatch"} onClick={(e) => this.clearAlertsHandler(e)}>{errorMessage} - click to clear</button>
);
}
})}
<label className="text-uppercase">Username</label>
<input
name="email"
type="text"
value={this.state.email}
placeholder="username"
onChange={this.handleEmailChange}
/>
<label className="text-uppercase">Password</label>
<input
className="mb20"
name="password"
type="password"
value={this.state.password}
placeholder="••••••••••"
onChange={this.handlePasswordChange}
/>
<button name="submit" className="primary mb20" onClick={this.onLoginClick}>
Login
</button>
</fieldset>
);
}
In my opinion, React doesn't know that error array changed if you don't clear it.
I think you should do something like this:
clearAlertsHandler() {
console.log("Clear alerts")
this.setState({
errors: [],
});
document.getElementById("misMatch").remove()
}

when using {react-select} Cannot read property 'name' of undefined

I am very beginning to reactJS and front end
I added react-select npm for my dropdown like below, before added react-select everything is working fine. How to define name in Select?
<div className="container">
<div className="row">
<div className="col-md-4" />
<div className="col-md-4">
<Select
options={this.state.allGenres}
onChange={this.props.onDataChange}
name="genre"
/>
</div>
<div className="col-md-4" />
</div>
</div>
this is my array,
var Data = response.data;
const map = Data.map((arrElement, index) => ({
label: arrElement,
value: index
}));
example:
[
{
"label": "Action",
"value": 0
},
{
"label": "Comedy",
"value": 1
},
{
"label": "Documentary",
"value": 2
}
]
error message coming in here,
dataChange(ev, action) {
this.setState({
[ev.target.name]: ev.target.value
});
}
render()
render() {
return (
<Movie
onPostData={this.postData.bind(this)}
onDataChange={this.dataChange.bind(this)}
/>
);
}
Error
Uncaught TypeError: Cannot read property 'name' of undefined
at Movies.dataChange
You expect the first argument in react-select´s onChange method to be an event object, but it isn't.
The first argument is the selected option (or options if you have isMulti set).
There is also a second argument which is an object with the following attributes:
action: The action which triggered the change
name: The name given to the Select component using the name prop.
So if you want to use the name:
onDataChange={(value, action) => {
this.setState({
[action.name]: value
})
}}
Reference in source code
I worked around this method and it worked.
handleSelectChange: function(name) {
return function(newValue) {
// perform change on this.state for name and newValue
}.bind(this);
},
render: function() {
return (
<div>
<Select ...attrs... onChange={this.handleSelectChange('first')} />
<Select ...attrs... onChange={this.handleSelectChange('second')} />
</div>);
}
This is how you can get the value(s) of the selected option(s) and the name of the input as well.
For more info, check this issue on Github.
handleChange = (selectedOptions, actionMeta) => {
const inputName = actionMeta.name;
let selectedValues;
if (Array.isArray(selectedOptions)) {
// An array containing values of the selected options (like: ["one", "two", "three"]
selectedValues = selectedOptions.map(option => option.value);
// OR, use this if you want the values of the selected options as a string separated by comma (like: "one,two,three")
selectedValues = selectedOptions.map(option => option.value).join(",");
} else {
// An array containing the selected option (like: ["one"])
selectedValues = [selectedOptions.value];
// OR, use this if you want the value of the selected option as a string (like: "one")
selectedValues = selectedOptions.value;
}
// Do whatever you want with the selected values
//..
this.setState({
[inputName]: selectedValues
});
}
<Select
name="genre"
options={this.state.allGenres}
onChange={this.handleChange}
/>

React-Bootstrap form validation - Need one function per field?

I am using the React-Bootstrap forms. I have around 15 fields that need to be filled out in the form. Does this mean I need to have 15 validation functions (e.g validateName, validateDate etc.)?
How is this generally approached?
My data looks something like this:
state = {
person : {
name: '',
startDate: null,
...
...
active: null
}
}
Say for eg you have 2 input fields
state = {
person : {
name: '',
age: 0
},
nameError: null,
ageError: null
}
handleInput = e => {
const { person } = this.state;
person[e.target.name] = e.target.value;
this.setState({
person
});
}
handleSubmit = () => {
const { person } = this.state;
if(person.name === null){
this.setState({
nameError: 'Name is required',
ageError: null
});
}else if(person.age === 0){
this.setState({
ageError: 'Age is required',
nameError: null
});
}else{
//send the values to the backend
//also reset both nameError and ageError here
}
}
render(){
const { person, nameError, ageError } = this.state;
return(
<div>
<input type='text' name='name' value={person.name} onChange={e => this.handleInput(e)} />
{nameError}
<input type='number' name='age' value={person.age} onChange={e => this.handleInput(e)} />
{ageError}
<button value='Submit' onClick={this.handleSubmit} />
</div>
);
}
Please Let me know if you have further queries. Sorry if there are any typos I answered on my mobile

How to verify that the checkbox is checked with rc-forms package?

When user is registering, I would like to check that TOS (Terms of service) have been read and accepted. To do that, there is a checkbox on my forms "I accept the TOS"
I'm using rc-form package to validate my reactstrap forms, but I don't find how to verify (via rc-form) that the checkbox is checked. Is there a solution using rc-form to avoid manual tests?
In this sample, tosErrors stay empty even if TOS checkbox is unchecked
onSubmit(e) {
e.preventDefault();
this.props.form.validateFields((error) => {
if (!error) {
const { register } = this.props;
const { email, password, read } = this.state;
//HERE IS A MANUAL TEST BECAUSE rules on checkbox are not working
if (read) {
register(email, password);
}
}
});
}
render() {
//...some code was removed because unuseful for stackoverflow question...
const { getFieldProps, getFieldError, getFieldValue } = this.props.form;
const tosErrors= getFieldError("read");
return (
<Form onSubmit={this.onSubmit}>
//... some form elements ...
<FormGroup check>
<Col sm={{ size: 8, offset: 4 }}>
<Label check>
<Input
type="checkbox"
name="read"
id="read"
className={tosErrors ? "is-invalid" : ""}
{...getFieldProps("read", {
initialValue: read,
rules:[{"required":true}], <==== THE RULES
onChange,
valuePropName: "checked"
})}
/>
</Label>
// BELOW THIS IS ONE OF MY MANUAL TEST because tosErrors stay empty
{getFieldValue("read") || <HelpBlock color={"danger"}>{t("validators:accept cgu")}</HelpBlock>}
</Col>
</FormGroup>
... SOME OTHER FORM ELEMENTS
</Form>
}
rc-form package is using async-validator package
Add validator function to you component, before render():
checkIsChecked = (rule, value, callback) => {
if (value === false) {
callback("You should agree with Terms of Service!");
} else {
callback();
}
}
And then add validator with this function to rules:
rules: [{ required: true }, {
validator: this.checkIsChecked,
}],
Hope this will help.

Warning when changing controlled input value in React

I'm making a little blog in React and I have a problem updating the state on input change event.
The warning is:
Warning: A component is changing a controlled input of type text to be
uncontrolled. Input elements should not switch from controlled to
uncontrolled (or vice versa). Decide between using a controlled or
uncontrolled input element for the lifetime of the component
This is my code:
Constructor:
constructor(props){
super(props);
this.state = {
id: '',
post: {
title: '',
slug: '',
content: ''
}
}
this.handleChange = this.handleChange.bind(this);
}
handleChange function
handleChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
post: {
[name]: value
}
});
}
input render:
render(){
return (
<div>
<AdminMenu />
<div id="admin-post-form">
<div className="input-group vertical">
<label>Título</label>
<input
name="title"
placeholder="Título"
type="text"
value={this.state.post.title}
onChange={this.handleChange}
/>
</div>
<div className="input-group vertical">
<label>Slug</label>
<input
name="slug"
placeholder="Slug"
type="text"
value={this.state.post.slug}
onChange={this.handleChange}
/>
</div>
</div>
</div>
)
}
What's wrong with my code ? The field is updated, but I get that warning.
Thanks!
This:
this.setState({
post: {
[name]: value
}
});
will replace this.state.post completely with an object that only has a single key. For example, if name is slug, you will replace post with { slug: 'something' }.
As a result, if you edit one field, all other fields will become undefined. React treats value={undefined} as an uncontrolled component and warns you.
To fix the issue, you probably want to merge post updates with the existing object instead of replacing it:
this.setState(prevState => ({
post: {
...prevState.post,
[name]: value
}
}));
Your set state is resetting the whole post object. You likely want to do something like:
this.setState({
post: {
...this.state.post
[name]: value
}
})
Solved using spread operator, this is the updated handleChange function that works with nested property:
handleChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
var post = {...this.state.post}
post[name] = value;
this.setState({post});
}

Resources