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!
Related
I am developing a small application in react, in which I have an edit option. On clicking the edit button, it will load the existing data and allows the user to edit any of the fields and submit.
Fetching the data and loading it in a form are working fine, but when I edit a textbox, the value changes to the existing fetched value, and it is not allowing me to hold the edited value.
Please note, the problem is with editing the input in a form not in submitting. Below is the edit component that I am using.
mport { useState, useEffect } from 'react';
import { json, Link } from 'react-router-dom';
import { useParams } from 'react-router-dom';
const EditTask = ({ onEdit }) => {
const [text, setText] = useState('');
const [day, setDay] = useState('');
const [reminder, setReminder] = useState(false);
const params = useParams();
useEffect(() => {
fetchTask();
});
const fetchTask = async () => {
const res = await fetch(`http://localhost:5000/tasks/${params.id}`);
const data = await res.json();
setText(data.text);
setDay(data.day);
setReminder(data.reminder);
};
const onSubmit = async (e) => {
e.preventdefault();
if (!text) {
alert('Please enter task name');
return;
}
onEdit({ text, day, reminder });
setText('');
setDay('');
setReminder(false);
};
const handleChange = ({ target }) => {
console.log(target.value); // displaying the input value
setText(target.value); // changes to existing value not the one I entered
};
return (
<form className="add-form" onSubmit={onSubmit}>
<div className="form-control">
<label>Task</label>
<input
id="AddTask"
type="text"
placeholder="Add Task"
value={text}
onChange={handleChange}
/>
</div>
<div className="form-control">
<label>Date & Time</label>
<input
id="Date"
type="text"
placeholder="Date & Time"
value={day}
onChange={(e) => setDay(e.target.value)}
/>
</div>
<div className="form-control form-control-check">
<label>Set Reminder</label>
<input
id="Reminder"
type="checkbox"
checked={reminder}
value={reminder}
onChange={(e) => setReminder(e.currentTarget.checked)}
/>
</div>
<input className="btn btn-block" type="submit" value="Save Task" />
<Link to="/">Home</Link>
</form>
);
};
export default EditTask;
Can someone explain what I am missing here? Happy to share other information if needed.
Expecting the input fields to get the value entered and submitting.
You missed adding dependency to useEffect
Yours
useEffect(() => {
fetchTask()
}
)
Should be changed
useEffect(()=>{
fetchTask()
}, [])
becasue of this, fetchTask is occured when view is re-rendered.
I'm writing a simple react code that adds a value to a list onClick of a button. and after adding, I'm logging it in the same block. Currently, my issue is, that the logging is happening with n-1 entered string. i.e. If I enter egg and then add milk, after adding milk, I see egg logged and so on. Here is my code.
function App() {
const [list, setList] = useState([]);
const [gItem, setGItem] = useState("");
const AddItem = (e) => {
e.preventDefault();
setList([...list, gItem]);
console.log(list);
};
return (
<>
<form className="grocery-form">
<h3>grocery bud</h3>
<div className="form-control">
<label htmlFor="name"></label>
<input
type="text"
placeholder="e.g. eggs"
className="grocery"
name="name"
id="name"
onChange={(e) => setGItem(e.target.value)}
/>
<button className="submit-btn" type="submit" onClick={AddItem}>
Submit
</button>
</div>
</form>
<div className="grocery-container">
<List items={list} />
</div>
</>
);
}
I'm unable to understand where I'm going wrong.
setList updates state asynchronously so if you log state after using it the previous value will be displayed, to make it log the current state after this list was changed you can use useEffect hook like this:
useEffect(() => {
console.log(list);
}, [list])
I want to get a specific item from a json using its unique ID but with the function that I have created I do not get any data. This is the function:
export function getPost(id) {
return fetch("http://localhost:3004/edit/"+id)
.then(data => data.json())
}
And this is the page where I want to print the item. The ID comes from another page and it's shown in the url, where I get it thanks to useParams:
interface IPost {
id: number;
title: string;
author: string;
content: string;
}
const Edit: React.FC = () => {
const [post, setPost] = useState<IPost>();
const {id} = useParams();
// Not working
getPost(id)
.then(items => {
setPost(items)
})
return (
<div className="containerHomepage">
<form className="formulari">
<div className="containerBreadCrumb">
<ul className="breadCrumb">
<li>Posts</li>
{/* THIS SHOWS AN ERROR */}
{post.author}
</ul>
</div>
<div className="containerTitleButton">
<input
className=""
type="text"
placeholder='Post title'
name="title"
// onChange={handleInputChange}
></input>
<button
className="button"
type="submit"
>Save</button>
</div>
<div className="containerEdit">
<input
className="editAuthor"
type="text"
placeholder='Author'
name="author"
// onChange={handleInputChange}
></input>
<input
className="editContent"
type="textarea"
placeholder='Content'
name="content"
// onChange={handleInputChange}
></input>
{/* <div className="errorEmpty">{error}</div> */}
</div>
</form>
</div>
);
};
// ========================================
export default Edit;
Throws an error in "{post.author}", and I guess that it's something wrong with my function "getPost".
Since you initialize post to undefined:
const [post, setPost] = useState<IPost>();
trying to access properties of it will throw:
{post.author}
Your TypeScript should have warned you about this - it's good to fix TypeScript warnings before running apps for real to avoid runtime errors. Check that the object exists before trying to access properties on it.
{post?.author}
There's no issue with your getPost function, except for the fact that you should probably only call it once, when the component mounts, not every time it re-renders.
useEffect(() => {
getPost(id).then(setPost);
}, []);
I'd also recommend not ignoring errors - catch them to avoid unhandled rejections.
useEffect(() => {
getPost(id).then(setPost).catch(handleError);
}, []);
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!
i have a problem with my react's form.
If I click twice on the submit button then at this moment, the form submits correctly and sends my various information to the database.
Where is the problem ?
import React, { useState } from 'react';
import axios from 'axios';
const Register = () => {
const [username, setUsername] = useState();
const [password, setPassword] = useState();
const onSubmit = (e) => {
e.preventDefault();
setUsername(document.querySelector(".usernameInput").value);
setPassword(document.querySelector(".passwordInput").value);
const user = {
username: username,
password: password
}
axios.post('http://localhost:5000/users/add', user)
.then(res => console.log(res.data));
console.log("lancement du formulaire");
}
return (
<div>
<h1>TEST Form</h1>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Username</label>
<input required type="text" className="usernameInput" />
</div>
<div className="form-group">
<label>Password</label>
<input required type="password" />
</div>
<div className="form-group">
<input type="submit" value="Create User" className="btn btn-primary" className="passwordInput" /> </div>
</form>
</div>
)
}
export default Register;
Thanks .
useState is asynchronous just like setState in class components. You can't update the state on one line and assume it's already changed on the next one. You'll likely use the unchanged state.
When you create the user object, the state is not yet updated.
You need to click twice on the submit button because:
on the first click you set the username and password states' value to the input value but as the state is not updated, you send the user objects with empty properties
on the second click (when the state is updated) you can send the user object, as the user object contains the state values
The following should work (though I would recommend not to use it):
const onSubmit = (e) => {
e.preventDefault();
const user = {
username: document.querySelector('.usernameInput').value,
password: document.querySelector('.passwordInput').value,
};
axios
.post('http://localhost:5000/users/add', user)
.then((res) => console.log(res.data));
console.log('lancement du formulaire');
};
But why do you use states username and password if you never use them? If you've already added the states to store the input values, you can update them on changes and submit them on form submit:
const Register = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const onSubmit = (e) => {
e.preventDefault();
const user = {
username,
password,
};
axios
.post('http://localhost:5000/users/add', user)
.then((res) => console.log(res.data));
console.log('lancement du formulaire');
};
return (
<div>
<h1>TEST Form</h1>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Username</label>
<input
required
type="text"
className="usernameInput"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div className="form-group">
<label>Password</label>
<input
required
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div className="form-group">
<input
type="submit"
value="Create User"
className="btn btn-primary"
className="passwordInput"
/>{' '}
</div>
</form>
</div>
);
};