Using local storage in ReactJS - javascript

I'm making a recipe box project, I have a recipe state that has an array of recipe objects.
I use the saveToLocal function to save the current state into local storage like this:
saveToLocal = () => {
const local = this.state.recipe;
localStorage.setItem("recipe", JSON.stringify(local));
}
and pass it back to functions that add, edit or delete new recipe like this
addNewRecipe = (newRecipe) => {
this.setState({
recipe: [...this.state.recipe,newRecipe]
}, this.saveToLocal);
}
editRecipe = (recipe) => {
let selectedRecipe =this.state.recipe.find(obj=>obj.count==recipe.count)
let editedRecipe = Object.assign(selectedRecipe,recipe);
this.setState(Object.assign(this.state.recipe,editedRecipe),this.saveToLocal)
}
deleteRecipe = (recipe) => {
let arr = this.state.recipe.filter(obj => obj.count !== recipe.count);
}
However this does not work as I refresh the app, but when I check the local storage inside the inspect tool the localstorage still has recipe data. What is the way to fix this?
Thank you

Saving to local storage and getting from local storage are 2 different methods.
localStorage.setItem(‘recipe’, JSON.stringify(this.state.recipe)
const recipe = localStorage.getItem(‘recipe’)
this.setState({...recipe})
You don’t need to create a saveToLocal method

If I understand right, you want to get saved recipe on refresh. You should use componentDidMount and get the recipe from localStorage, then set your state according to this.
componentDidMount() {
const recipe = JSON.parse( localStorage.getItem( "recipe" ) );
this.setState( { recipe } );
}
You can check for recipe and conditionally render your component:
render() {
if( !this.state.recipe.length ) {
return <p>No recipe</p>;
// or you can use a spinner here
}
return { how you handle your recipe here, map it etc. }
}

You can then use localStorage.getItem("recipe") to retrieve it. Do a console log for that inside your render function and check your browser's JS Console.
render() {
console.log('recipe is', localStorage.getItem("recipe"))
}
Your localStorage will stay on disk until you clear the cache.

Related

Local storage with hooks in react

So I have an array with the following structure:
`
export const transacciones = [
{
id:100,
cantidad: 0,
concepto : 'Ejemplo',
descripcion: 'Ejemplo',
},
]
`
This array will dynamically increase or decrease as I push or filter items in it (Exactly like data in a task list)
The problem is that I am trying to add some data persistence using local storage. I guess data is getting stored but not shown when I refresh my browser (chrome).
However, when I refresh data disappears from where it was in the upper image so I`m not even sure if I am correctly storing it.
I've tried two things using useEffect hooks.
First aproach:
`
const [transacciones,setTransacciones] = useState([]);
useEffect(()=>{
localStorage.setItem('transacciones',JSON.stringify(transacciones))
},[transacciones])
useEffect(() =>{
const transacciones = JSON.parse(localStorage.getItem('transacciones'))
if (transacciones){
setTransacciones(transacciones)
}
},[])
`
I read somewhere that as the initial value of use state is [] I should chage things in there, so...
Second aproach:
`
const [transacciones,setTransacciones] = useState([],()=>{
const localData = localStorage.getItem('transacciones');
return localData ? JSON.parse(localData) : [];
});
useEffect(()=>{
localStorage.setItem('transacciones',JSON.stringify(transacciones))
},[transacciones])
`
However, when I refresh I get the same result: No persistence.
What am I missing here? Any help would be appreciated
In both scenarios your transacciones array is empty when you perform the localStorage.setItem. if you're trying to keep your local state sync with localStorage this might help:
export function useTransacciones(initialValue){
const localData = localStorage.getItem('transacciones');
const [transacciones,_setTransacciones] = useState(localData?JSON.parse(localData) : initialValue); // you can choose your own strategy to handle `initialValue` and cachedValue
const setTransacciones = (data) => {
_setTransacciones(data)
localStorage.setItem(JSON.stringify(data))
}
hydrate(){
const data = localStorage.getItem("transacciones")
setTransacciones(JSON.prase(data))
}
return [ transacciones, setTransacciones, hydrate ]
}
which you can use it anywhere with caching compelexity hidden inside:
const [transacciones, setTransacciones] = useTransacciones([])

How to persist data from useLocation()?

is there a way to persist data from useLocation() ?
Basically I'm sending data that is located on a nested collection from firebase
(There's a lot of background on my question/explanation, you can just skip to the important bit)
Usually you have no issues when you only have one collection you can always access the UID pretty much from anywhere by sending it from the App.js for example:
function App() {
const [user, setUser] = useState([]);
useEffect(() => {
auth.onAuthStateChanged((authUser) => {
if (authUser) {
setUser(authUser);
} else {
setUser(false);
}
})
}, [])
return (
< div >
<Router>
<Switch>
<Route path = "/SomePath">
<SomeLocation user={user}/>
</Route>
</Switch>
</Router>
</div >
);
}
export default App;
and since the user have all the data you need to get any other piece of information (usually) you don't need to worry about nested collections however, what if I'm using nested collections ?
if you want to access all the data from a nested collection that's also fine you do not require any other extra information apart from the user
Ej:
useEffect(() => {
const dbRef= db.collection("users").doc(user.uid).collection("someCollection")
dbRef.onSnapshot(snapshot => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setDataSomewhere(tempData);
})
}, [user]);
However how you get the data of the uid of an specific document inside a nested location with just the user ? you can't (as far as I'm aware)
The Important bit without all the background
EJ:
if you have let's say a "parent" and he adds "students" which is my case:
and I wanted to edit this "student" that is in a nested collection, let's make an example on the first one the one named "alfonsa" this is the edit form which is in another "/Path"
The way I'm handling the data of the student is the following, when you check the student and you select the edit icon from the data-table it sends some data through useLocation()
//PATH-A (The one with the data table)
const editStudent= {
pathname: '/EDIT_STUDENT',
data: studentData
}
const editStudents= () => {
if(studentData== 0)
{
window.alert("Select a student")
}
else {
history.push(editStudent);
}
};
///EDIT_STUDENT (Edit form)
let studentData= useLocation();
let data = studentData.data;
console.log(data)
const uid = data[0].uid <- Here I get that student specific uid
This is what the console log returns (All correct data):
However all the data disappears on refresh (which makes sense because is no longer getting the useLocation data from the previous "/path" location) this is the question:
How can I keep/store/maintain that data that comes from the useLocation() on refresh
Any help/tip/example is welcome
Forgot to add this
This is basically what comes up when I refresh
UPDATE So I tried localStorage() as someone mention but now I can't access the data because is coming as a string how can I separate the data ?
This is how I'm storing the data:
useEffect(() => {
const localStorageEstData = window.localStorage.getItem("students");
localStorageEstData && setStudentsData(JSON.parse(localStorageEstData));
}, []);
useEffect(() => {
window.localStorage.setItem("students", JSON.stringify(studentsData));
}, [studentsData]);
This is how I'm getting the data + how it shows in console:
let data = window.localStorage.getItem("estudiantes")
is coming as a whole string how can I separate it ?
Since no one posted an answer I will based on what #Daniel Beck said
Instead of using useLocation() that is to pass data from one /Path to another is better to use localStorage for multiple reasons and the more important one is to persist/keep data on refresh.
if you use useLocation() and you refresh the data will disappear because is no longer parsing data from one /Path to another. However the localStorage will persist on refresh which is why is more useful for this case
simple solution for me was:
useEffect(() => {
const localStorageEstData = window.localStorage.getItem("students");
localStorageEstData && setStudentsData(JSON.parse(localStorageEstData));
}, []);
useEffect(() => {
window.localStorage.setItem("students", JSON.stringify(estudiantesData));
}, [studentsData]);
and then in the other end:
const data = JSON.parse(window.localStorage.getItem("students"))
const uid = data[0].uid
that way I have access to all the information not just the uid

Why react state (useState) is updated but not updated when log it?

Hi am trying to create a simple multi file component, But the files state is not behaving as expected.
The FileUpload component works fine and when the user chooses a file and it starts uploading the onStart prop method is called. And then when it finishes successfully the 'onFinish' prop method is called. This code seems fine to the point of consoling the object in the onfinish method. it consoles an old value of the object before it was modified by the onStart Method. I expected the file object value in the console to include the buffer key since it was added when the onStart method was called but it's not there.
Example initial state files should be [] when the use effect is called on the state files should be updated to [{_id:"example_unique_id"}] then a button for upload will appear and when user chooses a file and onStart modifies the object and the state should be updated to [{_id:"example_unique_id", buffer:{}] and finally when it finishes files should be [{_id:"example_unique_id", buffer:{}] but instead here it returns [{_id:"example_unique_id"}].
What could I be missing out on?
Also, I have React Dev tools installed and it seems the state is updated well in the dev tools.
import React, { useState } from 'react'
import { useEffect } from 'react';
import unique_id from 'uniqid'
import FileUpload from "./../../components/FileUpload";
const InlineFileUpload = ({ onFilesChange }) => {
const [files, setFiles] = useState([]);
function onFinish(file, id) {
const old_object = files.filter((file) => file._id == id)[0];
console.log("old object on after upload", old_object);
}
const addFile = (file, id) => {
const old_object = files.filter((file) => file._id == id)[0];
const index = files.indexOf(old_object);
const new_files = [...files];
new_files.splice(index, 1, { ...old_object, buffer: file });
setFiles(new_files);
};
useEffect(() => {
const new_attachments = files.filter(({ buffer }) => buffer == undefined);
if (new_attachments.length == 0) {
setFiles([...files, { _id: unique_id() }]);
}
const links = files.filter((file) => file.file !== undefined);
if (links.length !== 0) {
onFilesChange(links);
}
}, [files]);
return (
<>
{files.map((file) => {
const { _id } = file;
return ( <FileUpload
key={_id}
id={_id}
onStart={(e) => addFile(e, _id)}
onFinish={(e) => onFinish(e, _id)}
/>
);
})}
</>
);
};
export default InlineFileUpload
I think the problem is caused by the fact that your this code is not updating the state:
const addFile = (file, id) => {
const old_object = files.filter((file) => file._id == id)[0];
const index = files.indexOf(old_object);
const new_files = [...files];
new_files.splice(index, 1, { ...old_object, buffer: file });
setFiles(new_files);
}
files looks like an array of objects.
Spread operator will not do a deep copy of this array. There are a lot of examples on the internet, here is one.
let newArr = [{a : 1, b : 2},
{x : 1, y : 2},
{p: 1, q: 2}];
let arr = [...newArr];
arr[0]['a'] = 22;
console.log(arr);
console.log(newArr);
So your new_files is the same array. Splice must be making some modifications but that is in place. So when you are doing this setFiles(new_files);, you are basically setting the same reference of object as your newState. React will not detect a change, and nothing gets updated.
You have the option to implement a deep copy method for your specific code or use lodash cloneDeep.
Looking at your code, this might work for you : const new_files = JSON.parse(JSON.stringify(files)). It is a little slow, and you might lose out on properties which have values such as functions or symbols. Read
The reason you are getting the old log is because of closures.
When you do setFiles(new_files) inside addFiles function. React updates the state asynchronously, but the new state is available on next render.
The onFinish function that will be called is still from the first render, referencing files of the that render. The new render has the reference to the updated files, so next time when you log again, you will be getting the correct value.
If it's just about logging, wrap it in a useEffect hook,
useEffect(() => {
console.log(files)
}, [files);
If it's about using it in the onFinish handler, there are answers which explore these option.

Merging existing localstorage item with state for a shoppingcart

I got a situation where I do not have the experience to know which method is the best and what im doing wrong. The situation is as following:
I got a page with products which have a input + order button, which will add the order to the shoppingcart. My thought was to first set the state for each order you make:
const [amountItem, setAmountItem] = useState({
product: {
id: '',
amount: ''
}
});
Updating:
function handleChange(evt, id) {
const value = evt.currentTarget.value;
setAmountItem({
...amountItem,
product:{
id: id,
amount: value
}
});
console.log(amountItem);
}
Which then I push to the shoppingcart/checkout page (no modal):
if (e.target[0].value < productItem.stock) {
history.push({
pathname: `/winkelwagen/`,
state: {data: amountItem}
});
On this page, i first check if location.state exists before using the shoppingcart component:
if (location.state !== null && shoppingCartItems === '') {
console.log(location.state.data);
setShoppingCartItems(location.state.data);
setShoppingCartActive(true);
let cartString = JSON.stringify(shoppingCartItems);
localStorage.setItem('shopping_carts', cartString)
}
When it does exist, some product is ordered with an amount and must be set to localstorage, the product is 'always' visible when refreshing, etc. Until this point it works, the localstorage item exists:
(key)shopping_carts (value){"product":{"id":3,"amount":"2"}}
After that comes the shoppingcart component:
<ShoppingCart
shoppingCartItems={shoppingCartItems}
setShoppingCartItems={setShoppingCartItems}
shoppingCartActive={shoppingCartActive}
setShoppingCartActive={setShoppingCartActive}
/>
This is where my problem starts. Long story short, it only shows the single item from the state, which obviously will be gone.
In this file I got a useEffect part for the localstorage:
useEffect(() =>{
let shoppingCart = localStorage.getItem("shopping_carts");
console.log('shoppingcartitems ');
shoppingCart = JSON.parse(shoppingCart);
console.log(shoppingCart);
if (shoppingCart !== "") {
const id = shoppingCartItems.id;
const amount = shoppingCartItems.amount;
//setShoppingCartItems(shoppingCart)
setShoppingCartItems(prevState => ({
...prevState,
product: {
...shoppingCartItems,
id: id,
amount: amount
}
}))
}
}, [setShoppingCartItems])
The output for 'shoppingCart' is <empty string>. Why is that? Is the format wrong? I'm also using the localstorage for other info, which works fine. I know the setShoppingCartItems is not correct for multiple values, but I wanted to test this single entry first.
Update:
const CheckoutPage = () => {
const location = useLocation();
const [shoppingCartItems, setShoppingCartItems] = useState('');
const [shoppingCartActive, setShoppingCartActive] = useState(false);
const [mode, setMode] = useState('init');
let savedShoppingCart = JSON.parse(localStorage.getItem("shopping_carts"));
console.log('saved shopping cart: ')
console.log(savedShoppingCart);
if (savedShoppingCart !== "" && mode === 'init') {
const id = savedShoppingCart.id;
const amount = savedShoppingCart.amount;
//setShoppingCartItems(shoppingCart)
setShoppingCartItems(prevState => ({
...prevState,
product: {
...shoppingCartItems,
id: id,
amount: amount
}
}))
setMode('data');
//setShoppingCartActive(true);
}
if (location.state !== null && shoppingCartItems === '') {
console.log(location.state.data);
setShoppingCartItems(location.state.data);
setShoppingCartActive(true);
let cartString = JSON.stringify(shoppingCartItems);
localStorage.setItem('shopping_carts', cartString)
}
return (
<div className="shoppingCartPage">
<ShoppingCart
shoppingCartItems={shoppingCartItems}
setShoppingCartItems={setShoppingCartItems}
shoppingCartActive={shoppingCartActive}
setShoppingCartActive={setShoppingCartActive}
/>
</div>
)
}
So basically I want to do 3 things here:
Get the data from the localstorage item
Is there a saved localstorage item? Add it to existing shoppingCartItems (prevstate)
Save the updated (or new when no localstorage item exists) shoppingCartItems after that
After that I want to pass the data to the shoppingcart where i can increase/decrease items or remove/splice the values.
Treat useEffect with caution as an eventListener on React state.
Therefore you need to specify in the dependency array everything might change, in order to trigger the useEffect callback.
In your useEffect dependencies, where you are updating your shoppingCartItems, you have added only setShoppingCartItems - which I assume that its a setState function. This results in your useEffect te be called only once at the app start because setState functions never change.
So, to have your shoppingCartItems updated via useEffect you need to add it to dependencies.
useEffect(() => {
// your code
}, [setShoppingCartItems, shoppingCartItems])
This may fix your problem, because you never call logic that saves update shopping cart state, the second time, therefore you get empty in your console log.

React: using localStorage with React hooks, how would I make my components not reload unless the button is clicked?

Currently my app is accessing external data with fetch on mounting of the main component and whenever I reload the page it reloads the data and resets local storage items that I have linked to it.
However, what I would like is:
1) Very first time user opens the page the whole dataset must be loaded
2) If user removes any of the items in the table, then closes the tab or reloads the page, those removed items should not reappear in the dataset and the table (unless the "reload" button is clicked)
My main component currently looks like this:
function App() {
const DEFAULT_ERROR = null
const DEFAULT_IS_LOADED = false
const DEFAULT_DATASET = []
const DEFAULT_INPUT_VALUE = ''
const DEFAULT_DROPDOWN_VALUE = 'year'
const DEFAULT_GRAPH_DATA = []
const URL = 'https://reqres.in/api/unknown'
const [ error, setError ] = React.useState(DEFAULT_ERROR)
const [ isLoaded, setIsLoaded ] = React.useState(DEFAULT_IS_LOADED)
const [ dataset, setDataset ] = React.useState(DEFAULT_DATASET)
const [ inputValue, setInputValue ] = React.useState(DEFAULT_INPUT_VALUE)
const [ dropdownValue, setDropdownValue ] = React.useState(DEFAULT_DROPDOWN_VALUE)
const { graphData, setGraphData } = React.useState(DEFAULT_GRAPH_DATA)
localStorage.setItem('hasBeenLoaded', false)
let hasBeenLoaded = JSON.parse(localStorage.getItem('hasBeenLoaded'))
if (!hasBeenLoaded) {
localStorage.setItem('hasBeenLoaded', true)
hasBeenLoaded = JSON.parse(localStorage.getItem('hasBeenLoaded'))
console.log(hasBeenLoaded)
React.useEffect(() => {
requestData(URL, setIsLoaded, setDataset, setError)
}, [])
}
I tried storing a value in the local storage that tracks whether the app has been already loaded on users PC so it won't re-run the useEffect with fetch request, but for some reason it still seems to be running and any of the table items that I delete re-appear on reload.
The requestData function is the following:
function requestData(url, setIsLoaded, setDataset, setError) {
return fetch(url)
.then(response => response.json())
.then(
(result) => {
setIsLoaded(true)
for (let i=0; i < result.data.length; i++) {
localStorage.setItem(result.data[i].id, JSON.stringify(result.data[i]))
setDataset(
prevDataset => [...prevDataset, JSON.parse(localStorage.getItem(result.data[i].id))]
)
}
},
(error) => {
setIsLoaded(true)
setError(error)
}
)
}
What should I do in order to make my component work as planned? Here is my pen just in case.
Best regards,
Konstantin
As #charlietfl said, you are setting the value of hasBeenLoaded to false always, so it will fetch the data always, the logic for this to work would be:
check if the hasBeenLoaded variable exists in the LocalStorage
if not, call your requestData method (you don't even need the useEffect)
if yes, don't do anything
Then when the user clicks the reload button, then you set the variable to false
const hasBeenLoaded = localStorage.getItem('hasBeenLoaded')
if(!hasBeenLoaded){
requestData(URL, setIsLoaded, setDataset, setError)
localStorage.setItem('hasBeenLoaded', true)
}

Categories

Resources