New to react and currently working on a project with a backend.
Everything functions correctly apart from targeting the value of user selection.
basically whenever a user enters a number the setId is saved properly to the const with no problems while using the onChange method.
this method would render my page every change on text.
I am trying to save the Id only when the user clicks the button. however,
event.target.value does not work with onClick.
I tried using event.currentTarget.value and this does not seem to work.
Code:
<form onSubmit={handleSubmit}>
<label>Company ID</label>
<input value={id} onChange={(e) => setId(e.target.value)} type="number" />
{/* <button value={id} type="button" onClick={(e) => setId(e.currentTarget.value)}>Search</button> */}
</form>
Handle Submit:
const handleSubmit = (e) => {
e.preventDefault();
console.log(id)
}
is there a way of doing this with onclick? since I wouldn't like my component to render on every typo and only once a user has clicked the button.
Componenet:
interface GetOneCompanyProps {
company: CompanyModel;
}
interface RouteParam {
id: any;
}
interface CompanyById extends RouteComponentProps<RouteParam> {
}
function GetOneCompany(): JSX.Element {
const [id, setId] = useState('4');
const [company, setCompany] = useState<any>('');
const handleSubmit = (e) => {
e.preventDefault();
console.log(id)
}
async function send() {
try {
const response = await axios.get<CompanyModel>(globals.adminUrls.getOneCompany + id)
store.dispatch(oneCompanyAction(response.data));
console.log(response);
const company = response.data;
setCompany(company)
} catch (err) {
notify.error(err);
}
}
useEffect(() => {
send();
}, [id]);
return (
<div className="getOneCompany">
<h1>hi </h1>
<form onSubmit={handleSubmit}>
<label>Company ID</label>
<input value={id} onChange={(e) => setId(e.target.value)} type="number" />
{/* <button value={id} type="button" onClick={(e) => setId(e.currentTarget.value)}>Search</button> */}
</form>
<div className="top">
</div>
<br/>
Company: {id}
<br/>
Client Type: {company.clientType}
<br/>
Company Name: {company.name}
<br/>
Email Adress: {company.email}
<br/>
</div>
);
}
export default GetOneCompany;
Hope I am clear on this.
Thanks.
You can turn your input from being a controlled input to an uncontrolled input, and make use of the useRef hook. Basically, remove most of your attributes from the input element, and grab the current value of the input form on click of the button. From there, you can do whatever you want with the input value.
const inputRef = useRef()
...other code
<form onSubmit={handleSubmit}>
<label>Company ID</label>
<input type="number" ref={inputRef} />
<button value={id} type="button" onClick={() => console.log(inputRef.current.value)}>Search</button>
</form>
...other code
I'm afraid to say that here onChange is mandatory as we also are interested in the value which we set by setId. onClick can't be used as we can't set the value in the input.
Hope I'm clear.
Thankyou!
Related
I have the next form component in my application:
function App() {
const [state, setState] = useState(false)
const { register, control, handleSubmit, resetField, watch } = useForm();
const [checkedItems, setCheckedItems] = useState([]);
useEffect(()=> {
setCheckedItems(watch('test'))
},[watch('test')])
const onSubmit = (data) => alert(JSON.stringify(data, null, 4));
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h1>Field Array </h1>
<p>The following demo allow you to delete, append, prepend items</p>
<span className="counter">Render Count: {renderCount}</span>
<ul>
{[{value: 1}, {value:2}].map((item, index) => {
return (
<li key={item.userId}>
<Controller
render={({ field }) => <input type='checkbox' {...field} />}
name={`test[${index}]`}
control={control}
/>
</li>
);
})}
</ul>
<input onChange={e => setState(e.target.checked)} type='checkbox' />
{state && <input {...register(`test[${checkedItems.length}]`)}/>}
<input type="submit" />
</form>
);
}
If check this input:
<input onChange={e => setState(e.target.checked)} type='checkbox' />
I display an input where i can add data. Taking into account this scenario:
i click the checkbox, add some data, click again check box, the input dissapear, click again and add data and click submit, then as response i get a lot of data, but i need only the last value; Question: How to fix my issue?
demo: https://codesandbox.io/s/64182981-how-to-preserve-fields-in-react-hook-form-fieldarray-forked-qw18xs
I'm using the below Contact component to handle a simple form in react to send an email. I want the sendEmail function to also clear the form fields but I've tried referencing them through form.current and that doesnt seem to be working. Is there another way I need to reference them to set the values? I also tried e.target.reset() and it didn't seem to do anything.
import React, { useRef } from 'react';
import emailjs from 'emailjs-com';
const Contact = () => {
const form = useRef();
const sendEmail = (e) => {
e.preventDefault();
const serviceId='...';
const templateId='...';
const userId='...';
emailjs.sendForm(serviceId, templateId, form.current, userId)
.then((result) => {
console.log(result.text);
}, (error) => {
console.log(error.text);
});
};
return (
<form onSubmit={sendEmail} ref={form}>
<div className="field half first">
<label htmlFor="name">Name</label>
<input type="text" name="fromName" id="name"/>
</div>
<div className="field half">
<label htmlFor="email">Email</label>
<input type="text" name="fromEmail" id="email"/>
</div>
<div className="field">
<label htmlFor="message">Message</label>
<textarea name="message" id="message" rows="4"
placeholder = "..."></textarea>
</div>
<ul className="actions">
<li>
<input type="submit" value="Send Message" className="special"/>
</li>
</ul>
</form>
);
};
export default Contact
There are so many ways to doing this, basically on how the component has been structured to collect the data. However, following your pattern of arrangement, why don't you set a boolean variable or something coercible to a boolean value (value initially set to false) that is made aware (i.e value changes to true or 'sent') when the email has been successfully sent.
import {useState} from "react";
const [emailStatus, setEmailStatus] = useState("Not sent");
Then use the useEffect-hook to re-render the component, whenever the emailStatus variable changes to true or 'sent' and thereby clear-out the form values.
Hence, in your function:
emailjs.sendForm(serviceId, templateId, form.current, userId)
.then((result) => {
.... any other logic
console.log(result.text);
setEmailStatus('sent');
................
useEffect(() => {
//clear form fields here
}, [emailStatus]);
return(
.....
.....
);
Im just wondering if anyone could point out where im going wrong with my code.
Im relativly new to react so began with a simple todo list.
I then edited this to allow for various other forms such as menu, profile etc.
Below is the code attached for the menu section.
My back end works if I use postmaster which leads me to believe its my front end, and specifically my useState.
I can call the data and view it within my modal and it appears, however, I cant seem to edit the specific data within the form field and/or post it to my database.
Any help would be greatly appreciated.
Ive attached my code below.
import React, { Fragment, useState } from "react";
const EditMenu = ({ menu }) => {
//editText function
const [inputs, setInputs] = useState(menu.item_title, menu.item_price, menu.item_description, menu.item_category);
const { title, category, price, description } = inputs;
const onChange = e =>
setInputs({ ...inputs, [e.target.name]: e.target.value });
const editMenuItem = async (item_id) => {
try {
const body = { title, category, price, description };
const res = await fetch(`http://localhost:5000/menu/${item_id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
});
window.location = "/admin";
} catch (error) {
console.error(error.message);
}
};
return (
<Fragment>
<button type="button" className="btn btn-warning" data-toggle="modal" data-target={`#id${menu.item_id}`}>Edit</button>
{/*id = "id21"*/}
<div className="modal" id={`id${menu.item_id}`} onClick={() => setInputs(menu.item_title, menu.item_price, menu.item_description, menu.item_category)}>
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">Edit Menu</h4>
<button className="close btn-danger" data-dismiss="modal" onClick={() => setInputs(menu.item_title, menu.item_price, menu.item_description, menu.item_category)}>×</button>
</div>
<div className="modal-body">
<input type="text" name="title" placeholder="Title" className="form-control my-3" value={menu.item_title} onChange={e => onChange(e)} />
<input type="tel" name="price" placeholder="Price" className="form-control my-3" value={menu.item_price} onChange={e => onChange(e)} />
<input type="text" name="description" placeholder="Description" className="form-control my-3" value={menu.item_description} onChange={e => onChange(e)} />
<input type="text" name="category" placeholder="Category" className="form-control my-3" value={menu.item_category} onChange={e => onChange(e)} />
</div>
<div className="modal-footer">
<button type="button" className="btn btn-warning" data-dismiss="modal" onClick={() => editMenuItem(menu.item_id)}>Edit</button>
<button type="button" className="btn btn-danger" data-dismiss="modal" onClick={() => setInputs(menu.item_title, menu.item_price, menu.item_description, menu.item_category)}>Close</button>
</div>
</div>
</div>
</div>
</Fragment>
);
};
Update,
Ive tried various suggested fixes using the below answers so far.
Both of these fixes allow the form fields to be editable, and the information within the form fields changes and thus within the state also however it is not sent to the database. Upon refresh of the page, the old information is pulled from the database.
Ive discovered that if I removed all of the form fields but one, it successfully updates AND sends to the database.
Title OR Description OR Price OR Category.
Checking the network tab within the browser whilst updating shows that for more than one input field, the put request fails and no information/payload is sent to the body within the request tab.
As a result, the database returns a NOT NULL error.
Based off Oliviers answer below, that setInput is only recognises one parameter, I can only imagine that this is what is breaking when there is more than one form field/input added. I unfortunatly dont know enough react to know if this is the case or not.
I see a problem in your state initialization => const [inputs, setInputs] = useState(menu.item_title, menu.item_price, menu.item_description, menu.item_category); is not correct, useState take a single parameter, here you must build an object representing the inputs.
Here is a solution using a function to initialize the inputs state, to prevent computing the object each time the component is re-rendered
function buildInputs(menu) {
return {
title: menu.item_title,
category: menu.item_category,
price: menu.item_price,
description: menu.item_description
};
}
const EditMenu = ({ menu }) => {
//editText function
const [inputs, setInputs] = useState(() => buildInputs(menu));
const { title, category, price, description } = inputs;
// Needed if you want the inputs to be updtated when the menu property is updated
useEffect(() => setInputs(buildInputs(menu)), [menu]);
const onChange = e => setInputs({ ...inputs, [e.target.name]: e.target.value });
...
You must also change the input value to reflect the state variable :
<input type="text" name="title" placeholder="Title"
className="form-control my-3" value={title} onChange={onChange} />
You should set your state like this:
const [inputs, setInputs] = useState({
title: menu.item_title,
price: menu.item_price,
category: menu.item_category,
description: menu.item_description
});
also you need to change value attributes to be variables rather than setting them to the menu values, for example:
//code
<input name="title" value={inputs.title} onChange={onChange}/>
cause values inside inputs are changeable by your onChange method, on the other hand, values inside menu object will remain with the same values.
I eventually figured out the issue.
By splitting my setInput useState into seperate individual useStates, I was able to get it to work.
So my origional code of...
const EditMenu = ({ menu }) => {
const [inputs, setInputs] = useState(menu.item_title, menu.item_price, menu.item_description, menu.item_category);
const { title, category, price, description } = inputs;
changed to this.
const EditMenu = ({ menu }) => {
const [item_title, setTitle] = useState(menu.item_title);
const [item_price, setPrice] = useState(menu.item_price);
const [item_description, setDescription] = useState(menu.item_description);
and the onChange function and form input...
const onChange = e =>
setInputs({ ...inputs, [e.target.name]: e.target.value });
<input... onChange={e => onChange(e)} />
changed to this...
value={item_title} onChange={e => setTitle(e.target.value)} />
value={item_price} onChange={e => setPrice(e.target.value)} />
value={item_description} onChange={e => setDescription(e.target.value)} />
In the end, Oliviers reasoing was correct even if the soloution didnt work for me. That my setInput only allowed for one parameter. Splitting it up allowed me to pass the remaining parameters.
Thank you everyone for the help, hopefully this might help someone else some day too!
I make simple task for getting job. Working with react and redux. When i get value from input and send them to reducer they are lost in the way. Wait, not so easy. 1st item getting by reducer gets prop name, age, type, index and return new state. Nice. But other items lost prop name and age in the way. What? How did them it? Reducer return empty obj for render. Dont look on obj in dispatch i will rework it.
REDUCER
case 'EDIT_ITEM':
console.log(action.name, action.age, action.id);
return state.map((item, index) =>
action.id === index
? {
name: action.name,
age: action.age
}
: item
);
App.js
function EditUsers() {
const listItems = users.map(function (value, index) {
return (
<form>
<div className="input-group">
<div className="input-group-prepend">
<span className="input-group-text">{value.name}, {value.age}</span>
</div>
<input type="text" placeholder="New name" id="newName" className="form-control"/>
<input type="text" placeholder="New age" id="newAge" className="form-control" aria-describedby="button-addon2"/>
<div className="input-group-append">
<button onClick={() => dispatch({
type: 'EDIT_ITEM',
id: index,
name: document.getElementById('newName').value,
age: document.getElementById("newAge").value
})}
className="btn btn-outline-primary"
type="button"
id="button-addon2">
Изменить
</button>
</div>
</div>
</form>
)
});
return (
<div>{listItems}</div>
)
}
You won't be able to access the input values from the button's onClick event, but if you decide to leave the inputs uncontrolled and move the logic to the associated form's onSubmit callback, then you can access the form's field values from the onSubmit event.
Define a submitHandler function to consume both the index and submit event, e:
const submitHandler = index => e => {
e.preventDefault(); // <-- prevent the default form action, important!
const { newName, newAge } = e.target; // <-- destructure the inputs from the event target
dispatch({
type: "EDIT_ITEM",
id: index,
name: newName.value, // <-- extract the input value
age: newAge.value // <-- extract the input value
});
};
Here the path to the input value is e.target.<fieldId>.value. Notice I've also defined submitHandler to curry the index, which allows for more optimal usage when mapping elements.
Next, attach the submitHandler callback to the onSubmit prop of the form.
const listItems = users.map(function(value, index) {
return (
<form key={index} onSubmit={submitHandler(index)}>
...
Here the curried function submitHandler(index) takes the index and encloses it in an instance of the callback, returning a function that takes the onSubmit event object, e => {....
Finally, update the button to have type="submit" and no onClick handler.
<button
className="btn btn-outline-primary"
type="submit"
id="button-addon2"
>
Изменить
</button>
Full code
const submitHandler = index => e => {
e.preventDefault();
const { newName, newAge } = e.target;
dispatch({
type: "EDIT_ITEM",
id: index,
name: newName.value,
age: newAge.value
});
};
function EditUsers() {
const listItems = users.map(function(value, index) {
return (
<form key={index} onSubmit={submitHandler(index)}>
<div className="input-group">
<div className="input-group-prepend">
<span className="input-group-text">
{value.name}, {value.age}
</span>
</div>
<input
type="text"
placeholder="New name"
id="newName"
className="form-control"
/>
<input
type="text"
placeholder="New age"
id="newAge"
className="form-control"
aria-describedby="button-addon2"
/>
<div className="input-group-append">
<button
className="btn btn-outline-primary"
type="submit"
id="button-addon2"
>
Изменить
</button>
</div>
</div>
</form>
);
});
return <div>{listItems}</div>;
}
After I submit my form, which contains data fields and a file field, only the data fields are cleared, but the uploaded file field is kept. See image: Here
OnChange Function
onChange = (e) => {
if(e.target.name === 'audio') {
this.setState({
[e.target.name]: e.target.files[0], loaded: 0,
}, () => console.log(this.state.audio))
} else {
this.setState({
[e.target.name]: e.target.value
}, () => console.log(this.state))
}
}
Submit Function
onSubmit = e => {
e.preventDefault();
let { title, content, audio} = this.state;
//const story = { title, content, audio};
let formDataStory = new FormData();
formDataStory.append('audio', audio);
formDataStory.append('title', title);
formDataStory.append('content', content);
this.props.addStory(formDataStory);
this.setState({
title: "",
content:"",
audio: ""
});
};
Form
render() {
const {title, content, audio} = this.state;
return (
<div className="card card-body mt-4 mb-4">
<h2>Add Story</h2>
<form onSubmit={this.onSubmit}>
<div className="form-group">
<label>Title</label>
<input
className="form-control"
type="text"
name="title"
onChange={this.onChange}
value={title}
/>
</div>
<div className="form-group">
<label>Content</label>
<input
className="form-control"
type="text"
name="content"
onChange={this.onChange}
value={content}
/>
</div>
<div className="form-group">
<label>Audio</label>
<input
className="form-control"
type="file"
name="audio"
onChange={this.onChange}
//value={audio}
/>
</div>
<div className="form-group">
<button type="submit" className="btn btn-primary">
Submit
</button>
</div>
</form>
</div>
);
}
}
How can I reset the file upload field together with the other data fields after submitting the form?
Many thanks!
Since the file input is always uncontrolled you'll need to use a dom ref and manually clear the value.
Here's an example functional component that does this:
function ExampleFileInput() {
const ref = React.useRef();
function handleClick() {
ref.current.value = ""
}
return (
<div className="App">
<input type="file" ref={ref}/><br />
<button type="button" onClick={handleClick}>Clear file</button>
</div>
);
}
To use in a class component, see the docs. You can read about more ways to clear the file input in this question.
Docs
Create ref to file input this.inputRef = React.createRef();
add ref to input <input type="file" ref={this.inputRef} />
Inside submit function this.inputRef.current.value = '';
If you use Formik you can do this:
I had the same issue and I managed it creating a ref in the parent component (the one that use Formik) and passing it to the Field using innerRef prop.
Then on the onReset I used the ref to clear the real input value.
const UserForm = props => {
const logoRef = useRef();
const handleReset = (values, formikBag) => {
logoRef.current.value = null; //THIS RESETS THE FILE FIELD
}
const handleSubmit = (values, formikBag) => {
//...
axios({method, url, data})
.then(response => {
formikBag.resetForm();
})
}
//...
return (
<Formik initialValues={initialValues} onSubmit={handleSubmit} onReset={handleReset}>
...
<Field
type="file"
name="logo"
onChange={({target}) => setFieldValue(props.name, target.files[0])}
innerRef={logoRef}
/>
</Formik>
)
}