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
Related
I have a form component in my react app that can be used to create or update recipes depending on the route. The create functionality works fine, but the update is giving me trouble.
I pass fetched data into the component using props here is what the JSON looks like:
{
method: "Cook the guanciale in a large skillet over medium heat until deeply golden
(adjust the heat as necessary to render the fat [...]
name: "Pasta Alla Gricia"
}
I am trying to get the name to prefill into the form's name <input> and the method to prefill into the form's method <textarea>. I tried doing this with useEffect(), with:
useEffect(() => {
setName(props.data.name)
setMethodStepsList(props.data.method)
})
while it prefilled the name input it then locked the value to that. The method did not prefill the textarea at all.
I am pretty stumped with this one, and would be grateful for any assistance.
export default function Recipe(props) {
const [name, setName] = useState('')
const [methodStepsList, setMethodStepsList] = useState('')
const [methodStepObject, setMethodStepObject] = useState([])
const [ingredientList, setIngredientList] = useState([])
const [ingredientObject, setIngredientObject] = useState({
ingredient_name: '',
quantity: '',
measure: '',
})
const formLabel = props.data ? 'Update Recipe' : 'New Recipe'
useEffect(() => {
setName(props.data.name)
setMethodStepsList(props.data.method)
})
//new recipe logic
[...]
return (
<div>
<div className="recipe-form-container">
<form className="recipe-form">
<div className="page-header">
<h1>{formLabel}</h1>
</div>
{/* recipe name logic */}
<div className="recipe-title recipe-element">
<label>Recipe Name</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
></input>
</div>
//recipe method logic
<div className="recipe-blurb recipe-element">
<label>Recipe Method</label>
<span className="method-span">
<textarea
rows="5"
name="step_instructions"
type="text"
placeholder="Method will be split up based on new lines"
onChange={(e) => handleMethodChange(e)}
></textarea>
<button
onClick={(e) => {
console.log(methodStepObject)
setMethodStepsList(methodStepObject)
e.preventDefault()
}}
>
Add Method
</button>
[...]
}
Please remove useEffect statements and try like this
const [name, setName] = useState(props.data.name)
const [methodStepsList, setMethodStepsList] = useState(props.data.method)
You should be careful while initializing state with props in React.
See React component initialize state from props
class Recipe extends React.Component {
constructor(props) {
super(props)
this.state = {name:'', method:'', ingredients: []};
}
static getDerivedStateFromProps(props,state) {
return {name: props.data.name, method: props.data.method};
}
render() {
return <div>...</div>
}
}
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
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 would like to understand why is it when I'm switching between posts, the input fields are not changing their values, even though each product object has different name and description property.
Further explanation:
When clicking on each ProductView (Product Component) a new window is shown with details on that product that could be changed and saved (name and description) through input fields. but when switching between products (by clicking on different products) the text on these input fields do not change.
example product object:
product = {
name: 'product 1',
desc: 'product 1 desc'
}
this is the code:
// Main Store Component
const Store = () =>{
const [prods, setProducts] = useState([...products]);
const[show, showDetails] = useState(false);
const[productToShow, setProductToShow]=useState();
function onSaveProduct(newProduct){
let i = prods.findIndex((x)=> x['id']===newProduct.id);
prods[i] = newProduct;
setProductToShow(newProduct)
setProducts([...prods]);
}
return(<div className={'flex-container'}>
<Container className="store-container">
<div className={'column-align'}>
{([...prods]).map((pro)=>{
return <Product key={pro.id} product={pro} showDetails={showDetails}
setProductToShow={setProductToShow}/>
})}
</div>
</Container>
{show && <ProductToShow product={productToShow} onSave={onSaveProduct}/>}
</div>);
}
// Product component
const Product = ({product, setProductToShow, showDetails, deleteFromList}) =>{
const handleClick = () =>{
setProductToShow(product);
showDetails(true);
}
return (
<div className="product-container">
<div className="name-desc"onClick={handleClick}>
<h3>{product.name} </h3>
<h5>{product.desc}</h5>
</div>
</div>
);
}
// ProductToShow functional component
const ProductToShow = ({product, onSave}) =>{
const nameInput = useFormInput(product.name);
const descInput = useFormInput(product.desc);
const newProduct = {
id: product.id,
name: nameInput.value,
desc: descInput.value,
};
function useFormInput(initialValue){
const[value, setValue] = useState(initialValue);
function handleChangeEvent(e){
setValue(e.target.value);
}
return{
value: value,
onChange: handleChangeEvent
}
}
return (
<div className="to-show-container">
<h1>{product.name}</h1>
<label>Product Name: </label>
<input {...nameInput}/>
<label>Product Description: </label>
<input {...descInput}/>
<div className={'to-the-right'}>
<Button onClick={()=>onSave(newProduct)}>Save</Button>
</div>
</div>
);
}
screenshot (Product 3 is clicked, but the details of Product 1 is shown in the input fields):
The problem is in your productToShow functional component.
The values don't get reupdated after clicking on a different product.
Maybe consider changing it to something like this:
// ProductToShow functional component
const ProductToShow = ({ product, onSave }) => {
const [name, setName] = useState(product.name);
const [desc, setDesc] = useState(product.desc);
useEffect(() => {
setName(product.name);
setDesc(product.desc);
}, [product]);
return (
<div className="to-show-container">
<h1>{product.name}</h1>
<label>Product Name: </label>
<input value={name} onChange={(e) => setName(e.target.value)} />
<label>Product Description: </label>
<input value={desc} onChange={(e) => setDesc(e.target.value)} />
<div className={"to-the-right"}>
<Button
onClick={() => onSave({id:product.id,name,desc})}>
Save
</Button>
</div>
</div>
);
};
I used useState since I don't know if you want to use it further in that component or not.
If the only purpose of the component is to update the products, I would take the advice of the other answer and wrap the inputs in a form tag and take the inputs after submitting the form
It could be problem with reinitialization of inputs.
ProductToShow component is the same for any selected product. You pass props (they could be changed) but I'm not sure if nameInput is changed here after product props changes:
const nameInput = useFormInput(product.name);
I think it's better to wrap inputs with the <form>...</form> and control reinitialization with initialValues.
also:
would be helpful to have codesandbox or something.
not need to use [...spread] if spead is array[].
not need to have to states for show / not-to-show & productToShow. Just use productToShow with null option when you don't want to show any product.
This is my sample code, it's very basic i just want to console fname once submit is clicked.
When i pressed first time i get an empty line but when pressed second time the button i get some empty value. I am attaching the screenshot for the console.
I dont want to change my code to a class and use some method to get the value in console.
screenshot for console output
import React,{useState} from 'react'
export const anyComponent = () => {
const [fname, setFname] = useState('')
const submit = (event) =>{
setFname({fname: [event.target.value] })
console.log(fname)
}
return(
<div>
<input name="fname" type="text" placeholder="Enter your First Name" />
<button onClick={submit}>Submit</button>
</div>
)
}
From MDN Docs:
The target property of the Event interface is a reference to the object onto which the event was dispatched.
In your case, event.target would point to the button and not input.
What you need is a reference to the input, you can use useRef hook for it like this
import React, { useState, useRef } from "react";
export default anyComponent = () => {
const inputEl = useRef(null);
const [fname, setFname] = useState("");
const submit = event => {
setFname({ fname: [inputEl.current.value] });
};
console.log(fname);
return (
<div>
<input
name="fname"
ref={inputEl}
type="text"
placeholder="Enter your First Name"
/>
<button onClick={submit}>Submit</button>
</div>
);
};
Also, setState is asynchronous, that's why you wouldn't see the result in the console just after calling setFname. However you'd see the updated fName in console on the next render, that's why I've moved it out of the submit.
Without useRef
Alternatively, you can add onChange handler on input and update the state fname from there
function App(){
const [fname, setFname] = useState("");
const submit = () => {
console.log(fname);
};
const handleChange = ev => {
setFname({ fname: [ev.target.value] });
}
return (
<div>
<input
name="fname"
onChange={handleChange}
type="text"
placeholder="Enter your First Name"
/>
<button onClick={submit}>Submit</button>
</div>
);
};
Your console log should be outside submit method. Or log event.target.value instead fname.