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;
}
});
Related
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'm trying to make the cart with react context. But the problem here is when ever I do an increment on it it gives a NaN or on the price and no item quantity at the start when I view the cart. After I click increase quantity it gives the quantity as NaN as well. but after i refresh the page the quantity and price changes from NaN to a number. Please help me fix this.
The pictures of what the results are like are in this link :(https://imgur.com/a/QkktrZp)
And the codes for what I did are below:
Cart Context Code
`
import { createContext, useReducer, useEffect } from "react";
export const cartContext = createContext({});
export const CartContextProvider = ({ children }) => {
const reducer = (state, action) => {
switch (action.type) {
case "ADD":
const temporaryCart = state.filter(
(items) => action.payload.id === items.id
);
if (temporaryCart.length > 0) {
return state;
} else {
return [...state, action.payload];
}
case "INCREASE":
const increment = state.map((items) => {
if (items.id === action.payload.id) {
return {
...items,
quantity: items.quantity + 1,
};
} else {
return items;
}
});
return increment;
case "DECREASE":
const decrement = state.map((items) => {
if (items.id === action.payload.id) {
return {
...items,
quantity: items.quantity - 1,
};
} else {
return items;
}
});
return decrement;
case "REMOVECART":
const removeCart = state.filter(
(items) => items.id !== action.payload.id
);
return removeCart;
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, [], () => {
const localCart = localStorage.getItem("Cart");
return localCart ? JSON.parse(localCart) : [];
});
useEffect(() => {
localStorage.setItem("Cart", JSON.stringify(state));
}, [state]);
const cart = { state, dispatch };
return <cartContext.Provider value={cart}>{children}</cartContext.Provider>;
};
Cart Code:
import React, { useContext, useEffect, useState } from "react";
import { Button, Container, Stack } from "react-bootstrap";
import { cartContext } from "../Context/CartContext";
import { useAuthContext } from "../Context/useAuthContext";
const Cart = () => {
const { user } = useAuthContext();
const Cart = useContext(cartContext);
const state = Cart.state;
const dispatch = Cart.dispatch;
return (
<div>
{state.map((items, idx) => {
return (
<Container className="p-5">
<Stack gap={3}>
{state.map((items) => {
return <Container className="border d-flex justify-content-evenly align-items-center">
<div>
<img src={items.images[0].imageName} alt="/" width={"80px"} height={"80px"} />
</div>
<div>{items.title}</div>
<div className="d-flex justify-content-evenly align-items-center">
<Button onClick={()=>dispatch({type:"DECREASE", payload:items})}>-</Button>
<div>{items.quantity}</div>
<Button onClick={()=>dispatch({type:"INCREASE", payload:items})}>+</Button>
</div>
<div>
<Button onClick={()=>dispatch({type:"REMOVE", payload:items})}>Remove</Button>
</div>
<div>
{items.quantity*items.unitPrice[0].sellingPrice}
</div>
</Container>;
})}
</Stack>
</Container>
);
})}
</div>
);
};
export default Cart;
`
Any help would be appreciated. Thank you in advance!
Used usereducer hook to create and store items in local storage and fetched it in the cart page
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
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
Summarize the problem
I have a page within a Gatsby JS site that accepts state via a provider, and some of that activity is able to be used, however, I am unable to provide the contents from a mapping function that is given via context.
Expected result: the expected elements from the mapping function would render
Actual result: the elements in question are not rendered
No error messages
Describe what you've tried
I thought the issue was not explicitly entering in return on the arrow function in question, but that does not change any of the output
Also, rather than try to access the method directly on the page (via a context provider) I moved the method directly into the Provider hook. This did not change any of the rendering.
Show some code
here is Provider.js
import React, { useState, useEffect } from 'react';
import he from 'he';
export const myContext = React.createContext();
const Provider = props => {
const [state, setState] = useState({
loading: true,
error: false,
data: [],
});
const [page, setPage] = useState(1);
const [score, setScore] = useState(0);
const [correctAnswers, setCorrectAnswers] = useState([]);
const [allQuestions, setAllQuestions] = useState([]);
const [answers, setAnswers] = useState([]);
const [right, setRight] = useState([]);
const [wrong, setWrong] = useState([]);
function clearScore() {
updatedScore = 0;
}
function clearRights() {
while (rights.length > 0) {
rights.pop();
}
}
function clearWrongs() {
while (wrongs.length > 0) {
wrongs.pop();
}
}
let updatedScore = 0;
let rights = [];
let wrongs = [];
const calcScore = (x, y) => {
for (let i = 0; i < 10; i++) {
if (x[i] === y[i]) {
updatedScore = updatedScore + 1;
rights.push(i);
} else wrongs.push(i);
}
}
useEffect(() => {
fetch('https://opentdb.com/api.php?amount=10&difficulty=hard&type=boolean')
.then(response => {
return response.json()
})
.then(json => {
const correctAnswer = json.results.map(q => q['correct_answer']);
const questionBulk = json.results.map(q => q['question']);
setState({
data: json.results,
loading: false,
error: false,
});
setCorrectAnswers(correctAnswers.concat(correctAnswer));
setAllQuestions(allQuestions.concat(questionBulk));
})
.catch(err => {
setState({error: err})
})
}, [])
return (
<myContext.Provider
value={{
state, page, score, answers, right, wrong,
hitTrue: () => {setAnswers(answers.concat('True')); setPage(page + 1);},
hitFalse: () => {setAnswers(answers.concat('False')); setPage(page + 1);},
resetAll: () => {
setAnswers([]);
setPage(1);
setScore(0);
setRight([]);
setWrong([]);
clearScore();
clearWrongs();
clearRights();
},
calculateScore: () => calcScore(answers, correctAnswers),
updateScore: () => setScore(score + updatedScore),
updateRight: () => setRight(right.concat(rights)),
updateWrong: () => setWrong(wrong.concat(wrongs)),
showRightAnswers: () => {right.map((result, index) => {
return (
<p className="text-green-300 text-sm" key={index}>
+ {he.decode(`${allQuestions[result]}`)}
</p>)
})},
showWrongAnswers: () => {wrong.map((result, index) => {
return (
<p className="text-red-500 text-sm" key={index}>
- {he.decode(`${allQuestions[result]}`)}
</p>
)
})},
}}
>
{props.children}
</myContext.Provider>
);
}
export default ({ element }) => (
<Provider>
{element}
</Provider>
);
^the showRightAnswers() and showWrongAnswers() methods are the ones I am trying to figure out
and here is the results.js page.{context.showRightAnswers()} and {context.showWrongAnswers()} are where the mapped content is supposed to appear.
import React from 'react';
import Button from '../components/Button';
import { navigate } from 'gatsby';
import { myContext } from '../hooks/Provider';
const ResultsPage = () => {
return (
<myContext.Consumer>
{context => (
<>
<h1 className="">You Finished!</h1>
<p className="">Your score was {context.score}/10</p>
{context.showRightAnswers()}
{context.showWrongAnswers()}
<Button
buttonText="Try Again?"
buttonActions={() => {
context.resetAll();
navigate('/');
}}
/>
</>
)}
</myContext.Consumer>
);
}
export default ResultsPage;
You are returning inside your map, but you're not returning the map call itself - .map returns an array, and you have to return that array from your "show" functions, e.g.
showWrongAnswers: () => { return wrong.map((result, index) ...
^^^^
This will return the array .map generated from the showWrongAnswers function when it's called, and thus {context.showWrongAnswers()} will render that returned array