localStorage removing elements in array after browser refresh - javascript

I have a react app and I want to persist the array of favorites when the page refreshes.
The data is set correctly, I can see it in the dev tools. But when i refresh the page, the data is removed. Any ideas why this may be?
Link to sandbox - https://codesandbox.io/s/sad-surf-sqgo0q?file=/src/App.js:368-378
const App = () => {
const [favourites, setFavourites] = useState([]);
useEffect(() => {
localStorage.setItem("favourites", JSON.stringify(favourites));
}, [favourites]);
useEffect(() => {
const favourites = JSON.parse(localStorage.getItem("favourites"));
if (favourites) {
setFavourites(favourites);
}
}, []);
return (
<FavContext.Provider value={{ favourites, setFavourites }}>
<HashRouter>
<Routes>
<Route path={"/"} element={<Dashboard />} />
<Route path={"/favorites"} element={<Favorites />} />
</Routes>
</HashRouter>
</FavContext.Provider>
);
};
export default App;

Make sure to set item if array is not empty
useEffect(() => {
if(favourites.length) localStorage.setItem("favourites", JSON.stringify(favourites));
}, [favourites]);

Yes, it is because when you reload the app the useEffect will trigger and you have an empty array in your favorite for the first time so it set the empty array in local storage.
You can fix it by adding a simple check
useEffect(() => {
if(favourites.length > 0){
localStorage.setItem("favourites", JSON.stringify(favourites));
}
}, [favourites]);
By this setItem only work when there is something in the favorites state

Related

I am getting Null value on my props on initial render

My problem is I am having this output when I run/refresh my system
{user: Array(0)}
I use props to send my user info on my other components
Here is my App.js code
function App() {
const [user, setUser] = useState([])
const token = localStorage.getItem('mytoken')
let navigate = useNavigate()
useEffect(() => {
...
.then(result => setUser(result))
},[token])
return (
<div>
<Header user = {user}/>
<Routes>
<Route>
<Route path='/homepage' element = {<UserHomePage user = {user}/>} ></Route>
</Route>
</Routes>
</div>
);
And here is my other UserHomePage.js
function UserHomePage(props) {
console.log(props)
return (
<div>
</div>
);
}
You are setting the state to an array, but you are sending an object to the component.
setUser(result)
should be
setUser(result.user)
Put const token = localStorage.getItem('mytoken') inside of the useEffect and remove the [token]
Because the token variable is not a state, it is a static variable, useEffect trigger point runs on state change, your token variable is not a state. A quick fix to this is empty the square brackets

React Router v2.7 to v6 onEnter Migration

I am trying to migrate an application running on router v3, using the onEnter attribute for route after auth.
onEnter function in YAMain.jsx
static onEnter(store) {
return (nextState, replaceState, callback) => {
// Check if the user is logged in and allowed to make requests before letting them proceed
store.dispatch({
type: IS_LOGGED_IN,
onLoggedIn: () => {
store.dispatch({
type: GET_SELECTED_LOCATION_AND_CLASSROOM,
onSuccess: callback,
onFailure: () => {
// Get all of the required information from the store
const profile = getProfile(store.getState());
const selectedClassroom = getSelectedClassroom(store.getState());
const selectedLocation = getSelectedLocation(store.getState());
// No location has been selected by an admin, go to the locations page
if (profile.get('accessLevel') !== 'E' && !selectedLocation.get('id')) {
// Return early if this is the page we are going to
if (nextState.location.pathname.startsWith('/location')) {
return callback();
}
replaceState('/location');
return callback();
}
// No classroom has been selected by a user, go to the classrooms page
if (!selectedClassroom.get('id')) {
// Return early if this is the page we are going to
if (nextState.location.pathname.startsWith('/classroom')) {
return callback();
}
replaceState('/classroom');
return callback();
}
return callback();
}
});
},
onNotLoggedIn: () => {
replaceState('/login');
callback();
},
onFailure: (error) => {
if (isTimeGateError(error)) {
replaceState('/locked');
callback();
}
}
});
};
render function in YARouter.jsx, both classes extend component.
render() {
return (
<BrowserRouter>
<Routes>
{/* Handles the main logic and renders all but one of the pages */}
<Route
exact path="/"
element={<YAMain/>
}
onEnter={YAMain.onEnter(this.props.store)}
>
<Route path="/" element={YADashboard}/>
{/* Locations page displays the available list of locations */}
<Route
path="location"
element={YALocations}
/>
{/* Classrooms page displays the available list of classrooms */}
<Route
path="classroom"
element={YAClassrooms}
/>
this is not the entirety of the routing but should be enough to give you an idea of what's going on.
This is what I have now, I have tried a number of things suggested on various places. I'm trying to understand how I can either make this work so I can move on and work on this and make it proper later, OR make it proper now and fix this issue.
How can I go about ensuring proper redirection for user authentication, I've spent 2 days at work trying to figure anything out and am completely stuck.
Thanks.
If you are just looking for a way to call onEnter when the route is matched and rendered then I think calling it in a mounting useEffect hook in a wrapper component is probably what you are after.
Example:
const YAMainWrapper = ({ children, onEnter }) => {
useEffect(() => {
onEnter();
}, []);
return children;
};
...
render() {
return (
<BrowserRouter>
<Routes>
{/* Handles the main logic and renders all but one of the pages */}
<Route
path="/"
element={(
<YAMainWrapper onEnter={YAMain.onEnter(this.props.store)}>
<YAMain />
</YAMainWrapper>
)}
>
<Route path="/" element={<YADashboard />} />
{/* Locations page displays the available list of locations */}
<Route path="location" element={<YALocations />} />
{/* Classrooms page displays the available list of classrooms */}
<Route path="classroom" element={<YAClassrooms />} />
...
</Route>

Context API, Cannot retreive the data I am setting in seperate component

Working on a little project of mine but ran into an issue while working with the so called Context API. First time doing it as well.
The issue I am having is that I am unable to console.log the data I am setting. Now I am trying to make a "Add To Cart" button, and whenever the button is pressed. An object containing things such as name, price, color etc will be sent over to my ShoppingCart component.
I am able to set the value, whenever I press the button. And it appears in the console.log. Though, I am unable to console.log the value from ShoppingCart.
My files look like this:
./Contexts
./AddToCartContext.js
./Components
./Product
./ProductContent.js // Here the "add to cart" button is located.
./ShoppingCart
./ShoppingCart.js // Here I need the data from ProductContent.js
App.js
Here is my App.js code:
const [cartItems, setCartItems] = useState({});
console.log(cartItems); // The data is being logged here perfectly.
return (
<AddToCartContext.Provider value={{ cartItems, setCartItems }}>
<ThemeProvider theme={themeMode}>
{/* GlobalStyles skapas i ./themes.js */}
<GlobalStyles />
<Router>
<Route exact path="/cart">
<ShoppingCart theme={theme} toggleTheme={toggleTheme} />
</Route>
<Route exact path="/category/:type/:id/:productid">
<FetchAPI />
</Route>
// ...
</Router>
</ThemeProvider>
</AddToCartContext.Provider>
);
Here is my shoppingcart.js code:
import { AddToCartContext } from "../../Contexts/AddToCartContext";
const ShoppingCart = (props) => {
const { cartItems } = useContext(AddToCartContext);
console.log(cartItems); // This always result in an empty Object.
return (
<Fragment>
<TopNavigation />
<BottomNavigation theme={props.theme} toggleTheme={props.toggleTheme} />
<Cart />
<Footer />
</Fragment>
);
};
And here is the code in ProductContent.js (shorten as its pretty long):
import { AddToCartContext } from "../../Contexts/AddToCartContext";
const ProductContent = (props) => {
const { setCartItems } = useContext(AddToCartContext);
return (
<button
type="button"
onClick={() =>
setCartItems({ // This returns the object to App.js, but not Shoppingcart.js
name: props.name,
price: props.price,
color: props.mainImg?.colour,
img: props.mainImg?.url,
id: params.productid,
})
}
className={classes.add_to_cart}
>
<Cart />
add to cart
</button>
);
}
As mentioned, it's the first time I work with Context API. Am I missing anything?
EDIT:
Did some testing, and if I set the initial value on const [cartItems, setCartItems] = useState({}); to for example "test", then it is being rendered inside of the Cart page.
Nothing is logging inside of the cart upon the button press. But whenever the initial value is set to "test" it is loaded instantly.
Here is the code for creating the context (AddToCartContext.js):
import { createContext } from "react";
export const AddToCartContext = createContext({});
try without destructuring like this below
const cartItems = useContext(AddToCartContext);
console.log(cartItems);
and check if there is anything in that object

Passing data from parent to child using react.State but State reset to initial state

I am facing an issue while passing the state to the child component, so basically I am getting customer info from child1(Home) and saving in the parent state(App) and it works fine.
And then I am passing the updated state(basketItems) to child2(Basket). But when I click on the Basket button the basket page doesn't show any info in console.log(basketItems) inside the basket page and the chrome browser(console) looks refreshed too.
Any suggestion why it is happening and how can I optimize to pass the data to child2(basket) from main (APP).
update:2
i have tired to simulated the code issue in sand box with the link below, really appreciate for any advise about my code in codesandbox (to make it better) as this is the first time i have used it
codesandbox
Update:1
i have made a small clip on youtube just to understand the issue i am facing
basketItems goes back to initial state
Main (APP)___|
|_Child 1(Home)
|_Child 2 (Basket)
Snippet from Parent main(App) component
function App() {
const [basketItems, setBasketItems] = useState([]);
const addBasketitems = (product, quantity) => {
setBasketItems(prevItems => [...prevItems, { ...product, quantity }])
}
console.log(basketItems) // here i can see the updated basketItems having customer data as expected [{...}]
return (
<Router>
<div className="App">
<header className="header">
<Nav userinfo={userData} userstatus={siginalready} />
</header>
<Switch>
<Route path="/" exact render={(props) => (
<Home {...props} userData={userData} userstatus={siginalready}
addBasketitems={addBasketitems}
/>
)}
/>
<Route path="/basket" exact render={(props) =>
(<Basket {...props} basketItems={basketItems} />
)}
/>
</Switch>
</div>
</Router>
Snippet from the child(basket)
function Basket({basketItems}) {
console.log(basketItems) // here i only get the [] and not the cusotmer data from parent component
return (
<div>
{`${basketItems}`} // here output is blank
</div>
);
}
export default Basket;
Snippet from the child(Home)
... here once the button is pressed it will pass the userselected details to parent
....
<Button name={producNumber} value={quantities[productName]} variant="primary"
onClick={() => {
addBasketitems(eachproduct, quantities[productName])
}}>
Add to Basket
</Button >
Your function works fine, the reason your output in addbasketItem does not change is the when using setState it takes some time to apply the changes and if you use code below you can see the result.
useEffect(()=>{
console.log('basket:',basketItems)
},[basketItems])
Your Basket component only renders once so replace it with this code and see if it works:
function Basket({ basketItems }) {
const [items, setItems] = useState([]);
useEffect(() => {
setItems(basketItems);
}, [basketItems]);
return <div>{`${items}`}</div>;
}
but for passing data between several components, I strongly suggest that you use provided it is much better.

React router does not work correctly when used with react hooks?

I am working on a react application in which I have 3 component, AdminShopRoutes, AdminShop and Products.
The AdminShopRoutes Component:
const AdminShopRoutes = () => {
return (
<Router>
<Header>
<AdminShop exact path='/admin/shop' component={Dashboard} />
<AdminShop exact path='/admin/shop/customers' component={Customers} />
<AdminShop exact path='/admin/shop/products' component={Products} />
</Header>
</Router>)
}
The AdminShop Component
const AdminShop = ({ component: Component, ...rest }) => {
return (
<Route {...rest} render={(props) => (
true
? <Component {...props} />
: null
)} />
)
}
And finally the Product Component
const Products = (props) => {
useEffect(() => props.getProducts(), [])
const { products, loading } = props
return ( ... )
}
export default connect(mapStateToProps, { getProducts })(Products)
The links present in the other components beside these work i.e the url is changed but the page is blank as soon as the url changes for every route. If I then refresh the page, it renders fine but only on REFRESH. Also if I omit the route rendering the Products component all other routes work fine. Is there some other method of using hooks when working with react router because it is something to with Products component. Any help would be appreciated.
This is the warning I get when i render the product page
Arrow functions without {} will return the value of their one statement.
Arrow function with {} require an explicit return statement to return a value.
So the result of props.getProducts() is being returned from your effect.
However, useEffect() restricts the return value to only be a cleanup function for that effect. A non function return value is considered to be an error by React.
To fix this, just add {} to your arrow function so that it does not return a value:
useEffect(() => {
props.getProducts()
}, [])
do this
useEffect(() => {
props.getProducts()
}, [])
so that props.getProducts() doesn't get returned
Your error shows this has nothing to do with routing, but your useEffect is returning something when it shouldn't.
The error is pretty verbose and clear.

Categories

Resources