I'm trying to build a clone of a MovieDataBase site in react, and when I update my state variable the page jumps ( it jumps up exactly 60px, for whatever reason, and then back down again each time I toggle the switch). I thought it was maybe because I'm interacting with the DOM to get the toggle switch to work, but that doesn't seem to be the issue. I've also been told that happens with styled components, which I don't think I have (still pretty new to REACT, so maybe???). Anyway - I can't figure out why this is happening. I've included the code for the component in question below.
import React, { useState, useEffect } from "react";
import axios from "axios";
import API_KEY from "../../config";
const Popular = ({ imageUri }) => {
// GET POPULAR MOVIES
const [popularMovies, setPopularMovies] = useState("");
const [genre, setGenre] = useState("movie");
const getPopular = async () => {
const response = await axios.get(
`https://api.themoviedb.org/3/discover/${genre}?sort_by=popularity.desc&api_key=${API_KEY}`
);
setPopularMovies(response.data.results);
};
useEffect(() => {
getPopular();
}, [genre]);
const listOptions = document.querySelectorAll(".switch--option");
const background = document.querySelector(".background");
const changeHandler = (el) => {
let getGenre = el.target.dataset.genre;
setGenre(getGenre);
listOptions.forEach((option) => {
option.classList.remove("selected");
});
el = el.target.parentElement.parentElement;
let getStartingLeft = Math.floor(
listOptions[0].getBoundingClientRect().left
);
let getLeft = Math.floor(el.getBoundingClientRect().left);
let getWidth = Math.floor(el.getBoundingClientRect().width);
let leftPos = getLeft - getStartingLeft;
background.setAttribute(
"style",
`left: ${leftPos}px; width: ${getWidth + 1}px`
);
el.classList.add("selected");
};
const isMovieSelected = genre === "movie";
const isTvSelected = genre === "tv";
const movieData = "movie";
const tvData = "tv";
return (
<section className="container movie-list">
<div className="flex flex--align-center">
<MovieHeader>What's Popular</MovieHeader>
<div className="switch flex">
<Toggle
onChange={changeHandler}
selected={isMovieSelected}
data={movieData}
>
In Theaters
<div className="background"></div>
</Toggle>
<Toggle
onChange={changeHandler}
selected={isTvSelected}
data={tvData}
>
On TV
</Toggle>
</div>
</div>
<div className="scroller">
<div className="flex flex--justify-center">
<div className="flex flex--nowrap container u-overScroll">
{popularMovies &&
popularMovies.map((movie) => {
const { title, id, poster_path } = movie;
return (
<MovieItem
title={title}
imageUri={imageUri}
key={id}
poster_path={poster_path}
/>
);
})}
</div>
</div>
</div>
</section>
);
};
export default Popular;
const Toggle = (props) => {
const { children, onChange, selected, data } = props;
const className = selected ? "switch--option selected" : "switch--option";
return (
<div className={className}>
<h3>
<a data-genre={data} onClick={onChange} className="switch--anchor">
{children}
</a>
</h3>
</div>
);
};
const MovieHeader = (props) => {
const { children } = props;
return (
<div className="movie-list__header">
<h3>{children}</h3>
</div>
);
};
const MovieItem = (props) => {
const { title, imageUri, poster_path, id } = props;
return (
<div key={id} className="card">
<div className="image">
<img src={imageUri + "w500" + poster_path} />
</div>
<p>{title}</p>
</div>
);
};
Related
I'm trying to pass data to the parent component Top.js using props from a child component TagsInput.js where I can add tags but
I don't understand what is causing the error...
What I want to achieve
I want to pass "tags" to the parent component Top.js from TagsInput.js in the child component with props.
I got the error like
props.setTagsinput is not a function
TagsInput.js
import React from "react";
const TagsInput = (props) => {
//1, Define the tags variable to store the entered tags. (I want to pass the value of the tags variable to the parent component Top.js)
const [tags, setTags] = React.useState([]);
//2, Put formText in the function received from the parent component and return it.
props.setTagsinput(tags);
console.log(props)
let tag_list = []
tag_list.push(tags);
const addTags = event => {
if (event.key === "Enter" && event.target.value !== "") {
setTags([...tags, event.target.value]);
event.target.value = "";
}
};
const removeTags = index => {
setTags([...tags.filter(tag => tags.indexOf(tag) !== index)]);
};
return (
<div className="tags-input">
<div className="tags_section">
{tags.map((tag, index) => (
<div className="tag tag-flex" key={index}>
<p className="tag-p">{tag}</p>
</div>
))}
</div>
<input
type="text"
onKeyUp={event => addTags(event)}
placeholder="Press enter to add tags"
/>
</div>
);
};
export default TagsInput;
Top.js
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import Student from './Student';
import TagsInput from "./TagsInput";
const Top = () => {
const [ posts, setPosts] = useState([]);
const [ allPosts, setAllPosts] = useState([]);
let tag_list = []
const [searchKeyword, setSearchKeyword] = React.useState("");
const [searchTagKeyword, setTagSearchKeyword] = React.useState("");
console.log(searchKeyword)
const[tags_from_tagsinput, setTagsinput]= useState("");
console.log(tags_from_tagsinput);
useEffect(() => {
axios.get('xxx.com')
.then(result => {
setPosts(result.data.students);
setAllPosts(result.data.students);
if (searchKeyword) {
getSearchResult()
}
})},
[searchKeyword]);
const getSearchResult = () => {
console.log(searchKeyword)
const result = allPosts.filter((output, index) => {
return output.firstName.toLowerCase().includes(searchKeyword.toLowerCase())||output.lastName.toLowerCase().includes(searchKeyword.toLowerCase());
});
console.log(result)
setPosts(result);
};
const getTagSearchResult = () => {
console.log(searchTagKeyword)
const result = allPosts.filter((output, index) => {
return output.lastName.toLowerCase().includes(searchTagKeyword.toLowerCase());
});
console.log(result)
setPosts(result);
};
return (
<div>
<TagsInput setTagsinput={setTagsinput}/>
<div>
<input className="search-box" placeholder="" value={searchKeyword} onChange={(e) => setSearchKeyword(e.target.value)}/>
</div>
<div>
<input className="search-box" placeholder="" value={searchTagKeyword} onChange={(e) => setSearchKeyword(e.target.value)}/>
</div>
<div>
{searchKeyword &&
<p>{searchKeyword} Search</p>
}
{posts ?
<>
{posts.map((data, i) =>
<Student data={data} />
)}
</>
:
<div>
<p>Not Found!</p>
</div>
}
</div>
</div>
);
}
export default Top;
Student.js
import React, {useState} from 'react';
import TagsInput from './TagsInput';
const Student = (props) => {
const [show, setShow] = useState(false)
const gradesAverage = (grades) => {
let sum = 0;
grades.forEach(function(score) {
sum += Number(score);
});
let ave = sum / grades.length
return ave;
};
return (
<div className="flex">
<div className="image">
<img src={props.data.pic} className="profile" />
</div>
<div>
<p className="name">{props.data.firstName} {props.data.lastName}</p>
<button className="button" onClick={() => setShow(!show)}>
{show? <div className="button_p">-</div>:<div className="button_p">+</div>}
</button>
<div className="info">
<p>Email: {props.data.email}</p>
<p>Company: {props.data.company}</p>
<p>Skill: {props.data.skill}</p>
<p>Average Grade: {gradesAverage(props.data.grades)}%</p>
{show &&
<>
<p>Test 1: {props.data.grades[0]}%</p>
<p>Test 2: {props.data.grades[1]}%</p>
<p>Test 3: {props.data.grades[2]}%</p>
<p>Test 4: {props.data.grades[3]}%</p>
<p>Test 5: {props.data.grades[4]}%</p>
<p>Test 6: {props.data.grades[5]}%</p>
<p>Test 7: {props.data.grades[6]}%</p>
<p>Test 8: {props.data.grades[7]}%</p>
</>
}
<TagsInput />
</div>
</div>
</div>
);
}
export default Student;
You can not directly use one component hook declaration in another component, you need to have a callback function to update that state. I modified your code to use the top page setTagsinput in student tag input
Top.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import Student from "./Student";
import TagsInput from "./TagsInput";
const Top = () => {
const [posts, setPosts] = useState([]);
const [allPosts, setAllPosts] = useState([]);
let tag_list = [];
const [searchKeyword, setSearchKeyword] = React.useState("");
const [searchTagKeyword, setTagSearchKeyword] = React.useState("");
console.log(searchKeyword);
const [tags_from_tagsinput, setTagsinput] = useState("");
console.log(tags_from_tagsinput);
useEffect(() => {
axios.get("xxx.com").then((result) => {
setPosts(result.data.students);
setAllPosts(result.data.students);
if (searchKeyword) {
getSearchResult();
}
});
}, [searchKeyword]);
const getSearchResult = () => {
console.log(searchKeyword);
const result = allPosts.filter((output, index) => {
return (
output.firstName.toLowerCase().includes(searchKeyword.toLowerCase()) ||
output.lastName.toLowerCase().includes(searchKeyword.toLowerCase())
);
});
console.log(result);
setPosts(result);
};
const getTagSearchResult = () => {
console.log(searchTagKeyword);
const result = allPosts.filter((output, index) => {
return output.lastName
.toLowerCase()
.includes(searchTagKeyword.toLowerCase());
});
console.log(result);
setPosts(result);
};
const setTagsFromStudent = (tags) => {
setTagsinput(tags);
};
return (
<div>
<div>
<input
className="search-box"
placeholder=""
value={searchKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
/>
</div>
<div>
<input
className="search-box"
placeholder=""
value={searchTagKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
/>
</div>
<div>
{searchKeyword && <p>{searchKeyword} Search</p>}
{posts ? (
<>
{posts.map((data, i) => (
<Student data={data} setStudentTags={setTagsFromStudent} />
))}
</>
) : (
<div>
<p>Not Found!</p>
</div>
)}
</div>
</div>
);
};
export default Top;
Student.js
import React, { useState } from "react";
import TagsInput from "./TagsInput";
const Student = (props) => {
const [show, setShow] = useState(false);
const gradesAverage = (grades) => {
let sum = 0;
grades.forEach(function (score) {
sum += Number(score);
});
let ave = sum / grades.length;
return ave;
};
return (
<div className="flex">
<div className="image">
<img src={props.data.pic} className="profile" />
</div>
<div>
<p className="name">
{props.data.firstName} {props.data.lastName}
</p>
<button className="button" onClick={() => setShow(!show)}>
{show ? (
<div className="button_p">-</div>
) : (
<div className="button_p">+</div>
)}
</button>
<div className="info">
<p>Email: {props.data.email}</p>
<p>Company: {props.data.company}</p>
<p>Skill: {props.data.skill}</p>
<p>Average Grade: {gradesAverage(props.data.grades)}%</p>
{show && (
<>
<p>Test 1: {props.data.grades[0]}%</p>
<p>Test 2: {props.data.grades[1]}%</p>
<p>Test 3: {props.data.grades[2]}%</p>
<p>Test 4: {props.data.grades[3]}%</p>
<p>Test 5: {props.data.grades[4]}%</p>
<p>Test 6: {props.data.grades[5]}%</p>
<p>Test 7: {props.data.grades[6]}%</p>
<p>Test 8: {props.data.grades[7]}%</p>
</>
)}
{/*pass settag from topTag component*/}
<TagsInput setStudentTags={props.setStudentTags} />
</div>
</div>
</div>
);
};
export default Student;
TagsInput.js
import React from "react";
const TagsInput = (props) => {
const [tags, setTags] = React.useState([]);
let tag_list = [];
tag_list.push(tags);
const addTags = (event) => {
if (event.key === "Enter" && event.target.value !== "") {
setTags([...tags, event.target.value]);
// call function pass down from toptag
props.setStudentTags(tags);
event.target.value = "";
}
};
const removeTags = (index) => {
setTags([...tags.filter((tag) => tags.indexOf(tag) !== index)]);
};
return (
<div className="tags-input">
<div className="tags_section">
{tags.map((tag, index) => (
<div className="tag tag-flex" key={index}>
<p className="tag-p">{tag}</p>
</div>
))}
</div>
<input
type="text"
onKeyUp={(event) => addTags(event)}
placeholder="Press enter to add tags"
/>
</div>
);
};
export default TagsInput;
You should consider exploring React context -https://reactjs.org/docs/context.html, its built exactly for something like this.
You are getting this error because, like you mentioned, TagsInput component is used in Student component but it doesn’t pass the state setter setTagsInput function to the TagsInput component.
Now, assuming you need tags created inside Student and displayed in Top, also assuming that both are rendered in the same parent component, you can create a state for tags in the parent component. This component will pass a state setter function to Student which passes the setter to TagsInput and the state itself to Top to use the list of tags.
Something like:
const App = () => {
const [tags,setTags] = useState([]);
return (<div>
<Top tags={tags} />
<Student setTags={setTags} />
</div>);
}
Your Student component can then pass it to TagsInput like:
const Student = (props) => {
return (<div>
{/* everything else */}
<TagsInput setTagsinput={props.setTags} />
</div>)
}
In your Top component you can create a function that updates your tags_from_tagsinput hook then pass it as props to the child component
import TagsInput from "./TagsInput";
const Top = () => {
const[tags_from_tagsinput, setTagsinput]= useState("");
console.log(tags_from_tagsinput);
const getTag = (value) => {
setTagsinput(value);
};
return (
<div>
<TagsInput getTag={getTag} />
</div>
);
}
export default Top;
Now from your TagsInput component you can call this function to update tags_from_tagsinput of Top, let's suppose that you want to updated when the user click on a button
import React from "react";
const TagsInput = (props) => {
return (
<div className="tags-input">
...
<button onClick={()=>{props.getTag(tags)}}>updated parent component</button>
</div>
);
};
export default TagsInput;
I'm trying to make a standard effect, where clicking on an image in a gallery will enlarge the image, put it at the center of the screen (in front of everything else), and darken the background. I haven't set up the slideshow part yet (so it won't change images), but the aim is to create an index so that I can do that in the future. I'm following a tutorial and trying to adapt it to my backend, but I'm missing a beat. It's not registering which image has been clicked (and I'm getting two errors in the console- 404, and 500). I'm using Nextjs as my frontend, Sanity for my backend.
import React, { useState } from 'react';
import { client, urlFor } from '../lib/client';
import { Header, Footer, Modal } from '../components';
const sets = ({setData, imagesData}) => {
const [clickedImage, setClickedImage] = useState(null);
const [currentIndex, setCurrentIndex] = useState(null);
const handleClick = (imagesData, index) => {
setCurrentIndex(index);
setClickedImage(imagesData.image);
};
const handleRotationRight = () => {
const totalLength = imagesData.imageItems.length;
if(currentIndex + 1 >= totalLength){
setCurrentIndex(0);
const newData = imagesData.imageItems[0];
setClickedImage(newData);
return;
}
const newIndex = currentIndex + 1;
const newData = imagesData.imageItems.filter((image) => {
return imagesData.imageItems.indexOf(image) === newIndex;
});
const newItem = newData[0].image;
setClickedImage(newItem);
setCurrentIndex(newIndex);
};
return (
<div>
<Header />
<main className="slug-gallery">
<div className="title">
<div className="title-line-left"></div>
<h2>{setData.set_name}</h2>
<div className="title-line-right"></div>
</div>
<div className="images-container">
<ul className="overall-images">
{imagesData.imageItems && imagesData.imageItems.map((imagesData, index) => (
<li key={index}>
<img
src={urlFor(imagesData.image).auto('format').url()}
className="the_image"
alt='test a'
onClick={() => handleClick(imagesData, index)}
/>
</li>
))}
</ul>
</div>
{clickedImage && (
<Modal
clickedImage={clickedImage}
handleRotationRight={handleRotationRight}
setClickedImage={setClickedImage}
/>
)}
</main>
<Footer />
</div>
)
}
export default sets
export const getServerSideProps = async (pageContext) => {
const setSlug = pageContext.query.slug;
const setQuery = `*[_type == 'set' && slug.current == $setSlug][0]`;
const imagesQuery = `*[_type == 'set' && slug.current == $setSlug][0]{'imageItems':set_images[]{image{
asset->{_id, url}, alt, name, date, size, materials}}}`;
const setData = await client.fetch(setQuery, {setSlug});
const imagesData = await client.fetch(imagesQuery, {setSlug});
return {
props: {setData, imagesData}
}
}
Heres the Modal component:
import React from 'react'
const Modal = ({clickedImage, handleRotationRight, setClickedImage}) => {
const handleClick = (e) => {
if(e.target.classList.contains("dismiss")){
setClickedImage(null);
}
}
return(
<>
<div className="overlay dismiss" onClick={handleClick}>
<img src={clickedImage} alt='test b'/>
<span className="dismiss" onClick={handleClick}>x</span>
</div>
<div onClick={handleRotationRight} className="overlay-arrows_left">
<img src="/next_portfolio/public/images/cart.png" alt='test c'/>
</div>
</>
)
};
export default Modal;
I have a Header component which updates url parameters to make an api call, and uses next router to pass these parameters to the results page.
I want to be able to search again from this page using router, but the page does not refresh due to the results page being the same.
How can I acheive this?
Many thanks for any help, code below.
Header.js
import { useState, useEffect, useContext } from "react";
import { useRouter } from "next/router";
export default function Header() {
const [searchText, setSearchText] = useState("");
const [impact, setImpact] = useState("");
const [worldwide, setWorldwide] = useState(false);
const router = useRouter();
const onClick = (e) => {
setWorldwide(e.target.checked);
};
const handleSubmit = (event) => {
event.preventDefault();
setImpact(impact);
let remote = "";
if (worldwide === true) {
remote = "Remote";
} else {
remote = "";
}
router.push({
pathname: `/remote-jobs`,
query: {
term: `${searchText}`,
impact: `${impact}`,
location: `${remote}`,
},
});
};
return (
<div>
<div className="p-4 flex flex-col items-center justify-center w-1/3 md:w-full">
<form onSubmit={handleSubmit}>
<select
onChange={(e) => setImpact(e.target.value)}
className="pl-2 pr-2 mr-4 h-10 rounded bg-white text-black md:h-12 md:pl-4 md:pr-4"
>
<option value="">choose an option</option>
<option value="sdg1">option1</option>
<option value="sdg2">option2</option>
</select>
<input
placeholder={"search"}
className="pl-2 pr-2 h-10 my-2 rounded bg-white text-black md:h-12 md:pl-4 md:pr-4"
onChange={(event) => {
setSearchText(event.target.value);
}}
/>
<button className="ml-4 pl-2 pr-2 rounded bg-black text-white md:h-12 md:pl-4 md:pr-4">
Go
</button>
<input
className="ml-4"
type="checkbox"
onClick={onClick}
value={!worldwide}
/>{" "}
</form>
</div>
</div>
</div>
);
}
Results page:
import React, { useState, useEffect } from "react";
import Job from "../components/Job";
import Header from "../components/Header";
export default function App(key) {
const [jobs, setJobs] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const searchTerm = urlParams.get("term");
const searchImpact = urlParams.get("impact");
const searchLocation = urlParams.get("location");
const fetchJobs = async () => {
const url = `https://api.app/api/search?term=${searchTerm}&impact=${searchImpact}&location=${searchLocation}`;
const response = await fetch(url);
const info = await response.json();
setLoading(false);
setJobs(info);
};
fetchJobs();
}, []);
return (
<div className="App">
<Header />
{loading ? (
<div>...loading</div>
) : (
<div>
{jobs.length} jobs
<div>
{jobs.map((job) => (
<Job job={job} key={job.id} />
))}
</div>
</div>
)}
</div>
);
}
You should use next router to get the query values and then you can useEffect to fetch new data whenever the query changes
export default function App(key) {
const [jobs, setJobs] = useState([]);
const [loading, setLoading] = useState(true);
const { query: { term, impact, location } } = useRouter();
useEffect(() => {
const fetchJobs = async () => {
const url = `https://api.app/api/search?term=${term}&impact=${impact}&location=${location}`;
const response = await fetch(url);
const info = await response.json();
setLoading(false);
setJobs(info);
};
fetchJobs();
}, [term, impact, location]);
return (
<div className="App">
<Header />
{loading ? (
<div>...loading</div>
) : (
<div>
{jobs.length} jobs
<div>
{jobs.map((job) => (
<Job job={job} key={job.id} />
))}
</div>
</div>
)}
</div>
);
}
I am a beginner with React. I have a project I'm working on with some sample travel tours. I would like to use a "read more/show less" feature for the description of each tour. The read more/show less button is toggling, but it's showing more or less description for all of the tours when clicked, when I want it to just toggle the tour that's clicked. In other words, it's updating the state for ALL tours, rather than just the one that's clicked. Hopefully that makes sense. Please help! Thanks in advance.
import React, { useState, useEffect } from 'react';
import './index.css';
const url = 'https://course-api.com/react-tours-project';
const Tour = () => {
const [tourItem, setTourItem] = useState('');
const removeItem = (id) => {
let newList = tourItems.filter((item) => item.id !== id);
setTourItem(newList);
};
const [fetchingData, setFetchingData] = useState(true);
useEffect(() => {
const abortController = new AbortController();
const fetchUrl = async () => {
try {
const response = await fetch(url, {
signal: abortController.signal,
});
if (fetchingData) {
const data = await response.json();
setTourItem(data);
}
setFetchingData(false);
} catch (e) {
console.log(e);
}
};
fetchUrl();
return () => {
//cleanup!
abortController.abort();
};
});
const tourItems = Object.values(tourItem);
const [readMore, setReadMore] = useState(false);
return (
<>
{tourItems.map((item) => {
return (
<div key={item.id}>
<article className='single-tour'>
<img src={item.image} alt={item.name} />
<footer>
<div className='tour-info'>
<h4>{item.name}</h4>
<h4 className='tour-price'>
${item.price}
</h4>
</div>
{readMore ? (
<p>
{item.info}
<button
onClick={() => setReadMore(false)}
>
Show Less
</button>
</p>
) : (
<p>
{item.info.slice(0, 450) + '...'}
<button
onClick={() => setReadMore(true)}
>
Read More
</button>
</p>
)}
<button
className='delete-btn'
onClick={() => removeItem(item.id)}
>
Not Interested
</button>
</footer>
</article>
</div>
);
})}
</>
);
};
export default Tour;
Good question! It happened because you share the readMore state with all of the tour items. You can fix this by encapsulating the tour items into a component.
It should look something like this;
The component that encapsulates each tour items
import React, {useState} from "react";
import "./index.css";
const SpecificTourItems = ({item, removeItem}) => {
const [readMore, setReadMore] = useState(false);
return (
<div key={item.id}>
<article className="single-tour">
<img src={item.image} alt={item.name} />
<footer>
<div className="tour-info">
<h4>{item.name}</h4>
<h4 className="tour-price">${item.price}</h4>
</div>
{readMore ? (
<p>
{item.info}
<button onClick={() => setReadMore(false)}>Show Less</button>
</p>
) : (
<p>
{item.info.slice(0, 450) + "..."}
<button onClick={() => setReadMore(true)}>Read More</button>
</p>
)}
<button className="delete-btn" onClick={() => removeItem(item.id)}>
Not Interested
</button>
</footer>
</article>
</div>
);
};
export default SpecificTourItems;
the component that fetch & maps all the tour items (your old component :))
import React, {useState, useEffect} from "react";
import SpecificTourItems from "./SpecificTourItems";
const url = "https://course-api.com/react-tours-project";
const Tour = () => {
const [tourItem, setTourItem] = useState("");
const removeItem = (id) => {
let newList = tourItems.filter((item) => item.id !== id);
setTourItem(newList);
};
const [fetchingData, setFetchingData] = useState(true);
useEffect(() => {
const abortController = new AbortController();
const fetchUrl = async () => {
try {
const response = await fetch(url, {
signal: abortController.signal,
});
if (fetchingData) {
const data = await response.json();
setTourItem(data);
}
setFetchingData(false);
} catch (e) {
console.log(e);
}
};
fetchUrl();
return () => {
//cleanup!
abortController.abort();
};
});
const tourItems = Object.values(tourItem);
const [readMore, setReadMore] = useState(false);
return (
<>
{tourItems.map((item, key) => {
return (
<SpecificTourItems item={item} removeItem={removeItem} key={key} />
);
})}
</>
);
};
export default Tour;
I hope it helps, this is my first time answering question in Stack Overflow. Thanks & Good luck!
SOS! Here i have favourite meals from local storage assigned to favs state. I then use my favs to fetch a meal for each favs value. My favs state updates on initial render, but fetchedMeals is not. It updates only after SECOND render. It seems that i have problem somewhere with async function. Would appreciate any help. Will provide with more details immediately if needed.
import React from 'react';
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import NavBar from '../Home/NavBar';
function Favourites() {
const [favs, setFavs] = useState([]);
const [fetchedMeals, setFetchedMeals] = useState([]);
const getFavsFromLs = () => {
let itemsFromLs = JSON.parse(localStorage.getItem('favs')) || [];
setFavs(itemsFromLs);
};
const fetchMeal = async (mealName) => {
const linkByMealName = `https://www.themealdb.com/api/json/v1/1/search.php?s=${mealName}`;
const res = await fetch(linkByMealName);
const meal = await res.json();
return meal.meals[0];
};
const getFavMeals = async () => {
let items = [];
for (let i = 0; i < favs.length; i++) {
fetchMeal(favs[i]).then((res) => items.push(res));
}
setFetchedMeals(items);
};
useEffect(() => {
getFavsFromLs(); //Moved getFavMeals to another useEffect
}, []);
useEffect(() => {// New use effect that listens favs
if (favs && favs.length) {
getFavMeals();
}
}, [favs]);
return (
<>
<NavBar />
<section className="section">
<div className="row">
<div className="container col l12">
{fetchedMeals.map((meal) => {
const { strMeal, strMealThumb, idMeal } = meal;
return (
<Link to={`/favourites/${strMeal}`}>
<div class="col s12 m6 l3" key={idMeal}>
<div class="card">
<div class="card-image">
<img src={strMealThumb} alt="img"></img>
</div>
<div class="card-content">
<h5 className="truncate center">{strMeal}</h5>
</div>
</div>
</div>
</Link>
);
})}
</div>
</div>
</section>
</>
);
}
export default Favourites;
Since setting state is an asynchronous you need to make useEffect for favs and when they are set from localStorage you can make that getFavMeals and at that point favs will have data.
Also doing console.log immediately after setting state will also show old value, because it needs to rerender. You are trying also on console.log(fetchedMeals).
import React from 'react';
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import NavBar from '../Home/NavBar';
function Favourites() {
const [favs, setFavs] = useState([]);
const [fetchedMeals, setFetchedMeals] = useState([]);
const getFavsFromLs = () => {
let itemsFromLs = JSON.parse(localStorage.getItem('favs')) || [];
setFavs(itemsFromLs);
};
const fetchMeal = async (mealName) => {
const linkByMealName = `https://www.themealdb.com/api/json/v1/1/search.php?s=${mealName}`;
const res = await fetch(linkByMealName);
const meal = await res.json();
console.log(meal.meals[0]);
return meal.meals[0];
};
const getFavMeals = async () => {
let items = [];
for (let i = 0; i < favs.length; i++) {
fetchMeal(favs[i]).then((res) => items.push(res));
}
setFetchedMeals(items);
console.log(fetchedMeals);
console.log(items);
};
useEffect(() => {
getFavsFromLs(); //Moved getFavMeals to another useEffect
}, []);
useEffect(() => {// New use effect that listens favs
if (favs && favs.length) {
getFavMeals();
}
}, [favs]);
return (
<>
<NavBar />
<section className="section">
<div className="row">
<div className="container col l12">
{fetchedMeals &&
fetchedMeals.map((meal) => {
console.log(meal);
const { strMeal, strMealThumb, idMeal } = meal;
console.log(meal);
return (
<Link to={`/favourites/${strMeal}`}>
<div class="col s12 m6 l3" key={idMeal}>
<div class="card">
<div class="card-image">
<img src={strMealThumb} alt="img"></img>
</div>
<div class="card-content">
<h5 className="truncate center">{strMeal}</h5>
</div>
</div>
</div>
</Link>
);
})}
</div>
</div>
</section>
</>
);
}
export default Favourites;