I'am new in react an have a problem with react-hooks and data-binding. Hook my inputs to the data-field-list is not the problem and works fine. My idea is to change, for example after a api fetch, the data-field-list and show the changed data directly in the input fields.
If i click the button [switchData] the data-list will chnage but the input values will not. How can i solve this problem?
import React, { useState } from 'react';
import './App.css';
function App() {
const config_data = {
firstname: {label:'Firstname', value:'Ryan'},
lastname: {label:'Lastname', value:'Johnson'},
age: {label:'Age', value:25}
};
const [data, setData] = useState({
firstname: config_data.firstname.value,
lastname: config_data.lastname.value,
age: config_data.age.value
});
const onChange_event = (e) => {
const {id, value} = e.target;
setData(selData => ({
...selData,
[id]:value
}));
}
const showData = () => {
console.log(data);
}
const switchData = () => {
setData(selData => ({
...selData,
firstname: 'Michael',
lastname: 'Swayne',
age: 37
}));
}
return (
<div className="App">
<label>{config_data.firstname.label}</label>
<input type={'text'} id={'firstname'} defaultValue={data.firstname} onChange={e => {onChange_event(e)}}></input>
<label>{config_data.lastname.label}</label>
<input type={'text'} id={'lastname'} defaultValue={data.lastname} onChange={e => {onChange_event(e)}}></input>
<label>{config_data.age.label}</label>
<input type={'number'} id={'age'} defaultValue={data.age} onChange={e => {onChange_event(e)}}></input>
<button onClick={showData}>Show Data</button>
<button onClick={switchData}>Switch Data</button>
</div>
);
}
export default App;
use value instead of defaultValue on your inputs
defaultValue as the name suggests, only sets the default, not any subsequent values.
e.g.
<input type={'text'} id={'firstname'} value={data.firstname} onChange={e => {onChange_event(e)}} />
and so on
Related
I'm working on creating a dynamic input form, where I want to click on a button and get a pop-up asking for label name and input type(Eg: number or text). Here is a mock-up of what I want to create. I should be able to even remove these newly created label and input.
Once this is entered, it should create a new label and input form as below:
Any help will be greatly appreciated.
Looks like I'm doing someone else's work but...)))
A quick example so you know which way to go:
YourMainComponent.tsx:
import React, { useState } from "react";
import { DynamicForm } from "./dynamic-form";
export const Fields = () => {
const [getFields, setFields] = useState([]);
const addField = (field) => {
setFields((prevState) => [...prevState, field]);
};
return (
<>
<DynamicForm onSubmit={addField} />
{getFields &&
getFields.map((field, index) => (
<fieldset key={index}>
<label>{field.label}</label>
<input type={field.type} />
</fieldset>
))}
</>
);
};
YourDynamicFieldCreateComponent:
import React, { useState } from "react";
export const DynamicForm = ({ onSubmit }) => {
const [getField, setField] = useState({});
const formSubmit = (e) => {
e.preventDefault();
if (Object.keys(getField).length > 0) {
onSubmit(getField);
setField({});
}
};
const onFieldChanged = (e) => {
if (e.target.id === "label-field") {
setField((prevState) => ({
...prevState,
label: e.target.value
}));
} else if (e.target.id === "type-field") {
setField((prevState) => ({
...prevState,
type: e.target.value
}));
}
};
return (
<form onSubmit={formSubmit}>
<fieldset>
<label htmlFor="label-field">Your label </label>
<input
type="text"
id="label-field"
name="label-field"
onChange={onFieldChanged}
/>
</fieldset>
<fieldset>
<label htmlFor="type-field">Your type of field </label>
<input
type="text"
id="type-field"
name="type-field"
onChange={onFieldChanged}
/>
</fieldset>
<button>Add more</button>
</form>
);
};
Need add conditions and modals and other...
This is not production code, use this only for learning
help please I want to add value to state without overwriting. Currently, when adding a value, the array is overwritten. I want to use useState and I want to use the value from the form.
import {useState} from 'react';
const initialState = {
people: [
{email: 'Jan'},
{email: 'Izabela'},
{email: 'Michael'}
] }
const StateModification = () => {
const [names,setNames] = useState(initialState)
const handleSubmit = (e) => {
e.preventDefault();
}
const handleChange2 = (e) => {
setNames({
...names,
people: [{[e.target.name]: e.target.value}]
})
}
return (
<div>
<form onSubmit={handleSubmit}>
<label>E-mail</label>
<input
id='email'
type='text'
name='email'
value={names.email}
onChange={handleChange2}
/>
<button type='submit'>Add</button>
</form>
</div>`enter code here`
) }
export default StateModification;
I think you need to add an email in your data and after click on add button that email will store in people variable with your previous data so i have update your code and it should work for you.
import {useState} from 'react';
const initialState = {
people: [
{email: 'Jan'},
{email: 'Izabela'},
{email: 'Michael'}
] }
const StateModification = () => {
const [names,setNames] = useState(initialState)
const [email,setEmail] = useState("")
const handleSubmit = (e) => {
e.preventDefault();
setNames({
people: [...names.people, { email }]
})
}
const handleChange2 = (e) => {
e.preventDefault();
setEmail(e.target.value)
}
return (
<div>
<form onSubmit={handleSubmit}>
<label>E-mail</label>
<input
id='email'
type='text'
name='email'
value={email}
onChange={handleChange2}
/>
<button type='submit'>Add</button>
</form>
</div>
) }
export default StateModification;
Below is a React component that uses useState hook but I do not get the latest values.
import { FC, useRef,useState } from "react";
import { Album } from "../../../types/album";
import "./album.module.css";
interface Props {
addAlbum(newAlbum: Album): void;
}
const NewAlbum: FC<Props> = (props) => {
const [name, setName] = useState('');
const [releaseDate, setReleaseDate] = useState('');
console.info("New Album Component Render");
const releaseDateRef = useRef<HTMLInputElement>(null);
const albumNameRef = useRef<HTMLInputElement>(null);
const addAlbumHandler = () => {
setName(albumNameRef.current?.value!)
setReleaseDate(releaseDateRef.current?.value!)
// *LOC
props.addAlbum({
name: name,
releaseDate: releaseDate
id: Math.random().toString(),
});
};
return (
<>
<div>
<input type="text" placeholder="Album Name" ref={albumNameRef} />
</div>
<div>
<input type="text" placeholder="Release year" ref={releaseDateRef} />
</div>
<div>
<button type="button" onClick={addAlbumHandler}>
Add New Album
</button>
</div>
</>
)};
export default NewAlbum;
When I click the AddNewAlbum button, addAlbumHandler() gets called however, I don't get the latest value of the name & releaseDate.
However, when I update the code at *LOC as below
props.addAlbum({
name: albumNameRef.current?.value!,
releaseDate: releaseDateRef.current?.value!,
id: Math.random().toString(),
});
I do get the desired values,
I understand that useState hook behind the scenes does not execute immediately
How to make sure to get the latest values of input when using useState hook in React?
Some tips:
You should never have to use useRef to retrieve values from elements. This is totally unreactly and totally unnecessary. The usual react way to do this is to define state, which is used as values for inputs and provide appropriate value change handlers to the inputs which alter the state.
As you know yourself, state changes do not happen immediately. This is the only reason your first example does not work and also explains why the second one does.
A recommendation for code that should work:
import { FC, useRef,useState } from "react";
import { Album } from "../../../types/album";
import "./album.module.css";
interface Props {
addAlbum(newAlbum: Album): void;
}
const NewAlbum: FC<Props> = (props) => {
const [name, setName] = useState('');
const [releaseDate, setReleaseDate] = useState('');
const nameChangeHandler = (e) => setName(e.target.value);
const releaseDateChangeHandler = (e) => setReleaseDate(e.target.value);
console.info("New Album Component Render");
const addAlbumHandler = (e) => {
// *LOC
props.addAlbum({
name: name,
releaseDate: releaseDate
id: Math.random().toString(),
});
};
return (
<>
<div>
<input
type="text"
placeholder="Album Name"
value={name}
onChange={nameChangeHandler}
/>
</div>
<div>
<input
type="text"
placeholder="Release year"
value={releaseDate}
onChange={releaseDateChangeHandler}
/>
</div>
<div>
<button type="button" onClick={addAlbumHandler}>
Add New Album
</button>
</div>
</>
);
}
export default NewAlbum;
I'm currently using this plugin for my react application: https://www.npmjs.com/package/react-editext.
I have multiple fields:
<EdiText
value={contact.addressLine1}
type="text"
onSave={handleSave('addressLine1')}
onCancel={(e) => setEditing(v => !v)}
inputProps={{
placeholder: 'Address Line 1',
}}
/>
<EdiText
value={contact.addressLine2}
type="text"
onSave={handleSave('addressLine2')}
onCancel={(e) => setEditing(v => !v)}
inputProps={{
placeholder: 'Address Line 2',
}}
/>
With a save handle
const handleSave = (e) => value => {
setContact({...contact, [e]: value})
};
But, I need to be able to save all fields with one button.
Now, if these were controlled form fields, I would be able to grab the value, and submit. But they're not as there is no onChange event.
Any ideas?
I didn't find in the plugin a possibility to do that. I suggest that you use a form with refs to achieve what you want.
here is an example code
import React, { useState, useRef } from "react";
import "./styles.css";
export default function App() {
const [editing, setEditing] = useState(true);
const [contact, setContact] = useState({
addressLine1: "Address 1",
addressLine2: "Address 2"
});
const [adress1, setAdress1] = useState("adress 1");
const [adress2, setAdress2] = useState("adress 2");
const form = useRef(null);
const handleSave = () => {
const adresses = {
addressLine1: form.current["adress1"].value.toString(),
addressLine2: form.current["adress2"].value.toString()
};
setContact(adresses);
console.log(contact);
};
const handleEdit = () => {
const edit = editing;
setEditing(!edit);
};
return (
<div className="App">
<form ref={form}>
<input
type="text"
value={adress1}
name="adress1"
onChange={e => setAdress1(e.target.value)}
disabled={editing}
/>
<input
type="text"
value={adress2}
name="adress2"
onChange={e => setAdress2(e.target.value)}
disabled={editing}
/>
</form>
<button onClick={handleSave}>save</button>
<button onClick={handleEdit}>edit</button>
</div>
);
}
explanation
I used state variable editing to make the fields editable or not on button edit click
I used a state variable for each field and used the react onChange function to save the value of each field when it changes.
on save button click the values of all fields states get saved to contact state
you can change the code to make it suitable for your needs. Here is a sandbox for my code:https://codesandbox.io/s/eloquent-pine-wnsw3
Once button clicked the fields are validated and errors are assigned to state variable.
But when state is updated inside onSubmitClick the render/view isn't refreshed to show the errors.
I have cross checked TextField component by doing a log of error fields but error doesn't seem to show up..
import React, { useState } from "react";
const Register = () => {
const [user, setUser] = useState({
name: "",
email: "",
password: "",
});
const setData = (type) => e => {
user[type] = e.target.value;
setUser(user);
};
const onSubmitClick = e => {
// Check if fields are empty
if (!user.name) {
user.nameError = "Name field cannot be left blank !!";
}
if (!user.email) {
user.emailError = "Email field cannot be left blank !!";
}
if (!user.password) {
user.passwordError = "Password field cannot be left blank !!";
}
// Setting the error here
setUser(user);
};
return (
<div>
<TextField value={user.name} error={user.nameError} label="Name" onChange={setData("name")} />
<TextField value={user.email} error={user.emailError} label="Email" onChange={setData("email")} />
<TextField value={user.password} error={user.passwordError} abel="Password" onChange={setData("password")} />
<Button content="REGISTER" onClick={onSubmitClick} />
</div>
);
};
export default Register;
So basically on submit if fields have errors i need to show up the errors.
You are mutating the user state object and setting the state with the same object reference, this is why the component is not re-rendering it is a good practice to always construct a new object from the current state before updating the state.
const setData = (type) => e => {
setUser(prevState => ({
...prevState,
[type]: e.target.value,
}));
};
const onSubmitClick = e => {
// destruct the current state and make a new object of it
// now we can mutate it and assign it to state
const updatedState = { ...user };
// Check if fields are empty
if (!updatedState.name) {
updatedState.nameError = "Name field cannot be left blank !!";
}
if (!updatedState.email) {
updatedState.emailError = "Email field cannot be left blank !!";
}
if (!updatedState.password) {
updatedState.passwordError = "Password field cannot be left blank !!";
}
setUser(updatedState);
};
I dont understand why u need to send extra type you can do it simply by fetching name from input box. And in your case, you are not setting right state in setData i.e its not mutating userData. You can do like this :
In your case you can do like this :
setUser({...user,user[type] : e.target.value}); // ensure rest value will remain there and update existing value
import React, { useState } from "react";
import { render } from 'react-dom';
const Register = () => {
const [user, setUser] = useState({
name: "",
email: "",
password: "",
});
const setData = e => {
console.log(e.target.name)
console.log(user)
setUser({...user,[e.target.name] : e.target.value});
};
const onSubmitClick = e => {
// Check if fields are empty
if (!user.name) {
user.nameError = "Name field cannot be left blank !!";
}
if (!user.email) {
user.emailError = "Email field cannot be left blank !!";
}
if (!user.password) {
user.passwordError = "Password field cannot be left blank !!";
}
console.log(user)
// Setting the error here
setUser(user);
};
return (
<div>
<input value={user.name} error={user.nameError} name="name" label="Name" onChange={(e) => setData(e)} />
<input value={user.email} error={user.emailError} name="email" label="Email" onChange={(e) => setData(e)} />
<input value={user.password} error={user.passwordError} name="password" label="Password" onChange={(e) => setData(e)} />
<button content="REGISTER" onClick={onSubmitClick} >Submit</button>
</div>
);
};
export default Register;
render(<Register />, document.getElementById('root'));
Here is working link : https://stackblitz.com/edit/react-fbuzph
You are making updates to the same state object in reference instead of creating a new state altogether. React only re-renders your component through either receiving new props or a new state object.
See sandbox and code-below on how to add and clear errors onSubmit: https://codesandbox.io/s/blissful-river-ou4o0
Register.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import TextField from "./TextField";
import "./styles.css";
const Register = () => {
const [user, setUser] = useState({
name: "",
email: "",
password: ""
});
const setData = e => {
const updatedUser = { ...user };
updatedUser[e.target.name] = e.target.value;
setUser(updatedUser);
};
const onSubmitClick = e => {
// Check if fields are empty
const updatedUser = { ...user };
updatedUser.nameError = !updatedUser.name
? "Name field cannot be left blank !!"
: "";
updatedUser.emailError = !updatedUser.email
? "Email field cannot be left blank !!"
: "";
updatedUser.passwordError = !updatedUser.password
? "Password field cannot be left blank !!"
: "";
setUser(updatedUser);
};
return (
<div>
<TextField
value={user.name}
error={user.nameError}
name="name"
label="Name"
onChange={setData}
/>
<TextField
value={user.email}
error={user.emailError}
name="email"
label="Email"
onChange={setData}
/>
<TextField
value={user.password}
error={user.passwordError}
name="password"
label="Password"
onChange={setData}
/>
<button content="REGISTER" onClick={onSubmitClick}>
Submit
</button>
</div>
);
};
export default Register;
const rootElement = document.getElementById("root");
ReactDOM.render(<Register />, rootElement);
TextField.js
import React from "react";
const TextField = props => {
return (
<div>
<label>{props.label}</label>
<input value={props.value} name={props.name} onChange={props.onChange} />
{props.error ? <p>{props.error}</p> : null}
</div>
);
};
export default TextField;