Hey I am learning reactjs as much as i have learned I am trying to make note app
my code given below
my App.js file
import React , {useEffect, useState} from "react"
import { nanoid } from "nanoid"
import Editor from './Note/Editor'
import Sidebar from "./Note/Sidebar"
function App() {
const [notes , setNotes] = useState(JSON.parse(localStorage.getItem("notes"))||[])
const [currentNoteID , setCurrentNoteID] = useState(false)
useEffect(()=>{
localStorage.setItem("notes" , JSON.stringify(notes))
},[notes])
function createNewNotes(){
const newNotes = {
id: nanoid(),
title:"untitled",
body: "sdasda",
lastModified: Date.now()
}
setNotes(prevNote => [newNotes , ...prevNote])
setCurrentNoteID(newNotes.id)
}
function deleteNote(noteID){
setNotes(prevNote => prevNote.filter(note=> note.id !== noteID ))
}
function getNotes(){
return notes.find((note)=> note.id === currentNoteID)
}
return (
<div className="note">
<Sidebar
notes={notes}
createNewNotes={createNewNotes}
currentNoteID={currentNoteID}
setCurrentNoteID={setCurrentNoteID}
deleteNote={deleteNote}
/>
<Editor
notes={getNotes()}
currentNoteID={currentNoteID}/>
</div>
);
}
export default App;
my Sidebar.js file
import React from 'react'
import './style.css'
export default function Sidebar(props){
return(
<>
<div className='sidebar' >
<div className='sidebar-header'>
<h3>Notes</h3>
<button className='add' onClick={props.createNewNotes} >Add</button>
</div>
{ props.notes.map((note)=>{
return(
<div key={note.id}
className={`${note.id===props.currentNoteID ? "active" : ""}`}
onClick={()=>props.setCurrentNoteID(note.id)}
>
<div>
<div className="sidebar-tab">
<div className='sidebar-title'>
<p className='title'>Untitled</p>
<button className='delete' onClick={()=>props.deleteNote(note.id)}>Delete</button>
</div>
<p className='note-preview'>summary of text</p>
</div>
</div>
</div>
)
})}
</div>
</>
)
}
my Editor.js file
import React , {useState} from "react";
import './style.css'
export default function Editor(props){
const [edit , setEdit] = useState(props.notes)
function handleChange(event){
const {name , value} = event.target
setEdit(prevNote=> {
return {
...prevNote,
[name] : value
}
})
}
if(!props.currentNoteID)
return <div className="no-note">no note active</div>
return(
<>
<div className="main">
<input type="text" className="main-input" name="title" placeholder="Enter title here" value={edit.title} onChange={handleChange} autoFocus/>
<textarea className="main-textarea" name="body" placeholder="Type your notes" value={edit.body} onChange={handleChange} />
<div className="preview">
<h1 className="preview-title">{edit.title}</h1>
<div className="main-preview">{edit.body}</div>
</div>
</div>
</>
)
}
whenever i click add button or any sidebar button it shows me error
Uncaught TypeError: Cannot read properties of undefined (reading 'title')
please help me out how to fix this issue
You're expecting getNotes (which should probably be named getActiveNote, IMHO) to re-run every time notes or currentNoteID change.
To achieve this, you have to declare it as a callback (useCallback) and to declare its dependencies. Also you want to place the result in state (e.g: activeNote):
const getActiveNote = useCallback(
() => notes.find((note) => note.id === currentNoteID),
[notes, currentNoteID]
);
const [activeNote, setActiveNote] = useState(getActiveNote());
useEffect(() => {
setActiveNote(getActiveNote());
}, [getActiveNote]);
// ...
<Editor note={activeNote} />
... at which point, you no longer need the currentNoteID in the <Editor /> as you can get it from props.note.id.
See it working here: https://codesandbox.io/s/crazy-glade-qb94qe?file=/src/App.js:1389-1448
Note: the same thing needs to happen in <Editor>, when note changes:
useEffect(() => setEdit(note), [note]);
Related
let me explain my situation.
I am building a MERN project to my portfolio and I am trying to make a button toggle between the name of an item and a inputfield. So when the user click the pen (edit), it will add a class with the displain:none; in the div with the text coming from the MongoDB data base to hide it and will remove it from the div with the input. I could manage to do it. BUT since the amount of items can inscrease, clicking in one of them cause the toggle in all of them.
It was ok until I send some useState as props to the component.
This is my code from the App.jsx
import React, {useState, useEffect} from "react";
import Axios from "axios";
import "./App.css";
import ListItem from "./components/ListItem";
function App() {
//here are the use states
const [foodName, setFoodName] = useState("");
const [days, setDays] = useState(0);
const [newFoodName, setNewFoodName] = useState("");
const [foodList, setFoodList] = useState([]);
//here is just the compunication with the DB of a form that I have above those components
useEffect(() => {
Axios.get("http://localhost:3001/read").then((response) => {
setFoodList(response.data);
});
}, []);
const addToList = () => {
Axios.post("http://localhost:3001/insert", {
foodName: foodName,
days: days,
});
};
const updateFood = (id) => {
Axios.put("http://localhost:3001/update", {
id: id,
newFoodName: newFoodName,
});
};
return (
<div className="App">
//Here it starts the app with the form and everything
<h1>CRUD app with MERN</h1>
<div className="container">
<h3 className="container__title">Favorite Food Database</h3>
<label>Food name:</label>
<input
type="text"
onChange={(event) => {
setFoodName(event.target.value);
}}
/>
<label>Days since you ate it:</label>
<input
type="number"
onChange={(event) => {
setDays(event.target.value);
}}
/>
<button onClick={addToList}>Add to list</button>
</div>
//Here the form finishes and now it starts the components I showed in the images.
<div className="listContainer">
<hr />
<h3 className="listContainer__title">Food List</h3>
{foodList.map((val, key) => {
return (
//This is the component and its props
<ListItem
val={val}
key={key}
functionUpdateFood={updateFood(val._id)}
newFoodName={newFoodName}
setNewFoodName={setNewFoodName}
/>
);
})}
</div>
</div>
);
}
export default App;
Now the component code:
import React from "react";
//Material UI Icon imports
import CancelIcon from "#mui/icons-material/Cancel";
import EditIcon from "#mui/icons-material/Edit";
//import CheckIcon from "#mui/icons-material/Check";
import CheckCircleIcon from "#mui/icons-material/CheckCircle";
//App starts here, I destructured the props
function ListItem({val, key, functionUpdateFood, newFoodName, setNewFoodName}) {
//const [foodList, setFoodList] = useState([]);
//Here I have the handleToggle function that will be used ahead.
const handleToggle = () => {
setNewFoodName(!newFoodName);
};
return (
<div
className="foodList__item"
key={key}>
<div className="foodList__item-group">
<h3
//As you can see, I toggle the classes with this conditional statement
//I use the same classes for all items I want to toggle with one click
//Here it will toggle the Food Name
className={
newFoodName
? "foodList__item-newName-delete"
: "foodList__name"
}>
{val.foodName}
</h3>
<div
className={
newFoodName
? "foodList__item-newName-group"
: "foodList__item-newName-delete"
}>
//Here is the input that will replace the FoodName
<input
type="text"
placeholder="The new food name..."
className="foodList__item-newName"
onChange={(event) => {
setNewFoodName(event.target.value);
}}
/>
//Here it will confirm the update and toggle back
//Didn't implement this yet
<div className="foodList__icons-confirm-group">
<CheckCircleIcon
className="foodList__icons-confirm"
onClick={functionUpdateFood}
/>
<small>Update?</small>
</div>
</div>
</div>
//here it will also desappear on the same toggle
<p
className={
newFoodName
? "foodList__item-newName-delete"
: "foodList__day"
}>
{val.daysSinceIAte} day(s) ago
</p>
<div
className={
newFoodName
? "foodList__item-newName-delete"
: "foodList__icons"
}>
//Here it will update, and it's the button that toggles
<EditIcon
className="foodList__icons-edit"
onClick={handleToggle}
/>
<CancelIcon className="foodList__icons-delete" />
</div>
</div>
);
}
export default ListItem;
I saw a solution that used different id's for each component. But this is dynamic, so if I have 1000 items on the data base, it would display all of them, so I can't add all this id's.
I am sorry for the very long explanation. It seems simple, but since I am starting, I spent the day on it + searched and tested several ways.
:|
In my App.js component I have this state :
const [searchText, setSearchText] = useState('');
I have passed this into the Search component as a prop to update the text that I write in the search bar that I have created in the search component.
This is the error that is coming : bundle.js:461 Uncaught TypeError: props.handleSearchNote is not a function
Here is how i have passed in the function in App.js:
And this is my search component:
import React from 'react'
import { MdSearch } from 'react-icons/md'
const Search = (props) => {
return (
<div className='search'>
<MdSearch className='search-icons' size='1.3em' />
<input
onChange={(event) =>
props.handleSearchNote(event.target.value)
}
type='text'
placeholder='type to search...'
/>
</div>
);
};
export default Search;
This is where I have passed the Search component in App.js
return (
<>
<div className='container'>
<Search handlSearchNote={setSearchText} />
{console.log(searchText)}
<NotesList
notes={notes.filter((note) => note.text.toLowerCase().includes(searchText))}
handleAddNote={addNote}
handleDeleteNote={deleted} />
</div>
</>
);
}
export default App;
You're assigning setSearchText to handlSearchNote not handleSearchNote. You forgot the 'e' in handle. This happens to me all the time😅.
I just want to show toggled item. But all map items showing up. Basically this is the result I'm getting from onclick. I think i need to give index or id to each item but i don't know how to do it. i gave id to each question didn't work.
App.js.
import "./App.css";
import React, { useState, useEffect } from "react";
import bg from "./images/bg-pattern-desktop.svg";
import bg1 from "./images/illustration-box-desktop.svg";
import bg2 from "./images/illustration-woman-online-desktop.svg";
import { data } from "./data";
import Faq from "./Faq";
function App() {
const [db, setDb] = useState(data);
const [toggle, setToggle] = useState(false);
useEffect(() => {
console.log(db);
}, []);
return (
<>
<div className="container">
<div className="container-md">
<div className="faq">
<img src={bg} className="bg" />
<img src={bg1} className="bg1" />
<img src={bg2} className="bg2" />
<div className="card">
<h1>FAQ</h1>
<div className="info">
{db.map((dat) => (
<Faq
toggle={toggle}
setToggle={setToggle}
title={dat.title}
desc={dat.desc}
key={dat.id}
id={dat.id}
/>
))}
</div>
</div>
</div>
</div>
</div>
</>
);
}
export default App;
(map coming from simple data.js file that I created. it includes just id title desc.)
Faq.js
import React from "react";
import arrow from "./images/icon-arrow-down.svg";
const Faq = ({ toggle, setToggle, title, desc, id }) => {
return (
<>
{" "}
<div className="question" onClick={() => setToggle(!toggle)}>
<p>{title}</p>
<img src={arrow} className={toggle ? "ikon aktif" : "ikon"} />
</div>
<p className="answer border">{toggle ? <>{desc}</> : ""}</p>
</>
);
};
export default Faq;
You need to store the index value of the toggle item.
You can modify the code with only 2 lines with the existing codebase.
import "./App.css";
import React, { useState, useEffect } from "react";
import bg from "./images/bg-pattern-desktop.svg";
import bg1 from "./images/illustration-box-desktop.svg";
import bg2 from "./images/illustration-woman-online-desktop.svg";
import { data } from "./data";
import Faq from "./Faq";
function App() {
const [db, setDb] = useState(data);
const [toggle, setToggle] = useState(-1); //Modify Here
useEffect(() => {
console.log(db);
}, []);
return (
<>
<div className="container">
<div className="container-md">
<div className="faq">
<img src={bg} className="bg" />
<img src={bg1} className="bg1" />
<img src={bg2} className="bg2" />
<div className="card">
<h1>FAQ</h1>
<div className="info">
{db.map((dat, index) => ( //Modify Here
<Faq
toggle={index === toggle} //Modify Here
setToggle={() => setToggle(index)} //Modify Here
title={dat.title}
desc={dat.desc}
key={dat.id}
id={dat.id}
/>
))}
</div>
</div>
</div>
</div>
</div>
</>
);
}
export default App;
import React from "react";
import arrow from "./images/icon-arrow-down.svg";
const Faq = ({ toggle, setToggle, title, desc, id }) => {
return (
<>
{" "}
<div className="question" onClick={setToggle}>
<p>{title}</p>
<img src={arrow} className={toggle ? "ikon aktif" : "ikon"} />
</div>
<p className="answer border">{toggle ? <>{desc}</> : ""}</p>
</>
);
};
export default Faq;
You will need state for each toggle. Here is a minimal verifiable example. Run the code below and click ⭕️ to toggle an item open. Click ❌ to close it.
function App({ faq = [] }) {
const [toggles, setToggles] = React.useState({})
const getToggle = key =>
Boolean(toggles[key])
const setToggle = key => event =>
setToggles({...toggles, [key]: !getToggle(key) })
return faq.map((props, key) =>
<Faq key={key} {...props} open={getToggle(key)} toggle={setToggle(key)} />
)
}
function Faq({ question, answer, open, toggle }) {
return <div>
<p>
{question}
<button onClick={toggle} children={open ? "❌" : "⭕️"} />
</p>
{open && <p>{answer}</p>}
</div>
}
const faq = [
{question: "hello", answer: "world"},
{question: "eat", answer: "vegetables"}
]
ReactDOM.render(<App faq={faq} />, document.querySelector("#app"))
p { border: 1px solid gray; padding: 0.5rem; }
p ~ p { margin-top: -1rem; }
button { float: right; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Instead of doing this (in App component):
const [db, setDb] = useState(data);
const [toggle, setToggle] = useState(false);
you can write an useState hook like below to combine the two hooks and assign an isOpened property for each Faq element:
const [db, setDb] = useState(data.map(value=>{return {...value, isOpened:false}}));
and then right here you can do this (as the child of <div className="info">):
{db.map((dat, index) => (
<Faq
toggle={dat.isOpened}
setToggle={() => toggleById(dat.id)}
title={dat.title}
desc={dat.desc}
key={dat.id}
id={dat.id}
/>
))}
Also you need to declare toggleById function in App component:
const toggleById = (id) => {
const newDb = db.map(dat=>{
if(dat.id==id){
return {...dat,isOpened:!dat.isOpened}
}
return dat;
});
setDb(newDb);
}
and since setToggle prop of Faq, calls toggleById by its defined parameter, there is no need to do this in Faq component:
<div className="question" onClick={() => setToggle(!toggle)}>
you can simply write:
<div className="question" onClick={setToggle}>
I'm studying react and trying to learn, but I came across this error when I click the send button::
TypeError: comments.map is not a function
import React, { useState } from 'react';
function App() {
const [comments, setComments] = useState(['Comment1', 'Comment 2', 'Etc'])
const sendComment = () => {
setComments({
[comments]: [...comments, 'Comentário']
})
}
return (
<div>
{ /* New comment */ }
<div>
<textarea name="" id="" cols="30" rows="10"></textarea>
<button onClick={sendComment}>Enviar</button>
</div>
{ /* Comments */ }
<div>
{ /* Comment */ }
{comments.map( (comment) => {
return <div>Comment: {comment} </div>
})}
</div>
</div>
);
}
export default App;
The code is the one above, the intention is as soon as I click on the send button it will generate a new comment with the name "Comment"
Calling sendComment sets the comments variable as an object. But it should be an array to access map method on it. You can set the comments to an array when you call setComments inside the function sendComment.
Example codesandbox
import React, { useState } from "react";
function App() {
const [comments, setComments] = useState(["Comment1", "Comment 2", "Etc"]);
const sendComment = () => {
setComments([...comments, "Comentário"]);
};
console.log(comments);
return (
<div>
{/* New comment */}
<div>
<textarea name="" id="" cols="30" rows="10" />
<button onClick={sendComment}>Enviar</button>
</div>
{/* Comments */}
<div>
{/* Comment */}
{comments.map(comment => {
return <div>Comentário: {comment} </div>;
})}
</div>
</div>
);
}
export default App;
You originally defined the comments as array but when you click on send, you are setting the comments to be an object, check this quick fix:
import React, { useState } from "react";
function App() {
const [comments, setComments] = useState(["Comment1", "Comment 2", "Etc"]);
const sendComment = () => {
setComments((comments) => [...comments, "Comentário"]);
};
return (
<div>
{/* New comment */}
<div>
<textarea name="" id="" cols="30" rows="10"></textarea>
<button onClick={sendComment}>Enviar</button>
</div>
{/* Comments */}
<div>
{/* Comment */}
{comments.map((comment) => {
return <div>Comentário: {comment} </div>;
})}
</div>
</div>
);
}
export default App;
You have defined comments as an array but you're setting the state as an Object.
The correct way is:
const sendComment = () => {
setComments([...comments, 'Comentário'])
}
So, I made a react app that displays a list of items from a json file as in the pic.
I want to implement a search feature where i can enter the name and it checks for the name in list and scrolls to it.
A person told me about scroll-into-view , but I'm not understand how to make it compare the search term to the names in list.
My App.js code
import React,{useState} from 'react';
import Notes from './Notes';
import './App.css';
function App() {
const [notes] = useState([]);
const handleSubmit= ()=>{
//Upon submitting I want the search functionality to be implemented here . If thats the way to do it.
}
return (
<div className="App">
<div className="App-header">
<form><input type="text" placeholder="Start Typing.." onSubmit={handleSubmit} ></input></form>
<div className="pageTitle">Song Notes :</div>
<Notes thisNotes={notes}/>
</div>
</div>
);
}
export default App;
My Notes.js code:
import React from 'react';
const Notes = ({notes})=>{
const jsonNotes = require('./Notes.json');
const songNotes = jsonNotes.map(note => {
return(
<div key={note.id}>
<li class="noteAsList">
<div className="songTitle">{note.Name}</div>
<pre><br></br>{note.Notes}</pre>
</li>
</div>
)
})
return(
<div className="noteStyle">
{songNotes}
</div>
)
}
export default Notes;
I'm looking to implement such a feature. Either scrolling into view in the page or just displaying the item I asked for.
Thanks for the help in advance.
Codesandbox
My App.js code
import React, { useState } from "react";
import Notes from "./Notes";
import "./App.css";
const jsonNotes = require("./Notes.json");
const App = () => {
const [notes] = useState([]);
const handleSubmit = event => {
if (event.key === "Enter") {
console.log(event.target.value);
const obj = jsonNotes.find(item => item.Name === event.target.value);
const el = document.getElementById(obj.id);
if (el)
el.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "center"
});
}
};
return (
<div className="App">
<div className="App-header">
<input
type="text"
placeholder="Start Typing.."
onKeyPress={handleSubmit}
/>
<div className="pageTitle">Song Notes :</div>
<Notes thisNotes={notes} />
</div>
</div>
);
};
export default App;
My Notes.js code:
import React from "react";
const jsonNotes = require("./Notes.json");
const Notes = ({ notes }) => {
const songNotes = jsonNotes.map(note => {
return (
<div id={note.id} key={note.id}>
<li className="noteAsList">
<div className="songTitle">{note.Name}</div>
<pre>
<br />
{note.Notes}
</pre>
</li>
</div>
);
});
return <div className="noteStyle">{songNotes}</div>;
};
export default Notes;