I'm trying to use local storage to store and array it works but when I reload it resets the array. I have looked through some similar questions and did everything I could understand from the answers provided, so I guessed the problem might be from my code so please help me review it. Thanks in advance
import React, { useState } from 'react'
import Modal from './Modal'
function Boards() {
const [boards, setboards] = useState([]);
const [title, settitle] = useState('');
localStorage.setItem('boards', JSON.stringify(boards));
let storedboards = JSON.parse(localStorage.getItem('boards')) || [];
const handleChange = (e) => {
settitle(e.target.value)
}
const handleSubmit = () => {
if (title.length === 0) {
return;
}
setboards(prev => (
[
...prev,
title
]
))
}
return (
<div>
<ul id="boards">
<BoardList boards={boards} />
</ul>
<Modal title={title} handleChange={handleChange} handleSubmit={handleSubmit} />
</div>
)
}
function BoardList({ boards }) {
const history = useHistory()
return (
<>
{
boards.map((board, index) => (
<li key={index} onClick={() => { history.push('./workspace') }}>
<h3>{board}</h3>
</li>
))}
</>
)
}
export default Boards
It's reseting the array when you reload, because of this:
function Boards() {
// you're creating a state variable with an empty array
const [boards, setboards] = useState([]);
// you're overriding the item `boards` on the local storage
localStorage.setItem('boards', JSON.stringify(boards));
Try a slight change like this:
function Boards() {
// try to load `boards` from the local storage, if null set empty array to the var
const storedboards = JSON.parse(localStorage.getItem("boards")) || [];
// use the above var to create the state variable
// if you have `boards` in the local storage, the value will be the recovered value
// otherwise the initial value for this state will be an empty array
const [boards, setboards] = useState(storedboards);
Related
I am trying to create my own "vanilla-React" toast notification and I did manage to make it work however I cannot wrap my head around why one of the solutions that I tried is still not working.
So here we go, onFormSubmit() I want to run the code to get the notification. I excluded a bunch of the code to enhance readability:
const [notifications, setNotifications] = useState<string[]>([]);
const onFormSubmit = (ev: FormEvent<HTMLFormElement>) => {
ev.preventDefault();
const newNotifications = notifications;
newNotifications.push("success");
console.log(newNotifications);
setNotifications(newNotifications);
};
return (
<>
{notifications.map((state, index) => {
console.log(index);
return (
<ToastNotification state={state} instance={index} key={index} />
);
})}
</>
</section>
);
Inside the Toast I have the following:
const ToastNotification = ({
state,
instance,
}:
{
state: string;
instance: number;
}) => {
const [showComponent, setShowComponent] = useState<boolean>(true);
const [notificationState, setNotificationState] = useState(
notificationStates.empty
);
console.log("here");
const doNotShow = () => {
setShowComponent(false);
};
useEffect(() => {
const keys = Object.keys(notificationStates);
const index = keys.findIndex((key) => state === key);
if (index !== -1) {
const prop = keys[index] as "danger" | "success";
setNotificationState(notificationStates[prop]);
}
console.log(state);
}, [state, instance]);
return (
<div className={`notification ${!showComponent && "display-none"}`}>
<div
className={`notification-content ${notificationState.notificationClass}`}
>
<p className="notification-content_text"> {notificationState.text} </p>
<div className="notification-content_close">
<CloseIcon color={notificationState.closeColor} onClick={doNotShow} />
</div>
</div>
</div>
);
};
Now for the specific question - I cannot understand why onFormSubmit() I just get a log with the array of strings and nothing happens - it does not even run once - the props get updated with every instance and that should trigger a render, the notifications are held into a state and even more so, should update.
What is wrong with my code?
Whenever I dispatch a search action using context and useReducer for an object in an array stored in local storage, it returns the object, but when I delete the search query from the input box, the list is not returned and the page is blank, can anyone help please?
This is my context:
const NotesContext = createContext(null);
const NotesDispatchContext = createContext(null);
const getStoredNotes = (initialNotes = InitialNotes) => {
return JSON.parse(localStorage.getItem("storedNotes")) || initialNotes;
};
export const NotesProvider = ({ children }) => {
const [NOTES, dispatch] = useReducer(NotesReducer, getStoredNotes());
useEffect(() => {
localStorage.setItem("storedNotes", JSON.stringify(NOTES));
}, [NOTES]);
return (
<NotesContext.Provider value={NOTES}>
<NotesDispatchContext.Provider value={dispatch}>
{children}
</NotesDispatchContext.Provider>
</NotesContext.Provider>
);
};
export const useNotesContext = () => {
return useContext(NotesContext);
};
export const useNotesDispatchContext = () => {
return useContext(NotesDispatchContext);
};
const App = () => {
const [query, setQuery] = useState("");
const dispatch = useNotesDispatchContext();
useEffect(() => {
if (query.length !== 0) {
dispatch({
type: "searchNotes",
query: query,
});
}
}, [query]);
return (
<div className="container">
<header>
<Title title={"Notes"} className={"app_title"} />
<form className="search_container">
<span class="material-symbols-outlined">search</span>
<input
type="search"
placeholder="search notes"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
</form>
</header>
This is my reducer function
case "searchNotes": {
[...NOTES].filter((note) =>
note.title.toLowerCase().includes(action.query)
);
}
The function seems to actually remove the all data from the local storage instead of filtering based on the query string.
Issue
When you dispatch searchNotes you are changing NOTES and the blow useEffect runs. So if the filter resulted to an empty array, there would be nothing in localStorage.
useEffect(() => {
localStorage.setItem("storedNotes", JSON.stringify(NOTES));
}, [NOTES]);
Solution
What you can do is to remove that useEffect in App that has query as dependency and dispatching searchNotes. And filter directly while rendering, something like this:
{
NOTES.filter((note) => note.title.toLowerCase().includes(query)).map((note, index) => (
<div key={index}>{note.title}</div>
))
}
And at this point you can remove searchNotes case from your reducer.
please any help!
I create a simple code to filter items based on an input value but I got an ERROR :( even though am sure I did everything correctly following the same steps in the tutorial https://youtu.be/cahFm8eeeJI?list=PL6bwFJ82M6FXjyBTVi6WSCWin8q_g_8RR&t=1160
This is the Whole code pages/index.js
import React, {useState} from 'react'
const List = ({items}) => {
const [filtredItems, setFiltredItems] = useState(items)
const filterItems = (e) => {
const searchValue = e.target.value
const currentItems = [...items]
const matchingItems = currentItems.filter((item)=> item.includes(searchValue))
setFiltredItems(matchingItems)
}
return (
<>
<input onChanged={filterItems}/>
<ul>
{/* <li>React JS</li>
<li>Next JS</li>
<li>Javascript</li> */}
{filtredItems.map((item)=>(
<li key={item}>{item}</li>
))}
</ul>
</>
)
}
const ListContainer = () => {
return (
<List items={'ReactJS', 'NextJS', 'Javascript'}/>
)
}
export default ListContainer
output>>>>
TypeError: filtredItems.map is not a function
enter image description here
Thank you.
items={'ReactJS', 'NextJS', 'Javascript'} should be items={['ReactJS', 'NextJS', 'Javascript']} so you're passing an array. Without the [], you're just using the comma operator to pass the string "Javascript" as items.
Separately, though, it's problematic to copy props into state the way you are in that component. If the parent passes new items to the child component, it won't see them until/unless the user filters again, because items is only used as the initial value of filtredItems (sic).
Instead, keep track of the search value and do the filtering when either items or searchValue changes:
const List = ({ items }) => {
// Remember the search value
const [searchValue, setSearchValue] = useState(null);
// Remember the current filtered items (null = not filtering)
const [filteredItems, setFilteredItems] = useState(null);
// Filter the items by search filter when either `items` or `searchValue` changes
useEffect(() => {
const matchingItems = searchValue
? items.filter((item) => item.includes(searchValue))
: items;
setFilteredItems(matchingItems);
}, [items, searchValue]);
const filterItems = (e) => {
setSearchValue(e.target.value.trim());
};
return (
<>
<input onChange={filterItems} value={searchValue} />
<ul>
{
(filteredItems ?? items).map((item) => (
<li key={item}> {item} </li>
))
}
</ul>
</>
);
};
(I also fixed a couple of typos in there, such as filtredItems => filteredItems and onChanged => onChange.)
i'am learning TS yet and I trying to create an application where I get data from API, show results and if someone click on item, it shows a modal with more details, but i'am trouble cause basically my component doesn't render... Look at my code =) !
import IMovie from "../../models/movie.model";
import Modal from "../modal/Modal";
import "./style";
import {
ResultsBody,
ResultsContainer,
TitleResult,
MovieStats,
MovieCover,
MovieStatsDescription,
} from "./style";
interface ISearch {
search?: string;
}
const URL =
"#";
const Results = ({ search }: ISearch) => {
const [data, setData] = React.useState<IMovie[]>([]);
const [currentPage, setCurrentPage] = React.useState(1);
const [dataPerPage] = React.useState(10);
async function getData() {
const response: AxiosResponse<any> = await axios.get(URL);
setData(response.data.results);
}
React.useEffect(() => {
getData();
}, []);
const indexLastData = currentPage * dataPerPage;
const indexFirstData = indexLastData - dataPerPage;
const currentData = data.slice(indexFirstData, indexLastData);
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
const filteredData = data.filter((results) => {
return results.title.toLowerCase().includes(search!.toLocaleLowerCase());
});
return (
<>
<ResultsContainer>
<TitleResult>
<span>Personagem</span>
<span>Sinopse</span>
<span>Data</span>
</TitleResult>
{!search
? currentData.map((item) => (
<ResultsBody
key={item.id}
// onClick={() => {
// selectedMovie(item);
// }}
>
<MovieCover
src={`https://image.tmdb.org/t/p/w185${item.poster_path}`}
alt="poster"
/>
<MovieStats style={{ fontWeight: `bold` }}>
{item.title}
</MovieStats>
<MovieStatsDescription>{item.overview}</MovieStatsDescription>
<MovieStats>{item.release_date}</MovieStats>
</ResultsBody>
))
: filteredData.map((item) => (
<ResultsBody key={item.id}>
<MovieCover
src={`https://image.tmdb.org/t/p/w185${item.poster_path}`}
alt="poster"
/>
<MovieStats style={{ fontWeight: `bold` }}>
{item.title}
</MovieStats>
<MovieStatsDescription>{item.overview}</MovieStatsDescription>
<MovieStats>{item.release_date}</MovieStats>
</ResultsBody>
))}
</ResultsContainer>
<Modal data={data} /> //HERE IS WHERE I'AM CALLING MY MODAL, I want to pass data here
<Pagination
dataPerPage={dataPerPage}
totalData={data.length}
paginate={paginate}
currentPage={currentPage}
/>
</>
);
};
export default Results;
This is my MODAL component
import React from "react";
import { ModalContainer } from "./style";
import IMovie from "../../models/movie.model";
interface IData {
data: IMovie[];
}
const Modal = ({ data }: IData) => {
console.log(data);
return <ModalContainer>{data.title}</ModalContainer>; //HERE IS NOT WORKING
};
export default Modal;
As you can see guys, I can show all results on console.log, but when I put inside the return the log says ''TypeError: Cannot read property 'title' of undefined''
If someone could help me I'd really appreciate! Thanks a lot =)
Movie vs Array
You are getting the error
'Property 'title' does not exist on type 'IMovie[]'. TS2339
in your Modal component because data is an array of movies. An array doesn't have a title property.
You want the modal to show one movie, so you should only pass it one movie.
interface IData {
data: IMovie;
}
Current Selection
Changing the IData interface fixes the issues in Modal, but creates a new error in Results because we are still passing an array. The correct prop is the data for the movie that was clicked. What movie is that? We need to use a useState hook in order to store that data.
Depending on where you control the open/closed state of the Modal, you may also want to pass an onClose callback that clears the selected movie state.
the state:
const [selected, setSelected] = React.useState<IMovie | null>(null); // is a movie or null
in the movie:
onClick={() => setSelected(item)}
the modal:
{selected === null || (
<Modal
data={selected}
onClose={() => setSelected(null)}
/>
)}
Avoid Duplicated Code Blocks
You are rendering a movie the same way whether it's from currentData or filteredData, so we want to combine those. We could create a shared renderMovie callback or ResultsMovie component to use in both loops, but I think we can actually handle it higher up and just have one loop.
You also want your pagination to reflect the pages of just the matching movies when we are filtering based on a search.
// the matchingMovies is a filtered array when there is a search, or otherwise the entire array
const matchingMovies = search
? data.filter((result) =>
result.title.toLowerCase().includes(search.toLowerCase())
)
: data;
const indexLastData = currentPage * dataPerPage;
const indexFirstData = indexLastData - dataPerPage;
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
// total for the pagination should be based on matchingMovies instead of data
const totalData = matchingMovies.length;
// make the currentData from the matchingMovies
const currentData = matchingMovies.slice(indexFirstData, indexLastData);
There might be some bugs or potential additional improvements but I can't actually run this without your components :)
const Results = ({ search }: ISearch) => {
const [data, setData] = React.useState<IMovie[]>([]);
const [currentPage, setCurrentPage] = React.useState(1);
const [dataPerPage] = React.useState(10);
const [selected, setSelected] = React.useState<IMovie | null>(null); // is a movie or null
async function getData() {
const response: AxiosResponse<any> = await axios.get(URL);
setData(response.data.results);
}
React.useEffect(() => {
getData();
}, []);
// the matchingMovies is a filtered array when there is a search, or otherwise the entire array
const matchingMovies = search
? data.filter((result) =>
result.title.toLowerCase().includes(search.toLowerCase())
)
: data;
const indexLastData = currentPage * dataPerPage;
const indexFirstData = indexLastData - dataPerPage;
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
// make the currentData from the matchingMovies
const currentData = matchingMovies.slice(indexFirstData, indexLastData);
return (
<>
<ResultsContainer>
<TitleResult>
<span>Personagem</span>
<span>Sinopse</span>
<span>Data</span>
</TitleResult>
{currentData.map((item) => (
<ResultsBody key={item.id} onClick={() => setSelected(item)}>
<MovieCover
src={`https://image.tmdb.org/t/p/w185${item.poster_path}`}
alt="poster"
/>
<MovieStats style={{ fontWeight: `bold` }}>{item.title}</MovieStats>
<MovieStatsDescription>{item.overview}</MovieStatsDescription>
<MovieStats>{item.release_date}</MovieStats>
</ResultsBody>
))}
</ResultsContainer>
{selected === null || (
<Modal data={selected} onClose={() => setSelected(null)} />
)}
<Pagination
dataPerPage={dataPerPage}
totalData={matchingMovies.length}
paginate={paginate}
currentPage={currentPage}
/>
</>
);
};
interface ModalProps {
data: IMovie;
onClose: () => void;
}
const Modal = ({ data, onClose }: ModalProps) => {
console.log(data);
return <ModalContainer>{data.title}</ModalContainer>;
};
Datalist is an array I'm trying to concat the boards array with the Datalist array, but when I console it doesn't reflect. On the other hand when I assign Datalist.concat(boards) to a variable it reflects example
const newArr = Datalist.concat(boards);
console.log(newArr)
(main code) please help me review it. Thanks in advance
import React, { useState, useEffect } from 'react';
import Modal from './Modal';
import { Datalist } from '../Data/Boards';
function Boards() {
const [boards, setboards] = useState(JSON.parse(localStorage.getItem('boards')) || []);
const [title, settitle] = useState('');
localStorage.setItem('boards', JSON.stringify(boards));
Datalist.concat(boards);
console.log(Datalist);
const handleChange = (e) => {
settitle(e.target.value);
};
const handleSubmit = () => {
if (title.length === 0) {
return;
}
setboards((prev) => [...prev, title]);
};
return (
<div>
<ul id="boards">
<BoardList boards={boards} />
</ul>
<Modal title={title} handleChange={handleChange} handleSubmit={handleSubmit} />
</div>
);
}
function BoardList({ boards }) {
const history = useHistory();
return (
<>
{boards.map((board, index) => (
<li
key={index}
onClick={() => {
history.push('./workspace');
}}
>
<h3>{board}</h3>
</li>
))}
</>
);
}
export default Boards;
That is the expected behaviour. The concat function does not alter the original arrays. You can read about it in the MDN docs
For your case you should be able to do Datalist = Datalist.concat(boards); and it should work like you're expecting