React - Elements not rendering properly - javascript

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>
);
}

Related

Problem rendering data from Local Storage in React App

I have problem with rendering data from local storage on every refresh or reload. It renders only hard coded data but not data that I save in LS. It shows data in LS but not rendering. If anyone could explain or tell me what is wrong or give me directions to do something better would be grateful.I am farely new in Reactand would apretiate for insights. I ve put some dummy data. I ve sent componnets which could affect.
import { useState, useEffect } from "react";
//COMPONENTS:
import ScrollToTop from "./components/ScrollToTop";
import Footer from "./components/Footer";
import Home from "./components/Home";
import NavBar from "./components/NavBar";
import PhoneBook from "./components/PhoneBook";
function App() {
const date = new Date().toLocaleDateString();
const [contacts, setContacts] = useState([
{
id: Math.random().toString(36).substr(2, 9),
fullName: "Vekjko Petrovic",
address: "121 Town Commons Way Phoenix, AZ, 45215",
phone: 123_465_689,
date,
},
{
id: Math.random().toString(36).substr(2, 9),
fullName: "Marko Petrovic",
address: "Srbina 35, 11300 Smederevo Srbija",
phone: 256_269_866,
date,
},
{
id: Math.random().toString(36).substr(2, 9),
fullName: "Michael Jackson",
address: "52 City St, Detroit, Mi, 46218",
phone: 359_525_555,
date,
},
{
id: Math.random().toString(36).substr(2, 9),
fullName: "Vanessa Parady",
address: "11 Beogradska Beograd, SRB, 11000",
phone: 123_465_689,
date,
},
]);
useEffect(() => {
const savedContacts = JSON.parse(localStorage.getItem("contacts"));
if (savedContacts) {
setContacts(savedContacts);
}
}, []);
useEffect(() => {
localStorage.setItem("contacts", JSON.stringify(contacts));
}, [contacts]);
const [searchContact, setSearchContact] = useState("");
const [theme, setTheme] = useState("dark");
const changeTheme = () => {
theme === "dark" ? setTheme("light") : setTheme("dark");
};
const addContact = (fullName, phone, address, email) => {
const newContacts = {
id: Math.random().toString(36).substr(2, 9),
fullName,
address,
phone,
email,
date,
};
const newContact = [...contacts, newContacts];
setContacts(newContact);
};
const deleteContact = (id) => {
const remainedContacts = contacts.filter((item) => item.id !== id);
setContacts(remainedContacts);
};
return (
<div data-theme={theme} className="app-container">
<ScrollToTop />
<NavBar changeTheme={changeTheme} currentTheme={theme} />
<Home />
<PhoneBook
contacts={contacts.filter((contact) =>
contact.fullName.toLowerCase().includes(searchContact)
)}
handleAddContact={addContact}
deleteContact={deleteContact}
handleSearchContacts={setSearchContact}
/>
<Footer />
</div>
);
}
export default App;
import React from "react";
import "../index.css";
//ASSETS:
import NewContact from "./NewContact";
import Contact from "./Contact";
import Search from "./Search";
function PhoneBook({
contacts,
handleAddContact,
deleteContact,
handleSearchContacts,
}) {
return (
<div id="phone_book" className="contacts-list">
<Search handleSearchContacts={handleSearchContacts} />
{contacts.map((contact) => {
return (
<Contact
key={contact.id}
id={contact.id}
fullName={contact.fullName}
address={contact.address}
phone={contact.phone}
email={contact.email}
date={contact.date}
deleteContact={deleteContact}
/>
);
})}
<NewContact handleAddContact={handleAddContact} />
</div>
);
}
export default PhoneBook;
import React from "react";
import profile from "../assets/images/profile.png";
import { MdDeleteForever } from "react-icons/md";
function Contact({ fullName, address, phone, email, id, date, deleteContact }) {
return (
<div className="contact">
<p className="contact-header">
<span>
<i>{fullName} </i>
</span>
<img src={profile} alt="profile" />
</p>
<div className="contact-footer">
<p>
{" "}
<i>Address: </i>
{address}
</p>
<p>
<i>Phone:</i> {phone}
</p>
<p>
{" "}
<i>Email:</i> {email}
</p>
<MdDeleteForever
onClick={() => deleteContact(id)}
className="delete-icon"
size="1.3rem"
/>
<p className="span-date">
<i>Date: </i>
{date}
</p>
</div>
</div>
);
}
export default Contact;
import React, { useState } from "react";
function NewContact({ handleAddContact }) {
const [fullName, setFullName] = useState("");
const [phone, setPhone] = useState("");
const [address, setAddress] = useState("");
const [email, setEmail] = useState("");
const handleSaveClick = () => {
if (!(fullName.trim().length > 0)) {
return;
}
handleAddContact(fullName, phone, address, email);
setFullName("");
setPhone("");
setAddress("");
setEmail("");
};
return (
<div className="contact new last">
{" "}
<p className="inputs">
<span>Create New Contact</span>
<label>Full Name:</label>
<input
type="text"
placeholder="Enter..."
value={fullName}
onChange={(e) => setFullName(e.target.value)}
/>
<label> Address:</label>
<input
type="text"
placeholder="Enter..."
value={address}
onChange={(e) => setAddress(e.target.value)}
/>
<label> Phone:</label>
<input
type="text"
placeholder="Enter..."
value={phone}
onChange={(e) => setPhone(e.target.value)}
/>
<label>Email:</label>
<input
type="text"
placeholder="Enter..."
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</p>
{/* <img src={profile} alt="fullname" /> */}
<div className="save-list-footer">
<button className="save" onClick={handleSaveClick}>
SAVE
</button>
</div>
</div>
);
}
export default NewContact;
import styled from "styled-components";
import React from "react";
import { MdSearch } from "react-icons/md";
//STYLES
import "../index.css";
function Search({ handleSearchContacts }) {
return (
<SearchComponent className="search">
<MdSearch className="search-icon" size="1.3em" />
<input
type="text"
placeholder="Search..."
onChange={(e) => handleSearchContacts(e.target.value)}
/>
</SearchComponent>
);
}
export default Search;
Took me some messing around but I think I have an example that might be doing what you are describing. It seems like the following code may be the culprit:
useEffect(() => {
localStorage.setItem("contacts", JSON.stringify(contacts));
}, [contacts]);
Not in your example I assume you are initializing contacts like such:
const [contact, setContacts] = useState([])
When this state is initialized it will trigger that useEffect and set the localStorage.setItem("contacts" , []) which will make it look like nothing is being rendered. I think the easiest fix would be to move the localStorage.setItem into the addContacts function.
Here is a simplified version of how to set it up:
export const Contacts = () => {
const [contacts, setContacts] = useState([]);
useEffect(() => {
const savedContacts = localStorage.getItem("contacts");
if (savedContacts) {
setContacts(JSON.parse(savedContacts));
}
}, []);
// useEffect(() => {
// //This is your issue here Comment out this block and comment in the setItem in the addContact
// localStorage.setItem("contacts", JSON.stringify(contacts));
// }, [contacts]);
const addContact = (newContact) => {
const newContactList = [...contacts, newContact];
setContacts(newContactList);
localStorage.setItem("contacts", JSON.stringify(newContactList));
};
return (
<div>
<InputContact addContact={addContact} />
{contacts.map((data, i) => (
<Contact data={data} key={i} />
))}
</div>
);
};
You can find a working example of this on code sandbox. There is some explanation of the app in the App.js and Contacts.jsx. https://codesandbox.io/s/localstorage-contacts-s9dfzc?file=/src/Contacts.jsx:130-969

How can I get my Note element to update when I change its text?

sorry for the long post, but I'm really stuck in this part of my project. I'm using React to build a notes app, and I can't get my note to update when I alter the text inside of my modal popup...the commented out parts are where I'm having trouble (I wrote what I thought of doing, but don't know how. I've been using React for less than a week). Thank you in advance!
App.js
import React, { useState } from "react";
import { nanoid } from "nanoid";
import NotesList from "./Components/NotesList";
import AddNote from "./Components/AddNote";
const App = () => {
const [notes, setNotes] = useState([
/*empty array of notes*/
]);
const addNote = (title, text) => {
const date = new Date();
const newNote = {
id: nanoid(),
title: title,
text: text,
date: date.toDateString(),
};
const newNotes = [...notes, newNote];
setNotes(newNotes);
};
const deleteNote = (id) => {
const newNotes = notes.filter((note) => note.id !== id);
setNotes(newNotes);
};
const editNote = (text, title, id) => {
const editDate = new Date();
const editedNote = {
id: nanoid(),
title: title,
text: text,
date: editDate.toDateString(),
};
const editedNotes = notes.map((note) => {
if (note.id == id) {
return editedNote;
}
return note;
});
setNotes(editedNotes);
};
return (
<div className="container">
<AddNote handleAddNote={addNote} />
<NotesList
notes={notes}
handleAddNote={addNote}
handleDeleteNote={deleteNote}
editNote={editNote}
updateNote={updateNote}
/>
</div>
);
};
export default App;
NotesList.js
import Note from "./Note";
import React, { useState } from "react";
import MyModal from "./Modal";
const NotesList = ({ notes, handleDeleteNote, updateNote })=> {
const [open, setOpen] = useState(false);
const [note, setNote] = useState({});
const handleOpen = (id) => {
setNote(notes.filter((note) => note.id === id)[0]);
setOpen(true);
};
const handleClose = () => {
setNote({});
setOpen(false);
};
const clickedTodo = useState({});
return (
<div className="notes-list">
<div className="blank-note"></div>
<div className="existing-notes">
<MyModal
note={note}
clickedTodo={clickedTodo}
handleClose={handleClose}
open={open}
updateNote={updateNote}
/>
{notes.map((note) => (
<Note
id={note.id}
title={note.title}
text={note.text}
date={note.date}
handleOpen={handleOpen}
handleDeleteNote={handleDeleteNote}
/>
))}
</div>
</div>
);
};
export default NotesList;
AddNote.js
import { useState } from "react";
import React from "react";
const AddNote = ({ handleAddNote, isUpdate, note, updateNote }) => {
const [noteTitle, setNoteTitle] = useState("");
const [noteText, setNoteText] = useState("");
const characterLimit = 300;
const handleChange = (event) => {
if (characterLimit - event.target.value.length >= 0) {
setNoteText(event.target.value);
}
};
const handleChange2 = (event) => {
if (characterLimit - event.target.value.length >= 0) {
setNoteTitle(event.target.value);
}
};
const handleSaveClick = () => {
// if (!isUpdate) then do this
// if (!isUpdate) {
(noteText.trim().length > 0)
handleAddNote(noteTitle, noteText);
setNoteText("");
setNoteTitle("");
// } else updateNote(note.id)
// else we use all of the info we have gotten
// we call update note with note.id
};
return (
<div className="note new">
<h2>Add note</h2>
<textarea className="new-text-title"
placeholder={!isUpdate?"Add a title...":note.title}
value={noteTitle}
onChange={handleChange2}
></textarea>
<textarea className="new-text-body"
cols="8"
rows="10"
placeholder={!isUpdate?"Type your note here...":note.text}
value={noteText}
onChange={handleChange}
></textarea>
<div className="note-footer">
<small>Characters remaining: {characterLimit - noteText.length}</small>
<button className="save" onClick={handleSaveClick}>
<strong>Save</strong>
</button>
</div>
</div>
);
};
export default AddNote;
Note.js
import { MdDeleteForever } from "react-icons/md";
const Note = (props) => {
const { id, title, text, date, handleOpen, handleDeleteNote } = props;
const handleOpenModal = (id) => {
handleOpen(id);
};
return (
<div className="note">
<div className="note-upper" onClick={() => handleOpenModal(id)}>
<p className="title">
<textarea className="text-title">{title}</textarea>
</p>
<textarea className="text-body">{text}</textarea>
</div>
<div className="note-footer">
<div className="footer-left" onClick={() => handleOpenModal(id)}>
<small>{date}</small>
</div>
<div className="footer-right">
<MdDeleteForever
onClick={() => {
if (window.confirm("Delete this note?")) {
handleDeleteNote(id);
}
}}
className="delete-icon"
size="1.3em"
/>
</div>
</div>
</div>
);
};
export default Note;
Modal.js
import React, { useState } from "react";
import { Modal, Box } from "#mui/material";
import Note from "./Note";
export default function MyModal(props) {
const { open, handleClose, note, updateNote } = props;
return (
<div onClose={console.log("closing")}>
<Modal open={open} onClose={handleClose}>
<Box
sx={{
position: "relative",
top: "50%",
left: "32%",
outline: "none",
}}
>
<Note
updateNote={updateNote}
note={note}
id={note.id}
title={note.title}
text={note.text}
date={note.date}
/>
</Box>
</Modal>
</div>
);
}
Your Modal.js requires updateNote prop, but in App.js you don't supply it to NotesList.js, thus the parent prop of MyModal.js has no such prop:
<NotesList
notes={notes}
handleAddNote={addNote}
handleDeleteNote={deleteNote}
editNote={editNote}
/>
And later in NotesList.js:
<MyModal
note={note}
clickedTodo={clickedTodo}
handleClose={handleClose}
open={open}
updateNote={updateNote} // this is missing because you are not
// passing this prop from the App component
/>

React js useState&useEffect array duplicates elements after a change

I am a beginner in react js programming. I'm trying to do the todo project, which is a classic project. When I delete or add an element from the list, the newly formed list appears on the screen by combining with the previous one, I will show it with a picture below. I did not understand the source of the eror so wanted to post it here to get some advices suggestions about why it is happening.Thank you.(I am getting and storing data in firebase firestore database)
Before Adding an element initial array state
After adding an element to the array.
I am using useState for array and using useEffect to get initial data
MainPage.js that contains form and the list components.
const MainPage = () => {
const [isLoading, setLoding] = useState(true);
const [array, setArray] = useState([]);
const sub = async (email) => {
var result = [];
await onSnapshot(doc(db, "users", email), (doc) => {
var data = doc.data().todos;
data.forEach((element) => {
Object.keys(element).map(() => {
result.push(element["title"]);
});
});
setArray(result);
setLoding(false);
});
};
useEffect(() => {
sub(auth.currentUser.email);
}, []);
const onAddToDo = (todoTitle) => {
setArray((prevAray) => {
return [...prevAray, todoTitle];
});
};
const onRemove = (title) => {
setArray((prevAray) => {
return [array.pop(array.indexOf(title))];
});
};
return (
<div>
{isLoading && <h1>Loading</h1>}
{!isLoading && (
<div>
<section>
<NavBar></NavBar>
<ToDoForm passData={onAddToDo} />
</section>
<section>
<CardList removeCards={onRemove} array={array} />
</section>
</div>
)}
</div>
);
};
export default MainPage;
Firebase.js that stores the firebase update methods
export const deleteItem = (title) => {
updateDoc(doc(db, "users", auth.currentUser.email), {
todos: arrayRemove({ title: title }),
});
};
export const addnewTodo = (title) => {
updateDoc(doc(db, "users", auth.currentUser.email), {
todos: arrayUnion({ title: title }),
});
};
TodoForm.js component
const ToDoForm = (props) => {
const [todoTitle, setTitle] = useState("");
const titleChangeHandler = (event) => {
setTitle(event.target.value);
};
const newTodoAdder = (event) => {
event.preventDefault();
addnewTodo(todoTitle);
props.passData(todoTitle);
};
return (
<div className="form_holder">
<div className="form_container">
<form onSubmit={newTodoAdder}>
<h3>Add Events</h3>
<label>Title</label>
<input
onChange={titleChangeHandler}
type="text"
placeholder="Title"
id="title"
></input>
<div className="holder">
<button type="sumbit">Add</button>
</div>
</form>
</div>
</div>
);
};
export default ToDoForm;
CardList.js component
const CardList = (props) => {
const array = props.array;
if (array.length === 0) {
return (
<div className="grid_container">
<h2>Found no todos</h2>
</div>
);
}
return (
<div className="grid_container">
{array.map((element, index) => {
return (
<Card
removeSelf={() => {
props.removeCards(element);
}}
key={index}
title={element}
/>
);
})}
</div>
);
};
export default CardList;
Card.js component
const Card = (props) => {
const handleRemove = (event) => {
event.preventDefault();
deleteItem(props.title);
props.removeSelf();
};
return (
<div className="card">
<h2 className="card__title">{props.title}</h2>
<button type="button" onClick={handleRemove}>
Delete
</button>
</div>
);
};
export default Card;
EDIT ;
Index.js file
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
SOLUTION
I fixed the issue by changing the add and remove functions that were inside of MainPage.js file You can see the new versions bellow. Hope someday it will help somebody.
Use effect was called once all I had to do get the data again after a change...
New Remove and Add functions
const onAddToDo = (todoTitle) => {
console.log(todoTitle + " Added");
sub(auth.currentUser.email);
};
const onRemove = (title) => {
console.log(title + " Deleted");
sub(auth.currentUser.email);
};

getting empty item and same item when fetching data react hook

I ma creating app to get pokemons, first I save them in a list and after that show them, but I am getting empty card and after that the same pokemn is showing even if I am searching for another one.
Also I am getting distortion view when I use my component
import { useState, useEffect } from "react";
import "./App.css";
import { v4 } from "uuid";
import Button from "./Components/Button";
import Input from "./Components/Input";
import Label from "./Components/Label";
import Card from "./Components/Card";
import axios from "axios";
function App() {
// const [textFromInput, setTextFromInput] = useState("");
const [name, setName] = useState("");
const [nameFromButtonClick, setNameFromButtonClick] = useState("");
const [pokemon, setPokemon] = useState({
name: "",
picture: "",
id: 0,
type1: "",
type2: "",
});
const [list, setList] = useState([]);
const handleChange = (event) => setName(event.target.value);
const handleClick = () => {
setNameFromButtonClick(name);
setList([...list, pokemon]);
};
// const handleClick = () => setList([...list, pokemon]);
useEffect(() => {
axios
.get(`https://pokeapi.co/api/v2/pokemon/${name}/`)
.then((res) => {
console.log(res);
// setPokemon(res.data);
console.log("res.data=>", res.data);
setPokemon({
name: res.data.name,
picture: res.data.sprites.front_default,
id: res.data.id,
type1: res.data.types[0].type.name,
type2: res.data.types[1].type.name,
});
})
.catch((err) => {
console.log(err);
});
}, [nameFromButtonClick]);
return (
<div className="App">
<div>
<h1>Pokémon Effect</h1>
</div>
<div className="centered">
<div className="container">
{list.map((entry) => (
<Card key ={v4()}
name={entry.name}
picture={entry.picture}
id={entry.id}
type1={entry.type1}
type1={entry.type2}
/>
))}
</div>
<div className="dashboard">
<Input className="input" value={name} onChange={handleChange} />
<Button
className="getPokemon"
text="GetPokemon"
onClick={handleClick}
/>
<Label text={name} />
</div>
</div>
</div>
);
}
export default App;
this is my component Card, I don't know how to make it look like when I ma writing directly in app.js
export default function Card(props){
const{name, picture,id,type1,type2}=props
return(
<div className="card">
<div><img src={picture} alt={name} /></div>
<p>n:{id}</p>
<div> name={name}</div>
<div className="pokeType">
<div className="classType">type={type1}</div>
<div className="classType">type={type2}</div>
</div>
</div>
)
}

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