Mapping through an array not rendering [duplicate] - javascript

This question already has answers here:
When should I use a return statement in ES6 arrow functions
(6 answers)
Closed 23 days ago.
I'm trying to render some cards through the mapping of an array.
My parent component seems to be consuming the information but the cards are still not rendering. In the example below I went back to basics and I'm not including the cards, I'm just trying to render an h1 in the parent component, and that also doesn't work:
This is my parent component:
export const Home = () => {
const { store, actions } = useContext(Context);
const data_types = Object.keys(store.results);
useEffect(() => {
for (let data_type of data_types) {
actions.getData(data_type)
}
}, []);
return (
<div className="text-center mt-5">
{data_types.map((data_type) => {
store.results[data_type].map((item, index) => {
<div className="container mb-4">
{console.log(index, item)}
<h1>{item}</h1>
</div>
})
This is how I fetch and store the data:
const getState = ({ getStore, getActions, setStore }) => {
return {
store: {
results: {
people: [],
planets: [],
starships: [],
vehicles: [],
}
},
actions: {
getData: async (data_type) => {
const local_results = JSON.parse(localStorage.getItem("results"));
if (local_results !== null && local_results[data_type].length > 0) {
console.log(`The data type ${data_type} is already in local storage`)
let result = {}
result[data_type] = [...local_results[data_type]]
setStore({
results: {
...getStore().results,
...result
}
})
return
}
const baseURL = `https://swapi.dev/api/${data_type}/`;
try {
const response_1 = await fetch(`${baseURL}`);
const data_1 = await response_1.json();
const response_2 = await fetch(`${data_1.next}`);
const data_2 = await response_2.json();
let result = {}
result[data_type] = [...data_1.results, ...data_2.results]
setStore({
results: {
...getStore().results,
...result
}
})
localStorage.setItem("results", JSON.stringify(getStore().results));
} catch (error) {
console.log(`Error loading message from https://swapi.dev/api/${data_type}/`, error);
}
}
This is the console print out:
I've also tried using a React State like this:
export const Home = () => {
const { store, actions } = useContext(Context);
const [arr, setArr] = useState([])
const data_types = Object.keys(store.results);
useEffect(() => {
for (let data_type of data_types) {
actions.getData(data_type)
}
setArr(store.results);
}, []);
return (
<div className="text-center mt-5">
{data_types.map((data_type) => {
arr !== "undefined" && data_type in arr ? arr[data_type].map((item, index) => {
<div className="container mb-4">
{console.log(index, item)}
<h1>{item}</h1>
</div>
}) : null
})
}
</div>
)
}

Your map function is returning an array of undefineds because you don't return anything in the arrow function.
either add the return keyword
return (
<div className="text-center mt-5">
{data_types.map((data_type) => {
return store.results[data_type].map((item, index) => {
<div className="container mb-4">
{console.log(index, item)}
<h1>{item}</h1>
</div>
})
or remove the curly brackets
return (
<div className="text-center mt-5">
{data_types.map((data_type) => {
return store.results[data_type].map((item, index) => (
<div className="container mb-4">
{console.log(index, item)}
<h1>{item}</h1>
</div>
))

Related

React - Delete Functionality is not working. uuid() is not generating IDs

Please help me! Delete Icon is not functional, when I click on delete icon it delete all the contact, on refreshing, it returns all the previous contacts. I am also using localStorage.
I have added all the Component of the React App Project.
App.js
import { v4 as uuid } from "uuid";
const App = () => {
const LOCAL_STORAGE_KEY = "contacts";
const [contacts, setContacts] = useState([]);
const addContactHandler = (contact) => {
console.log(contact);
setContacts([...contacts, { id: uuid(), ...contact }]);
};
const removeContactHandler = (id) => {
const newContactList = contacts.filter((contact) => {
return contact.id !== id;
});
setContacts(newContactList);
};
useEffect(() => {
const retrieveContacts = JSON.parse(
localStorage.getItem(LOCAL_STORAGE_KEY)
);
if (retrieveContacts) {
setContacts(retrieveContacts);
}
}, []);
useEffect(() => {
if (contacts.length) {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
}
}, [contacts]);
return (
<>
<div className="app">
<Header />
<AddContact addContactHandler={addContactHandler} />
<ContactList contacts={contacts} getContactId={removeContactHandler} />
</div>
</>
);
};
export default App;
ContactList.js
const ContactList = (props) => {
const deleteContactHandler = (id) => {
props.getContactId(id);
};
const renderContactList = props.contacts.map((contact) => {
return (
<>
<ContactCard
contact={contact}
clickHandler={deleteContactHandler}
key={contact.id}
/>
</>
);
});
return (
<>
<div className="contactList">
<h2 className="contactList__title">Contact List</h2>
<div className="contactList__container">
{renderContactList}
</div>
</div>
</>
);
};
ContactCard.js
const ContactCard = (props) => {
const { id, name, email } = props.contact;
return (
<>
<div className="contactCard">
<div className="contactCard__contact">
<img
className="contactCard__userIcon"
src={userIcon}
alt="user-icon"
/>
<div className="contactCard__userName">
<h2>{name}</h2>
<p>{email}</p>
</div>
</div>
<div className="contactCard__delIcon">
<img
src={delIcon}
alt="del-icon"
onClick={() => props.clickHandler(id)}
/>
</div>
</div>
</>
);
};
export default ContactCard;
I have researched out the references. Unable to get the Solution.
The effect to store the contacts do not save empty arrays.
Thats why you get the old array after refreshing your page.
Just remove the condition.
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
}, [contacts]);
But you should consider to remove this effect.
Save the contacts directly after setting the state instead.
const addContactHandler = (contact) => {
console.log(contact);
const newContactList = [...contacts, { id: uuid(), ...contact }];
setContacts(newContactList);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(newContactList));
};
const removeContactHandler = (id) => {
const newContactList = contacts.filter((contact) => {
return contact.id !== id;
});
setContacts(newContactList);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(newContactList));
};

How to implement cleanup function in useEffect while fetching data from API in contex provider

I want to display a list of products based on specific categories fetched from api, like below:
const API = "https://dummyjson.com/products";
const ProductsList = () => {
const { cate } = useParams(); //here I am getting category from Viewall component
const { getFilterProducts, filter_products } = useFilterContext();
useEffect(() => {
getFilterProducts(`${API}/category/${cate}`);
}, [cate]);
return (
<div className="mx-2 mt-2 mb-16 md:mb-0 grid grid-cols-1 md:grid-cols-12">
<div className="h-9 w-full md:col-span-2">
<FilterSection />
</div>
<div className="md:col-span-10">
<ProductListDetails products={filter_products} />
</div>
</div>
);
};
My FilterContextProvider is as follows
const initialState = {
filter_products: [],
};
const FilterProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const { products } = useAppContext();
const getFilterProducts = async (url) => {
dispatch({ type: "FILTERS_LOADING" });
try {
const res = await fetch(url);
const data = await res.json();
if (!res.ok) {
var error = new Error("Error" + res.status + res.statusText);
throw error;
}
dispatch({ type: "LOAD_FILTER_PRODUCTS", payload: data.products });
} catch (err) {
dispatch({ type: "FILTERS_ERROR", payload: err.message });
}
};
return (
<FilterContext.Provider value={{ ...state, getFilterProducts }}>
{children}
</FilterContext.Provider>
);
};
I tried using this simple approach in my ProductList component to clean up:
useEffect(() => {
let inView = true;
getFilterProducts(`${API}/category/${cate}`);
return () => {
inView = false;
};
}, [cate]);
But it does not seem to work. When I move to the ProductList component, it first displays data of my previous filer_products value, then after a few fractions of seconds, updates the data and shows current data.
I am expecting that when the ProductList component unmounts, its rendered data should vanish, and when I navigate it again, it should render the current data directly, not after a fraction of seconds.
As you explained, I assume your context is wrapping your routes, and it's not re-rendering when switching between pages. A simple solution is to have a loader in ProductsList, wait for the new data to replace the old, and have the user notice what's happening with a loader:
const ProductsList = () => {
const { cate } = useParams(); //here I am getting category from Viewall component
const { getFilterProducts, filter_products } = useFilterContext();
const [loading, setLoading] = useState(true);
useEffect(() => {
getFilterProducts(`${API}/category/${cate}`).then(() => {
setLoading(false);
});
}, [cate]);
if (loading) {
return <p>Hang tight, the data is being fetched...</p>;
}
return (
<div className="mx-2 mt-2 mb-16 md:mb-0 grid grid-cols-1 md:grid-cols-12">
<div className="h-9 w-full md:col-span-2">
<FilterSection />
</div>
<div className="md:col-span-10">
<ProductListDetails products={filter_products} />
</div>
</div>
);
};
If you need to clear your store in a clean-up function, you can add dispatch as part of your context value, grab it in ProductsList and call it like so:
<FilterContext.Provider value={{ ...state, getFilterProducts, dispatch }}>
{children}
</FilterContext.Provider>
const { getFilterProducts, filter_products, dispatch } = useFilterContext();
useEffect(() => {
getFilterProducts(`${API}/category/${cate}`);
return () => {
dispatch({ type: "LOAD_FILTER_PRODUCTS", payload: {} });
};
}, [cate]);

Render list with React

I am trying to render a dynamic list but inside the jsx rendered, I can't display any item this is my code, I've also tried with useState const [orderList, setOrderList] = useState([]) and setOrderList(prev => [...prev, childData]) but it returns me an empty array so I've opted for the classic javascript way but it won't work, it also won't console log the data inside the render
const OrdiniModuloVideoAds = () => {
let orderList = [];
const ordiniRef = ref(realtimeDatabase, "ordinazioneVideoAds/modulo/ordini");
useEffect(() => {
onValue(ordiniRef, (snapshot) => {
snapshot.forEach((childSnapshot) => {
const childData = childSnapshot.val();
orderList.push(childData);
});
console.log(orderList);
});
}, []);
return (
<StyledOrdiniModuloVideoAds>
<div className='ordiniWrapper'>
{orderList.map((i) => {
return (
<span>{i.mail}</span>
);
})}
</div>
</StyledOrdiniModuloVideoAds>
);
};
EDIT
This is the snippet with the useState:
const OrdiniModuloVideoAds = () => {
const [orderList, setOrderList] = useState([])
const ordiniRef = ref(realtimeDatabase, "ordinazioneVideoAds/modulo/ordini");
useEffect(() => {
onValue(ordiniRef, (snapshot) => {
snapshot.forEach((childSnapshot) => {
const childData = childSnapshot.val();
setOrderList((prev) => [...prev, childData])
});
console.log(orderList);
});
}, []);
return (
<StyledOrdiniModuloVideoAds>
<div className='ordiniWrapper'>
{orderList.map((i) => {
return (
<span>{i.mail}</span>
);
})}
</div>
</StyledOrdiniModuloVideoAds>
);
};
The data is actually added because it logs to me the array on the useEffect Any suggestion?
Try this solution hopefully it will fix your issue.
const OrdiniModuloVideoAds = () => {
const [orderList, setOrderList] = React.useState([]);
const ordiniRef = ref(realtimeDatabase, "ordinazioneVideoAds/modulo/ordini");
useEffect(() => {
onValue(ordiniRef, (snapshot) => {
snapshot.forEach((childSnapshot) => {
const childData = childSnapshot.val();
setOrderList(prev => ([...prev, childData])); /// Order list array is empty because you're not returning the data properly that's why it just gives you the default empty array in the console.
});
});
}, []);
return (
<StyledOrdiniModuloVideoAds>
<div className='ordiniWrapper'>
{orderList.map((i) => <span key={i.mail}>{i.mail}</span>)}
</div>
</StyledOrdiniModuloVideoAds>
);
};
This is because your map callback does not return anything:
<div className='ordiniWrapper'>
{array.map((i) => {
return (
<span>{i.mail}</span>
);
})}
</div>
Or the short version:
<div className='ordiniWrapper'>
{array.map((i) => (
<span>{i.mail}</span>
))}
</div>

Make React JS await for a async func to complete before running

I'm trying to make react not load until after an axios get requests finishes. I'm pretty rough on react all around, so sorry in advance.
I'm getting an array of objects
const { dogBreedsTest } = useApplicationData()
And I need it to be the default value of one of my states
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest);
However, I'm getting an error that my value is coming up as null on the first iteration of my app starting. How can I ensure that my value has completed my request before my app tries to use it?
Here is how I am getting the data for useApplicationData()
const [dogBreedsTest, setDogBreeds] = useState(null);
const getDogBreeds = async () => {
try{
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
}
useEffect(() => {
getDogBreeds()
}, []);
return {
dogBreedsTest,
setDogBreeds
}
And I am importing into my app and using:
import useApplicationData from "./hooks/useApplicationData";
const { dogBreedsTest } = useApplicationData()
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest[0]);
const [breedList1, updateBreedList1] = useState(dogBreedsTest[0])
function handleOnDragEnd(result) {
if (!result.destination) return;
const items = Array.from(dogBreeds);
const [reorderedItem] = items.splice(result.source.index, 1);
items.splice(result.destination.index, 0, reorderedItem);
for (const [index, item] of items.entries()) {
item['rank'] = index + 1
}
updateDogBreeds(dogBreedsTest[0]);
updateBreedList1(dogBreedsTest[0])
}
return (
<div className="flex-container">
<div className="App-header">
<h1>Dog Breeds 1</h1>
<DragDropContext onDragEnd={handleOnDragEnd}>
<Droppable droppableId="characters">
{(provided) => (
<ul className="dogBreeds" {...provided.droppableProps} ref={provided.innerRef}>
{breedList1?.map(({id, name, rank}, index) => {
return (
<Draggable key={id} draggableId={id} index={index}>
{(provided) => (
<li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<p>
#{rank}: { name }
</p>
</li>
)}
</Draggable>
);
})}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
</div>
)
error: TypeError: Cannot read property 'map' of null
(I am mapping the data later in the program)
const getDogBreeds = async () => {
try {
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
}
useEffect(() => {
getDogBreeds() // -> you are not awaiting this
}, []);
Do this instead
useEffect(() => {
axios.get('https://dog.ceo/api/breeds/list/all')
.then(res => {
const newDogList = generateDogsArray(res.data['message']);
const generatedDogs = selectedDogs(newDogList);
setDogBreeds(generatedDogs);
})
.catch(err => console.log(err));
}, []);
I know this looks awful, but I don't think you should use async/await inside useEffect
Use this in your application
useEffect will update whenever dogBreedsTest is changed. In order to make it work, start with null values and update them to the correct initial values once your async operation is finished.
const { dogBreedsTest } = useApplicationData();
const [dogBreeds, updateDogBreeds] = useState(null);
const [breedList1, updateBreedList1] = useState(null);
useEffect(() => {
updateDogBreeds(dogBreedsTest[0]);
updateBreedList1(dogBreedsTest[0]);
}, [dogBreedsTest]);
The problem is, that react first render and then run useEffect(), so if you don't want to render nothing before the axios, you need to tell to react, that the first render is null.
Where is your map function, to see the code? to show you it?.
I suppose that your data first is null. So you can use something like.
if(!data) return null
2nd Option:
In your map try this:
{breedList1 === null
? null
: breedList1.map(({id, name, rank}, index) => (
<Draggable
key={id} draggableId={id} index={index}>
{(provided) => (
<li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<p>
#{rank}: { name }
</p>
</li>
)}
</Draggable> ))}
You have null, because your axios is async and react try to render before any effect. So if you say to react that the list is null, react will render and load the data from the api in the second time.
Option 1 use the optional chaining operator
dogBreedsTest?.map()
Option 2 check in the return if dogBreedsTest is an array
retrun (<>
{Array.isArray(dogBreedsTest) && dogBreedsTest.map()}
</>)
Option 3 return early
if (!Array.isArray(dogBreedsTest)) return null
retrun (<>
{dogBreedsTest.map()}
</>)
Option 4 set initial state
const [dogBreedsTest, setDogBreeds] = useState([]);
You could also add a loading state and add a loading spinner or something like that:
const [dogBreedsTest, setDogBreeds] = useState(null);
const [loading, setLoading] = useState(true)
const getDogBreeds = async () => {
setLoading(true)
try{
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
setLoading(false)
}
useEffect(() => {
getDogBreeds()
}, []);
return {
dogBreedsTest,
loading,
setDogBreeds
}
Edit
Try to use a useEffect hook to update the states when dogBreedsTest got set.
const { dogBreedsTest } = useApplicationData()
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest?.[0] ?? []);
const [breedList1, updateBreedList1] = useState(dogBreedsTest?.[0] ?? [])
useEffect(() => {
updateDogBreeds(dogBreedsTest?.[0] ?? [])
updateBreedList1(dogBreedsTest?.[0] ?? [])
}, [dogBreedsTest])

Strange behaviour when passing variable down to child component

I am seeing some strange behaviour when I am trying to pass down a variable to a child component in react. When I console.log just before the return statement (so console.log(1)) in the parent component the data is correct, however when I console.log in the child component (so console.log(2)) the data has changed??
I have a suspicion that it relates to the randomSelect() function but again when console logging out this looks to only be called twice (as expected).
I have pasted a 'playerOneId' in directly (avoiding using the randomSelect() function) and the data shows correctly in the child component when doing this, hence my suspicion around the randomSelect() function. It could be unrelated but not sure.
A gold medal to anyone can answer this one as it has had me for hours now and I've run out of ideas.
PARENT COMPONENT:
const Board = () => {
const [starships, setStarships] = useState([]);
const [playerSelected, setPlayerSelected] = useState(false);
const [result, setResult] = useState('');
const [playerScore, setPlayerScore] = useState(0);
const [computerScore, setComputerScore] = useState(0);
const STARSHIP_QUERY = `{
allStarships {
starships {
id
name
starshipClass
maxAtmospheringSpeed
costInCredits
passengers
filmConnection {
films {
title
}
}
}
}
}
`
useEffect(() => {
fetch('https://connectr-swapi.herokuapp.com/', {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({query: STARSHIP_QUERY})
})
.then(response => response.json())
.then(data => setStarships(data.data.allStarships.starships))
.catch(error => console.log({'Error': error}))
},[])
const randomSelect = () => {
const random = Math.floor(Math.random() * starShipIds.length);
const selectedId = starShipIds[random];
return selectedId;
}
const starShipIds = starships.map(ship => ship.id)
const valueOneID = randomSelect();
const valueTwoID = randomSelect();
const playerOneId = valueOneID;
const computerId = valueTwoID;
const playerOneShip = starships.filter(ship => ship.id === playerOneId) ;
const computerShip = starships.filter(ship => ship.id === computerId);
const catergorySelect = (key, value) => {
let computerValue = key === 'filmConnection' ? computerShip[0][key].films.length : computerShip[0][key];
if (value > computerValue) {
setResult('You Win!');
setPlayerScore(playerScore + 1)
}
if (value === computerValue) setResult('You Draw!');
if (value < computerValue) {
setResult('You Lose!');
setComputerScore(computerScore + 1)
}
setPlayerSelected(true);
}
console.log(1, playerOneShip[0]); // data is showing correctly
return (
<div className="background">
<div className="row">
<div className="col-12 col-sm-4">
{playerOneShip.length &&
<GameCard
ship={playerOneShip[0]} // data passed in
player='player-one'
select={catergorySelect}
/>
}
{playerSelected &&
<Score
score={playerScore}
colour="white"
/>
}
</div>
<div className="col-12 col-sm-4">
<div className="row">
<h1>{result}</h1>
</div>
<div className="row">
<DefaultBtn
text="START AGAIN"
colour="white"
/>
</div>
</div>
<div className="col-12 col-sm-4">
{playerSelected &&
<React.Fragment>
<div>
{computerShip.length &&
<GameCard
ship={computerShip[0]}
player='computer'
catergorySelect={catergorySelect}
/>
}
</div>
<div>
<Score
score={computerScore}
colour="white"
/>
</div>
</React.Fragment>
}
</div>
</div>
</div>
)
}
CHILD COMPONENT:
const GameCard = props => {
const [selected, setSelected] = useState(0);
const [disableCategory, setDisableCategory] = useState(false);
const {
ship,
player,
select,
} = props;
console.log(2, ship) // different data is showing
const categories = Object.entries(props.ship).map(([key, value], index) => {
const choosenCategory = selected === index ? 'selected' : '';
const disableButton = disableCategory ? 'disable' : '';
switch (key) {
case 'maxAtmospheringSpeed':
return <li className={`card ${player} ${choosenCategory} ${disableButton}`} onClick={(() => { select(key, value); setSelected(index); setDisableCategory(true)})} key={index}>{`Maximum Speed: ${value}`}</li>
case 'costInCredits':
return <li className={`card ${player} ${choosenCategory} ${disableButton}`} onClick={(() => { select(key, value); setSelected(index); setDisableCategory(true)})} key={index}>{`Cost In Credits: ${value}`}</li>
case 'passengers':
return <li className={`card ${player} ${choosenCategory} ${disableButton}`} onClick={(() => { select(key, value); setSelected(index); setDisableCategory(true)})} key={index}>{`Number Of Passengers: ${value}`}</li>
case 'filmConnection':
return <li className={`card ${player} ${choosenCategory} ${disableButton}`} onClick={(() => { select(key, value.films.length); setSelected(index); setDisableCategory(true)})} key={index}>{`Number Of films: ${value.films.length}`}</li>
default:
return null
}
});
return (
<div className="card">
<img className="card-image" src="assets/img/starships/2.jpg" />
<div className="card-body">
<p className="card-title">{`Ship Name: ${ship.name}`}</p>
<p className="card-sub-title">{`Class: ${ship.starshipClass}`}</p>
<ul>
{categories}
</ul>
</div>
</div>
)
}
It's probably a reference issue, the variable passed in props is updated by another render in the parent.
A way of fixing it could be to put all this section of code in a useEffect depending on the loading of the starships:
const starShipIds = starships.map(ship => ship.id)
const valueOneID = randomSelect();
const valueTwoID = randomSelect();
const playerOneId = valueOneID;
const computerId = valueTwoID;
const playerOneShip = starships.filter(ship => ship.id === playerOneId) ;
const computerShip = starships.filter(ship => ship.id === computerId);
It could look like this:
useEffect(() => {
const starShipIds = starships.map(ship => ship.id)
const valueOneID = randomSelect(starShipIds);
const valueTwoID = randomSelect(starShipIds);
const playerOneId = valueOneID;
const computerId = valueTwoID;
setPlayerOneShip(starships.filter(ship => ship.id === playerOneId));
setComputerShip(starships.filter(ship => ship.id === computerId));
},[starships])
For this you need to create a state for player ship and computer ship and replace previous usage of these, like in my example above.
Also, you should pass the starship ids to random select as a parameter and not use a const and assume it has the correct value because it is in the scope of the function.

Categories