Am requesting help setting the cartItems state to local storage so that when never I refresh, I still get the items.
import React, { createContext, useState } from 'react';
export const AppContext = createContext();
export function Context(props) {
const [cartItems, setCartItems] = useState([]);
const cart = (item) => {
const exist = cartItems.find((x) => x.id === item.id);
if (!exist) {
setCartItems([...cartItems, { ...item }]);
} else {
alert('Item Already Taken');
}
};
const onAdd = (items) => {
console.log(cartItems);
const exist = cartItems.find((x) => x.id === items.id);
if (exist) {
setCartItems(
cartItems.map((x) =>
x.id === items.id ? { ...exist, qty: exist.qty + 1 } : x,
),
);
console.log(cartItems);
} else {
console.log(items);
setCartItems([...cartItems, { ...items, qty: 1 }]);
}
};
const onRemove = (product) => {
const exist = cartItems.find((x) => x.id === product.id);
if (exist.qty === 1) return;
setCartItems(
cartItems.map((x) =>
x.id === product.id ? { ...exist, qty: exist.qty - 1 } : x,
),
);
};
const onDeleted = (product) => {
if (window.confirm('Do you want to delete this product?')) {
setCartItems(cartItems.filter((x) => x.id !== product.id));
}
};
const itemPrice =
cartItems && cartItems.reduce((a, c) => a + c.qty * c.price, 0);
const delieveryPrice = '3000';
// eslint-disable-next-line
const totalPrice =
parseInt(itemPrice) + (itemPrice && parseInt(delieveryPrice));
return (
<AppContext.Provider
value={{
cartItems,
setCartItems,
onAdd,
onRemove,
cart,
onDeleted,
itemPrice,
totalPrice,
delieveryPrice,
}}
>
{props.children}
</AppContext.Provider>
);
}
You can acheive your goal by creating a customHook to initialize state with value in localStorage and alos write state on the localStorage on every update, like this:
import * as React from 'react'
function useLocalStorageState(
key,
defaultValue = '',
{serialize = JSON.stringify, deserialize = JSON.parse} = {},
) {
const [state, setState] = React.useState(() => {
const valueInLocalStorage = window.localStorage.getItem(key)
if (valueInLocalStorage) {
try {
return deserialize(valueInLocalStorage)
} catch (error) {
window.localStorage.removeItem(key)
}
}
return typeof defaultValue === 'function' ? defaultValue() : defaultValue
})
const prevKeyRef = React.useRef(key)
React.useEffect(() => {
const prevKey = prevKeyRef.current
if (prevKey !== key) {
window.localStorage.removeItem(prevKey)
}
prevKeyRef.current = key
window.localStorage.setItem(key, serialize(state))
}, [key, state, serialize])
return [state, setState]
}
and use it in your component like this:
const [cartItems, setCartItems] = useLocalStorageState('cartItems',[]);
You use can use localStorage.setItem().but since you have a list of items, you will first stringify it while saving and parsing when you fetch it back.
import React, { createContext, useState } from 'react';
export const AppContext = createContext();
export function Context(props) {
const [cartItems, setCartItems] = useState([]);
const onAdd = (items) => {
const tempItems = [...cartItems];
const exist = cartItems.findIndex((x) => x.id === items.id);
console.log(exist);
if (exist >= 0) {
tempItems[exist].qty = tempItems[exist].qty + 1;
}
else {
// setCartItems([...cartItems, { ...items, qty: 1 }]);
tempItems.push(items)
}
setCartItems(tempItems)
localStorage.setItem("cart" , JSON.stringify(tempItems))
};
//to fetch data
const getCart = () => {
const FetchedcartItems = localStorage.getItem("cart");
if (FetchedcartItems) {
const parsedCartItems = JSON.parse(FetchedcartItems);
console.log(parsedCartItems);
setCartItems(parsedCartItems);
}
};
useEffect(() => {
getCart();
}, []);
return (
<AppContext.Provider
value={{
cartItems,
setCartItems,
onAdd,
onRemove,
cart,
onDeleted,
itemPrice,
totalPrice,
delieveryPrice,
}}
>
{props.children}
</AppContext.Provider>
);
}
What I do, usually, is to set up a file just to handle localStorage, so in your case, cart.services.js. Inside that file you can create functions that receive, save, read, modify and etc the localStorage.
Something like this:
const getCart = () => {
return JSON.parse(localStorage.getItem("cart"));
};
const setCart = (cart) => {
localStorage.setItem("cart", JSON.stringify(cart));
};
const removeUser = () => {
localStorage.removeItem("cart");
};
Obviously you might need some more fancier logic to add items based on previous state, etc, but the basic logic is that and it's super straightforward.
More info: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
Related
i am trying to query data for dbUser restaurant and basket but im getting error type possible unhandled promise rejection (id:17)
type error: o.userID is not a function. (o.userID('eq', dbUser.id)
type error null is not an object ( evaluating restaurant.id).
type here
import { createContext, useState, useEffect, useContext } from "react";
import { DataStore } from "aws-amplify";
import { Basket, BasketDish } from "../models";
import { useAuthContext } from "./AuthContext";
const BasketContext = createContext({});
const BasketContextProvider = ({ children }) => {
const { dbUser } = useAuthContext();
const [restaurant, setRestaurant] = useState(null);
const [basket, setBasket] = useState(null);
const [basketDishes, setBasketDishes] = useState([]);
const totalPrice = basketDishes.reduce(
(sum, basketDish) => sum + basketDish.quantity * basketDish.Dish.price,
restaurant?.deliveryFee
);
useEffect(() => {
DataStore.query(Basket, (b) =>
b.restaurantID("eq", restaurant.id).userID("eq", dbUser.id)
).then((baskets) => setBasket(baskets[0]));
}, [dbUser, restaurant]);
useEffect(() => {
if (basket) {
DataStore.query(BasketDish, (bd) => bd.basketID("eq", basket.id)).then(
setBasketDishes
);
}
}, [basket]);
const addDishToBasket = async (dish, quantity) => {
// get the existing basket or create a new one
let theBasket = basket || (await createNewBasket());
// create a BasketDish item and save to Datastore
const newDish = await DataStore.save(
new BasketDish({ quantity, Dish: dish, basketID: theBasket.id })
);
setBasketDishes([...basketDishes, newDish]);
};
const createNewBasket = async () => {
const newBasket = await DataStore.save(
new Basket({ userID: dbUser.id, restaurantID: restaurant.id })
);
setBasket(newBasket);
return newBasket;
};
return (
<BasketContext.Provider
value={{
addDishToBasket,
setRestaurant,
restaurant,
basket,
basketDishes,
totalPrice,
}}
>
{children}
</BasketContext.Provider>
);
};
export default BasketContextProvider;
export const useBasketContext = () => useContext(BasketContext);
please help me out. https://www.youtube.com/live/WFo_IxhBxF4?feature=share I'm doing ubereat by Vadim
I am doing an Ecommerce project and I came with this issue, When I click the “Add to cart” button twice on the first product and navigate to Cart page it works fine (2 quantity of same product added) but after I add something to cart and when I did the same thing it throws me the error after I pressed the Cart Icon:
TypeError: Cannot read properties of undefined (reading 'name') at components\Cart.tsx (73:32)
Then I found out that sometimes the onAdd() function on StateContext.js updates the cartItem to be undefined.
Little explanation about my code:
onAdd(): It is a function that adds the selected item to cart. It takes 2 arguments→products,qty. It's defined in StateContext.js
cartItems and setCartItems(): These are the useState function variables defined in StateContext.js. It stores all the items that are being added to cart.
Here's my code which is the Add to cart snippet:
<button type="button" className={styles.add_to_cart} onClick={() =>onAdd(product,qty)}>Add to Cart</button>
Here is my Cart.tsx file code (CartItems maps a undefined item in the array which is causing this error):
{
cartItems.length >= 1 &&
cartItems?.map((item) => {
console.log(item)
return (
<div
className={styles.product}
key={item._id}>
<img
src={urlFor(item?.image[0])}
className={styles.cart_product_image}
/>
<div className={styles.item_desc}>
<div className={`${styles.flex} ${styles.top}`}>
<h5>{item.name}</h5>
<h4>₹ {item.price}</h4>
</div>
//some more code....
</div>
</div>
)
})
}
StateContext.js file. The onAdd() function code snippet Which I doubt is causing the error.
import React, {
createContext,
useContext,
useState,
useEffect,
} from 'react'
import {toast} from 'react-hot-toast'
const Context = createContext()
export const StateContext = ({children}) => {
const [showCart, setShowCart] = useState(false)
const [cartItems, setCartItems] = useState([])
const [totalPrice, setTotalPrice] = useState(0)
const [totalQuantities, setTotalQuantities] = useState(0)
const [qty, setQty] = useState(1)
const onAdd = (product, quantity) => {
const checkProductInCart = cartItems.find(
(item) => item._id === product._id
)
setTotalPrice(
(prevTotalPrice) => prevTotalPrice + product.price * quantity
)
setTotalQuantities(
(prevTotalQuantities) => prevTotalQuantities + quantity
)
if (checkProductInCart) {
const updatedCartItems = cartItems.map((cartProduct) => {
if (cartProduct._id === product._id)import React, { createContext, useContext, useState, useEffect } from 'react';
import { toast } from 'react-hot-toast';
const Context = createContext();
export const StateContext = ({ children }) => {
const [showCart, setShowCart] = useState(false);
const [cartItems, setCartItems] = useState([]);
const [totalPrice, setTotalPrice] = useState(0);
const [totalQuantities, setTotalQuantities] = useState(0);
const [qty, setQty] = useState(1);
const onAdd = (product, quantity) => {
const checkProductInCart = cartItems.find((item) => item._id === product._id);
setTotalPrice((prevTotalPrice) => prevTotalPrice + product.price * quantity);
setTotalQuantities((prevTotalQuantities) => prevTotalQuantities + quantity);
if(checkProductInCart) {
const updatedCartItems = cartItems.map((cartProduct) => {
if(cartProduct._id === product._id) return {
...cartProduct,
quantity: cartProduct.quantity + quantity
}
})
setCartItems(updatedCartItems);
} else {
product.quantity = quantity;
setCartItems([...cartItems, { ...product }]);
}
toast.success(`${qty} ${product.name} added to the cart.`);
}
// Some more codes...
return (
<Context.Provider
value={{
//all global state exports...
}}
>
{children}
</Context.Provider>
)
}
export const useStateContext = () => useContext(Context);
return {
...cartProduct,
quantity: cartProduct.quantity + quantity,
}
})
setCartItems(updatedCartItems)
} else {
product.quantity = quantity
setCartItems([...cartItems, {...product}])
}
toast.success(`${qty} ${product.name} added to the cart.`)
}
// Some more codes...
return (
<Context.Provider
value={
{
//all global state exports...
}
}>
{children}
</Context.Provider>
)
}
export const useStateContext = () => useContext(Context)
The problem is you're pushing single object into cartItems. Therefore object can't contain length properties. Therefor you should Like below sample code:
cartItems.map((cartProduct)=>{
if(cartProduct._id===product._id){
let newQantity=cartProduct.quantity + quantity;
setCartProduct([
...cartItems,
{
...cartProduct,
quantity:newQuantity
}
])
}
})
The problem stems from the way you generate updatedCartItems:
const updatedCartItems = cartItems.map((cartProduct) => {
if (cartProduct._id === product._id) {
return {
...cartProduct,
quantity: cartProduct.quantity + quantity
};
}
});
Array.prototype.map() creates an array element for every element in the original array. For every case where the if condition is not matched, you implicitly return undefined. This new array with some valid items and some undefined ones is then set into your state.
To exemplify, the code you have right now is functionally equivalent to the following:
const updatedCartItems = cartItems.map((cartProduct) => {
if (cartProduct._id === product._id) {
return {
...cartProduct,
quantity: cartProduct.quantity + quantity
};
} else {
return undefined;
}
});
I've been making a game which at the end, requires the user to type their guess. To avoid confusion in my actual project, I created something in codesandbox which demonstrates the problem I'm having. I should add that the game in codesandbox isn't suppose to make much sense. But essentially you just click any box 5 times which generates a random number and when the component mounts, it also creates an array with 5 random number. At the end, you type a number and it checks if both arrays contain the key entered and colors them accordingly. The problem I'm having is that once the guess component is shown, all the hooks states return to their initial states.
Main.tsx
import { Guess } from "./Guess";
import { useHook } from "./Hook";
import { Loading } from "./Loading";
import "./styles.css";
export const Main = () => {
const {loading, count, handleClick, randArr} = useHook()
return (
<div className="main">
{!loading && count < 5 &&
<div className='click-container'>
{Array.from({length: 5}).fill('').map((_, i: number) =>
<div onClick={handleClick} className='box' key={i}>Click</div>
)}
</div>
}
{loading && <Loading count={count} />}
{!loading && count >= 5 && <Guess arr={randArr} />}
</div>
);
}
Hook.tsx
import { useEffect, useState } from 'react'
export const useHook = () => {
type guessType = {
keyNum: number
isContain: boolean
}
const [disable, setDisable] = useState(true)
const [randArr, setRandArr] = useState<number[]>([])
const [initialArr, setInitialArr] = useState<number[]>([])
const [count, setCount] = useState<number>(0)
const [loading, setLoading] = useState(true)
const [guess, setGuess] = useState<guessType[]>([])
const randomNum = () => {
return Math.floor(Math.random() * (9 - 0 + 1) + 0);
}
useEffect(() => {
const handleInitialArr = () => {
for (let i = 0; i < 5; i++) {
let num = randomNum()
setInitialArr((prev) => [...prev, num])
}
}
handleInitialArr()
}, [])
const handleClick = () => {
if (!disable) {
let num = randomNum()
setRandArr((prev)=> [...prev, num])
setCount((prev) => prev + 1)
setDisable(true)
setLoading(true)
}
}
useEffect(()=> {
const handleLoading = () => {
setTimeout(() => {
setLoading(false)
}, 500)
}
const handleRound = () => {
setDisable(false)
}
handleLoading()
handleRound()
}, [count])
const handleKeyUp = ({key}) => {
const isNumber = /^[0-9]$/i.test(key)
if (isNumber) {
if (randArr.includes(key) && initialArr.includes(key)) {
setGuess((prev) => [...prev, {keyNum: key, isContain: true}])
console.log(' they both have this number')
} else {
setGuess((prev) => [...prev, {keyNum: key, isContain: false}])
console.log(' they both do not contain this number ')
}
}
}
console.log(count)
console.log(randArr, ' this is rand arr')
console.log(initialArr, ' this is initial arr')
return {
count,
loading,
handleClick,
randArr,
handleKeyUp,
guess
}
}
Guess.tsx
import React, { useEffect } from "react";
import { useHook } from "./Hook";
import "./styles.css";
type props = {
arr: number[];
};
export const Guess: React.FC<props> = (props) => {
const { handleKeyUp, guess } = useHook();
useEffect(() => {
window.addEventListener("keyup", handleKeyUp);
return () => {
window.removeEventListener("keyup", handleKeyUp);
};
}, [handleKeyUp]);
console.log(props.arr, " this is props arr ");
return (
<div className="content">
<div>
<p>Guesses: </p>
<div className="guess-list">
{guess.map((item: any, i: number) =>
<p key={i} className={guess[i].isContain ? 'guess-num-true': 'guess-num-false'} >{item.keyNum}</p>
)}
</div>
</div>
</div>
);
};
Also, here is the codesandbox if you want to take a look for yourself: https://codesandbox.io/s/guess-numbers-70fss9
Any help would be deeply appreciated!!!
Fixed demo: https://codesandbox.io/s/guess-numbers-fixed-kz3qmw?file=/src/my-context.tsx:1582-2047
You're under the misconception that hooks share state across components. The hook will have a new state for every call of useHook(). To share state you need to use a Context.
type guessType = {
keyNum: number;
isContain: boolean;
};
type MyContextType = {
count: number;
loading: boolean;
handleClick: () => void;
randArr: number[];
handleKeyUp: ({ key: string }) => void;
guess: guessType[];
};
export const MyContext = createContext<MyContextType>(null as any);
export const MyContextProvider: FC<PropsWithChildren<{}>> = ({ children }) => {
// Same stuff as your hook goes here
return (
<MyContext.Provider
value={{ count, loading, handleClick, randArr, handleKeyUp, guess }}
>
{children}
</MyContext.Provider>
);
};
export const App = () => {
return (
<div className="App">
<MyContextProvider>
<Page />
</MyContextProvider>
</div>
);
};
export const Main = () => {
const { loading, count, handleClick, randArr } = useContext(MyContext);
...
}
export const Guess: React.FC<props> = (props) => {
const { handleKeyUp, guess } = useContext(MyContext);
...
}
Your handleKeyUp function is also bugged, a good example of why you need to type your parameters. key is a string, not a number. So the condition will always be false.
const handleKeyUp = ({ key }: {key: string}) => {
const num = parseInt(key);
if (!isNaN(num)) {
if (randArr.includes(num) && initialArr.includes(num)) {
setGuess((prev) => [...prev, { keyNum: num, isContain: true }]);
console.log(" they both have this number");
} else {
setGuess((prev) => [...prev, { keyNum: num, isContain: false }]);
console.log(" they both do not contain this number ");
}
}
};
I am trying to optimize my react application, while profiling my application I found that when I click on Add to cart page my whole page is getting re-rendered. Could anyone help me with, how to avoid that and why it is happening?
FYR, GitHub repo:https://github.com/sandeep8080/shopping-cart-assignment
import { useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import SideBar from "../../components/sideBar/SideBar";
import { getProductsData } from "../../redux/action/products";
import ProductCard from '../../components/productCard/ProductCard';
import './products.css';
import { getCategoryData } from "../../redux/action/category";
import Cart from "../cart/cart";
import Modal from '../../components/modal/Modal';
import { useHistory, useParams } from "react-router";
const ProductsPage = () => {
const dispatch = useDispatch();
const router = useHistory();
const { id } = useParams();
console.log(` product comp : ${id}`);
const productsData = useSelector(data => data.Products.products);
const sideBarData = useSelector(data => {
const listItems = data.Categories.CategoriesItems;
const activeListItems = listItems.filter(item => item.enabled === true);
return activeListItems;
});
const openCart = useSelector(state => state.CartDetails.isOpen);
const [fProductData, setFProductData] = useState([]);
useEffect(() => {
dispatch(getProductsData());
dispatch(getCategoryData());
}, []);
useEffect(() => {
if (id) {
filterDataByCategory(id);
} else {
setFProductData(productsData);
}
}, [productsData, id]);
// Function to filter out the data based on category
const filterDataByCategory = (id) => {
console.log("Filter data function called")
const filterData = productsData.filter(item => item.category === id);
setFProductData(filterData);
};
const handleClickProduct = useCallback((id) => {
filterDataByCategory(id);
router.push(`/products/${id}`);
}, [id]);
return (
<div className='product-main'>
<SideBar
sideBarData={sideBarData}
handleClickProduct={handleClickProduct}
/>
<div className='product-container'>
<div className='product-row'>
{
(fProductData).map((product) => {
return (
<div key={product.id} className='card-wrapper' >
<ProductCard key={product.id} {...product} />
</div>
)
})
}
</div>
</div>
{
openCart &&
<Modal>
<Cart />
</Modal>
}
</div >
)
};
export default ProductsPage;
// Product Card component
import './ProductCard.css';
import Button from '../button/Button';
import React from 'react';
import { useDispatch } from 'react-redux';
import { updateCart } from '../../redux/action/cart';
import priceFromatter from '../../lib/priceFromatter';
const ProductCard = ({ name, price, description, imageURL, id }) => {
const dispatch = useDispatch();
const handleClick = () => {
console.log('product clicked', id);
dispatch(updateCart(id, 'add'));
};
let imgURL = `../../${imageURL}`;
// imgURL = imgURL.replace(/([^:]\/)\/+/g, "$1");
// const image = React.lazy(() => import (`${imgURL}`));
// console.log(image);
return (
<article className='card-container'>
<h6 className='card-header'>
{name}
</h6>
<div className='content-container'>
<img
className='content-img'
// src={require(`${imgURL}`).default}
src={imageURL}
/>
<div className='content'>
<p className='content-desc'>{description}</p>
<div className='content-footer'>
<p>{priceFromatter(price)}</p>
<Button btnText='Add To Cart' handleClick={() => handleClick(id)} />
</div>
</div>
</div>
</article>
)
};
export default ProductCard;
import { callApi } from "../../lib/api";
import { actions } from '../actionContants/actionConstant';
export const toggleCart = (isToggle) => {
return {
type: actions.OPEN_CART,
payload: isToggle,
}
};
export const updateCart = (id, operation) => {
return async (dispatch, getState) => {
const productList = getState().Products.products;
const cartItems = getState().CartDetails.cartItems;
const currItem = productList.find(({ id: currentItemId }) => currentItemId === id);
const isItemInCart = cartItems.find(({ id }) => id === currItem.id);
let finalItem = [];
if (!isItemInCart) {
finalItem = [...cartItems, { ...currItem, count: 1 }]
} else {
finalItem = cartItems.map(item => {
if (item.id === currItem.id) {
operation === 'add' ? item.count = item.count + 1 : item.count = item.count - 1
}
return item;
}).filter(({ count }) => count)
}
try {
const result = await callApi.post('/addToCart', id);
result && dispatch({
type: actions.UPDATE_TO_CART,
payload: finalItem
})
} catch (error) {
console.log(error)
}
}
};
In products.js change the following block of code:
const sideBarData = useSelector(data => {
const listItems = data.Categories.CategoriesItems;
const activeListItems = listItems.filter(item => item.enabled === true);
return activeListItems;
});
to:
const sideBarData = useSelector(data => {
const listItems = data.Categories.CategoriesItems;
const activeListItems = listItems.filter(item => item.enabled === true);
return activeListItems;
}, shallowEqual);
useSelector will force a component to re-render when the selector returns a new reference that is different than the previous reference (it uses the === operator). Ref: https://react-redux.js.org/api/hooks#equality-comparisons-and-updates. As you are filtering the array returned from the store, it will always be a different object reference to the one in the store.
The use of shallowEqual as the equalityFn to useSelector() can be used to change the comparison and prevent an unnecessary re-render of the <ProductsPage> component.
did you try using e.preventDefault() otherwise the answer above might work
I have this exported const in one file useLocation.tsx where I get the user's location and retrieve the user's county, state/province, and country. I also have an exported const in another file useCountryData.tsx where I fetch the COVID cases and deaths from an API. There is a variable in useLocation.tsx that is called countryNameshort. How do I use this variable in useCountryData.tsx?
useLocation.tsx
export const useLocation = () => {
var [stateName, setstateName] = useState(String);
var [countyName, setCountyName] = useState(String);
var [countryName, setCountryName] = useState(String);
var [stateNameshort, setstateNameshort] = useState(String);
var [countryNameshort, setCountryNameshort] = useState(String);
const [latitude, setlatitude] = useState(Number);
const [longitude, setlongitude] = useState(Number);
const [location, setLocation] = useState(Object);
const [errorMsg, setErrorMsg] = useState(String);
useEffect(() => {
(async () => {
if (Platform.OS === "android" && !Constants.isDevice) {
setErrorMsg(
"Oops, this will not work on Snack in an Android emulator. Try it on your device!"
);
return;
}
let { status } = await Location.requestPermissionsAsync();
if (status !== "granted") {
setErrorMsg("Permission to access location was denied");
return;
}
let location = await Location.getCurrentPositionAsync({});
setLocation(location);
const latitude = location.coords.latitude;
setlatitude(latitude);
const longitude = location.coords.longitude;
setlongitude(longitude);
})();
}, []);
let text = "Waiting..";
if (errorMsg) {
text = errorMsg;
} else if (location) {
text = JSON.stringify(location);
}
fetch(
"https://maps.googleapis.com/maps/api/geocode/json?address=" +
latitude +
"," +
longitude +
"&key=" +
apiKey
)
.then((response) => response.json())
.then((responseJson) => {
const resState = responseJson.results[0].address_components.filter(
(x: any) =>
x.types.filter((t: Object) => t == "administrative_area_level_1")
.length > 0
)[0].long_name;
setstateName(resState);
const resCounty = responseJson.results[0].address_components.filter(
(x: any) =>
x.types.filter((t: Object) => t == "administrative_area_level_2")
.length > 0
)[0].long_name;
setCountyName(resCounty);
const resCountry = responseJson.results[0].address_components.filter(
(x: any) => x.types.filter((t: Object) => t == "country").length > 0
)[0].long_name;
setCountryName(resCountry);
const resStateShort = responseJson.results[0].address_components.filter(
(x: any) =>
x.types.filter((t: Object) => t == "administrative_area_level_1")
.length > 0
)[0].short_name;
setstateNameshort(resStateShort);
const resCountryShort = responseJson.results[0].address_components.filter(
(x: any) => x.types.filter((t: Object) => t == "country").length > 0
)[0].short_name;
setCountryNameshort(resCountryShort);
if (countryNameshort === "US") {
countryNameshort = "US" + "A";
}
})
.catch((err) => {
console.log(err);
});
return { countryName, countyName, stateName, stateNameshort, countryNameshort };
};
useCountryData.tsx
import { useLocation } from './useLocation';
export const useCountryData = () => {
const [earliest2, setEarliest2] = useState([]);
const [countryDeaths, setcountryDeaths] = useState(Number);
const [countryCases, setcountryCases] = useState(Number);
useEffect(() => {
axios
.get("https://coronavirus-19-api.herokuapp.com/countries")
.then((response) => {
setEarliest2(response.data);
const countryArray = response.data.filter(
(item) => item.country === props.countryNameshort //???
);
const resCountryDeaths = countryArray[0].deaths;
setcountryDeaths(resCountryDeaths);
const resCountryCases = countryArray[0].cases;
setcountryCases(resCountryCases);
console.log("hiiii", countryCases);
})
.catch((err) => {
console.log(err);
});
}, []);
return { countryCases, countryDeaths };
};
CountryCard.tsx
const CountryCard = (props) => {
const mappedLocation = useMappedLocation();
const countryName = mappedLocation.country;
return (
<RectButton style={[styles.container, { backgroundColor: "white" }]}>
<Text style={[styles.textLocation, { top: 15, left: 10 }]}>
{countryName} /???
</Text>
)
}
Here is a pseudo-code suggestion for how you could refactor these stages, without adopting useEffect and useState for operations that are more conventionally just async, followed with a 'hook-style' pattern which does useState and useEffect to make the async results available to your UI. There is no chance on earth that this code will run as I don't have access to your environment to really try it, but it gives you an idea of how it might be refactored. If the state needs to be consumed by multiple parts of your UI, then makes sense for the useMappedLocation hook to assign a mappedLocation variable in an ancestor component, with the result passed down to descendants through Context, Composition or Props. This will have the effect of caching the result.
I've also sketched out how a second hook might consume the first hook as I think having re-read your question that was the point you got stuck with your original approach. However, embedding the useMappedLocation hook in multiple places will cause it to be re-executed multiple times and will not benefit from caching compared to hoisting it into an ancestor component.
const apikey = "myapikey";
interface GeoEntry {
address_components:[
{types:("country"|"administrative_area_level_1")[]
short_name:string,
long_name:string
}
]
}
interface MappedLocation {
state:string,
country:string
}
async function getLocation(){
return await Location.getCurrentPositionAsync({});
}
async function getFirstGeoEntry() : Promise<GeoEntry>{
const {latitude,longitude} = await getLocation();
const response = await fetch(
"https://maps.googleapis.com/maps/api/geocode/json?address=" +
latitude +
"," +
longitude +
"&key=" +
apikey
)
const json = await response.json();
return json.results[0]
}
function getStateNameLong(geoEntry:GeoEntry){
return geoEntry.address_components.filter(
(x: any) =>
x.types.filter((t: Object) => t == "administrative_area_level_1")
.length > 0
)[0].long_name
}
function getCountryNameShort(geoEntry:GeoEntry){
return geoEntry.address_components.filter(
(x: any) => x.types.filter((t: Object) => t == "country").length > 0
)[0].short_name
}
async function getMappedLocation() : Promise<MappedLocation>{
const geoEntry = await getFirstGeoEntry();
return {
country:getCountryNameShort(geoEntry),
state:getStateNameLong(geoEntry),
}
}
const useMappedLocation = () => {
const [mappedLocation,setMappedLocation] = useState<MappedLocation>(null);
useEffect(() => {
(async () => {
setMappedLocation(await getMappedLocation())
})()
}, [])
return mappedLocation
}
Here's how a second hook ( useCountryData ) might consume the first ( useMappedLocation ). Note the useEffect handles the case that the location hasn't arrived yet, and mappedLocation is in the dependency array to ensure the useEffect runs a second time when the mappedLocation DOES finally arrive.
import { useMappedLocation } from './useMappedLocation';
export const useCountryData = () => {
const [earliest2, setEarliest2] = useState([]);
const [countryDeaths, setcountryDeaths] = useState(Number);
const [countryCases, setcountryCases] = useState(Number);
const mappedLocation = useMappedLocation()
useEffect(() => {
if(mappedLocation !== null){
axios.get("https://coronavirus-19-api.herokuapp.com/countries")
.then((response) => {
setEarliest2(response.data);
const countryArray = response.data.filter(
(item) => item.country === mappedLocation.country
);
const resCountryDeaths = countryArray[0].deaths;
setcountryDeaths(resCountryDeaths);
const resCountryCases = countryArray[0].cases;
setcountryCases(resCountryCases);
console.log("hiiii", countryCases);
})
.catch((err) => {
console.log(err);
});
}
}, [mappedLocation]);
return { countryCases, countryDeaths };
};