My app is using react, redux and redux-thunk. I want to add a new product to https://fakestoreapi.com/products. The code below works, so I am happy with that, but it adds a single stringe. I want it to add a object of key-value pairs like that:
title: 'test product',
price: 13.5,
description: 'lorem ipsum set',
image: 'https://i.pravatar.cc',
category: 'electronic'
My code:
import React from "react";
export const ProductForm = ({ addProduct }) => {
const [product, setProduct] = React.useState("");
const updateProduct = (event) => {
setProduct(event.target.value);
};
const onAddProductClick = () => {
addProduct(product);
setProduct("");
};
return (
<div>
<input
onChange={updateProduct}
value={product}
type="text"
name="title"
placeholder="title"
/>
<button onClick={onAddProductClick}>Add product</button>
</div>
);
};
I know that i have to use a form to do this task. Although I don't know how to change the code below so that it woudl still works. If in form onSubmit i use same function as onClik in code above, the page refresh itself and do not add a product to an array.
You are setting product to the value from the input here:
const updateProduct = (event) => {
setProduct(event.target.value);
};
event.target.value is a string. It's whatever you enter in your input.
If you want an object you can:
change product to productName and use productName in onAddProductClick. Like addProduct({title: productName})
OR
change updateProduct to:
const updateProduct = (event) => {
setProduct({title: event.target.value});
};
Related
New to React. I have a NewItem component that should be pretty simple (and I have managed to do similar stuff before without problems): it has a form with two input text fields and it should update a stateful object (I think that's what it's called) when the form is submitted. Yet the state is updated only the second time the user submit the form. Why is that and how can I fix this? (Again, I am aware it's probably a very silly thing to ask, but I have done similar stuff with arrays and thought I got it).
Thank you for your patience and help.
Here's the component:
import { useState } from "react";
const NewItem = () => {
const [newName, setNewName] = useState("");
const [newType, setNewType] = useState("");
const [newItem, setNewItem] = useState({ name: "", type: "" });
const getNewName = (event) => {
const changedName = event.target.value;
setNewName(changedName);
};
const getNewType = (event) => {
const changedType = event.target.value;
setNewType(changedType);
};
const handleSubmit = (event) => {
event.preventDefault();
const toAdd = { ...newItem, name: newName, type: newType };
setNewItem(toAdd);
console.log(toAdd);
};
return (
<form onSubmit={handleSubmit}>
<label>
Name
<input type="text" value={newName} onChange={getNewName} />
</label>
<label>
Type
<input type="text" value={newType} onChange={getNewType} />
</label>
<button type="submit">Add</button>
</form>
);
};
export default NewItem;
Upon updating the name of an ingredient, I want to submit this data is an ingredient with the up-to-date name: from "Milk" to "Cow's milk".
I've provided simple "1,2,3" steps as comments to briefly illustrate the flow of things, but you can assume that the console logged values you see here all happen right after I press the submit button (FloatingButtons):
export const Ingredient = ({
ingredient: ingredientProp,
}: IngredientProps) => {
const [name, setName] = useState(ingredientProp.name);
// Prints expected updated name: "Cow's milk"
console.log("name", name);
const [ingredient, setIngredient] = useState(ingredientProp);
// Prints expected updated ingredient containing the name: "Cow's milk"
console.log("ingredient", ingredient);
useEffect(() => {
// 2. Replace ingredient name with newName
// Prints expected updated name: "Cow's milk"
const newName = name;
console.log("newName", newName);
setIngredient({ ...ingredient, name: newName });
}, [name]);
return (
<form
className="Ingredient"
id={ingredientProp.id}
onSubmit={(e) => {
e.preventDefault();
console.log(ingredient);
// 3. Submit updated ingredient
// Prints ingredient with outdated name ("Milk"), why?
submitData(ingredient);
}}
>
<EditableField
defaultValue={name}
onChange={(newName) => {
console.log("newName", newName)
//1. Set name to newName
// Prints "Cow's milk", check!
setName(newName);
}}
/>
{/* Is a submit button that refers to the parent form */}
<FloatingButtons
formId={ingredientProp.id}
/>
</form>
);
};
I would think that you need to refactor your code a little bit
Create a handleSubmit function and wrap it around a useCallback hook
...
const handleSubmit = useCallback(() => {
submitData(ingredient);
}, [ingredient])
...
return <form
onSubmit={handleSubmit}
>
...
</form>
That's one way to do it, but you could also remove setIngredient since only name property will be changing; And that should give you the following
export const Ingredient = ({
ingredient: ingredientProp,
}: IngredientProps) => {
const [name, setName] = useState(ingredientProp.name);
const handleSubmit = useCallback(() => {
submitData({
...ingredient,
name,
});
}, [name])
return (
<form
className="Ingredient"
id={ingredientProp.id}
onSubmit={handleSubmit}
>
...
</form>
);
};
Thank you for your refactoring suggestion, that certainly helps but I did some more digging and realized the underlying issue was that there was more than one form with the same form id and that was somehow messing with the onSubmit event. I've now made sure that every form and its corresponding submit button have more specific ids.
I have a form where user can enter a name that will then be displayed on a list. Upon entering a new name the list should automatically be sorted in alphabetical order. Current attempt with useEffect does work but is buggy(list will only be sorted after user start deleting previous input text).
A few notable things to highlight with current setup:
Submission component is used for rendering list of names
Form component is used to store state of app and input fields
handleSortName() will execute sorting
useEffect() executes handleSortName() when there is a change to submissions value
import React, { useEffect, useState } from "react";
const Submission = ({ submission }) => {
return <div>name: {submission.name}</div>;
};
const Form = () => {
const [values, setValues] = useState({
name: ""
});
const [submissions, setSubmission] = useState([
{ name: "John" }
]);
const addSubmission = (values) => {
const newSubmissions = [...submissions, values];
setSubmission(newSubmissions);
};
const handleChange = (event) => {
const value = event.target.value;
setValues({ ...values, [event.target.name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
addSubmission(values);
handleSortName(submissions);
};
const handleSortName = (submissions) => {
return submissions.sort((a, b) => a.name.localeCompare(b.name));
};
useEffect(() => {
handleSortName(submissions);
}, [submissions]);
return (
<>
<form onSubmit={handleSubmit}>
<h1>Student Enrollment</h1>
<div>
<label>name: </label>
<input
required
type="text"
name="name"
value={values.name}
onChange={handleChange}
/>
<input type="submit" value="Submit" />
</div>
</form>
<h1>Submitted Student</h1>
{submissions.map((submission, index) => (
<Submission key={index} submission={submission} />
))}
</>
);
};
export default Form;
Working Sample: https://codesandbox.io/s/usestate-form-oj61v9?file=/src/Form.js
I am aware that useState is asynchronous and will not update value right away.
Any suggestion on other implementations such as functional updates, a custom hook or current UseEffect approach? Thanks in Advance!
UPDATE:
because React re-renders the component when the props or state changes. that means inside your handleSortName() function you have to call setSubmissions with the new sorted array, then React will know that the state was changed.
const handleSortName = (submissions) => {
// create a new copy of the array with the three dots operator:
let copyOfSubmissions = [...submissions];
// set the state to the new sorted array:
setSubmissions(
copyOfSubmissions.sort((a, b) => a.name.localeCompare(b.name))
);
};
or you can do both steps in 1 line:
const handleSortName = (submissions) => {
// set the state to the newly created sorted array with the three dots operator:
setSubmissions(
[...submissions].sort((a, b) => a.name.localeCompare(b.name))
);
};
sandbox link here
I'm updating one of my state properties without using the setter and using a normal variable assignment in js, that causes me the problem that the property state only updates once, so when i want to check a checkbox more than once the state in app does not update correctly, i think that the problem causing it is that i'm messing up that property assignment.
I searched for how to update a property of a state without overwritting the whole object and every post i find answering that question is using this.setState instead of declaring a state and using the declared setter method and i don't know how to adapt that this update to my code.
For example, the top post called "How to update nested state properties in React" declares his state like this:
this.state = {
someProperty: {
flag:true
}
}
While i'm declaring my state like this:
const [EditedTask, setEditedTask] = useState({
name: props.task.name,
completed: props.task.completed,
_id: props.task._id,
});
const { name, completed } = EditedTask;
And the top answer to that question is:
this.setState(prevState => ({
...prevState,
someProperty: {
...prevState.someProperty,
someOtherProperty: {
...prevState.someProperty.someOtherProperty,
anotherProperty: {
...prevState.someProperty.someOtherProperty.anotherProperty,
flag: false
}
}
}
}))
In this solution, with which object should i replace the this in the this.setState? Or the prevState param?
In my code i need to update the checkbox/ completed state, with this solution i'm only receiving the object EditedTask in my app component when i edit the checkbox once, if i check it more than once the state doesn't update in app, meanwhile if i edit the name the state updates in app correctly with both the name and completed propertys.
import React, { useState } from "react";
import "../App.css";
const EditTask = (props) => {
const [EditedTask, setEditedTask] = useState({
name: props.task.name,
completed: props.task.completed,
_id: props.task._id,
});
const { name, completed } = EditedTask;
const onChange = (e) => {
setEditedTask({
...EditedTask,
[e.target.name]: e.target.value,
});
};
const onSubmit = (e) => {
e.preventDefault();
setEditedTask(EditedTask);
props.saveEditedTask(EditedTask);
props.changeEdit(false);
};
return (
<>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Task Id: ${props.task._id}</label>
<div className="inputEdit">
<input
type="text"
placeholder={props.msg}
name="name"
onChange={onChange}
value={name}
/>
</div>
</div>
<div className="form-check">
<input
className="form-check-input"
type="checkbox"
defaultChecked={completed}
value={completed}
onChange={() => (EditedTask.completed = !EditedTask.completed)}
name="completed"
/>
<label className="form-check-label">Completed</label>
</div>
<div className="submit">
<button type="submit">Edit</button>
</div>
</form>
</>
);
};
export default EditTask;
I tried replacing the onChange={() => (EditedTask.completed = !EditedTask.completed)} like the top answer/ my onChange for the name value with something like this but it doesn't update the completed value
const updateCompleted = (e) => {
setEditedTask({
...EditedTask,
[e.target.name]: !e.target.value,
});
};
You can try something like this in your setter functions.
const updateCompleted = (e) => {
setEditedTask((currentState) => ({
...currentState,
[e.target.name]: !e.target.value
})
)};
This solution will give you your previous state, which your can use to update your state.
I'm new in ReactJS. I have a task - to do an app like Notes. User can add sublist to his notes, and note have to save to the state in subarray. I need to save sublist in the array inside object. I need to get state like this:
[...notes, { _id: noteId, text: noteText, notes: [{_id: subNoteId, text: subNoteText, notes[]}] }].
How can I to do this?
Sandbox here: https://codesandbox.io/s/relaxed-lamarr-u5hug?file=/src/App.js
Thank you for any help, and sorry for my English
const NoteForm = ({ saveNote, placeholder }) => {
const [value, setValue] = useState("");
const submitHandler = (event) => {
event.preventDefault();
saveNote(value);
setValue("");
};
return (
<form onSubmit={submitHandler}>
<input
type="text"
onChange={(event) => setValue(event.target.value)}
value={value}
placeholder={placeholder}
/>
<button type="submit">Add</button>
</form>
);
};
const NoteList = ({ notes, saveNote }) => {
const renderSubNotes = (noteArr) => {
const list = noteArr.map((note) => {
let subNote;
if (note.notes && note.notes.length > 0) {
subNote = renderSubNotes(note.notes);
}
return (
<div key={note._id}>
<li>{note.text}</li>
<NoteForm placeholder="Enter your sub note" saveNote={saveNote} />
{subNote}
</div>
);
});
return <ul>{list}</ul>;
};
return renderSubNotes(notes);
};
export default function App() {
const [notes, setNotes] = useState([]);
const saveHandler = (text) => {
const trimmedText = text.trim();
const noteId =
Math.floor(Math.random() * 1000) + trimmedText.replace(/\s/g, "");
if (trimmedText.length > 0) {
setNotes([...notes, { _id: noteId, text: trimmedText, notes: [] }]);
}
};
return (
<div>
<h1>Notes</h1>
<NoteList notes={notes} saveNote={saveHandler} />
<NoteForm
saveNote={saveHandler}
placeholder="Enter your note"
/>
</div>
);
}
The code in your saveHandler function is where you're saving your array of notes.
Specifically, this line:
setNotes([...notes, { _id: noteId, text: trimmedText, notes: [] }]);
But at the moment you're saving an empty array. What if you create another stateful variable called currentNote or something like that, relative to whatever note the user is currently working on within the application? While they are working on that note, the stateful currentNote object is updated with the relevant data, e.g. noteID, content, and parentID. Then, when the user has finished editing that particular note, by either pressing save or the plus button to add a new subnote, etc, that should fire a function such as your saveHandler to add the currentNote object to the "notes" array in the stateful "notes" variable. I'm not sure I like that the stateful variable notes contains an array within it called notes as well. I think this may cause confusion.
But in short, your setNotes line could change to something like (bear with me my JS syntax skills suck):
let newNotes= [...notes.notes];
newNotes.push(currentNote);
setNotes([...notes, { _id: noteId, text: trimmedText, notes: newNotes }]);
Wherein your stateful currentNote object is copied into the notes array on every save.