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;
Related
I can't make the edit option work, the add option works and the UI is working fine but when i select the option edit it sends me where im going to edit but it doesn't let me write or delete anything.
I can't make the Edit option work, im using a hook called useForm.
import React, { useState } from "react";
export const useForm = (initialState = {}) => {
const [values, setValues] = useState(initialState);
const reset = () => {
setValues(initialState);
};
const handleInputChange = (target) => {
console.log(target.value);
setValues({
...values,
[target.name]: target.value,
});
};
return {
values,
handleInputChange,
reset,
};
};
Then i called it in the file where it was suposed to edit but it doesn't let me to write anything.
import React, { useState, useContext, useEffect } from "react";
import { GlobalContext } from "./context/GlobalState";
import { Link, useNavigate, useParams } from "react-router-dom";
import { useForm } from "./context/useForm";
const EditUser = () => {
const { values, handleInputChange } = useForm({
name: "",
lastName: "",
email: "",
description: "",
location: "",
services: "",
price: "",
schedule: "",
});
const [selectedUser, setSelectedUser] = useState({
name: values.name,
lastName: values.lastName,
email: values.email,
description: values.description,
location: values.location,
services: values.services,
price: values.price,
schedule: values.schedule,
});
const { users, editUser } = useContext(GlobalContext);
const history = useNavigate();
const { id } = useParams();
const currentUserId = id;
useEffect(() => {
const userId = currentUserId;
const selectedUser = users.find((user) => user.id === userId);
setSelectedUser(selectedUser);
}, [currentUserId, users]);
const onSubmit = () => {
editUser(selectedUser);
history("/customers");
};
return (
<>
<div
onSubmit={onSubmit}
className="w-screen h-screen flex justify-center items-center"
>
<div className="box-border shadow-md bg-white border-current border-1 h-4/5 w-4/12 p-4 rounded-xl flex justify-center place-items-center flex-col text-black">
<p className="text-lg font-semibold">
Actualiza los datos del usuario
</p>
<div>
<input
type="text"
name="name"
value={selectedUser.name}
// onChange={onChangeName}
onChange={({ target }) => handleInputChange(target)}
className="textfield-outline"
placeholder="Nombre"
required
/>
</div>
<div>
<input
type="text"
name="lastName"
value={selectedUser.lastName}
// onChange={onChangeLastName}
onChange={({ target }) => handleInputChange(target)}
className="textfield-outline"
placeholder="Apellidos"
required
/>
</div>
<div>
<input
type="text"
name="email"
value={selectedUser.email}
// onChange={onChangeEmail}
onChange={({ target }) => handleInputChange(target)}
className="textfield-outline"
placeholder="Correo electronico"
required
/>
</div>
<div>
<input
type="text"
name="description"
value={selectedUser.description}
// onChange={onChangeDescription}
onChange={({ target }) => handleInputChange(target)}
className="textfield-outline"
placeholder="Descripcion"
required
/>
</div>
<div>
<input
type="text"
name="location"
value={selectedUser.location}
// onChange={onChangeLocation}
onChange={({ target }) => handleInputChange(target)}
className="textfield-outline"
placeholder="Ubicacion"
required
/>
</div>
<div>
<input
type="text"
name="services"
value={selectedUser.services}
// onChange={onChangeServices}
onChange={({ target }) => handleInputChange(target)}
className="textfield-outline"
placeholder="Servicios"
required
/>
</div>
<div>
<input
type="text"
name="price"
value={selectedUser.price}
// onChange={onChangePrice}
onChange={({ target }) => handleInputChange(target)}
className="textfield-outline"
placeholder="Precio"
required
/>
</div>
<div>
<input
type="text"
name="schedule"
value={selectedUser.schedule}
// onChange={onChangeEmail}
onChange={({ target }) => handleInputChange(target)}
className="textfield-outline"
placeholder="Horarios"
required
/>
</div>
<button
type="addUser"
className="normal-button"
onClick={() => onSubmit()}
>
Actualizar
</button>
<Link to="/customers">
<button type="cancel" className="normal-button">
Cancelar
</button>
</Link>
</div>
</div>
</>
);
};
export default EditUser;
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,
})
}
I would like to combine several states and handle them at the same time with useState hook, check the following example which updates some text on user input:
const {useState} = React;
const Example = ({title}) => {
const initialState = {
name: 'John',
age: 25
};
const [{name, age}, setFormState] = useState(initialState);
const handleNameChange = (e) => {
setFormState({
name: e.target.value,
age
});
};
const handleAgeChange = (e) => {
setFormState({
name,
age: e.target.value
})
};
return (
<form onSubmit={e=>e.preventDefault()}>
<input type='text' id='name' name='name' placeholder={name} onChange={handleNameChange} />
<p>The person's name is {name}.</p>
<br />
<label htmlFor='age'>Age: </label>
<input type='text' id='age' name='age' placeholder={age} onChange={handleAgeChange} />
<p>His/her age is {age}.</p>
</form>
);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<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="react"></div>
The code works well, but as you can see I'm using 2 functions to handle name and age separately., which was against my intention to save some code. Is it possible to just use 1 function to change name and age separately? Tried this but obviously it would update both with the same value.
const {useState} = React;
const Example = ({title}) => {
const initialState = {
name: 'John',
age: 25
};
const [{name, age}, setFormState] = useState(initialState);
const handleChange = (e) => {
setFormState({
name: e.target.value,
age: e.target.value
});
};
return (
<form onSubmit={e=>e.preventDefault()}>
<input type='text' id='name' name='name' placeholder={name} onChange={handleChange} />
<p>The person's name is {name}.</p>
<br />
<label htmlFor='age'>Age: </label>
<input type='text' id='age' name='age' placeholder={age} onChange={handleChange} />
<p>His/her age is {age}.</p>
</form>
);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<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="react"></div>
It's not possible to set several different states at the same time with a single useState hook. You can either set them separately,
example:
const [userName, setUserName] = useState('');
const [password, setPassword] = useState('');
or put all of the states into an object, and update that object using the useState hook.
Source: https://daveceddia.com/usestate-hook-examples/
Example:
const [form, setState] = useState({
username: '',
password: ''
});
const updateField = e => {
setState({
...form,
[e.target.name]: e.target.value
});
};
return (
<form onSubmit={printValues}>
<label>
Username:
<input
value={form.username}
name="username"
onChange={updateField}
/>
</label>
<br />
<label>
Password:
<input
value={form.password}
name="password"
type="password"
onChange={updateField}
/>
</label>
<br />
<button>Submit</button>
</form>
);
You have a couple options to get your intended effect.
You could, for example, make a single function factory.
const {useState} = React;
const Example = ({title}) => {
const initialState = {
name: 'John',
age: 25
};
const [{name, age}, setFormState] = useState(initialState);
const handleChange = key => e => {
const newValue = e.target.value
setFormState(oldState => ({
...oldState,
[key]: newValue
}));
};
return (
<form onSubmit={e=>e.preventDefault()}>
<input type='text' id='name' name='name' placeholder={name} onChange={handleChange('name')} />
<p>The person's name is {name}.</p>
<br />
<label htmlFor='age'>Age: </label>
<input type='text' id='age' name='age' placeholder={age} onChange={handleChange('age')} />
<p>His/her age is {age}.</p>
</form>
);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<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="react"></div>
Alternatively, if you need a function factory with more flexibility (e.g. if you were trying to store other state in there), you could use useReducer() instead of useState(), then curry the dispatch function. As a rough, untested example
const reducer = (state, {type, payload}) => {
switch (type) {
case 'name': return {...state, name: payload}
case 'age': return {...state, age: payload}
default: throw new Error()
}
}
const [{name, age}, dispatch] = useReducer(reducer, { name: 'john', age: 25 })
const makeEventListener = type => e => dispatch({type, payload: e.target.value})
// Now, you can use either use makeEventListener, or dispatch() directly
return <input type="text" value={name} onChange={makeEventListener('name'))
Lastly, it is possible to do exactly what you're saying, have one function that'll just update the state no matter who called it, by using of refs. But this is not a recommended approach.
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>
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: ''
});