Uncontrolled input React Hooks - javascript

I've started learing about react-hooks with a simple tutorial and to my surprise I got an error that I cannot figure out:
Warning: A component is changing an uncontrolled input of type text to
be controlled. Input elements should not switch from uncontrolled to
controlled (or vice versa). Decide between using a controlled or
uncontrolled input element for the lifetime of the component.
After this error, my component disappears but I still can input data that will correctly print out in console.
I've tried setting initial state for inputs and changing
setInputs(inputs => ({
...inputs, [event.target.name]: event.target.value
}));
to
setInputs({...inputs, [event.target.name]: event.target.value});
but I'm still getting error.
JSX
import React from 'react';
import './styles/form.scss';
import useSignUpForm from './hooks/customHooks.jsx';
const Form = () => {
const {inputs, handleInputChange, handleSubmit} = useSignUpForm();
return (
<React.Fragment>
<div className="formWrapper">
<h1 className="header">Form</h1>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="nicknameInput">Nickname</label>
<input type="text" id="nicknameInput" name="nickname" onChange={handleInputChange}
value={inputs.nickname} required/>
<label htmlFor="emailInput">Email Address</label>
<input type="text" id="emailInput" name="email" onChange={handleInputChange}
value={inputs.email} required/>
<label htmlFor="lastName">Last Name</label>
<input type="text" id="lastName" name="lastName" onChange={handleInputChange}
value={inputs.lastName} required/>
</div>
<button type="submit" className="btn btn-primary">Submit</button>
</form>
</div>
</React.Fragment>
)
};
export default Form;
Hooks
import React, {useState} from 'react';
const useSignUpForm = (callback) => {
const [inputs, setInputs] = useState({});
console.log(inputs);
const handleSubmit = (event) => {
if (event) {
event.preventDefault();
}
};
const handleInputChange = (event) => {
event.persist();
setInputs(inputs => ({
...inputs, [event.target.name]: event.target.value
})
);
};
return {
handleSubmit,
handleInputChange,
inputs
};
};
export default useSignUpForm;
Any ideas what causes this error?

You are getting the error, because your inputs start their life as undefined and then have a value. If you replace this const [inputs, setInputs] = useState({}); with this:
const [inputs, setInputs] = useState({
nickname: '',
lastname: '',
email: ''
});
it will go away.

my favorite way of handling controlled inputs in react hooks is this syntax.. Make seperate state for each input you are trying to handle and then inside the onChange just call the setInput
onChange={e => setInput(e.target.value)}
the reason why you have an error is because the initial state is just an empty object, if you wanted to do it that way you would have to change your state to.
const [inputs, setInputs] = useState({
nickname: '',
lastname: '',
email: ''
});

Related

react input form returns undefined

it updates only the lastly typed input box value in the state and other are undefined
i get this in console
Object { Name: undefined, Age: "123", City: undefined }
second time
Object { Name: undefined, Age: undefined, City: "city" }
Form.jsx
import React, {useState} from 'react';
const Form = (props) => {
const [formData, setFormData] = useState({ Name:'', Age:'', City:''});
const infoChange = e => {
const { name,value} = e.target;
setFormData({
[e.target.name]: e.target.value,
})
}
const infoSubmit = e =>{
e.preventDefault();
let data={
Name:formData.Name,
Age:formData.Age,
City:formData.City
}
props.myData(data);
}
return (
<div className="">
<form onSubmit={infoSubmit} autoComplete="off">
<div className="form-group mb-6">
<label className="">Name:</label>
<input type="text" onChange={infoChange} name="Name" value={formData.Name} className=""placeholder="Enter Name" />
</div>
<div className="form-group mb-6">
<label className="">City:</label>
<input type="text" onChange={infoChange} name="City" value={formData.City} className=""
placeholder="Enter Age" />
</div>
<button type="submit" className="">Submit</button>
</form>
</div>
);
};
export default Form;
App.jsx
this is App.jsx file, here i get the data prop and display it in console.log
import React from 'react';
import Form from './components/Form';
import Table from './components/Table';
const App = () => {
const create = (data) => {
console.log(data);
}
return (
<div className='flex w-full'>
<div className=''>
<Form myData={create} />
</div>
<div className=''>
<Table />
</div>
</div>
);
};
export default App;
You're stomping the previous state with the most recent change. If you want to preserve the existing state you have to include it in the update.
setFormData({
...formData,
[e.target.name]: e.target.value,
})
with react-hooks you need to set the entire object again.
const [formData, setFormData] = useState({ Name:'', Age:'', City:''});
const infoChange = e => {
const { name,value} = e.target;
setFormData({
// spread the current values here
...formData,
// update the current changed input
[name]: value,
})
or, even better IMHO. You have one state for each prop
const [name, setName] = useState('');
const [age, setAge] = useState('');
const [city, setCity] = useState('');
// ...
<input onChange={({target: {value}}) => setName(value)} />
<input onChange={({target: {value}}) => setAge(value)} />
<input onChange={({target: {value}}) => setCity(value)} />
Change this
const infoChange = e => {
const { name,value} = e.target;
setFormData({...formData
[e.target.name]: e.target.value,
})
}

Using regex with react hooks

I am basically trying to save the phone number entered by the user without braces, spaces or dashes but I somehow fail to do that. I am calling the regex after submitting the form in handleSubmit function through the setting of state and it prints out (and renders) without any change. Any idea what went wrong?
import React, { useContext, useState, useEffect } from "react";
import DataContext from "../store/data-context";
function Form() {
const [name, setName] = useState("");
const [secName, setSecName] = useState("");
const [tel, setTel] = useState("");
const [note, setNote] = useState("");
const [state, setState] = useState({
name: "",
secName: "",
tel: "",
note: "",
});
const { dispatchDataState } = useContext(DataContext);
const handleSubmit = (e) => {
e.preventDefault();
setTel((tel)=>tel.replace(/[^+\d]+/g, ""))
console.log(name);
dispatchDataState({ type: "ADD_DATA", payload: state });
setState(
{
name: "",
secName: "",
tel: "",
note: "",
}
)
console.log(state);
};
return (
<div>
<form onSubmit={handleSubmit}>
<label>
Jméno
<input
type="text"
required
value={state.name}
onChange={(e) => setState({ ... state, name: e.target.value })}
/>
</label>
<label>
Příjmení
<input
type="text"
required
value={state.secName}
onChange={(e) => setState({ ... state, secName: e.target.value })}
/>
</label>
<label>
Telefonní číslo
<input
type="text"
required
value={state.tel}
onChange={(e) => setState({ ... state, tel: e.target.value })}
/>
</label>
<label>
Poznámka
<input
type="text"
value={state.note}
onChange={(e) => setState({ ... state, note: e.target.value })}
/>
</label>
<input type="submit" value="Odeslat" />
</form>
</div>
);
}
export default Form;

react js form inputs using eval?

I have a form with 58 inputs so, i wanted to do this dinamically, having this fields with the values i need(name, placeholder, value)...I've mapped throw an array of strings and got name and placeholder ok BUT when I tried to obtain the value I've read a bit and found the eval function...and in deed it solved my problem...but then react js sent me an 'eval can be harmful' warning so...
Question 1: In my case is really eval harmfull?How can I avoid that in my way of being a by the book programmer?
Question2: In which other way can I obtain my value's value?
now I show a piece of my code...:
{variables.map((variable, index) =>
<div className="form-group">
<input
key={index}
type="text"
name={variable}
className="form-control"
placeholder={variable}
onChange={handleInputchange}
value={eval(variable)}
/>
</div>
)}
where variables is an array of strings
thanks for your time
It would be better to have an object if possible for the fields, that way you can access the values by key like this:
import { useState } from 'react';
const Test = () => {
const [formData, setFields] = useState({
field1: '',
field2: '',
field3: '',
});
const handleChange = e => {
e.preventDefault();
setFields(prev => ({ ...prev, [e.target.name]: e.taget.value }));
};
return (
<div>
{Object.keys(formData).map(fieldName => (
<div className="form-group" key={fieldName}>
<input
type="text"
className="form-control"
name={fieldName}
placeholder={fieldName}
onChange={handleChange}
value={formData[fieldName]}
/>
</div>
))}
</div>
);
};
export default Test;
If you must have the initial fields as an Array i'd probably just use reduce to turn the initial form data into an object like this:
import { useState } from 'react';
const fields = ['field 1', 'field 2', 'field 3'];
const initialState = fields.reduce((acc, field) => ({ ...acc, [field]: field }), {});
const Test = () => {
const [formData, setFormData] = useState(initialState);
const handleChange = e => {
e.preventDefault();
setFormData(prev => ({ ...prev, [e.target.name]: e.taget.value }));
};
return (
<div>
{Object.keys(formData).map(fieldName => (
<div className="form-group" key={fieldName}>
<input
type="text"
className="form-control"
name={fieldName}
placeholder={fieldName}
onChange={handleChange}
value={fields[fieldName]}
/>
</div>
))}
</div>
);
};
export default Test;

What is best way to create forms in react?

I am beginner in react. I have following code:
import React, { useState, useEffect } from 'react';
import { Card, Form, Button } from 'react-bootstrap';
import Axios from 'axios'
export function StudentForm({ student, onSuccess, onError, setState }) {
const url = `http://localhost:9899/api/StudentData`;
const intialStudent = { Firstname: '', Middlename: '', Lastname: '', DOB: '', Gender: '' };
const [Student, setStudent] = useState(intialStudent);
useEffect(() => {
setStudent(student ? student : intialStudent);
}, [student]);
const SaveData = function (studentData) {
if (student._id) {
Axios.post(url, { ...studentData }, { headers: { 'accept': 'application/json' } })
.then(res => {
setState(null);
onSuccess(res);
})
.catch(error => {
alert('Error To Edit data');
});
}
else {
Axios.post(url, studentData, { headers: { 'accept': 'application/json' } })
.then(res => {
setState(null);
onSuccess(res);
})
.catch(err => onError(err));
}
}
return (
<Card>
<Card.Header><h5>{student ? "Edit" : "Add"} Student</h5></Card.Header>
<Card.Body>
<Form onSubmit={(e) => { e.preventDefault(); SaveData(Student); }}>
<Form.Group><Form.Control type="text" name="Firstname" placeholder="Firstname" value={Student.Firstname} onChange={e => { setStudent({ ...Student, Firstname: e.target.value }) }} /></Form.Group>
<Form.Group><Form.Control type="text" name="Middlename" placeholder="Middlename" value={Student.Middlename} onChange={e => setStudent({ ...Student, Middlename: e.target.value })} /></Form.Group>
<Form.Group><Form.Control type="text" name="Lastname" placeholder="Lastname" value={Student.Lastname} onChange={e => setStudent({ ...Student, Lastname: e.target.value })} /></Form.Group>
<Form.Group><Form.Control type="date" name="DOB" placeholder="DOB" value={Student.DOB} onChange={e => setStudent({ ...Student, DOB: e.target.value })} /></Form.Group>
<Form.Group><Form.Control type="text" name="Gender" placeholder="Class" value={Student.Gender} onChange={e => setStudent({ ...Student, Gender: e.target.value })} /></Form.Group>
<Button variant="primary" type="submit">Submit</Button>
</Form>
</Card.Body>
</Card>
);
}
In above code I am setting state on change event on each field. So it will render again and again when I change any of the field.If it is large form so it may take a lot of time to re-render so is there a better way to create to handle this kind of situation, or any best practices for using forms with react?
You can use only one Function for all onChanges. Looks like this;
<Form.Group>
<Form.Control
type="text"
name="Firstname"
placeholder="Firstname"
value={Student.Firstname}
onChange={handleChange}
/>
</Form.Group>
And this is your handleChange function;
const handleChange = e => {
const {name, value} = e.target
setValues({...values, [name]: value})
}
This is your state;
const [values, setValues] = useState({
Firstname: "",
Middlename: "",
Lastname: "",
DOB: "",
Gender: ""
})
I think this way is more effective with less code.
Managing forms in react is a task complex enough to delegate it to a library.
Alo, big forms are not a good candidate for functional components because the problems that you outlined. You can, of course, spend the time to tune it up, but I think the effort may not worth the benefit.
My personal recommendation is to try one of the many react form libraries out there. One that I personally like is Formik
If you want to manage the form yourself I recommend to encapsulate the form on stateful component and use the key property for easier reset when you need it.
Another alternative will be the usage of memoization, for example using react.memo. But that will not guarantee success unless your data has the proper shape. This means, simple values that can be compared between themselves, not arrays, not functions, not objects.
You have to re render the form when an input changed but you don't need to re render every input when you make sure the onChange function doesn't change reference on every render and your input is a pure component (using React.memo for functional component and inherit from React.PureComponent for class components).
Here is an example of optimized inputs.
const {
useEffect,
useCallback,
useState,
memo,
useRef,
} = React;
function App() {
return <StudentForm />;
}
//put initial student here so it doesn't change reference and quits the linter
// in useEffect
const initialStudent = {
Firstname: '',
Middlename: '',
};
function StudentForm({ student }) {
const [Student, setStudent] = useState(initialStudent);
//useCallback so onChange is not re created and causes re rendering
// of components that didn't change
const onChange = useCallback(
(key, value) =>
setStudent(student => ({ ...student, [key]: value })),
[]
);
useEffect(() => {
setStudent(student ? student : initialStudent);
}, [student]);
const SaveData = function(studentData) {
console.log('saving data:', studentData);
};
return (
<form
onSubmit={e => {
e.preventDefault();
SaveData(Student);
}}
>
<InputContainer
type="text"
name="Firstname"
placeholder="Firstname"
value={Student.Firstname}
stateKey="Firstname" //provide state key
onChange={onChange}
/>
<InputContainer
type="text"
name="Middlename"
placeholder="Middlename"
value={Student.Middlename}
stateKey="Middlename"
onChange={onChange}
/>
<button type="submit">Submit</button>
</form>
);
}
//make this a pure component (does not re render if nothing changed)
const InputContainer = memo(function InputContainer({
type,
name,
placeholder,
value,
onChange,
stateKey,
}) {
const rendered = useRef(0);
rendered.current++;
return (
<div>
<div>{rendered.current} times rendered.</div>
<input
type={type}
name={name}
value={value}
placeholder={placeholder}
onChange={e =>
//pass state key and new value to onChange
onChange(stateKey, e.target.value)
}
/>
</div>
);
});
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

React hooks not keeping data

I've been trying to learn React hooks in order to start building a personal project but ran into a few road blocks. Currently, when I do an axios request, the page resets and no data is shown.
In order to make sure it was working the correct way, I made a class version and was able to retrieve the data plus upload it to state with setState.
import React, { useState } from "react";
import axios from "axios";
const Form = () => {
const [signup, setForm] = useState({ username: "", email: "", password: "" });
const [user, setUser] = useState({ user: "" });
const submit = () => {
axios.get("/api/users").then(user => {
setUser({ user });
});
};
return (
<div>
{user.username}
<h1>This is the Landing Page!</h1>
<form onSubmit={submit}>
<input
type="text"
placeholder="Enter Username"
value={signup.username}
onChange={e => setForm({ ...signup, username: e.target.value })}
/>
<input
type="text"
placeholder="Enter Email"
value={signup.email}
onChange={e => setForm({ ...signup, email: e.target.value })}
/>
<input
type="password"
placeholder="Enter Your Password"
value={signup.password}
onChange={e => setForm({ ...signup, password: e.target.value })}
/>
<input type="submit" value="Submit" />
</form>
</div>
);
};
The class option works
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
user: ""
};
}
loadData = () => {
console.log(this.state);
axios.get("/api/users").then(user => {
console.log(user.data);
debugger;
this.setState({ user });
});
};
render() {
return (
<div>
<h1>This is the header</h1>
<button onClick={this.loadData}>This is a button</button>
</div>
);
}
}
What I'm expecting is for the data to persist. It does appear in console.log but even that disappears within a few seconds as if the entire page keeps reloading. When I do input typing it works but not on a axios call.
Change your onSubmit function to the following to avoid a page reload.
const submit = (e) => {
e.preventDefault();
axios.get("/api/users").then(user => {
setUser({ user });
});
};
You want to disable the browser from submitting the form by adding an event.preventDefault(), and let your submit() function send that request with Axios (or fetch) instead:
const submit = event => {
event.preventDefault();
...
}

Categories