I keep getting map is not a function - javascript

import React, { useState, useEffect } from "react";
import { checkRef } from "./firebase";
function Dashboard() {
const [count, setCount] = useState([]);
let hi = [];
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
checkRef.on("value", snapshot => {
let items = snapshot.val();
let newState = [];
for (let tracker in items) {
newState.push({
reason: items[tracker].reason,
teacher: items[tracker].teacher
});
}
setCount({ items: newState });
});
// Update the document title using the browser API
// checkRef.on('value',(snapshot) => {
// console.log(snapshot.val())
// })
}, []);
return (
<div>
{count.items.map(item => {
return <h1>{item.reason}</h1>;
})}
</div>
);
}
export default Dashboard;
I am trying to return each item as an h1 after getting the item but i keep getting the error ×
TypeError: Cannot read property 'map' of undefined. I apoligize i AM new to web dev and trying to learn. I have spent way to much time with no results. thanks

Problem lies with your initalization of count.
For the very first render your count state variable doesn't contain any property named items. hence it fails.
your state variable is getting items prepery only after useEffect which excutes after first render.
based on your useeffct code, you should initialize count state variable with an object like follwing,
const [count, setCount] = useState({items:[]});

It sounds like useEffect is not calling the call back function (the one you defined).
You could try initializing the count.items to be an array
Or make sure that useEffect calls the callback function

Another way to fix your issue is simply do this:
{
count && count.items && count.items.map( item => {
// do something with the item
})
}

Related

localStorage resets to empty on refresh in NextJS

I have a shopping cart system in my next.js app using Context.
I define my cart with useState:
const [cartItems, setCartItems] = useState([]);
Then I use useEffect to check and update the localStorage:
useEffect(() => {
if (JSON.parse(localStorage.getItem("cartItems"))) {
const storedCartItems = JSON.parse(localStorage.getItem("cartItems"));
setCartItems([...cartItems, ...storedCartItems]);
}
}, []);
useEffect(() => {
window.localStorage.setItem("cartItems", JSON.stringify(cartItems));
}, [cartItems]);
This stores the items in localStorage fine, but when I refresh, it resets the cartItems item in localStorage to an empty array. I've seen a few answers where you get the localStorage item before setting the cart state but that throws localStorage is not defined errors in Next. How can I do this?
setCartItems sets the value of cartItems for the next render, so on the initial render it's [] during the second useEffect
You can fix this by storing a ref (which doesn't rerender on state change) for whether it's the first render or not.
import React, { useState, useRef } from "react";
// ...
// in component
const initialRender = useRef(true);
useEffect(() => {
if (JSON.parse(localStorage.getItem("cartItems"))) {
const storedCartItems = JSON.parse(localStorage.getItem("cartItems"));
setCartItems([...cartItems, ...storedCartItems]);
}
}, []);
useEffect(() => {
if (initialRender.current) {
initialRender.current = false;
return;
}
window.localStorage.setItem("cartItems", JSON.stringify(cartItems));
}, [cartItems]);
React never updates state immediately It's an asynchronous process, for example, if you console.log(stateValue) just after the setState() method you'll get prevState value in a log. You can read more about it here.
That is exactly happening here, you have called setState method inside the first useEffect, state has not updated yet by react and we're trying to update localStorage with the latest state value(for now it's [] since react has not updated the state yet). that's why the localStorage value holds an empty array.
For your case, you can skip the first execution of 2nd useEffect as #Samathingamajig's mentioned in his answer.
PS: Thanks #Samathingamajig for pointing out the silly mistake, I don't know how missed that. LOL
thanks to jamesmosier for his answer on gitHub
The issue you are seeing is because localStorage (aka window.localStorage) is not defined on the server side. Next server renders your components, so when that happens and it tried to access localStorage it can't find it. You'll have to wait until the browser renders it in order to use localStorage.
full answer link here
EDIT
you can try this :
useEffect(() => {
if (typeof window !== "undefined") {
const storedCartItems = JSON.parse(localStorage.getItem("cartItems"));
if(storedCartItems !== null) {
setCartItems([...cartItems, ...storedCartItems]);
}
}
}, []);
useEffect(() => {
if (typeof window !== "undefined") {
window.localStorage.setItem("cartItems", JSON.stringify(cartItems));
}
}, [cartItems]);

How to prevent state updates from a function, running an async call

so i have a bit of a weird problem i dont know how to solve.
In my code i have a custom hook with a bunch of functionality for a fetching a list
of train journeys. I have some useEffects to that keeps loading in new journeys untill the last journey of the day.
When i change route, while it is still loading in new journeys. I get the "changes to unmounted component" React error.
I understand that i get this error because the component is doing an async fetch that finishes after i've gone to a new page.
The problem i can't figure out is HOW do i prevent it from doing that? the "unmounted" error always occur on one of the 4 lines listed in the code snippet.
Mock of the code:
const [loading, setLoading] = useState(true);
const [journeys, setJourneys] = useState([]);
const [hasLaterDepartures, setHasLaterDepartures] = useState(true);
const getJourneys = async (date, journeys) => {
setLoading(true);
setHasLaterDepartures(true);
const selectedDateJourneys = await fetchJourney(date); // Fetch that returns 0-3 journeys
if (condition1) setHasLaterDepartures(false); // trying to update unmounted component
if (condition2) {
if (condition3) {
setJourneys(something1); // trying to update unmounted component
} else {
setJourneys(something2) // trying to update unmounted component
}
} else {
setJourneys(something3); // trying to update unmounted component
}
};
// useEffects for continous loading of journeys.
useEffect(() => {
if (!hasLaterDepartures) setLoading(false);
}, [hasLaterDepartures]);
useEffect(() => {
if (hasLaterDepartures && journeys.length > 0) {
const latestStart = ... // just a date
if (latestStart.addMinutes(5).isSameDay(latestStart)) {
getJourneys(latestStart.addMinutes(5), journeys);
} else {
setLoading(false);
}
}
}, [journeys]);
I can't use a variable like isMounted = true in the useEffect beacuse it would reach inside the if statement and reach a "setState" by the time i'm on another page.
Moving the entire call into a useEffect doesn't seem to work either. I am at a loss.
Create a variable called mounted with useRef, initialised as true. Then add an effect to set mounted.current to false when the component unmounts.
You can use mounted.current anywhere inside the component to see if it's mounted, and check that before setting any state.
useRef gives you a variable you can mutate but which doesn't cause a rerender.
When you use useEffect hook with action which can be done after component change you should also take care about clean effect when needed. Maybe example help you, also check this page.
useEffect(() => {
let isClosed = false
const fetchData = async () => {
const data = await response.json()
if ( !isClosed ) {
setState( data )
}
};
fetchData()
return () => {
isClosed = true
};
}, []);
In your use case, you probably want to create a Store that doesn't reload everytime you change route (client side).
Example of a store using useContext();
const MyStoreContext = createContext()
export function useMyStore() {
const context = useContext(MyStoreContext)
if (!context && typeof window !== 'undefined') {
throw new Error(`useMyStore must be used within a MyStoreContext`)
}
return context
}
export function MyStoreProvider(props) {
const [ myState, setMyState ] = useState()
//....whatever codes u doing with ur hook.
const exampleCustomFunction = () => {
return myState
}
const getAllRoutes = async (mydestination) => {
return await getAllMyRoutesFromApi(mydestination)
}
// you return all your "getter" and "setter" in value props so you can use them outside the store.
return <MyStoreContext.Provider value={{ myState, setMyState, exampleCustomFunction, getAllRoutes }}>{props.children}</MyStoreContext.Provider>
}
You will wrap the store around your entire App, e.g.
<MyStoreProvider>
<App />
</MyStoreProvider>
In your page where you want to use your hook, you can do
const { myState, setMyState, exampleCustomFunction, getAllRoutes } = useMyStore()
const onClick = async () => getAllRouters(mydestination)
Considering if you have client side routing (not server side), this doesn't get reloaded every time you change your route.

useEffect break array of useState at first time

I am learning react hooks. I am having mock data js call "MockFireBase.js" as below:
const userIngredientsList = [];
export const Get = () => {
return userIngredientsList;
}
export const Post = (ingredient) => {
ingredient.id = userIngredientsList.length + 1;
userIngredientsList.push(ingredient);
return ingredient;
}
Then my react hooks component "Ingredients.js" will call this mock utilities as following details:
const Ingredients = () => {
const [userIngredients, setUserIngredients] = useState([]);
// only load one time
useEffect(() => { setUserIngredients(Get()); }, []);
const addIngredienHandler = ingredient => {
let responsData = Post(ingredient);
setUserIngredients(preIngredients => {
return [...preIngredients, responsData]
});
}
return (
<div className="App">
<IngredientForm onAddIngredient={addIngredienHandler} />
<section>
<IngredientList ingredients={userIngredients} />
</section>
</div>
);
)
}
When I added first ingredient, it added two (of course I get same key issue in console.log). Then I added second ingredient is fine.
If I remove the useEffect code as below, it will work good.
// only load one time
useEffect(() => { setUserIngredients(loadedIngredients); }, []);
I am wondering what I did anything wrong above, if I use useEffect
The problem is not in useEffect. It's about mutating a global userIngredientsList array.
from useEffect you set initial component state to be userIngredientsList.
Then inside addIngredienHandler you call Post(). This function does two things:
2a. pushes the new ingredient to the global userIngredientsList array`. Since it's the same instance as you saved in your state in step 1, your state now contains this ingredient already.
2a. Returns this ingredient
Then, addIngredienHandler adds this ingredient to the state again - so you end up having it in the state twice.
Fix 1
Remove userIngredientsList.push(ingredient); line from your Post function.
Fix 2
Or, if you need this global list of ingredients for further usage, you should make sure you don't store it in your component state directly, and instead create a shallow copy in your state:
useEffect(() => { setUserIngredients([...Get()]); }, []);

Can't access state inside function in React Function Component

I have the following component in React:
import React, { useState, useEffect } from 'react';
import api from '../../services/api';
const Store = () => {
const [products, setProducts] = useState([]);
let pagination = null;
const getProducts = async (page = 1) => {
const { data } = await api.get('products', { params: { page } });
setProducts([...products, ...data.products]);
pagination = data.pagination;
if (pagination.current < pagination.max) {
document.addEventListener('scroll', loadMore);
}
};
const loadMore = () => {
const { scrollTop, clientHeight, scrollHeight } = document.documentElement;
if (scrollTop + clientHeight >= scrollHeight - 300) {
getProducts(pagination.current + 1);
document.removeEventListener('scroll', loadMore);
}
};
useEffect(() => {
getProducts();
}, []);
useEffect(() => {
console.log(products);
}, [products]);
return (
<div>
{products.map(product => (
<p>{product.name}</p>
))}
</div>
);
};
export default Store;
My console.log inside the useEffect hook do print the products array correctly, and the component is also rendering the products titles correctly.
But when I try to access the products variable inside the getProducts function it doesn't get the updated products value, it gets the value I have set in the useState hook.
For example, if I start the state with one product, calling products within the getProducts function will always bring this one product, and not the ones loaded from the API fetch, that were correctly logged in the console.
So, when I try to add more products to the end of the array it actually just add the products to an empty array.
Any idea why this is happening? Being able to access the products state inside the useState hook but not inside the getProducts function?
This is happening because the getProducts is using the value products had when getProducts was being declared, not the one products has when the getProducts function is called. This is always the case when you want to access the most recent state directly in non-inline functions. Mostly, you will need to pass the updated value through the function arguments.
But this is not an option in your case. The only option in your case is to use the previous value as the argument from a callback passed to setState (in your case, setProducts).
Hope this helps someone else in future :).

Passing props to URL

I am using deckofcardsapi.com and I cannot pass my deck_id in props to fetch url.
Props are passing ok because when I use them in line 40 they display as normal string. And when I paste this string to variable deckId in line 23 it is giving me list of cards.
But when instead of copying string to deckId i use props.deckId there is error "TypeError: cards.cards is undefined"
On the other hand props.quantity works well.
Here is my code
import React, { useState, useEffect } from 'react';
function NewDeck(props) {
const [deck, setDeck] = useState({ deck_id: [] });
async function draw() {
const result = await fetch(`https://deckofcardsapi.com/api/deck/new/shuffle/?deck_count=1`);
const json = await result.json();
setDeck(json);
};
useEffect(() => {
draw();
}, []);
return(
<Draw deckId={deck.deck_id} quantity={props.quantity} />
);
}
function Draw(props) {
const [cards, setCards] = useState({ cards: [] });
var deckId = props.deckId; //when i put here for egzample "l31hmefvilqe" it is working
async function draw() {
const result = await fetch(`https://deckofcardsapi.com/api/deck/${deckId}/draw/?count=${props.quantity}`);
const json = await result.json();
setCards(json);
};
useEffect(() => {
draw();
}, []);
return(
<ul>
{cards.cards.map(item => (
<li>{item.code}</li>
))}
<li>deckId: {props.deckId}</li>
<li>quantity: {props.quantity}</li>
</ul>
);
}
export default NewDeck;
How to pass props.deckId to my fetch's url
I was searching for answer but with no result. This is probably my stupid mistake but i can't find it.
Thank you in adwance
The problem is that at the first render of NewDeck the value of deck_id isn't set and it pass an empty array. You can fix it rendering the Draw component conditionally to the deck_id having a value . https://codesandbox.io/s/brave-margulis-olgxt In my example i set the deck_id to null and render draw only when deck_id exists.
PD: props.quantity is undefined and maybe you meant deck.remaining? Also check the draw() dependency for useEffect in the component, maybe you need to useCallback() (I0m not so sure of this because I'm still learning hooks)

Categories

Resources