UseState value doesn't change when re-render in NextJs - reactjs

I am creating a blog application on NextJs and on the page which displays posts by category i faced with a problem
i have a useState which contains all posts that i get from backend by category that i pass
The problem is when i click on the link which changes category of displayed posts i still got same posts on page, and to get actual useState value i have to reload page, how to fix it?
Here is full component
const News: FC<NewsCat> = ({ news, category }) => {
const [selectedCateg, setSelectedCateg] = useState(category);
//UseState with posts
const [postsList, setPostsList] = useState(news);
const [page, setPage] = useState(1);
const handleClick = (categ: string) => {
setSelectedCateg(categ);
};
return (
<div className={styles.wrap}>
<nav className={styles.categories}>
//Cetegory list
{list.map((i: string, index) => (
<Link href={"/category/" + listEng[index]} key={index}>
<button
className={styles.active}
onClick={() => handleClick(listEng[index])}
>
<h2>{i}</h2>
</button>
</Link>
))}
</nav>
<div className={styles.catTitle}>
<h1>{newsTranslate(selectedCateg)}</h1>
</div>
<div className={styles.mainBlock}>
{postsList.map((i: News) => (
<div key={i._id} className={styles.normal_card}>
<div className={styles.normal_card_img}>
<Link href={"/news/" + i._id}>
<img src={i?.image} alt="" />
</Link>
<div className={styles.desc}>
<div className={styles.up_desc}>
<Link href={"/category/" + category}>
<h6>{newsTranslate(i.category)}</h6>
</Link>
<h6>| {moment(i.createdAt).format("LLL")}</h6>
</div>
<Link href={"/news/" + i._id}>
<h2 className={styles.titleDesc}>
{i.title?.length > 150
? `${i.title?.substring(0, 90)}...`
: i.title}
</h2>
</Link>
</div>
</div>
<div className={styles.normal_card_desc}>
<h4>{moment(i.createdAt).format("LLL")}</h4>
</div>
</div>
))}
</div>
<div className={styles.loadMoreButton}>
<button
onClick={async () => {
setPage(page + 1);
console.log(page);
const getNextPosts = await axios.get(
"http://localhost:3000/api/category/" + category,
{
params: {
page: page,
},
}
);
setPostsList([...postsList, ...getNextPosts.data]);
}}
>
LoadMore
</button>
</div>
</div>
);
};
export default News;
export const getServerSideProps: GetServerSideProps = async ({
params,
}: any) => {
const res = await axios.get(
`http://localhost:3000/api/category/${params.category}`,
{
params: {
page: 1
}
}
);
return {
props: {
news: res?.data,
category: params?.category,
},
};
};
I know that i can fix it like this:
useEffect(() => {
setPostsList(news)
}, [news])
but in my opinion its not the best way

postsList will only change value when you call setPostsList (except for the initial value which you're passing in on first load).
So after the category is changed, you'll need to fetch the posts from the API and call setPostsList (similar to how you are doing on page change)

Related

React - setState in function gets executed X times - Updated

The bounty expires in 2 days. Answers to this question are eligible for a +50 reputation bounty.
Cinc-B is looking for an answer from a reputable source:
Fixing the re-render situation.
Quick Overview:
I have a context with meals in it. That meals gets used in the MealDetails-Component. In this component is a list filled with a certain amount of Food-Components. If you click on the X in the Food component it gets removed otherwise you go to the FoodDetailsPage
Then when I press the button a function called deleteFood gets executed which sets the meals state new without that food. The problem is that the setState inside the function doesn't get called once but twice in this component. I tried using it in 2 other components and once it executed only once and in the other it executed 4 times.
Update
My addFood function has a pretty similiar problem. But this one gets called in another component. It adds Food to 2 different states in 2 different contexts and in both contexts the added Value is doubled. One thing I could "find" was that the console in my browser prints the a certain value I console logged inside the setState function a second time, but not via the MealsContext, but via react_devtools_backend.js.
And this only happens with those 2 functions where I have an error.
Update 2
I'm displaying the Food-Components inside the MealsDetails Component which has meals in it which comes from the MealsContext. Could this be the problem?
Update 3
I missed saying it. I have a lot more functions in this MealsContext that are changing the state. One of them is called changeFoodWeight and it gets called in the same component where addFood gets called, but it doesn't show any problems plus the react_devtools_backend.js logs nothing which is great. I added the function to the MealsContext down below.
MealsDetails.tsx
const MealDetails = () => {
const navigate = useNavigate();
let { id } = useParams();
const { meals } = useMeals();
const style = { "--percentage": "75%" } as React.CSSProperties;
return (
<div className="MealDetails">
<header className="BarcodeScannerHeader">
<ArrowBackIcon
onClick={() => {
navigate("/");
}}
sx={{ fontSize: 35 }}
></ArrowBackIcon>
<div className="HeaderText">{meals.meals[Number(id)].name}</div>
</header>
<div className="MealDetailsContent">
<span className="label">{meals.meals[Number(id)].calories} ate</span>
<hr className="SolidMealDetails"></hr>
{meals.meals[Number(id)] != null
? meals.meals[Number(id)].food.map((food: any, index: number) => <Food key={index} food={food}/>)
: "Not loading"}
</div>
<Link className="Link" to={`/AddFood/${id!}`}>
<div className="MealDetailsAddFoodButton">
<img className="SVG" id="SVG" src={SVG} alt="+" />
</div>
</Link>
</div>
);
};
MealsContext.tsx
let initialState: MealsType = {
calories: 0,
carbs: 0,
meals: [
{
calories: 0,
carbs: 0,
food: []
}
],
};
const MealsProvider = ({ children }: any) => {
const [meals, setMeals] = useState<MealsType>(initialState);
const addFood = async (
id: number,
addedFood: FoodType,
selectedLocation: string,
date: Date
) => {
//Removed the fetch for readability
console.log("func")
setMeals((prevMeals) => {
console.log("state");
const updatedMeals = prevMeals;
let foodExists = false;
updatedMeals.calories += addedFood.kcal;
updatedMeals.carbs += addedFood.carbs;
updatedMeals.meals[id].calories += addedFood.kcal;
updatedMeals.meals[id].carbs += addedFood.carbs;
updatedMeals.meals[id].food.forEach((food : FoodType) => {
if(food.code === addedFood.code){
food.kcal += addedFood.kcal;
food.carbs += addedFood.carbs;
foodExists = true;
}
})
if(!foodExists){
updatedMeals.meals[id].food.push(addedFood);
}
return {...prevMeals,updatedMeals};
});
setUser(prevUser => {
const updatedStorage = prevUser.storage;
updatedStorage.map((storage : any) => {
if(storage.location === selectedLocation){
storage.storedFood.map((storedFood : any) => {
if(storedFood.code === addedFood.code){
storedFood.weight -= addedFood.weight;
}
})
}
})
return {...prevUser, updatedStorage};
})
};
const changeFoodWeight = async (
id: number,
foodDiff: any,
selectedLocation: string,
date: Date,
newWeight: number
) => {
//Removed the fetch for readability
console.log("func");
setMeals((prevMeals) => {
console.log("state");
const updatedMeals = prevMeals;
updatedMeals.calories += foodDiff.kcalDiff;
updatedMeals.carbs += foodDiff.carbsDiff;
updatedMeals.meals[id].calories += foodDiff.kcalDiff;
updatedMeals.meals[id].carbs += foodDiff.carbsDiff;
updatedMeals.meals[id].food.forEach((food) => {
if (food.code === foodDiff.code) {
food.kcal += foodDiff.kcalDiff;
food.carbs += foodDiff.carbsDiff;
}
});
return {...prevMeals,updatedMeals};
});
setUser(prevUser => {
const updatedStorage = prevUser.storage;
updatedStorage.map((storage : any) => {
if(storage.location === selectedLocation){
storage.storedFood.map((storedFood : any) => {
if(storedFood.code === foodDiff.code){
storedFood.weight -= foodDiff.weightDiff;
}
})
}
})
return {...prevUser, updatedStorage};
})
};
const deleteFood = (id: number, deletedFood: FoodType) => {
setMeals((prevMeals) => {
const updatedMeals = prevMeals;
updatedMeals.calories -= deletedFood.kcal;
updatedMeals.carbs -= deletedFood.carbs;
updatedMeals.meals[id].calories -= deletedFood.kcal;
updatedMeals.meals[id].carbs -= deletedFood.carbs;
for(let i = 0; i < updatedMeals.meals[id].food.length; i++){
if(updatedMeals.meals[id].food[i].code === deletedFood.code){
updatedMeals.meals[id].food.splice(i, 1);
}
}
return {...prevMeals,updatedMeals};
});
};
return (
<MealsContext.Provider
value={{
meals,
deleteFood
}}
>
{children}
</MealsContext.Provider>
);
};
const useMeals = () => useContext(MealsContext);
export { MealsProvider, useMeals };
};
Food.tsx
const Food = ({ food }: any) => {
const location = useLocation();
const {deleteFood} = useMeals();
return (
<Link className="FoodContent Link" to={`/FoodDetails/`} state= {{ food: food , location: location.pathname}} >
<div className="Food">
<div className="FoodDetail">
<div className="FoodNameFoodDelete">
<div className="FoodName">{food.name}</div>
<img className="FoodDelete SVG" onClick={(e : any) => {e.preventDefault(); deleteFood(parseInt(location.pathname[location.pathname.length - 1]), food)}} src={SVG}></img>
</div>
<div className="FoodGram-FoodKcal">
<div className="FoodBrand-FoodGram">
<div className="FoodBrand">{food.brand + ","} </div>
<div className="FoodGram">
{food.weight ? food.weight + " g" : 100 + " g"}{" "}
</div>
</div>
<div className="FoodKcal">{food.kcal} kcal</div>
</div>
</div>
<div className="FoodNutritions">
<div className="FoodNutrition">
<div className="FoodNutritionGrams">{Math.round(food.carbs*10)/10} g</div>
<div className="FoodNutritionText">Carbs</div>
</div>
<div className="FoodNutrition">
<div className="FoodNutritionGrams">{Math.round(food.protein*10)/10} g</div>
<div className="FoodNutritionText">Protein</div>
</div>
<div className="FoodNutrition">
<div className="FoodNutritionGrams">{Math.round(food.fat*10)/10} g</div>
<div className="FoodNutritionText">Fat</div>
</div>
</div>
</div>
</Link>
);
};
The issue stems from wrapping your entire component with a Link tag which is causing some internal state-collision between your setMeals call and the Link state pass.
<Link className="FoodContent Link" to={`/FoodDetails`} state= {{ food: food , location: location.pathname}}>
<div>Block-level Elements...</div>
</Link>
Link is an inline element and is simply meant to wrap some text (or a word) that's to be used as a link somewhere. The resulting view can then access the passed-in state.
From the React Router Link v6 documentation on how to handle passing state:
The state property can be used to set a stateful value for the new location which is stored inside history state. This value can subsequently be accessed via useLocation().
<Link to="new-path" state={{ some: "value" }} />
You can access this state value while on the "new-path" route:
let { state } = useLocation();
In your situation, the Link wrap is trying to passing the food state object into the entire Food component as you are updating your entire context state (useMeals).
To better visualize the problem, I reproduced your issue "before" and "after".
In the "before" sandbox, clicking Delete Cookies removes more items than anticipated due to this issue. In the "after" sandbox the same steps work as expected. This is because only the actual link is wrapped in Link.
Essentially, you need to use your Link wrap more like this so the state it's trying to pass doesn't overwrite the state that's in the component:
const Food = ({ food }: any) => {
const location = useLocation();
const {deleteFood} = useMeals();
return (
<div className="FoodContent">
<div className="Food">
<div className="FoodDetail">
<div className="FoodNameFoodDelete">
<Link className="FoodName Link" to={`/FoodDetails/`} state= {{ food: food , location: location.pathname}} >{food.name}</Link> {/* <--- Just wrap the actual link */}
<img className="FoodDelete SVG" onClick={(e : any) => {e.preventDefault(); deleteFood(parseInt(location.pathname[location.pathname.length - 1]), food)}} src={SVG}></img>
</div>
<div className="FoodGram-FoodKcal">
<div className="FoodBrand-FoodGram">
<div className="FoodBrand">{food.brand + ","} </div>
<div className="FoodGram">
{food.weight ? food.weight + " g" : 100 + " g"}{" "}
</div>
</div>
<div className="FoodKcal">{food.kcal} kcal</div>
</div>
</div>
<div className="FoodNutritions">
<div className="FoodNutrition">
<div className="FoodNutritionGrams">{Math.round(food.carbs*10)/10} g</div>
<div className="FoodNutritionText">Carbs</div>
</div>
<div className="FoodNutrition">
<div className="FoodNutritionGrams">{Math.round(food.protein*10)/10} g</div>
<div className="FoodNutritionText">Protein</div>
</div>
<div className="FoodNutrition">
<div className="FoodNutritionGrams">{Math.round(food.fat*10)/10} g</div>
<div className="FoodNutritionText">Fat</div>
</div>
</div>
</div>
</div>
);
};
Here are the sandboxes for reference:
"Before" Sandbox
"After" Sandbox

How can I create Single Page

How can I pass map items (title, category and images) in my id.jsx file.
Basically, I just want to create a single page for my projects. But I can only access post ID. I don't know how to pass other data items.
'Projects folder'
[id].js
import { useRouter } from "next/router";
const Details = () => {
const router = useRouter();
return <div>Post #{router.query.id}
// Single Project Title = {project.title} (like this)
</div>;
};
export default Details;
index.js
import { MyProjects } from "./MyProjects";
const Projects = () => {
const [projects, setProjects] = useState(MyProjects);
{projects.map((project) => (
<Link
href={"/projects/" + project.id}
key={project.id}
passHref={true}
>
<div className="project__item">
<div className="project__image">
<Image src={project.image} alt="project" />
</div>
<div className="project_info">
<h5>{project.category}</h5>
<h3>{project.title}</h3>
</div>
</div>
</Link>
))}
If I understand your question correctly, you want to send some "state" along with the route transition. This can be accomplished using an href object with the "state" on the query property, and the as prop to hide the query string.
Example:
{projects.map((project) => (
<Link
key={project.id}
href={{
pathname: "/projects/" + project.id,
query: {
id: project.id,
category: project.category,
title: project.title
}
}}
passHref={true}
as={"/projects/" + project.id}
>
<div className="project__item">
<div className="project__image">
<Image src={project.image} alt="project" />
</div>
<div className="project_info">
<h5>{project.category}</h5>
<h3>{project.title}</h3>
</div>
</div>
</Link>
))}
...
const Details = () => {
const router = useRouter();
return (
<div>
<div>Post #{router.query.id}</div>
<div>Title {router.query.title}</div>
<div>Category {router.query.category}</div>
</div>
);
};

Use State not updating as expected

Fairly new to react and trying to build a clone of The Movie Database site. I want this toggle switch to change my api call from movies to tv. It starts working after a couple clicks, but then it throws everything off and it's not displaying the correct items anyway. Not really sure what's going on here...or even why it starts working after two clicks. Anyone know whats up with this?
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");
console.log(genre);
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();
}, []);
const listOptions = document.querySelectorAll(".switch--option");
const background = document.querySelector(".background");
const changeOption = (el) => {
let getGenre = el.target.dataset.genre;
setGenre(getGenre);
getPopular();
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}px`
);
el.classList.add("selected");
};
return (
<section className="container movie-list">
<div className="flex">
<div className="movie-list__header">
<h3>What's Popular</h3>
</div>
<div className="switch flex">
<div className="switch--option selected">
<h3>
<a
data-genre="movie"
onClick={(e) => changeOption(e)}
className="switch--anchor"
>
In Theaters
</a>
</h3>
<div className="background"></div>
</div>
<div className="switch--option">
<h3>
<a
data-genre="tv"
onClick={(e) => changeOption(e)}
className="switch--anchor"
>
On TV
</a>
</h3>
</div>
</div>
</div>
<div className="scroller">
<div className="flex flex--justify-center">
<div className="flex flex--nowrap container u-overScroll">
{popularMovies &&
popularMovies.map((movie, idX) => (
<div key={idX} className="card">
<div className="image">
<img src={imageUri + "w500" + movie.poster_path} />
</div>
<p>{movie.title}</p>
</div>
))}
</div>
</div>
</div>
</section>
);
};
export default Popular;
You're using the array index as your key prop when you're mapping your array.
You should use an id that is specific to the data that you're rendering.
React uses the key prop to know which items have changed since the last render.
In your case you should use the movie id in your key prop instead of the array index.
popularMovies.map((movie) => (
<div key={movie.id} className="card">
<div className="image">
<img src={imageUri + 'w500' + movie.poster_path} />
</div>
<p>{movie.title}</p>
</div>
));
Also
You're calling the api directly after setGenre. However state changes aren't immediate. So when you're making your api call you're still sending the last movie genre.
Two ways of fixing this:
You could call your function with the genre directly, and change your function so it handles this case:
getPopular('movie');
Or you could not call the function at all and add genre as a dependency of your useEffect. That way the useEffect will run each time the genre change.
useEffect(() => {
getPopular();
}, [genre]);
PS: You should consider splitting your code into more component and not interacting with the DOM directly.
To give you an idea of what it could look like, I refactored a bit, but more improvements could be made:
const Popular = ({ imageUri }) => {
const [popularMovies, setPopularMovies] = useState('');
const [genre, setGenre] = useState('movie');
const getPopular = async (movieGenre) => {
const response = await axios.get(
`https://api.themoviedb.org/3/discover/${movieGenre}?sort_by=popularity.desc&api_key=${API_KEY}`
);
setPopularMovies(response.data.results);
};
useEffect(() => {
getPopular();
}, [genre]);
const changeHandler = (el) => {
let getGenre = el.target.dataset.genre;
setGenre(getGenre);
};
const isMovieSelected = genre === 'movie';
const isTvSelected = genre === 'tv';
return (
<section className="container movie-list">
<div className="flex">
<MovieHeader>What's Popular</MovieHeader>
<div className="switch flex">
<Toggle onChange={changeHandler} selected={isMovieSelected}>
In Theaters
</Toggle>
<Toggle onChange={changeHandler} selected={isTvSelected}>
On TV
</Toggle>
</div>
</div>
<div className="scroller">
<div className="flex flex--justify-center">
<div className="flex flex--nowrap container u-overScroll">
{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 } = props;
const className = selected ? 'switch--option selected' : 'switch--option';
return (
<div className={className}>
<h3>
<a
data-genre="movie"
onClick={onChange}
className="switch--anchor"
>
{children}
</a>
</h3>
<div className="background"></div>
</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 } = props;
return (
<div key={idX} className="card">
<div className="image">
<img src={imageUri + 'w500' + poster_path} />
</div>
<p>{title}</p>
</div>
);
};

useEffect call different API onClick

I am working with useEffect and want to figure out how to call a different API when an onClick event happens. When my page loads the following is run:
useEffect(() => {
if (userData.user) {
axios
.get(`http://localhost:5000/reviews/reviews?email=${userData.user.email}`, userData)
.then((response) => {
console.log(response)
setReview(response.data)
});
}
}, [userData]);
however when a user selects an onClick event on the webpage, I want a different API to be called and for the data from the original request to disappear. Here is the second request I want to send:
axios
.get(`http://localhost:5000/reviews/reviews?reviewNumber=1`)
.then(response => {
console.log(response)
setallreviews(response.data)
})
Adding the page code here as requested:
return(
<div>
{userData.user ? (
<div>
<div>
<FeedbackNav />
</div>
<div>
<div>
{filterMode ? (
<div onClick={() => setFilterMode(false)}>
<h1 className='testing123'>My reviews and feedback</h1>
<p className='testing1234'>Reviews are reviews that you have provided on another project</p>
<p className='testing1234'>Feedback is feedback that others have provided on your own project</p>
<button className='rectangle' onClick={() => setFilterMode(false)}>
<img className='filterImage' src={Filter} />Filter
</button>
<ul className='filteroptions'>
<p className='allreviewsfeedback' onClick={() => {
setFilterMode();
getReviews();
}}>My reviews</p>
<p className='myfeedback' onClick={() => {
setFilterMode();
}}>My feedback</p>
</ul>
</div>
) : (
<div>
<h1 className='testing123'>My reviews and feedback</h1>
<p className='testing1234'>Reviews are reviews that you have provided on another project</p>
<p className='testing1234'>Feedback is feedback that others have provided on your own project</p>
<button className='rectangle' onClick={() => setFilterMode(true)}>
<img className='filterImage' src={Filter}/>
Filter
</button>
</div>
)}
</div>
<div className='testing1234'>
{allreviews.map(allreviews => (
<p key={allreviews._id}>{allreviews.review}</p>
))}
</div>
<div className='testing1234'>
{review.map(review => (
<p key={review._id}>{review.review}</p>
))}
</div>
</div>
</div>
) : (
<div>
<Landing />
</div>
)}
</div>
)
}
What is the best way to do this? Thank you!
You can do something like this:
const [clicked, setClicked] = useState(false);
const handleClick = () => {
setClicked(true);
};
useEffect(() => {
if (clicked) {
axios
.get(`http://localhost:5000/reviews/reviews?reviewNumber=1`)
.then(response => {
console.log(response)
setallreviews(response.data)
setReview(null);
});
} else if (userData.user) {
axios
.get(`http://localhost:5000/reviews/reviews?email=${userData.user.email}`, userData)
.then((response) => {
console.log(response)
setReview(response.data)
});
}
}, [userData]);
return (
<div>
some text...
<button onClick={handleClick}>Click!</button>
</div>
);
Listen to search params and call axios.get if search params changed.
const { search } = useLocation(); // import { useLocation } from 'react-router-dom';
useEffect(() => {
if (!search) return;
axios.get(`http://localhost:5000/reviews/reviews${search}`).then();
}, [search]);

Update className with UseEffect hook

Currently i am practising my React skills. Now i am working on my Hooks knowledge. I have used a Coctail API to load some data. Just for fun i divided this up into three categories with a navigation bar.
When i click on the navigation items i go to the subcategories. But my classes won't update so the tab system doesn't work. You guys know what i have to do here? I tried this in a few different ways but none worked.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const OverView = () => {
const [ term, setTerm ] = useState('Gin');
const [ categorie, setCategorie ] = useState('Cocktail');
const [ debouncedTerm, setDebouncedTerm ] = useState(term);
const [ results, setResults] = useState([]);
useEffect(() => {
const timerId = setTimeout(() => {
setDebouncedTerm(term);
}, 10);
return () =>{
clearTimeout(timerId);
};
}, [term]);
useEffect(() => {
const search = async () => {
const { data } = await axios({
'method':'GET',
'url':'https://the-cocktail-db.p.rapidapi.com/filter.php',
'headers':{
'content-type':'application/octet-stream',
'x-rapidapi-host':'the-cocktail-db.p.rapidapi.com',
'x-rapidapi-key':'49adf3a5ccmsh5d525b0005370d3p1b460bjsn7b0add17579e',
'useQueryString':true
},'params':{
'i': debouncedTerm
},
});
setResults(data.drinks);
};
search();
}, [debouncedTerm]);
const renderResults = results.map((result) => {
return (
<div key={result.idDrink} className="four wide column" style={{marginBottom:"20px"}}>
<a href={result.idDrink}>
<div className="card">
<img src={result.strDrinkThumb} width="250"/>
<div className="header">{result.strDrink}</div>
</div>
</a>
</div>
);
});
return (
<div className="ui grid">
<div className="row">
<h3>Select your favourate drink to get started</h3>
<div className="ui top attached tabular menu">
<div
className={'item active'}
onClick={(e) => setTerm('Gin') }>
Gin
</div>
<div
className={'item'}
onClick={(e) => setTerm('Vodka')}>
Vodka
</div>
<div
className={'item'}
onClick={(e) => setTerm('Whiskey')}>
Whiskey
</div>
</div>
</div>
<div className="row">
{renderResults}
</div>
</div>
);
};
export default OverView;
Thanks in advance.My first problem is that i don't know how to add an extra action upon my hook. I can't attach an extra onClick event and don't know how to add this to my useEfect functions.
Set the className based on the term state atom, simple as that.
I also refactored things a bit:
the result component is now, well, a separate component
searching is refactored into a separate function
import React, { useState, useEffect } from "react";
import axios from "axios";
const ResultCard = ({ result }) => (
<div className="four wide column" style={{ marginBottom: "20px" }}>
<a href={result.idDrink}>
<div className="card">
<img src={result.strDrinkThumb} width="250" />
<div className="header">{result.strDrink}</div>
</div>
</a>
</div>
);
async function doSearch(term) {
const { data } = await axios({
method: "GET",
url: "https://the-cocktail-db.p.rapidapi.com/filter.php",
headers: {
"content-type": "application/octet-stream",
"x-rapidapi-host": "the-cocktail-db.p.rapidapi.com",
"x-rapidapi-key": "49adf3a5ccmsh5d525b0005370d3p1b460bjsn7b0add17579e",
useQueryString: true,
},
params: {
i: term,
},
});
return data;
}
const OverView = () => {
const terms = ["Gin", "Vodka", "Whiskey"];
const [term, setTerm] = useState("Gin");
const [results, setResults] = useState([]);
useEffect(() => {
doSearch(term).then((data) => setResults(data.drinks));
}, [term]);
return (
<div className="ui grid">
<div className="row">
<h3>Select your favourate drink to get started</h3>
<div className="ui top attached tabular menu">
{terms.map((t) => (
<div
className={["item", term === t ? "active" : null].filter(Boolean).join(" ")}
onClick={(e) => setTerm(t)}
>
{t}
</div>
))}
</div>
</div>
<div className="row">
{results.map((result) => (
<ResultCard result={result} key={result.idDrink} />
))}
</div>
</div>
);
};
export default OverView;
You may want to look into the classnames module; the arcane [].filter().join() expression would become cx({item: true, active: t === term}) :)

Resources