When I am using the setItem() Method, i get no errors and everything is working fine. But when I try to retrieve objects via the getItem() Method, im getting the error: "src\components\UserCourses.js Line 33:8: 'setItems' is not defined no-undef"
//LocalStorage
useEffect(() => {
localStorage.setItem('basket', JSON.stringify(basket));
}, [basket]);
useEffect(() => {
const basket = JSON.parse(localStorage.getItem('basket'));
if (basket) {
setItems(basket);
}
}, []);
basket is my empty array at the start, where i put in items.
import { useStateValue } from './StateProvider'
import {useState, useEffect} from 'react'
function UserCourses ({id, name, prof, language}) {
const [{basket}, dispatch] = useStateValue();
const navigate = useNavigate()
const goCourseDetail = () => navigate(`/course/id=${id}`)
const removeFromBasket = () => {
dispatch({
type: 'REMOVE_FROM_BASKET',
id: id,
});
}
useEffect(() => {
localStorage.setItem('basket', JSON.stringify(basket));
}, [basket]);
useEffect(() => {
const basket = JSON.parse(localStorage.getItem('basket'));
if (basket) {
setItems(basket);
}
}, []);
//Provider
import React, {createContext, useContext, useReducer} from 'react';
//data layer
export const StateContext = createContext();
//Provider
export const StateProvider = ({reducer, initialState, children}) => (
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
//use inside of a component
export const useStateValue = () => useContext(StateContext);
I had a problem with localStorage in a shopping cart I was developing, if it helps. I was able to solve my problem, with the next code:
const initialState = []
const [carrito, setCarrito] = useState(initialState)
useEffect(() => {
const carritoLS = JSON.parse(localStorage.getItem('carrito'))
if (carritoLS) {
setCarrito(carritoLS)
}
}, [])
useEffect(() => {
if (carrito !== initialState){
localStorage.setItem('carrito', JSON.stringify(carrito))
}
}, [carrito])
Related
So I am building an e-commerce website checkout page with commerce.js. I have a context that allows me to use the cart globally. But on the checkout page when I generate the token inside useEffect , the cart variables have not been set until then.
My context is as below
import { createContext, useEffect, useContext, useReducer } from 'react';
import { commerce } from '../../lib/commerce';
//Provides a context for Cart to be used in every page
const CartStateContext = createContext();
const CartDispatchContext = createContext();
const SET_CART = 'SET_CART';
const initialState = {
id: '',
total_items: 0,
total_unique_items: 0,
subtotal: [],
line_items: [{}],
};
const reducer = (state, action) => {
switch (action.type) {
case SET_CART:
return { ...state, ...action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
};
export const CartProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const setCart = (payload) => dispatch({ type: SET_CART, payload });
useEffect(() => {
getCart();
}, []);
const getCart = async () => {
try {
const cart = await commerce.cart.retrieve();
setCart(cart);
} catch (error) {
console.log('error');
}
};
return (
<CartDispatchContext.Provider value={{ setCart }}>
<CartStateContext.Provider value={state}>
{children}
</CartStateContext.Provider>
</CartDispatchContext.Provider>
);
};
export const useCartState = () => useContext(CartStateContext);
export const useCartDispatch = () => useContext(CartDispatchContext);
Now on my checkout page
const CheckoutPage = () => {
const [open, setOpen] = useState(false);
const [selectedDeliveryMethod, setSelectedDeliveryMethod] = useState(
deliveryMethods[0]
);
const [checkoutToken, setCheckoutToken] = useState(null);
const { line_items, id } = useCartState();
useEffect(() => {
const generateToken = async () => {
try {
const token = await commerce.checkout.generateToken(id, {
type: 'cart',
});
setCheckoutToken(token);
} catch (error) {}
};
console.log(checkoutToken);
console.log(id);
generateToken();
}, []);
return <div> {id} </div>; //keeping it simple just to explain the issue
};
In the above code id is being rendered on the page, but the token is not generated since on page load the id is still blank. console.log(id) gives me blank but {id} gives the actual value of id
Because CheckoutPage is a child of CartProvider, it will be mounted before CartProvider and the useEffect will be called in CheckoutPage first, so the getCart method in CartProvider hasn't been yet called when you try to read the id inside the useEffect of CheckoutPage.
I'd suggest to try to call generateToken each time id changes and check if it's initialised first.
useEffect(() => {
if (!id) return;
const generateToken = async () => {
try{
const token = await commerce.checkout.generateToken(id, {type: 'cart'})
setCheckoutToken(token)
} catch(error){
}
}
console.log(checkoutToken)
console.log(id)
generateToken()
}, [id]);
I am trying to render this CardsContainerCopy component after making an AJAX call with Redux-thunk.
If I leave the dependencies array in useEffect empty, the component doesn't render at all.
If I add cartItems to the dependencies array, the components will render but the fetchItems function keeps being called infinitely.
Code:
import React, { useEffect, useState } from "react";
import SingleCard from "./SingleCard";
import { createServer } from "miragejs";
import axios from "axios";
import itemsData from "../../config/ItemsData";
import { useDispatch, useSelector } from "react-redux";
import { selectCartItems } from "./shopSlice";
let server = createServer();
server.get("/api/food", itemsData);
const fetchItems = async (dispatch) => {
const itemsData = await axios.get("/api/food");
dispatch({ type: "shop/fetchedItems", payload: itemsData.data });
};
const CardsContainerCopy = () => {
const [items, setItems] = useState([]);
const dispatch = useDispatch();
const cartItems = useSelector(selectCartItems);
useEffect(() => {
dispatch(fetchItems);
setItems(cartItems);
}, [cartItems]);
return (
<>
{items?.map((item, i) => {
return <SingleCard props={item} key={i} />;
})}
</>
);
};
export default CardsContainerCopy;
Your useEffect function does create an infinite loop, as you're listening to cartItems changes which triggers dispatch again. To avoid infinite re render you can do something like this:
const CardsContainerCopy = () => {
const [items, setItems] = useState([]);
const dispatch = useDispatch();
const cartItems = useSelector(selectCartItems);
useEffect(()=>{
dispatch(fetchItems);
}, [])
useEffect(() => {
setItems(cartItems);
}, [cartItems]);
return (
<>
{items?.map((item, i) => {
return <SingleCard props={item} key={i} />;
})}
</>
);
};
import React, {useState, useEffect} from 'react';
import {connect} from 'react-redux';
import {
fetchRecipes
} from '../../store/actions';
import './BeerRecipes.css';
const BeerRecipes = ({recipesData, fetchRecipes}) => {
const [page, setPage] = useState(1);
const [recipes, setRecipes] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchRecipes();
}, [])
return (
<div className='beer_recipes_block'>
<div className='title_wrapper'>
<h2 className='title'>Beer recipes</h2>
</div>
<div className='beer_recipes'>
<ul className='beer_recipes_items'>
{
recipesData && recipesData.recipes && recipesData.recipes.map(recipe =>
<li className='beer_recipes_item' id={recipe.id}>{recipe.name}</li>
)
}
</ul>
</div>
</div>
);
};
const mapStateToProps = state => {
return {
recipesData: state.recipes
}
}
const mapDispatchToProps = dispatch => {
return {
fetchRecipes: () => dispatch(fetchRecipes())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(BeerRecipes);
this is my component where I would like to create infinite scroll and below is my redux-action with axios:
import axios from "axios";
import * as actionTypes from "./actionTypes";
export const fetchRecipesRequest = () => {
return {
type: actionTypes.FETCH_RECIPES_REQUEST
}
}
export const fetchRecipesSuccess = recipes => {
return {
type: actionTypes.FETCH_RECIPES_SUCCESS,
payload: recipes
}
}
export const fetchRecipesFailure = error => {
return {
type: actionTypes.FETCH_RECIPES_FAILURE,
payload: error
}
}
export const fetchRecipes = (page) => {
return (dispatch) => {
dispatch(fetchRecipesRequest)
axios
.get('https://api.punkapi.com/v2/beers?page=1')
.then(response => {
const recipes = response.data;
dispatch(fetchRecipesSuccess(recipes));
})
.catch(error => {
const errorMsg = error.message;
dispatch(fetchRecipesFailure(errorMsg));
})
}
}
I want to create a scroll. I need, firstly, to display first 10 elements and then to add 5 elements with every loading. I have 25 elements altogether and when the list is done it should start from the first five again.
I'm trying to get my current position and to get some cafe lists around me.
I made a getLocation function and I import it inside of my Context file CafeContext. However, I can't get the position before getting the cafe list.
It works sometimes when I set the lat/long in the range [37.~~, 125.~~].
This is getLocation
import { useState, useEffect } from "react";
import * as Location from "expo-location";
const getLocation = () => {
const [myX, setMyX] = useState(0);
const [myY, setMyY] = useState(0);
try {
const currentLocation = async () => {
await Location.requestPermissionsAsync();
const coordsObj = await Location.getCurrentPositionAsync();
await setMyY(coordsObj.coords.latitude);
await setMyX(coordsObj.coords.longitude);
};
useEffect(() => {
currentLocation();
}, []);
return { myX, myY };
} catch (err) {
setMyY(37.5572);
setMyX(126.9279);
return { myX, myY };
}
};
export default getLocation;
And this is CafeContext:
import React, { useState, createContext } from "react";
import cafeApi from "../api/cafeApi";
import AsyncStorage from "#react-native-community/async-storage";
import testArray from "../api/testArray.json";
import { navigate } from "../RootNavigation";
import getLocation from "../hooks/getLocation";
const CafeContext = React.createContext();
export const CafeProvider = ({ children }) => {
const [cafeList, setCafeList] = useState([]);
const [errorMessage, setErrorMessage] = useState("");
const [distance, setDistance] = useState(300);
//#####This line. I want to get location before getCafeList...
const { myX, myY } = getLocation();
const getCafeList = async () => {
const response = await cafeApi.get("/search", {
params: {
// category_group_code: "CE7",
x: myX,
y: myY,
radius: distance,
},
});
await setCafeList(response.data);
};
return (
<CafeContext.Provider
value={{
cafeList,
getCafeList,
distance,
setDistance,
term,
setTerm,
searchCafeList,
getLikedCafeList,
}}
>
{children}
</CafeContext.Provider>
);
};
export default CafeContext;
Your logic in getLocation is wrong. You are using hooks incorrectly, you can't return a values from a component.
If you want to return values you should create a custom hook also stop awaiting setState functions.
Custom hooks.
import { useState, useEffect } from 'react';
function useLocation() {
const [myX, setMyX] = useState(37.5572);
const [myY, setMyY] = useState(126.9279);
useEffect(() => {
const currentLocation = async () => {
await Location.requestPermissionsAsync();
const coordsObj = await Location.getCurrentPositionAsync();
setMyY(coordsObj.coords.latitude);
setMyX(coordsObj.coords.longitude);
};
currentLocation();
}, []);
return return { myX, myY };
}
Context
const CafeContext = React.createContext();
export const CafeProvider = ({ children }) => {
const [cafeList, setCafeList] = useState([]);
const [errorMessage, setErrorMessage] = useState("");
const [distance, setDistance] = useState(300);
//#####This line. I want to get location before getCafeList...
const { myX, myY } = useLocation();
const getCafeList = async () => {
const response = await cafeApi.get("/search", {
params: {
// category_group_code: "CE7",
x: myX,
y: myY,
radius: distance,
},
});
setCafeList(response.data);
};
return (
<CafeContext.Provider
value={{
cafeList,
getCafeList,
distance,
setDistance,
term,
setTerm,
searchCafeList,
getLikedCafeList,
}}
>
{children}
</CafeContext.Provider>
);
};
export default CafeContext;
Here is my entire component. In the console the correct data is showing up at "data" but when I try to run map on it it says "map is not a function." The 16 items in the console are the correct beaches.
import React, {useState, useEffect} from 'react';
import axios from 'axios';
export default function Beaches() {
const [data, setData] = useState({beaches: []})
// const [hasError, setErrors] = useState(false)
useEffect(() => {
const fetchBeaches = async () => {
const result = await axios('http://localhost:3000/beaches');
setData(result.data);}
fetchBeaches();
}, [])
console.log(data)
return (
<ul>
{data.beaches.map(beach => (
<button>{beach.name}</button>
))}
</ul>
)
}
Because you're not setting the beaches data in state correctly.
Replace useEffect code with this:
useEffect(() => {
const fetchBeaches = async () => {
const result = await axios('http://localhost:3000/beaches');
setData({beaches: result.data});
}
fetchBeaches();
}, [])
furthermore, you can improve the state structure of beaches data:
import React, { useState, useEffect } from "react";
import axios from "axios";
export default function Beaches() {
const [beaches, setBeaches] = useState([]);
// const [hasError, setErrors] = useState(false)
useEffect(() => {
const fetchBeaches = async () => {
const result = await axios("http://localhost:3000/beaches");
setBeaches(result.data);
};
fetchBeaches();
}, []);
return (
<ul>
{beaches.map((beach) => (
<button>{beach.name}</button>
))}
</ul>
);
}