Simple Notes App Not Working With Local Storage - javascript

I have a bare bones project that displays a list of notes and a button that will add a new note to that list to be displayed. My issue is that the new notes are not added/retrieved from local storage and I'm not sure what is causing it.
import React, { useEffect, useState } from 'react'
function App() {
const [notesList, setNotesList] = useState([])
useEffect(() => {
const notesJSON = localStorage.getItem('ayo')
if (notesJSON != null) setNotesList(JSON.parse(notesJSON))
console.log(notesList)
}, [])
useEffect(() => {
localStorage.setItem('ayo', JSON.stringify(notesList))
}, [notesList])
function addNote(e) {
e.preventDefault()
const newNote = {
id: Date.now() + Math.random(),
text: 'this is a new note',
status: false,
}
setNotesList([...notesList, newNote])
}
return (
<>
<h1>Hello</h1>
{notesList.map((note) => {
return <h1 key={note.id}>{note.text}</h1>
})}
<form onSubmit={addNote}>
<button type='submit'>Add Note</button>
</form>
</>
)
}
export default App;

Just remove the console log from the first useEffect where we retrieve data from localStorage and where we update notesList state if there is data previously stored in it. For logging purposes, add a button to check the data that is stored in the localStorage like below.
We can'not watch the changes of localStorage by using useEffect with deps notesList. If you try, you will always get data that one step behind the notesList state value.
import React, { useCallback, useEffect, useState } from "react";
export default function App() {
const [notesList, setNotesList] = useState([]);
useEffect(() => {
const notesJSON = localStorage.getItem("ayo");
notesJSON && setNotesList(JSON.parse(notesJSON));
}, []);
// this will always log the data that one step behind the notelist state
useEffect(() => {
const notesJSON = localStorage.getItem("ayo");
notesList && console.log(JSON.parse(notesJSON));
}, [notesList]);
useEffect(() => {
if (notesList.length > 0) {
localStorage.setItem("ayo", JSON.stringify(notesList));
}
}, [notesList]);
const addNote = useCallback(() => {
const newNote = {
id: Date.now() + Math.random(),
text: "this is a new note",
status: false,
};
setNotesList([...notesList, newNote]);
}, [notesList]);
const resetNote = () => {
localStorage.setItem("ayo", JSON.stringify([]));
setNotesList([]);
};
const logNote = () => {
console.log(localStorage.getItem("ayo"));
};
return (
<>
<h1>Hello</h1>
{notesList.map((note) => {
return (
<div key={note.id}>
<h1>{note.text}</h1>
<p>{note.id}</p>
</div>
);
})}
<button onClick={addNote}>Add Note</button>
<button onClick={resetNote}>Reset Note</button>
<button onClick={logNote}>Log Note</button>
</>
);
}

The second useEffect is unnecessary, you should set the appended list to your state and the storage in the addNote function and the first useEffect hook should be used to load your initial state and that's it.
import React, { useEffect, useState } from "react";
function App() {
const [notesList, setNotesList] = useState([]);
useEffect(() => {
const notesJSON = localStorage.getItem("ayo");
if (notesJSON != null) {
setNotesList(JSON.parse(notesJSON));
}
}, []);
function addNote(e) {
e.preventDefault();
const newNote = {
id: Date.now() + Math.random(),
text: "this is a new note",
status: false
};
const appendedList = [...notesList, newNote];
setNotesList(appendedList);
localStorage.setItem("ayo", JSON.stringify(appendedList));
}
return (
<>
<h1>Hello</h1>
{notesList.map((note) => {
return <h1 key={note.id}>{note.text}</h1>;
})}
<form onSubmit={addNote}>
<button type="submit">Add Note</button>
</form>
</>
);
}
export default App;

Related

While rendering a component it is showing an error- "Cannot update a component (`App`) while rendering a different component (`EventList`). "

I Can't render my events. Its showing this error -
"Cannot update a component (App) while rendering a different component (EventList). To locate the bad setState() call inside EventList, follow the stack trace as described in https://reactjs.org/link/setstate-in-render"
Here is EventList Component code -
import { useEffect, useState } from "react";
import EventList from "../../event-list";
import EventForm from "../event-form";
const EventAction = ({
getEventsByClockID,
addEvent,
updateEvent,
clockID,
deleteEvent,
deleteEventsByClockID,
}) => {
const [isCreate, setIsCreate] = useState(false);
const [isToggle, setIsToggle] = useState(false);
const [eventState, setEventState] = useState(null)
const handleCreate = () => {
setIsCreate(!isCreate);
}
useEffect(() => {
setEventState(getEventsByClockID(clockID, true));
}, [isToggle])
const handleToggle = () => {
setIsToggle(!isToggle);
}
return (
<div>
<div>
<button onClick={handleCreate}>Create Event</button>
<button onClick={handleToggle}>Toggle Events</button>
</div>
{isCreate && (
<>
<h3>Create Event</h3>
<EventForm
clockID={clockID}
handleEvent={addEvent}
/>
</>
)}
{isToggle && (
<>
<h3>Events of this clock</h3>
<EventList
clockID={clockID}
eventState={eventState}
deleteEvent={deleteEvent}
updateEvent={updateEvent}
deleteEventsByClockID={deleteEventsByClockID}
/>
</>
)}
</div>
)
}
export default EventAction;
Here is my App Component Code -
import ClockList from "./components/clock-list";
import LocalClock from "./components/local-clock";
import useApp from "./hooks/useApp";
import { localClockInitState } from "./initialStates/clockInitState";
const App = () => {
const {
localClock,
clocks,
updateLocalClock,
createClock,
updateClock,
deleteClock,
getEventsByClockID,
addEvent,
deleteEvent,
updateEvent,
deleteEventsByClockID,
} = useApp(localClockInitState);
return (
<div>
<LocalClock
clock={localClock}
updateClock={updateLocalClock}
createClock={createClock}
/>
<ClockList
clocks={clocks}
localClock={localClock.date}
updateClock={updateClock}
deleteClock={deleteClock}
getEventsByClockID={getEventsByClockID}
addEvent={addEvent}
deleteEvent={deleteEvent}
updateEvent={updateEvent}
deleteEventsByClockID={deleteEventsByClockID}
/>
</div>
)
}
export default App;
and Here is my useApp hook -
import { useState } from "react";
import deepClone from "../utils/deepClone";
import generateID from "../utils/generateId";
import useEvents from "./useEvents";
const getID = generateID('clock');
const useApp = (initValue) => {
const [localClock, setLocalClock] = useState(deepClone(initValue));
const [clocks, setClocks] = useState([]);
const {
// events,
// getEvents,
getEventsByClockID,
addEvent,
deleteEvent,
deleteEventsByClockID,
updateEvent,
} = useEvents();
const updateLocalClock = (data) => {
setLocalClock({
...localClock,
...data,
})
}
const createClock = (clock) => {
clock.id = getID.next().value;
setClocks((prev) => ([
...prev, clock
]))
}
const updateClock = (updatedClock) => {
setClocks(clocks.map(clock => {
if(clock.id === updatedClock.id) return updatedClock;
return clock;
}));
}
const deleteClock = (id) => {
setClocks(clocks.filter(clock => clock.id !== id));
}
return {
localClock,
clocks,
updateLocalClock,
createClock,
updateClock,
deleteClock,
getEventsByClockID,
addEvent,
deleteEvent,
updateEvent,
deleteEventsByClockID,
}
}
export default useApp;
I want to show all events incorporated with each individual clock.

LocalStorage doesn't set items into itself

I've got a bug with LocalStorage on react.js. I try to set a todo into it, but it doesn't load. This is the code:
import React, { useState, useRef, useEffect } from 'react';
import './App.css';
import TodoList from './TodoList';
const { v4: uuidv4 } = require('uuid');
const LOCAL_STORAGE_KEY = 'todoApp.todos'
function App() {
const [todos, setTodos] = useState([]);
const TodoNameRef = useRef()
useEffect(() => {
const storedTodos = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY))
if (storedTodos) setTodos(storedTodos)
}, [])
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(todos))
}, [todos])
function HandleAddTodo(e){
const name = TodoNameRef.current.value
if (name==='') return
setTodos(prevTodos => {
return[...prevTodos, { id:uuidv4(), name:name, complete:false}]
})
TodoNameRef.current.value = null
}
return (
<>
<TodoList todos={todos}/>
<input ref={TodoNameRef} type="text" />
<button onClick={HandleAddTodo}>Add todo</button>
<button>clear todo</button>
<p>0 left todo</p>
</>
)
}
export default App;
This is TodoList.js
import React from 'react'
import Todo from './Todo';
export default function TodoList({ todos }) {
return (
todos.map(todo =>{
return <Todo key ={todo.id} todo={todo} />
})
)
}
And as last Todo.js:
import React from 'react'
export default function Todo({ todo }) {
return (
<div>
<label>
<input type="checkbox" checked={todo.complete}/>
{todo.name}
</label>
</div>
)
}
What the code has to do is load a todo into the local storage, and after refreshing the page reload it into the document. The code I implemented
I just started with react but I hope anyone can pass me the right code to make it work. If anyone need extra explenation, say it to me.
Kind regards, anonymous
Try to decouple your local storage logic into it's own react hook. That way you can handle getting and setting the state and updating the local storage along the way, and more importantly, reuse it over multiple components.
The example below is way to implement this with a custom hook.
const useLocalStorage = (storageKey, defaultValue = null) => {
const [storage, setStorage] = useState(() => {
const storedData = localStorage.getItem(storageKey);
if (storedData === null) {
return defaultValue;
}
try {
const parsedStoredData = JSON.parse(storedData);
return parsedStoredData;
} catch(error) {
console.error(error);
return defaultValue;
}
});
useEffect(() => {
localStorage.setItem(storageKey, JSON.stringify(storage));
}, [storage]);
return [storage, setStorage];
};
export default useLocalStorage;
And you'll use it just like how you would use a useState hook. (Under the surface it is not really more than a state with some side effects.)
const LOCAL_STORAGE_KEY = 'todoApp.todos'
function App() {
const [todos, setTodos] = useLocalStorage(LOCAL_STORAGE_KEY, []);
const handleAddTodo = event => {
setTodos(prevTodos => {
return[...prevTodos, {
id: uuidv4(),
name,
complete: false
}]
})
};
return (
<button onClick={HandleAddTodo}>Add todo</button>
);
}
You added the getItem and setItem methods of localStorage in two useEffect hooks.
The following code intializes the todo value in localStorage when reloading the page.
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(todos))
}, [todos])
So you need to set the todo value in HandleAddTodo event.
I edited your code and look forward it will help you.
import React, { useState, useRef, useEffect } from 'react';
import './App.css';
import TodoList from './TodoList';
const { v4: uuidv4 } = require('uuid');
const LOCAL_STORAGE_KEY = 'todoApp.todos'
function App() {
const [todos, setTodos] = useState([]);
const TodoNameRef = useRef()
useEffect(() => {
const storageItem = localStorage.getItem(LOCAL_STORAGE_KEY);
const storedTodos = storageItem ? JSON.parse(storageItem) : [];
if (storedTodos) setTodos(storedTodos)
}, []);
function HandleAddTodo(e){
const name = TodoNameRef.current.value;
if (name==='') return;
const nextTodos = [...todos, { id:uuidv4(), name:name, complete:false}];
setTodos(nextTodos);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(nextTodos));//replace todos to nextTodos
TodoNameRef.current.value = null
}
return (
<>
<TodoList todos={todos}/>
<input ref={TodoNameRef} type="text" />
<button onClick={HandleAddTodo}>Add todo</button>
<button>clear todo</button>
<p>0 left todo</p>
</>
)
}
export default App;
There is no need of adding the second useEffect.
You can set your local Storage while submitting in the handleTodo function.
Things you need to add or remove :
Remove the Second useEffect.
Modify your handleTodo function :
const nextTodos = [...todos, { id:uuidv4(), name:name,complete:false}];
setTodos(nextTodos);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(nextTodos));
Note: Make sure you won't pass todos instead of nextTodos as we know setTodos is an async function There might be a chance we are setting a previous copy of todos

Unexpected token u in JSON at position 0 in React.js todo app after deployment on netlify

I am trying to deploy the react app on netlify. Locally, my react todo app is working fine and local storage is persisting the data as well (locally). but when I deployed the web app on netlify, after visiting the live link it's showing me this error: "Unexpected token u in JSON at position 0 at JSON.parse" as it is retrieving the undefined.
Solutions I tried: checked the typo
2: checked the object in the console which is displaying the data correctly.
3: i also kept the getTodoFromLocal function inside App() function and kept initial state of
const [todos, setTodos] = useState([]); (to empty array) but this is not persisting data on page reloads
my code App.js
import React, {useEffect, useState} from 'react';
import './App.css';
import { Header, Form, TodoList } from './components';
// get data from local
const getTodoFromLocal = () => {
if(localStorage.getItem("todos") === null){
localStorage.setItem("todos", JSON.stringify([]));
} else {
try{
let localTodo = localStorage.getItem("todos");
let parsedTodo = JSON.parse(localTodo)
return parsedTodo;
} catch(error) {
console.log(error);
}
}
}
const App = () => {
// states
const [inputText, setInputText] = useState("");
const [todos, setTodos] = useState(getTodoFromLocal());
const [status, setStatus] = useState("all");
const [filteredTodo, setFilteredTodo] = useState([]);
// run this only once when app loads
useEffect(() => {
getTodoFromLocal();
}, [])
// Run once when app loads and every time when there is any change in todos ot status
useEffect(() => {
filterHandler();
saveToLocal();
}, [todos, status])
// functions
const filterHandler = () =>{
switch (status) {
case 'completed':
setFilteredTodo(todos.filter(todo => todo.completed === true));
break;
case 'incompleted':
setFilteredTodo(todos.filter(todo => todo.completed === false));
break;
default:
setFilteredTodo(todos);
break;
}
}
// save to local storage / set todos;
const saveToLocal = () => {
localStorage.setItem("todos", JSON.stringify(todos));
};
return (
<div className="App">
<Header />
<Form inputText = {inputText}
setInputText={setInputText}
todos={todos}
setTodos={setTodos}
setStatus={setStatus}
/>
<TodoList
todos={todos}
setTodos={setTodos}
filteredTodo={filteredTodo}
/>
</div>
);
}
export default App;
TodoList.js
import React from 'react';
import TodoItem from './TodoItem';
const TodoList = ({todos, setTodos, filteredTodo}) => {
return (
<div className='todo-container'>
<ul className='todo-list'>
{filteredTodo && filteredTodo.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
todos={todos}
setTodos={setTodos}
text={todo.text}
/>
))}
</ul>
</div>
)
}
export default TodoList;
Form.js
import React from 'react';
import './form.css';
import { v4 as uuidv4 } from 'uuid';
function Form({inputText, setInputText, todos, setTodos, setStatus}) {
const inputHandler = (e) => {
setInputText(e.target.value);
}
const submitHandler = (e) =>{
e.preventDefault();
// generate unique id for todo lists.
const uniqueId = uuidv4();
//add todo object on click of button
const addItem = !inputText ? alert("enter somthing") : setTodos([
...todos, {id: uniqueId, text: inputText, completed: false }
]);
//reset the input field after adding todo
setInputText("");
return addItem;
}
// filtered todo
const statusTodo = (e) => {
setStatus(e.target.value);
}
return (
<form>
<input type="text" className="todo-input" onChange={inputHandler} value={inputText}/>
<button className="todo-button" type="submit" onClick={submitHandler}>
<i className="fas fa-plus-square"></i>
</button>
<div className="select">
<select onChange={statusTodo} name="todos" className="filter-todo">
<option value="all">All</option>
<option value="completed">Completed</option>
<option value="incompleted">Incompleted</option>
</select>
<span><i className="fas fa-chevron-down"></i></span>
</div>
</form>
)
}
export default Form;
TodoItem.js
import React from 'react';
import './todo.css';
const TodoItem = ({text, todo, todos, setTodos}) => {
//delete an item;
const deleteHandler = () => {
setTodos(todos.filter(el => el.id !== todo.id))
}
const completeHandler = () => {
setTodos(todos.map((item )=> {
if(item.id === todo.id){
return {
...item, completed: !todo.completed
}
}
return item;
}));
}
return (
<div className='todo'>
<li className={`todo-item ${todo.completed ? 'completed' : "" }`}>{text}</li>
<button className='complete-btn' onClick={completeHandler}>
<i className='fas fa-check'></i>
</button>
<button className='trash-btn' onClick={deleteHandler}>
<i className='fas fa-trash'></i>
</button>
</div>
)
}
export default TodoItem;
Live link: https://clinquant-parfait-ceab31.netlify.app/
Github link: https://github.com/Mehreen57/Todo-app-react
I figured the error. Going step by step.
You have getTodoFromLocal which is called when you setTodos const [todos, setTodos] = useState(getTodoFromLocal()); here.
As localStorage.getItem("todos") is null, you set todos to [] but do not return anything which returns undefined and value of todos is changed to undefined.
Then you have the function saveToLocal in which you store todos in localStorage.which is called in useEffect whenever todos change.
Todos changed to undefined > useEffect is called > in saveToLocal function todos(undefined) is stored in localStorage.
updated code:
if (localStorage.getItem("todos") === null) {
localStorage.setItem("todos", JSON.stringify([]));
return []
}

Update a component after useState value updates

Having a monaco-editor inside a React component:
<Editor defaultValue={defaultValue} defaultLanguage='python' onChange={onChangeCode} />
The defaultValue, the default code inside of the editor, is sent via props to the component:
const MyComponent = ({
originalCode
}: MyComponentProps) => {
const [defaultValue, setDefaultValue] = useState(originalCode);
When the user edits the code, onChange={onChangeCode} is called:
const onChangeCode = (input: string | undefined) => {
if (input) {
setCode(input);
}
};
My question is, how to reset the code to the original one when the user clicks on Cancel?
Initially it was like:
const handleCancel = () => {
onChangeCode(defaultValue);
};
but it didn't work, probably because useState is asynchronous, any ideas how to fix this?
Here is the whole component for more context:
import Editor from '#monaco-editor/react';
import { useState, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { Button, HeaderWithButtons } from '../shared/ui-components';
import { ICalculationEngine } from '../../../lib/constants/types';
import { usePostScript } from '../../../lib/hooks/use-post-script';
import { scriptPayload } from '../../../mocks/scriptPayload';
import { editorDefaultValue } from '../../../utils/utils';
export interface ScriptDefinitionProps {
realInputDetails: Array<ICalculationEngine['RealInputDetails']>;
realOutputDetails: ICalculationEngine['RealInputDetails'];
originalCode: string;
scriptLibId: string;
data: ICalculationEngine['ScriptPayload'];
}
const ScriptDefinition = ({
realInputDetails,
realOutputDetails,
originalCode
}: ScriptDefinitionProps) => {
const [defaultValue, setDefaultValue] = useState(originalCode);
const [code, setCode] = useState(defaultValue);
const { handleSubmit } = useForm({});
const { mutate: postScript } = usePostScript();
const handleSubmitClick = handleSubmit(() => {
postScript(scriptPayload);
});
const handleCancel = () => {
onChangeCode(defaultValue);
};
const onChangeCode = (input: string | undefined) => {
if (input) {
setCode(input);
}
};
useEffect(() => {
setDefaultValue(editorDefaultValue(realInputDetails, realOutputDetails));
}, [realInputDetails, realOutputDetails, originalCode]);
return (
<div>
<HeaderWithButtons>
<div>
<Button title='cancel' onClick={handleCancel} />
<Button title='save' onClick={handleSubmitClick} />
</div>
</HeaderWithButtons>
<Editor defaultValue={defaultValue} defaultLanguage='python' onChange={onChangeCode} />
</div>
);
};
export default ScriptDefinition;
If you need the ability to change the value externally, you'll need to use the Editor as a controlled component by passing the value prop (sandbox):
For example:
const defaultValue = "// let's write some broken code 😈";
function App() {
const [value, setValue] = useState(defaultValue);
const handleCancel = () => {
setValue(defaultValue);
};
return (
<>
<button title="cancel" onClick={handleCancel}>
Cancel
</button>
<Editor
value={value}
onChange={setValue}
height="90vh"
defaultLanguage="javascript"
/>
</>
);
}

How to I wrap a useState variable in a if statment, but still have it's value be available outside the if reactjs

I have the following code I have a cards state variable using useState, I have atttempted to add my array above to it, but it just adds an empty array, I wasn't able to put the state inside of the if becuase then my variable was undefined. I tried wrapping everything beflow the state and the state in the if , but the then I get some return issues. So the focus is passing into the useState(stateReplace)
Any help would be great
import React, { useState, useCallback, useEffect, useMemo } from "react";
import { Card } from "./Card";
import update from "immutability-helper";
import { LeadsBuilderCollection } from "../../api/LeadsCollection";
import { useTracker } from "meteor/react-meteor-data";
const style = {
width: 400,
};
export const Container = ({ params }) => {
const { leadsBuilder, isLoading } = useTracker(() => {
const noDataAvailable = { leadsBuilder: [] };
if (!Meteor.user()) {
return noDataAvailable;
}
const handler = Meteor.subscribe("leadsBuilder");
if (!handler.ready()) {
return { ...noDataAvailable, isLoading: true };
}
const leadsBuilder = LeadsBuilderCollection.findOne({ _id: params._id });
return { leadsBuilder };
});
const [cards, setCards] = useState([]);
let stateReplace = useMemo(() => {
if (!isLoading && leadsBuilder?.inputs?.length) {
leadsBuilder.inputs.map((leadInput, i) => {
({ id: i, text: leadInput.name });
});
}
return [];
}, [isLoading, leadsBuilder]);
useEffect(() => {
setCards(stateReplace);
}, [setCards, stateReplace]);
const moveCard = useCallback(
(dragIndex, hoverIndex) => {
const dragCard = cards[dragIndex];
setCards(
update(cards, {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, dragCard],
],
})
);
},
[cards]
);
const renderCard = (card, index) => {
return (
<>
{isLoading ? (
<div className="loading">loading...</div>
) : (
<>
<Card
key={card.id}
index={index}
id={card.id}
text={card.text}
moveCard={moveCard}
/>
</>
)}
</>
);
};
return (
<>
{isLoading ? (
<div className="loading">loading...</div>
) : (
<>
<div style={style}>{cards.map((card, i) => renderCard(card, i))}</div>
</>
)}
</>
);
};
Update: I can get it to run if I place a setState in a useEffect but then I get a warning and the drag and drop doesnt work
useEffect(() => {
setCards(stateReplace);
});
Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
Update #2
const [cards, setCards] = useState([]);
let stateReplace = useMemo(() => {
console.log("memo");
if (!isLoading && leadsBuilder?.inputs?.length) {
return leadsBuilder.inputs.map((leadInput, i) => {
({ id: i, text: leadInput.name });
});
}
return [];
}, [isLoading]);
console.log(stateReplace);
useEffect(() => {
setCards(stateReplace);
console.log(setCards);
}, [setCards, stateReplace]);
current output
(4) [undefined, undefined, undefined, undefined]
memo
cannot read propery `id`
i would do it like that
//for preveting updates of memo if ledsBulder will changes on each render
const leadsBuilderRef = useRef(leadsBuilder)
let stateReplace = useMemo(()=>{
if (!isLoading && leadsBuilder.current?.inputs?.length) {
return leadsBuilder.current.inputs.map((leadInput, i) => {
return { id: i, text: leadInput.name };
});
}
return []
}, [isLoading, leadsBuilderRef]);
and then
useEffect(() => {
setCards(stateReplace);
}, [setCards, stateRepalce]);

Categories

Resources