React loop with useState - javascript

I'm trying to grab a todo and add it to an object of todos, can you tell me what's the problem here?
Newbie in React.
import React, { useState } from "react"
function Content() {
const [todo, setTodo] = useState("") // todo: user input
const [todos, setTodos] = useState({}) // todos: object
function handleClick(e) {
e.preventDefault()
setTodos({ ...todo, todos })
}
return (
<>
<h1>Todos</h1>
<form>
<input onChange={(e) => setTodo(e.target.value)} type="text" placeholder="Add Todo Here" />
<button onClick={handleClick}>Add</button>
</form>
{console.log(todos)}
{todos.map((todo) => todo)}
</>
)
}
export default Content

function Content() {
const [todo, setTodo] = useState("") // todo: user input
const [todos, setTodos] = useState([]) // todos: object
function handleClick(e) {
e.preventDefault()
setTodos([ ...todos, todo ])
}
return (
<>
<h1>Todos</h1>
<form>
<input onChange={(e) => setTodo(e.target.value)} type="text" placeholder="Add Todo Here" />
<button onClick={handleClick}>Add</button>
</form>
{console.log(todos)}
{todos.map((todo) => todo)}
</>
)
}
export default function App() {
return (
<div className="App">
<Content />
</div>
);
}
You've mixed todos and todo.. You were trying to spread todo which is a string not an array.
and also, you've not set array right. it should be [] not {}.
:)
here's a code sandbox with a working example:
https://codesandbox.io/s/modest-cerf-soxii
here's the diff between your code and mine:
https://www.diffchecker.com/nBReZt19

Related

Why isn't the todo being deleted?

I am working through a tutorial for a course I'm taking. The lab I'm working on walks through creating a to-do app. I'm on step 3, which asks us to create a button that deletes a task. I feel ridiculous, because I know I can figure it out but...well, I haven't yet! I will post the code to see if there are any initial issues, and then update with the methods I've already tried. Any help is greatly appreciated. Thank you!
import React, { useState } from "react";
import "./App.css";
const App = () => {
const [todos, setTodos] = useState([]);
const [todo, setTodo] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
};
if (newTodo.text.length > 0) {
setTodos([...todos].concat(newTodo));
setTodo("");
} else {
alert("Enter Valid Task");
setTodo("");
}
}
const deleteTodo = (id) => {
let updatedTodos = [...todos].filter((todo) => todo.id !== id);
setTodos(updatedTodos);
}
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
return (
<div>
<h1>To-do List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new task"
value={todo}
/>
<button type="submit">Add Todo</button>
</form>
{todos.map((todo) => <div>ID: {todo.id} Task: {todo.text} {button}</div>)}
</div>
);
};
export default App;
I didn't just copy and paste, so it's possible that I messed something up while typing. I'm expecting the deleteTodo() function to accept a todo.id and filter the list of todos, excluding the one I want to delete. I'm thinking that the issue may be cause by the way I've created the button? Again, I'm not sure why I can't figure it out. TIA.
EDIT: Okay, it works now! Thank you all so much for explaining this. For anyone else that comes across this problem, here's where I mis-stepped:
const button = <button onClick={() => deleteTodo(todo.id)}Delete<button>
#Nicholas Tower's explanation was very clear--creating this outside of .map(...)causes deleteTodo to get the todo state, not the not the todo I want it to delete from the todos array. #Lars Vonk, #0stone0, and #Sudip Shrestha all said this as well. #Sudip Shrestha and #pilchard also helped correct the deleteTodo function. Again, I really appreciate all the help. The code works now. I'll show the updates so people having a similar issue can compare:
import React from "react";
import "./App.css";
const App = () => {
const [todos, setTodos] = React.useState([]);
const [todo, setTodo] = React.useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
};
if (newTodo.text.length > 0) {
setTodos(todos.concat(newTodo));
setTodo("");
} else {
alert("Enter a valid task");
setTodo("");
}
}
// update the state using setState, rathar than mutating it directly #Sudip Shrestha
const deleteTodo = id => {
setTodos(prevState => {
return prevState.filter(todo => todo.id !== id)
});
};
// line 51: button placed inside .map(), as per many suggestions below.
return (
<>
<h1>Todo List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new task..."
value={todo}
/>
</form>
{todos.map((todo) =>
<div>
ID: {todo.id} Task: {todo.text}
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</div>)}
</>
);
};
export default App;
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
You're creating this button element just once, and the todo variable it refers to is the todo state, which is a string (usually an empty string). Since todo is a string, todo.id is undefined, and deleteTodo can't do anything with that.
You need to create separate buttons for each item, so you should move this code down into your .map:
{todos.map((todo) => (
<div>
ID: {todo.id} Task: {todo.text}
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</div>
))}
Now each item has its own button, with its own onClick function. And in those functions, todo is the item of the array.
The button cannot access which todo it has I think you should put the code from the const button where you are referring to it or by changing it to const button = (todo) => <button onClick={ () => deleteTodo(todo.id); }>Delete</button> and access it by doing {button()}
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
This has the same callBack for each todo, you should move this inside your map so that todo.id refers to the iterator of the map():
{todos.map((todo) => (
<React.Fragment>
<div>ID: {todo.id} Task: {todo.text}</div>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</React.Fragment>
))}
Updated Demo:
const { useState } = React;
const App = () => {
const [todos, setTodos] = useState([]);
const [todo, setTodo] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
};
if (newTodo.text.length > 0) {
setTodos([...todos].concat(newTodo));
setTodo("");
} else {
alert("Enter Valid Task");
setTodo("");
}
}
const deleteTodo = (id) => {
let updatedTodos = [...todos].filter((todo) => todo.id !== id);
setTodos(updatedTodos);
}
return (
<div>
<h1>To-do List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new task"
value={todo}
/>
<button type="submit">Add Todo</button>
</form>
{todos.map((todo) => (
<React.Fragment>
<div>ID: {todo.id} Task: {todo.text}</div>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</React.Fragment>
))}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Try this:
const button = (t) => <button onClick={() => deleteTodo(t.id)}>Delete</button>
and then, in the map
{todos.map((todo) => <div>ID: {todo.id} Task: {todo.text} {button(todo)}</div>)}
this way, the "delete todo" button will be bound to the specific todo ID, avoiding being bound to whatever the current value of todo is in the app.
Its better to update the state using setState. Muting the state directly breaks the primary principle of React's data flow (which is made to be unidirectional), making your app very fragile and basically ignoring the whole component lifecycle.
Also You need to change the delete from string to function and pass the id or place the jsx directly inside map function.
import React, { useState } from 'react'
const App = () => {
const [todos, setTodos] = useState([])
const [todo, setTodo] = useState('')
const handleSubmit = e => {
e.preventDefault()
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
}
if (newTodo.text.length > 0) {
setTodos([...todos].concat(newTodo))
setTodo('')
} else {
alert('Enter Valid Task')
setTodo('')
}
}
/*
* Changed Here
*/
const deleteTodo = id => {
setTodos(prevState => {
return prevState.filter(todo => todo?.id != id)
})
}
const button = id => <button onClick={() =>
deleteTodo(id)}>Delete</button>
return (
<div>
<h1>To-do List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={e => setTodo(e.target.value)}
placeholder="Add a new task"
value={todo}
/>
<button type="submit">Add Todo</button>
</form>
{todos.map(todo => (
<div key={todo.id}>
ID: {todo.id} Task: {todo.text} {button(todo.id)}
</div>
))}
</div>
)
}
export default App
Problem in:
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
You can use
const Button = (props) => {
return (
<button
className={`btn ${props.className}`}
title={`${props.title}`}
onClick={props.onClick ? () => props.onClick() : null}
>
{props.children}
</button>
);
};
after that, call it like this
<Button className="delete" title="delete" onClick={()=>deleteTodo(todo.id)}>Delete</Button>

React - Elements not rendering properly

I'm making a note taking app. I have an array set to state that holds the notes and each note is set to state as an object. My NoteList component maps over the array to render a new Note component when the save button is pressed. Everything works so far, except for the first note. When the first note is saved, the delete button renders but not the user input or date. On every subsequent save, everything renders how it should. I've looked over my code but I'm not sure what is causing this. Can someone please point me in the right direction?
import { useState } from 'react';
import uniqid from 'uniqid';
import NoteList from './components/NoteList';
function App() {
const [note, setNote] = useState({
note: '',
date: '',
id: ''
})
const [notes, setNotes] = useState([])
const [error, setError] = useState(false);
function handleAddNote(text) {
const date = new Date();
const newNote = {
text: text,
date: date.toLocaleDateString(),
id: uniqid()
}
setNote(newNote);
const newNotes = [
...notes,
note
]
setNotes(newNotes);
}
return (
<div className="App">
<h1>My Notes</h1>
<input placeholder='Type to search...'></input>
<NoteList notes={notes} handleAddNote={handleAddNote}/>
</div>
);
}
export default App;
import Note from './Note'
import AddNote from './AddNote'
function NoteList({ notes, handleAddNote }) {
return (
<div className='list-container'>
{notes.map((note) => (
<Note text={note.text} id={note.id} date={note.date}
key={note.id} notes={notes} note={note}/>
))}
<AddNote handleAddNote={handleAddNote}/>
</div>
)
}
export default NoteList;
function Note({ note }) {
return (
<div className='note-container'>
<span className='note-text'>{note.text}</span>
<div className='note-footer'>
<p className='note-date'>{note.date}</p>
<button>Delete</button>
</div>
</div>
)
}
export default Note;
import { useState } from 'react';
function AddNote({ handleAddNote } ) {
const [noteText, setNoteText] = useState('');
function handleChange(e) {
setNoteText(e.target.value);
}
function handleSaveNote() {
if (noteText) {
handleAddNote(noteText);
setNoteText('');
}
}
return (
<div className='new-note-container'>
<textarea onChange={handleChange} value={noteText}
rows='5' cols='30' placeholder='Type to enter a note...'
</textarea>
<div className='count-container'>
<p>Character Count</p>
<button onClick={handleSaveNote}>Save</button>
</div>
</div>
)
}
export default AddNote;
I think that the thing you are missing is that after calling setNote it does not change note on the current render. Only in the next render for that component note will get the new state.
In your case I don't see way you need to have a state for the new note so you can change your App component to be something like this:
function App() {
const [notes, setNotes] = useState([])
const [error, setError] = useState(false);
function handleAddNote(text) {
const date = new Date();
const newNote = {
text: text,
date: date.toLocaleDateString(),
id: uniqid()
}
setNotes((prevNotes) => [...prevNotes, newNote]);
}
return (
<div className="App">
<h1>My Notes</h1>
<input placeholder='Type to search...'></input>
<NoteList notes={notes} handleAddNote={handleAddNote}/>
</div>
);
}
All of these functions, such as adding notes, deleting notes, and searching for notes, are implemented in this code and work properly. I think this might be helpful for you!
import { useState } from "react";
import { uuid } from "uuidv4";
const SelectChip = () => {
const [notes, setNotes] = useState([]);
const [searchTerm, setSearchTerm] = useState("");
function handleAddNote(text) {
const date = new Date();
setNotes((prev) => [
...prev,
{
text: text,
date: date.toLocaleDateString(),
id: uuid()
}
]);
}
return (
<div className="App">
<h1>My Notes</h1>
<input
value={searchTerm}
onChange={(event) => setSearchTerm(event.target.value)}
placeholder="Type to search..."
/>
<NoteList
notes={notes}
setNotes={setNotes}
handleAddNote={handleAddNote}
search={searchTerm}
/>
</div>
);
};
export default SelectChip;
function NoteList({ notes, setNotes, handleAddNote, search }) {
const filteredItems = notes.filter((item) =>
item.text.toLowerCase().includes(search.toLowerCase())
);
return (
<div className="list-container">
{filteredItems.map((note) => {
return (
<Note
text={note.text}
id={note.id}
date={note.date}
key={note.id}
setNotes={setNotes}
note={note}
/>
);
})}
<AddNote handleAddNote={handleAddNote} />
</div>
);
}
function Note({ note, setNotes }) {
function handleDelete(noteId) {
setNotes((prev) => prev.filter((note) => note.id !== noteId));
}
return (
<div className="note-container">
<span className="note-text">{note.text}</span>
<div className="note-footer">
<p className="note-date">{note.date}</p>
<button onClick={() => handleDelete(note.id)}>Delete</button>
</div>
</div>
);
}
function AddNote({ handleAddNote }) {
const [noteText, setNoteText] = useState("");
function handleChange(e) {
setNoteText(e.target.value);
}
function handleSaveNote() {
if (noteText) {
handleAddNote(noteText);
setNoteText("");
}
}
return (
<div className="new-note-container">
<textarea
onChange={handleChange}
value={noteText}
rows="5"
cols="30"
placeholder="Type to enter a note..."
></textarea>
<div className="count-container">
<p>Character Count</p>
<button onClick={handleSaveNote}>Save</button>
</div>
</div>
);
}

How to pass multiple onChange form data from child to parent element in react

I am trying to print real-time user input from input tags by the user. I am even getting multiple user inputs from the child element to the parent element as a form of an object using useState. But whenever the user tries to fill the second input field, then the first input is re-render and it's replaced by the primary state which is an empty string.
code:-
Child Element
import React, { useState } from "react";
const Child = (props) => {
const [name, setName] = useState("");
const [age, setAge] = useState("");
let userData = {
name: "",
age: ""
};
const nameChangeHandler = (e) => {
setName(e.target.value);
userData.name = e.target.value;
};
const ageChangeHandler = (e) => {
setAge(e.target.value);
userData.age = e.target.value;
};
const formOnChageHandler = (e) => {
e.preventDefault();
props.getData(userData);
};
const fromOnSubmitHandler = (e) => {
e.preventDefault();
};
return (
<React.Fragment>
<form onChange={formOnChageHandler} onSubmit={fromOnSubmitHandler}>
<label htmlFor="name">Name:</label>
<input
id="name"
placeholder="Enter Name"
value={name}
onChange={nameChangeHandler}
/>
<br />
<label htmlFor="age">Age:</label>
<input
id="age"
placeholder="Enter Age"
value={age}
onChange={ageChangeHandler}
/>
</form>
</React.Fragment>
);
};
export default Child;
Parent Element
import React, { useState } from "react";
import Child from "./components/Child";
function App() {
const [name, setName] = useState("");
const [age, setAge] = useState("");
let userData = (data) => {
setName(data.name);
setAge(data.age);
};
return (
<React.Fragment>
<Child getData={userData} />
<h1>Your name is:{name}</h1>
<h1>Your age is:{age}</h1>
</React.Fragment>
);
}
export default App;
code sandbox Link- https://codesandbox.io/s/from-traversing-child-to-parent-to-another-child-ynwyqd?file=/src/App.js:0-441
How I can get both data being reflected by using onChange from child to parent element?
I suggest you accumulate the user data in one state.
Like this.
const [user, setUser] = useState({
name: "",
age: null
});
And put the state on the parent and pass as props, also just have one handleChange function to update both the name and age by the input id
Child.js
import React, { useState } from "react";
const Child = ({ user, setUser }) => {
const handleChange = (e) => {
setUser((prev) => ({
...prev,
[e.target.id]: e.target.value
}));
};
const formOnChageHandler = (e) => {
e.preventDefault();
};
const fromOnSubmitHandler = (e) => {
e.preventDefault();
};
return (
<React.Fragment>
<form onChange={formOnChageHandler} onSubmit={fromOnSubmitHandler}>
<label htmlFor="name">Name:</label>
<input
id="name"
placeholder="Enter Name"
value={user.name}
onChange={handleChange}
/>
<br />
<label htmlFor="age">Age:</label>
<input
id="age"
placeholder="Enter Age"
value={user.age}
onChange={handleChange}
/>
</form>
</React.Fragment>
);
};
export default Child;
App.js
import React, { useState } from "react";
import Child from "./components/Child";
function App() {
const [user, setUser] = useState({
name: "",
age: null
});
return (
<React.Fragment>
<Child user={user} setUser={setUser} />
<h1>Your name is:{user.name}</h1>
<h1>Your age is:{user.age}</h1>
</React.Fragment>
);
}
export default App;
CODESANDBOX
Try using the child component as below,
import React, { useState } from "react";
const Child = (props) => {
const [name, setName] = useState("");
const [age, setAge] = useState("");
let userData = {
name: name, // the value "name" comes for the local state will be listen to the onChange event every time
age: age // same applies here as well
};
const nameChangeHandler = (e) => {
setName(e.target.value);
};
const ageChangeHandler = (e) => {
setAge(e.target.value);
};
const formOnChageHandler = (e) => {
e.preventDefault();
props.getData(userData);
};
const fromOnSubmitHandler = (e) => {
e.preventDefault();
};
return (
<React.Fragment>
<form onSubmit={fromOnSubmitHandler}>
<label htmlFor="name">Name:</label>
<input
id="name"
placeholder="Enter Name"
value={name}
onChange={nameChangeHandler}
/>
<br />
<label htmlFor="age">Age:</label>
<input
id="age"
placeholder="Enter Age"
value={age}
onChange={ageChangeHandler}
/>
</form>
</React.Fragment>
);
};
export default Child;
just use simple one state to manage data. just take a look below example component created from your child component.
we simply use single object state.
use name prop as key to store value in state.
import React, { useState } from "react";
const Child = (props) => {
const [formData, setFormData] = useState({});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
};
const formOnChageHandler = (e) => {
e.preventDefault();
props.getData(userData);
};
const fromOnSubmitHandler = (e) => {
e.preventDefault();
};
return (
<React.Fragment>
<form onChange={formOnChageHandler} onSubmit={fromOnSubmitHandler}>
<label htmlFor="name">Name:</label>
<input
id="name"
placeholder="Enter Name"
value={name}
onChange={handleChange}
name="name"
/>
<br />
<label htmlFor="age">Age:</label>
<input
id="age"
placeholder="Enter Age"
value={age}
onChange={handleChange}
name="age"
/>
</form>
</React.Fragment>
);
};
export default Child;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
it happens because you dont watch to the state, try this:
Child.js
import React, { useState } from "react";
const Child = (props) => {
const [name, setName] = useState("");
const [age, setAge] = useState("");
let userData = {
name,
age
};
const nameChangeHandler = (e) => {
setName(e.target.value);
userData.name = e.target.value;
};
const ageChangeHandler = (e) => {
setAge(e.target.value);
userData.age = e.target.value;
};
const formOnChageHandler = (e) => {
e.preventDefault();
props.getData(userData);
};
const fromOnSubmitHandler = (e) => {
e.preventDefault();
};
return (
<React.Fragment>
<form onChange={formOnChageHandler} onSubmit={fromOnSubmitHandler}>
<label htmlFor="name">Name:</label>
<input
id="name"
placeholder="Enter Name"
value={name}
onChange={nameChangeHandler}
/>
<br />
<label htmlFor="age">Age:</label>
<input
id="age"
placeholder="Enter Age"
value={age}
onChange={ageChangeHandler}
/>
</form>
</React.Fragment>
);
};
export default Child;
Try this, i check in codesandbox and it works:
In App.js:
import React, { useState } from "react";
import Child from "./components/Child";
function App() {
const [name, setName] = useState("");
const [age, setAge] = useState("");
return (
<React.Fragment>
<Child name={name} age={age} setName={setName} setAge={setAge} />
<h1>Your name is:{name}</h1>
<h1>Your age is:{age}</h1>
</React.Fragment>
);
}
export default App;
In Child.js:
import React, { useState } from "react";
const Child = ({ name, age, setName, setAge }) => {
const nameChangeHandler = (e) => {
setName(e.target.value);
};
const ageChangeHandler = (e) => {
setAge(e.target.value);
};
const fromOnSubmitHandler = (e) => {
e.preventDefault();
};
return (
<React.Fragment>
<form onSubmit={fromOnSubmitHandler}>
<label htmlFor="name">Name:</label>
<input
id="name"
placeholder="Enter Name"
value={name}
onChange={nameChangeHandler}
/>
<br />
<label htmlFor="age">Age:</label>
<input
id="age"
placeholder="Enter Age"
value={age}
onChange={ageChangeHandler}
/>
</form>
</React.Fragment>
);
};
export default Child;
If you want to improve your code, you can research and use state management like: redux, zustand, react context,...
Hope it useful for you.

React: Fetch Data onSubmit, not on onChange

I got this code working pretty much how I want it. However, it's fetching & display data after each keystroke. I only want it to fetch once, when I hit submit.
Also, if there's anything i'm doing that's not "best practice" please let me know so I don't make silly mistakes in the future.
import React, { useEffect, useState } from "react";
export default function App() {
const [data, setData] = useState(null);
const [query, setQuery] = useState("");
useEffect(() => {
if (!query) return;
async function fetchData() {
const response = await fetch(
`https://www.omdbapi.com/?apikey=2e8b5857&s=${query}`
);
const data = await response.json();
const results = data.Search;
setData(results);
}
fetchData();
}, [query]);
const handleSubmit = (e) => {
e.preventDefault();
setQuery(query);
};
return (
<div
style={{
margin: 20,
}}
>
<form onSubmit={handleSubmit}>
<br />
<label>
Input Movie:{" "}
<input
type="text"
placeholder="ex. Harry Potter"
value={query}
onChange={(e) => {
setQuery(e.target.value);
}}
/>
</label>
<input type="submit" value="Submit" onClick={() => setQuery} />
</form>
{data &&
data.map((movie) => (
<div key={movie.imdbID}>
<h1>{movie.Title}</h1>
<h4>
{movie.Year} | {movie.imdbID}
</h4>
<img alt={movie.imdbID} src={`${movie.Poster}`} />
</div>
))}
</div>
);
}
Since you only want it after submit, you can skip the useEffect with [query] and just copy the same logic inside your handleSubmit like so :-
import React, { useEffect, useState } from "react";
export default function App() {
const [data, setData] = useState(null);
const [query, setQuery] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (!query) return;
async function fetchData() {
const response = await fetch(
`https://www.omdbapi.com/?apikey=2e8b5857&s=${query}`
);
const data = await response.json();
const results = data.Search;
setData(results);
}
fetchData();
};
return (
<div
style={{
margin: 20,
}}
>
<form onSubmit={handleSubmit}>
<br />
<label>
Input Movie:{" "}
<input
type="text"
placeholder="ex. Harry Potter"
value={query}
onChange={(e) => {
setQuery(e.target.value);
}}
/>
</label>
<input type="submit" value="Submit"/>
</form>
{data &&
data.map((movie) => (
<div key={movie.imdbID}>
<h1>{movie.Title}</h1>
<h4>
{movie.Year} | {movie.imdbID}
</h4>
<img alt={movie.imdbID} src={`${movie.Poster}`} />
</div>
))}
</div>
);
}
Here's the codesandbox :-
Pass the code that is inside the useEffect, that is, the fetch function, inside the submit function. leaving useEffect unused

Managing the state and working with inputs

I'm making a recipe box App, and I decided to start by my handleTitle and handleInput methods. I was able to pull that out, but now I want to make an array of objects (the objects contain the title and the description) and then I would map through this array and display the data.
but my handleSubmit function is not working the way I wanted. I want the user to be able to write several titles and descriptions, and those will keep being added to the recipes array in the state. Take a look at the code:
import React, { useState } from "react";
import "./styles.css";
import { Form } from "./components/containers/Form";
export default function App() {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [recipe, setRecipe] = useState([]);
const handleTitle = e => {
setTitle(e.target.value);
};
const handleDescription = e => {
setDescription(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
if (title !== "" && description !== "") {
setRecipe(prevState => {
const data = { title: title, description: description };
return {
...prevState,
recipe: prevState.recipe.concat(data)
};
});
}
};
return (
<div className="App">
<Form
title={title}
description={description}
handleTitle={handleTitle}
handleDescription={handleDescription}
handleSubmit={handleSubmit}
/>
</div>
);
}
import React from "react";
export const Form = ({
handleTitle,
handleDescription,
handleSubmit,
title,
description
}) => {
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={handleTitle}
placeholder="title"
value={title}
/>
<input
type="text"
onChange={handleDescription}
placeholder="description"
value={description}
/>
<button>Add</button>
</form>
</div>
);
};
When you set the recipes, you're changing the primitive type of recipes state to an object. Instead you should just return a new array with the previous recipes and the new recipe.
I've attached a runnable example below:
const Form = ({
handleTitle,
handleDescription,
handleSubmit,
title,
description
}) => {
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={handleTitle}
placeholder="title"
value={title}
/>
<input
type="text"
onChange={handleDescription}
placeholder="description"
value={description}
/>
<button>Add</button>
</form>
</div>
);
};
function App() {
const [title, setTitle] = React.useState("");
const [description, setDescription] = React.useState("");
const [recipes, setRecipes] = React.useState([]);
const handleTitle = e => {
setTitle(e.target.value);
};
const handleDescription = e => {
setDescription(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
if (title !== "" && description !== "") {
setRecipes(prevState => {
const data = { title: title, description: description };
return [...prevState, data];
});
setTitle("");
setDescription("");
}
};
return (
<div className="App">
<Form
title={title}
description={description}
handleTitle={handleTitle}
handleDescription={handleDescription}
handleSubmit={handleSubmit}
/>
<h5>Recipes</h5>
{recipes.length === 0
? (
<div>No recipes</div>
)
: (
<ul>
{recipes.map(({ title, description }) => (
<li>
{title} : {description}
</li>
))}
</ul>
)}
</div>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Categories

Resources