Okay so as you can tell,I am building a recipe app where in each recipe detail page, I can rate the the recipe...but now the problem is that when I rate one recipe now, the same rating is set for all the other recipes as well. Any idea how to fix that? Apreciate any advice...thanks so much :))
Details.js
import { useParams } from 'react-router-dom';
import './Details.css';
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Image from './vitaminDfood-1132105308-770x553.jpg';
import {Link} from 'react-router-dom'
import ReactStars from 'react-rating-stars-component'
import { RecipeContext } from './RecipeContext';
import { useContext } from 'react';
function Details() {
const [details, setDetails] = useState([]);
const { recipeId } = useParams();
const{recipes,setRecipes}=useContext(RecipeContext)
const initialRatings = JSON.parse(localStorage.getItem("rating") || "[]");
const[rating,setRating]=useState(initialRatings)
useEffect(() => {
axios
.get(`https://cookbook.ack.ee/api/v1/recipes/${recipeId}`)
.then((res) => setDetails(res.data));
});
useEffect(() => {
localStorage.setItem("rating", JSON.stringify(rating));
}, [rating])
const ratingChanged = (newRating) => {
var rate={
score:newRating
}
setRating(newRating)
axios.post(`https://cookbook.ack.ee/api/v1/recipes/${recipeId}/ratings`,rate)
.then((res) => {
console.log(res.data)
setRecipes(recipes)
})
};
return (
<>
<div className="details">
<div className="food-photo">
<img src={Image} alt="" />
<Link to="/"> <i className="fas fa-arrow-left arrow"></i></Link>
<h1>{details.name}</h1>
</div>
<div className="star-line">
{new Array(rating).fill(null).map(() => (
<i className="fas fa-star stari"/>
))}
<p className="duration"><i className="far fa-clock"></i>{details.duration}<span>min.</span></p>
</div>
<p className="info">{details.info}</p>
<h1 className="ingredience">Ingredience</h1>
<div className="ing">{details.ingredients}</div>
<h1 className="ingredience">Příprava</h1>
<p className="description">{details.description}</p>
</div>
<div className="stars">
<ReactStars
classNames="star"
size={48}
onChange={ratingChanged}
count={5}
value={1}
edit
/>
</div>
</>
);
}
export default Details;
You have a couple options:
Store a different localStorage key for each recipe
Just have one large "ratings" key in localStorage and make that an object keyed off of your recipeId.
I think the first option is the most straightforward and can be accomplished with these minor modifications:
const initialRatings = JSON.parse(localStorage.getItem(`ratings-${recipeId}`) || "[]");
useEffect(() => {
localStorage.setItem(`ratings-${recipeId}`, JSON.stringify(rating));
}, [rating, recipeId])
Related
Here I am dispatching an action on button click but now I want to dispatch one more action from redux on the same button and the action which I want to dispatch is already imported on top named totalHandler so how am I supposed to do this thanks :)
import React from "react";
import { useParams } from "react-router-dom";
import "./ProductDetail.css";
import { useDispatch, useSelector } from "react-redux";
import { cartHandler } from "../../store/DataStore";
import { totalHandler } from "../../store/DataStore";
const Detail = () => {
const { id } = useParams();
const dispatch = useDispatch();
let data = useSelector((state) => state.data.DUMMY_DATA);
data = data.filter((val) => val.product_id === id);
data = data[0];
return (
<div className="detail_wrapper">
<div>
<img src={data.product_image} alt="" className="detail_image" />
</div>
<div className="inner">
<div className="detail_title">{data.product_title}</div>
<div className="detail_description">{data.product_description}</div>
<div className="detail_price">{data.product_price}</div>
<button
className="button"
onClick={() => dispatch(cartHandler(data.product_id))}
>
Add to Cart
</button>
</div>
</div>
);
};
export default Detail;
<button className="button" onClick={()=>{dispatch(cartHandler(data.product_id));dispatch(totalHandler())}}>Add to Cart</button>
OR create a function like
function Dispatch(){
dispatch(totalHandler());
dispatch(cartHandler(data.product_id));
}
<button className="button"
onClick={Dispatch}>Add to Cart</button>
Just add braquet at the right place {}
import React from "react";
import { useParams } from "react-router-dom";
import "./ProductDetail.css";
import { useDispatch, useSelector } from "react-redux";
import { cartHandler } from "../../store/DataStore";
import { totalHandler } from "../../store/DataStore";
const Detail = () => {
const { id } = useParams();
const dispatch = useDispatch();
let data = useSelector((state) => state.data.DUMMY_DATA);
data = data.filter((val) => val.product_id === id);
data = data[0];
return (
<div className="detail_wrapper">
<div>
<img src={data.product_image} alt="" className="detail_image" />
</div>
<div className="inner">
<div className="detail_title">{data.product_title}</div>
<div className="detail_description">{data.product_description}</div>
<div className="detail_price">{data.product_price}</div>
<button
className="button"
onClick={() => {dispatch(cartHandler(data.product_id));
dispatch(cartHandler(data.product_id_2))}}
>
Add to Cart
</button>
</div>
</div>
);
};
export default Detail;
One little precision because I have been struggling because of that detail, don't forget to await if necessary, in my case:
function handleClick(dice: DiceViewModel) {
return dice.isTenzies
? async () => {
await dispatch(initializeDice())
await dispatch(rollDice())
}
: () => dispatch(rollDice())
}
I've used redux to maintain state if user has already subscribed but on reload, already subscribed users are also redirected to planPage instead of homePage because on reload state sets back to initial null state how should i do it then ?
This is my App.jsx
import { useEffect, useState } from "react";
import DetailPage from "./pages/DetailPage";
import ProfilePage from "./pages/ProfilePage";
import HomePage from "./pages/HomePage";
import LoginPage from "./pages/LoginPage";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import { selectUser, login, logout } from "./features/userSlice";
import PlanPage from "./pages/PlanPage";
import { getDocs } from "firebase/firestore";
import { auth, users } from "./firebase";
import { selectSubscription } from "./features/subscriptionSlice";
function App() {
const user = useSelector(selectUser);
const isSubscribed = useSelector(selectSubscription);
const dispatch = useDispatch(); //hook to access redux dispatch function
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((userAuth) => {
// observe the state change in user's sign in activity
if (userAuth) {
//logged in
dispatch(login({ uid: userAuth.uid, email: userAuth.email }));
} else {
//logged out
dispatch(logout());
}
});
return unsubscribe; // for cleanup the previous state
}, [dispatch]);
console.log(isSubscribed);
return (
<div>
<BrowserRouter>
{!user ? (
<LoginPage />
) : (
<>
{!isSubscribed ? (
<PlanPage />
) : (
<>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="detailScreen" element={<DetailPage />} />
<Route path="profileScreen" element={<ProfilePage />} />
<Route path="planScreen" element={<PlanPage />} />
</Routes>
</>
)}
</>
)}
</BrowserRouter>
</div>
);
}
export default App;
planPage.jsx file
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { auth } from "../firebase";
import { logout } from "../features/userSlice";
import { AiOutlineCheck } from "react-icons/ai";
import { products, users } from "../firebase";
import { getDocs, setDoc, doc } from "firebase/firestore";
import Table from "../components/Table";
//import { BiLoaderAlt } from "react-icons/bi";
import {
showSubscriptionDetail,
selectSubscription,
} from "../features/subscriptionSlice";
import { useNavigate } from "react-router-dom";
export default function PlanPage() {
const navigate = useNavigate();
const subscription = useSelector(selectSubscription);
const [prod, setProducts] = useState([]); //useState() hook, sets initial state to an empty array
const [selectedPlan, setSelectedPlan] = useState(null);
useEffect(() => {
async function unsubscribe() {
const item = await getDocs(products);
const productItem = item.docs.map((doc) => ({
id: doc.id, //id and data pushed into productItems array
...doc.data(),
}));
setProducts(productItem);
setSelectedPlan(productItem[2]);
}
unsubscribe();
}, []);
const dispatch = useDispatch();
const handleClick = () => {
auth.onAuthStateChanged(() => {
dispatch(logout());
});
};
const manageSubscription = () => {
if (subscription) navigate("/");
navigate(-1);
};
async function setData(data) {
await setDoc(doc(users, `${auth.currentUser.email}`), {
productType: data,
email: auth.currentUser.email,
subscribed: true,
activateTime: new Date().toLocaleString(),
planEndTime: `${
new Date().getMonth() + 2
}/${new Date().getDate()}/${new Date().getFullYear()}`,
});
}
const subscribeToPlan = () => {
if (!auth) return;
dispatch(showSubscriptionDetail({ subscription: true }));
setData(selectedPlan?.name);
};
return (
<div>
<header className="border-b border-white/10 bg-[#141414] ">
<img
alt=""
src="https://rb.gy/ulxxee"
width={150}
height={90}
className="cursor-pointer object-contain"
onClick={manageSubscription}
/>
<button
className="text-lg font-medium hover:underline"
onClick={handleClick}
>
Sign Out
</button>
</header>
<main className="mx-auto max-w-5xl px-5 pt-28 pb-12 transition-all md:px-10">
<h1 className="mb-3 text-lg md:text-3xl font-medium">
Choose the plan that's right for you
</h1>
<ul>
<li className="flex items-center gap-x-2 text-sm md:text-lg">
<AiOutlineCheck className=" h-5 w-5 md:h-7 md:w-7 text-[#E50914]" />{" "}
Watch all you want. Ad-free.
</li>
<li className="flex items-center gap-x-2 text-sm md:text-lg">
<AiOutlineCheck className=" h-5 w-5 md:h-7 md:w-7 text-[#E50914]" />{" "}
Recommendations just for you.
</li>
<li className="flex items-center gap-x-2 text-sm md:text-lg">
<AiOutlineCheck className=" h-5 w-5 md:h-7 md:w-7 text-[#E50914]" />{" "}
Change or cancel your plan anytime.
</li>
</ul>
<div className="mt-4 flex flex-col space-y-4">
<div className="flex w-full items-center self-end md:w-3/5">
{prod.map((product) => (
<div
className={`planBox ${
selectedPlan?.id === product.id ? "opacity-100" : "opacity-60"
}`}
key={product.id}
onClick={() => setSelectedPlan(product)} //here if i have directly called the stateSetter i.e setSelectedPlan then it is getting called soon after the component mount stage, and keeps on rerender the state and getting stuck into loops. hence ()=> setSelected()
>
{product.name}
</div>
))}
</div>
<Table products={prod} selectedPlan={selectedPlan} />
<button
disabled={!selectedPlan}
className={`mx-auto w-11/12 rounded bg-[#E50914] py-4 text-xl shadow hover:bg-[#f6121d] md:w-[420px] `}
onClick={subscribeToPlan}
>
Subscribe
</button>
</div>
</main>
</div>
);
}
subscriptionSlice.jsx (redux-reducer code)
import { createSlice } from "#reduxjs/toolkit";
export const subscriptionSlice = createSlice({
name: "subscription",
initialState: {
subscription: null,
},
reducers: {
showSubscriptionDetail: (state, action) => {
state.subscription = action.payload;
},
},
});
export const { showSubscriptionDetail } = subscriptionSlice.actions;
export const selectSubscription = (state) =>
state.subscription.subscription?.subscription;
export const subscriptionReducer = subscriptionSlice.reducer;
I implemented this features just by defining a state(issubscribed) in App.js and have access to db. if subscription exist in firebase sent back a data if not it will stay null and you can have the simple logic in your routing.
i'm just starting to learn react, where did i go wrong, can't undestand what am i doing wrong this is my problem
my goal: to ensure that the picture changes as true / false
maybe I am not passing props correctly??
it's my code:
import React, { useState, useEffect } from 'react'
import styles from './styles.module.scss'
import { Link } from 'react-router-dom'
import classNames from 'classnames'
import DjalKokildak from '../../../../assets/images/DjalKokildak.png'
import Turpachaty from '../../../../assets/images/Turpachaty.png'
const Fields = ({image}) => {
const data = [
{
img: {
true : DjalKokildak,
false : Turpachaty
}
}
]
console.log(data)
const [image, setImage] = useState(true)
return (
<div className={styles.container}>
<div className={styles.wrapper}>
<div className={styles.line} />
<div className={styles.contentBlock}>
<div className={styles.titleBlock}>
<h1 className={styles.title}>месторождения</h1>
<p className={styles.text}>“Джал-Кокильдак” и “Турпачаты”</p>
<Link to='/' className={styles.link}>подробнее</Link>
</div>
<div className={styles.actionBlock}>
<button onClick={() => setImage(false)} className={styles.button}>след</button>
<div className={styles.imgBlock}>
{data.map(item => item.img === img && (
<img src={item.img[setImage]}>{image}</img>
))
}
</div>
<button onClick={() => setImage(true)} className={styles.button}>пред</button>
</div>
</div>
</div>
</div>
)
}
export default Fields
How can I let the star rating stay even after I leave the details page or after refreshing? Setting of the state isnt enough here. I suppose Localstorage type of saving should be used here right? Not really sure how to do that here. Hope you can help. Apreciate any advice guys. Thanks so much :))).
Details.js
import { useParams } from 'react-router-dom';
import './Details.css';
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Image from './vitaminDfood-1132105308-770x553.jpg';
import {Link} from 'react-router-dom'
import ReactStars from 'react-rating-stars-component'
import { RecipeContext } from './RecipeContext';
import { useContext } from 'react';
function Details() {
const [details, setDetails] = useState([]);
const { recipeId } = useParams();
const[rating,setRating]=useState([])
const{recipes,setRecipes}=useContext(RecipeContext)
useEffect(() => {
axios
.get(`https://cookbook.ack.ee/api/v1/recipes/${recipeId}`)
.then((res) => setDetails(res.data));
});
const ratingChanged = (newRating) => {
var rate={
score:newRating
}
setRating(newRating)
axios.post(`https://cookbook.ack.ee/api/v1/recipes/${recipeId}/ratings`,rate)
.then((res) => {
console.log(res.data)
setRecipes(recipes)
})
};
return (
<>
<div className="details">
<div className="food-photo">
<img src={Image} alt="" />
<Link to="/"> <i className="fas fa-arrow-left arrow"></i></Link>
<h1>{details.name}</h1>
</div>
<div className="star-line">
{new Array(rating).fill(null).map(() => (
<i className="fas fa-star stari"/>
))}
<p className="duration"><i className="far fa-clock"></i>{details.duration}<span>min.</span></p>
</div>
<p className="info">{details.info}</p>
<h1 className="ingredience">Ingredience</h1>
<div className="ing">{details.ingredients}</div>
<h1 className="ingredience">Příprava</h1>
<p className="description">{details.description}</p>
</div>
<div className="stars">
<ReactStars
classNames="star"
size={48}
onChange={ratingChanged}
count={5}
value={1}
edit
/>
</div>
</>
);
}
export default Details;
You can use an effect with the useEffect hook to save your ratings to localStorage. On page load, you can load initial ratings from localStorage as well and use that as the default value in useState.
const initialRatings = JSON.parse(localStorage.getItem("ratings") || "[]");
function Details() {
const [ratings, setRatings] = useState(initialRatings);
// Save to localstorage on change
useEffect(() => {
localStorage.setItem("ratings", JSON.stringify(ratings));
}, [ratings])
}
i am beginner in react . when i fetch request from API, i got data in console ,but when i am trying to display data in web page in that time data isn't show.
I want to display data in web page.
here is my console log
https://ibb.co/YLmLQz1
App.js
import React from 'react';
import './App.css';
import Header from './components/Header';
import Movie from './components/Movies';
const App = () => {
return (
<div className="App">
<Header />
<div className='container'>
<Movie />
</div>
</div>
);
}
export default App;
Header.js
In header file i created my navbar and search form
import React, { useState } from 'react'
const Header = () => {
const [search, setSearch] = useState('');
return (
<div className="jumbotron">
<h1 className="display-1">
<i className="material-icons brand-icon">LatestMovie</i> Movie
</h1>
<div className="input-group w-50 mx-auto">
<input
type="text"
className="form-control"
placeholder="Search Your Movie..."
value={search}
onChange={e => setSearch(e.target.value)}
/>
<div className="input-group-append">
<button className="btn btn-dark">
Search Movie
</button>
</div>
</div>
</div>
)
}
export default Header;
Movies.js
here i fetch my movies request throght axios
import React, { useEffect, useState } from 'react'
import Axios from 'axios';
const Movie = () => {
const [movies, setMovie] = useState([]);
const apiurl = "http://www.omdbapi.com/?apikey=642b793e&s=marvel"
const getMovies = async () => {
const res = await Axios.get(apiurl);
console.log(res);
setMovie(res.data.hits);
}
useEffect(() => {
getMovies();
}, []);
return (
<div className='row'>
{
movies && movies.map(movie => (
<div className='col-md-3'>
<div className='card'>
<div className='card-body'>
<h4>{movie.Year}</h4>
</div>
</div>
</div>
))
}
</div>
)
}
export default Movie;