After click on like button the whole website become disappear but after refresh the webpage it increments why does it happens? - javascript

I am working with a video application website with a fake json server. Everything is going well but the problem appears while I want to like a video it becomes disappears and throwing an error: Objects are not valid as react child. Why this is happening?
The whole state slice:
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit"
import { getVideo, updateReaction } from "./videoApi"
//initializing the state
const initialState = {
video: {}, //while we are going to push a single video into the state we should put it an object instance...
loading: false,
isError: false,
error: ''
}
// async thunk for getting single video
export const fetchVideo = createAsyncThunk('vidoes/fetchVideo', async (id) => {
const videos = await getVideo(id);
return videos;
});
// another async thunk to react on a single video (like or dislike)
export const fetchReact = createAsyncThunk('video/reaction', async ({ id, reaction }) => {
const videoReaction = await updateReaction({ id, reaction })
return videoReaction;
})
//creating slice
const videoSlice = createSlice({
name: 'video',
initialState,
extraReducers: (builder) => {
builder.addCase(fetchVideo.pending, (state) => {
state.loading = true;
state.isError = false;
})
builder.addCase(fetchVideo.fulfilled, (state, action) => {
state.loading = false;
state.video= action.payload;
})
builder.addCase(fetchVideo.rejected, (state, action) => {
state.loading = false;
state.isError = true;
state.error = action.error?.message;
state.video = [];
})
//writing codes for emplementing like funcitonality
builder.addCase(fetchReact.pending, (state) => {
return state;
})
builder.addCase(fetchReact.fulfilled, (state, action) => {
state.video.likes = action.payload;
state.video.unlikes = action.payload;
})
builder.addCase(fetchReact.rejected, (state) => {
return state;
})
}
});
export default videoSlice.reducer;
This one is video api
import axios from '../../utils/axios'
export const getVideo = async (id) => {
const response = await axios.get(`/videos/${id}`)
return response.data;
}
export const updateReaction = async ({ id, reaction }) => {
console.log(id)
const { data } = await axios.get(`/videos/${id}`)
if (data) {
let updatedReaction =
reaction === 'like'
? {
likes: data.likes + 1,
}
: {
unlikes: data.unlikes + 1,
};
const response = await axios.patch(`/videos/${id}`, updatedReaction);
return response.data;
}
}
Video description component
import LIke from '../Like/LIke';
const VidoeDescription = ({ video }) => {
const { title, date, description, likes, unlikes, id } = video;
return (
<div>
<h1 className="text-lg font-semibold tracking-tight text-slate-800">
{title}
</h1>
<div className="pb-4 flex items-center space-between border-b">
<h2 className="text-sm leading-[1.7142857] text-slate-600 w-full">
Uploaded on {date}
</h2>
<LIke likes={likes} unlikes={unlikes} id={id} />
</div>
<div className="mt-4 text-sm text-[#334155] dark:text-slate-400">
{description}
</div>
</div>
);
};
export default VidoeDescription;
Like component where I put my like and unlike component
import React from 'react';
import { useDispatch } from 'react-redux';
import LikeImg from '../../assets/like.svg';
import UnLikeImg from '../../assets/unlike.svg'
// import { updateReaction } from '../../features/video/videoApi';
import { fetchReact } from '../../features/video/video_slice';
const LIke = ({ likes, unlikes, id }) => {
console.log(id)
const dispatch = useDispatch();
//handle like features
const reactionHandler = ({ id, reaction }) => {
dispatch(fetchReact({ id, reaction }))
}
return (
<div className="flex gap-10 w-48">
<div className="flex gap-1">
<div
className="shrink-0 cursor-pointer"
onClick={() => reactionHandler({ id, reaction: 'like' })}
>
<img className="w-5 block" src={LikeImg} alt="Like" />
</div>
<div className="text-sm leading-[1.7142857] text-slate-600">
{likes >= 1000 ? `${ likes }K` : likes}
</div>
</div>
<div className="flex gap-1">
<div
onClick={() => reactionHandler({ id, reaction: 'unlike' })}
className="shrink-0 cursor-pointer"
>
<img className="w-5 block" src={UnLikeImg} alt="Unlike" />
</div>
<div className="text-sm leading-[1.7142857] text-slate-600">
{unlikes >= 1000 ? `${ unlikes }K` : unlikes}
</div>
</div>
</div>
);
};
export default LIke;
This one is root video page
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import Player from '../component/Player/Player';
import VidoeDescription from '../component/VideoDescription/VidoeDescription';
import { fetchVideo } from '../features/video/video_slice';
import Loading from '../component/Loading/Loading';
import RelatedVideo from '../component/VideoList/RelatedVideo';
const Videos = () => {
const { loading, isError, video, error } = useSelector(state => state.video)
const { id, link, title , tags } = video || {};
const dispatch = useDispatch()
const { videoId } = useParams();
useEffect(() => {
dispatch(fetchVideo(videoId))
}, [dispatch, videoId])
//decide what to render in the ui
let content = null;
if (loading) {
content = <Loading />
}
if (loading && isError) {
content = <div className="col-span-12"><srong>{error}</srong></div>
}
if (!loading && !isError && video?.id) {
content = <div className="col-span-12"><srong>{error}</srong></div>
}
if (!loading && !isError && video?.id) {
content = <div className="mx-auto max-w-7xl px-2 pb-20 min-h-[400px]">
<div className="grid grid-cols-3 gap-2 lg:gap-8">
<div className="col-span-full w-full space-y-8 lg:col-span-2">
<Player link={link} title={title} />
<VidoeDescription video={video} />
</div>
<RelatedVideo currentId={id} tags={tags} />
</div>
</div>
}
return (
<div>
<section className="pt-6 pb-20">
{content}
</section>
</div>
);
};
export default Videos;

Problem
Here:
builder.addCase(fetchReact.fulfilled, (state, action) => {
state.video.likes = action.payload;
state.video.unlikes = action.payload;
})
action.payload looks like this:
{
likes: 1,
}
because it's how you created it in updateReaction
Solution
You wanted to do this instead:
builder.addCase(fetchReact.fulfilled, (state, action) => {
state.video.likes = action.payload.likes;
state.video.unlikes = action.payload.unlikes;
})

Related

How can I toggle one icon which triggers the disabling of another icon in React?

I am developing an app with react and redux toolkit. I am using an API. the part that I would like help with is this:
I have a favorite Icon. this icon takes a copy of the movie and displays it in the favorite section. There is another icon which is for watched movies. so the idea is that if I click on watched it should disable only the favorite icon of the movie card I clicked. however, it disables all favorite icons for all movie cards. The watched icon is referred to as Eye the favorite icon is referred to as star
This is the eye component (Watched)
import { useState } from "react";
import { BsEyeSlash, BsEyeFill } from "react-icons/bs";
import { useDispatch, useSelector } from "react-redux";
import {
add_watched,
remove_watched,
add_occupied,
remove_occupied,
} from "../features/animeSlice";
const Eye = ({ anime, type }) => {
const [active_eye, setActive_eye] = useState(false);
const dispatch = useDispatch();
const toggle_eye = () => {
if (!active_eye) {
setActive_eye((prev) => {
return !prev;
});
dispatch(add_watched(anime));
dispatch(add_occupied(type));
} else {
setActive_eye((prev) => {
return !prev;
});
dispatch(remove_watched(anime?.mal_id));
dispatch(remove_occupied());
}
};
return (
<div>
{!active_eye ? (
<BsEyeSlash
className="text-xl text-green-500 icons_1"
onClick={toggle_eye}
/>
) : (
<BsEyeFill
className={"text-xl text-red-500 icons_1"}
onClick={toggle_eye}
/>
)}
</div>
);
};
export default Eye;
This is the Star Component (Favorite)
import { useState } from "react";
import { AiOutlineStar, AiFillStar } from "react-icons/ai";
import { add_favourite, remove_favourite } from "../features/animeSlice";
import { useDispatch, useSelector } from "react-redux";
const Star = ({ anime }) => {
const { value} = useSelector((state) => state.anime);
const [active_star, setActive_star] = useState(false);
const dispatch = useDispatch();
const toggle_star = () => {
if (!active_star) {
setActive_star((prev) => {
return !prev;
});
dispatch(add_favourite(anime));
} else {
setActive_star((prev) => {
return !prev;
});
dispatch(remove_favourite(anime?.mal_id));
}
};
return (
<div>
{!active_star ? (
<AiOutlineStar
className={
value === "occupied"
? "text-xl text-gray-300 pointer-events-none"
: "text-xl text-yellow-500 icon_1"
}
onClick={toggle_star}
/>
) : (
<AiFillStar
className={
value === "occupied"
? "text-xl text-gray-300 pointer-events-none"
: "text-xl text-yellow-500 icon_1"
}
onClick={toggle_star}
/>
)}
</div>
);
};
export default Star;
this is the redux toolkit slice
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
favourite: [],
watched: [],
value: "",
page:""
};
const animeSlice = createSlice({
name: "anime",
initialState,
reducers: {
add_favourite(state, { payload }) {
state.favourite.push(payload);
},
remove_favourite(state, { payload }) {
state.favourite = state.favourite.filter(
(anime) => anime?.mal_id !== payload
);
},
add_watched(state, { payload }) {
state.watched.push(payload);
},
remove_watched(state, { payload }) {
state.watched = state.watched.filter((anime) => anime?.mal_id !== payload);
},
add_occupied(state, { payload }) {
state.value = payload;
},
remove_occupied(state) {
state.value = "";
},
pageNumber(state, { payload }) {
state.page = payload
}
},
});
export const {
add_favourite,
remove_favourite,
add_watched,
remove_watched,
add_occupied,
remove_occupied,
pageNumber
} = animeSlice.actions;
export default animeSlice.reducer;
This is the component that holds the component of eye and star
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import Eye from "./Eye";
import Star from "./Star";
const TopAnime = ({ anime }) => {
//pagination
//
//
let colorYear = (year) => {
if (year === "N/A") {
return "text-violet-500";
} else {
return "";
}
};
return (
<section className="grand px-4 pt-2 pb-5 w-64 bg-white/5 bg-opacity-80 backdrop-blur-sm rounded-lg cursor-pointer font-poppins animate-slideup">
<div className="pb-1 wrapper_icons">
<div className="wrapper_hover">
<Eye anime={anime} type="occupied" />
<Star anime={anime} />
</div>
</div>
<Link to={`/anime/${anime?.mal_id}`}>
<div className="wrapper_1 flex flex-col items-center justify-center">
<div className="h-[313px] w-[219px]">
<img
src={anime?.images?.jpg?.large_image_url}
alt={anime?.title}
className="h-full w-full"
/>
</div>
</div>
<div className="flex flex-col mt-3">
<p className="text-sm text-white truncate mx-1">
{anime?.title_english ? anime?.title_english : anime?.title}
</p>
<div className="flex justify-between items-center text-sm text-yellow-500 mx-1">
<p className={colorYear(anime?.year ? anime?.year : "N/A")}>
{anime?.year ? anime?.year : "N/A"}
</p>
<p
className={
anime?.score <= 7
? "text-cyan-500"
: anime?.score <= 5
? "text-red-600"
: "text-green-500"
}
>
{anime?.score}
</p>
</div>
</div>
</Link>
</section>
);
};
export default TopAnime;
This is where TopAnime is rendered
import { useGetAnimeQuery } from "../features/API";
import { useDispatch, useSelector } from "react-redux";
import { useState, useEffect } from "react";
import TopAnime from "../components/TopAnime";
import Spinner from "../components/Spinner";
import { NavLink, Link } from "react-router-dom";
import Paginator from "../components/Paginator";
//import ReactPaginate from "react-paginate";
import Pagination from "#mui/material/Pagination";
const Home = () => {
const [page, setPage] = useState(1);
const {
data: manga = [],
isLoading,
isFetching,
error,
} = useGetAnimeQuery(page);
const { data, pagination } = manga;
//destructuring objects
//const { data, pagination } = manga;
//const top_anime = data;
const total = Math.ceil(pagination?.items?.total / 24)
//const current_page = pagination?.current_page;
//const per_page = pagination?.items?.per_page;
//const { items: pages } = total;
/* let fetchData = async (page = 1) => {
let res = await fetch(
`https://api.jikan.moe/v4/top/anime?page=${page}&limit=24`
);
let query = await res.json();
const { data, pagination } = query;
let totalPages = Math.ceil(pagination?.items.total / 24);
setPageCount(totalPages);
setData(data);
};
useEffect(() => {
fetchData();
}, []);
*/
import { useGetAnimeQuery } from "../features/API";
import { useDispatch, useSelector } from "react-redux";
import { useState, useEffect } from "react";
import TopAnime from "../components/TopAnime";
import Spinner from "../components/Spinner";
import { NavLink, Link } from "react-router-dom";
import Paginator from "../components/Paginator";
//import ReactPaginate from "react-paginate";
import Pagination from "#mui/material/Pagination";
const Home = () => {
const [page, setPage] = useState(1);
const {
data: manga = [],
isLoading,
isFetching,
error,
} = useGetAnimeQuery(page);
const { data, pagination } = manga;
//destructuring objects
//const { data, pagination } = manga;
//const top_anime = data;
const total = Math.ceil(pagination?.items?.total / 24)
//const current_page = pagination?.current_page;
//const per_page = pagination?.items?.per_page;
//const { items: pages } = total;
/* let fetchData = async (page = 1) => {
let res = await fetch(
`https://api.jikan.moe/v4/top/anime?page=${page}&limit=24`
);
let query = await res.json();
const { data, pagination } = query;
let totalPages = Math.ceil(pagination?.items.total / 24);
setPageCount(totalPages);
setData(data);
};
useEffect(() => {
fetchData();
}, []);
*/
const handleChange = (event, value) => {
setPage(value);
};
const display = data?.map((anime) => (
<TopAnime anime={anime} key={anime.mal_id} />
));
//const pageCount = Math.ceil(pagination?.items?.total / 24);
if (isLoading) {
return <Spinner color_1={"#141e30"} color_2={"#243b55"} />;
} else if (isFetching) {
return <Spinner color_1={"#141e30"} color_2={"#243b55"} />;
} else if (error) {
console.log("ERROR =>", error.message);
}
return (
<section className="bg-gradient-to-r from-[#141e30] to-[#243b55]">
<div className="container font-poppins">
<div className="grid grid-cols-4 gap-3 place-items-center px-20">
{/* {top_anime &&
top_anime?.map((anime) => (
<TopAnime anime={anime} key={anime.mal_id} />
))} */}
{display}
</div>
<div className="button text-yellow-500 flex items-center justify-center mt-2 pb-2 cursor-pointer">
{/* <Paginator paginated={paginated} NumP={pagination?.last_visible_page} /> */}
{/* <ReactPaginate
previousLabel={"Previous"}
nextLabel={"Next"}
onPageChange={(page) => fetchData(page.selected + 1)}
pageCount={pageCount}
className="flex space-x-2"
activeClassName="active"
/> */}
<Pagination count={total} page={page} onChange={handleChange} defaultPage={1} boundaryCount={3} color="secondary" sx={{button:{color:'#ffffff'}}} />
</div>
</div>
</section>
);
};
export default Home;
Issue
The issue with the code/current implementation is that there is only one state.anime.value and one "occupied" value. Each time a movie's "watched" status is toggled the Redux state sets/unsets the single state.anime.value state. In other words, you can toggle N movies "watched" and the state.anime.value value is "occupied", but then toggling just 1 movie back to "unwatched" and state.anime.value is reset back to "". All the Star components read this single state value and this is why the stars all toggle together.
Solution
If you want a specific movie to toggle the Star component when the Eye icon is toggled then I think the solution is a bit simpler than you are making it out to be in your code.
The "eye" and "star" components effectively duplicate your Redux state and don't synchronize well with it. It's often considered a React anti-pattern to duplicate state. The state.anime.favourite and state.anime.watched arrays are all the app needs to know what has been marked "watched" and what has been favourited.
Update the animeSlice to store the anime.mal_id properties in objects instead of arrays to provide O(1) lookup in the UI. Remove the anime.value state as it's unnecessary.
animeSlice.js
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
favourite: {},
watched: {},
page: ""
};
const animeSlice = createSlice({
name: "anime",
initialState,
reducers: {
add_favourite(state, { payload }) {
state.favourite[payload] = true;
},
remove_favourite(state, { payload }) {
delete state.favourite[payload];
},
add_watched(state, { payload }) {
state.watched[payload] = true;
delete state.favourite[payload]; // un-star when adding as watched
},
remove_watched(state, { payload }) {
delete state.watched[payload];
},
pageNumber(state, { payload }) {
state.page = payload;
}
}
});
export const {
add_favourite,
remove_favourite,
add_watched,
remove_watched,
pageNumber
} = animeSlice.actions;
export default animeSlice.reducer;
Update the Eye and Star components to read the true state from the store. These components should pass anime.mal_id to the dispatched actions and use anime.mal_id to check the watched/favourites map objects.
Eye
import { BsEyeSlash, BsEyeFill } from "react-icons/bs";
import { useDispatch, useSelector } from "react-redux";
import { add_watched, remove_watched } from "../features/animeSlice";
const Eye = ({ anime }) => {
const dispatch = useDispatch();
const { watched } = useSelector((state) => state.anime);
const isWatched = watched[anime.mal_id];
const toggle_eye = () => {
dispatch((isWatched ? remove_watched : add_watched)(anime.mal_id));
};
const Eye = isWatched ? BsEyeFill : BsEyeSlash;
const className = [
"text-xl icons_1",
isWatched ? "text-green-500" : "text-red-500"
].join(" ");
return (
<div>
<Eye className={className} onClick={toggle_eye} />
</div>
);
};
export default Eye;
Star
import { AiOutlineStar, AiFillStar } from "react-icons/ai";
import { add_favourite, remove_favourite } from "../features/animeSlice";
import { useDispatch, useSelector } from "react-redux";
const Star = ({ anime }) => {
const { favourite, watched } = useSelector((state) => state.anime);
const dispatch = useDispatch();
const isFavorited = favourite[anime.mal_id];
const isWatched = watched[anime.mal_id];
const toggle_star = () => {
dispatch((isFavorited ? remove_favourite : add_favourite)(anime.mal_id));
};
const Star = isFavorited ? AiFillStar : AiOutlineStar;
const className = [
"text-xl",
isWatched ? "text-gray-300 pointer-events-none" : "text-yellow-500 icon_1"
].join(" ");
return (
<div>
<Star className={className} onClick={toggle_star} />
</div>
);
};
export default Star;

How to filters by category using redux-toolkit in ReactJS

How to implement filter by category in shopping cart projects in redux-toolkit in ReactJS projects. I tried in my filterSlice.js file but I am not getting means data is not filtering by categories. Kindly help me, what should I implement in my code for the product categories filter in my projects. I want only filters in categories like (laptops, smartphones, home decoration, etc) by using redux-toolkit.
Home.js
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Products, SearchBar } from "../components";
import Filters from "../components/Filters";
import { fetchProducts, handleSearchProducts, STATUSES } from "../redux/features/productSlice";
import { FaFilter } from "react-icons/fa";
import SidebarFilterPanel from "../components/SidebarFilterPanel";
import { setCategoryFilter } from "../redux/features/filterSlice";
const categories = [
"Smartphones",
"Laptops",
"Fragrances",
"Skincare",
"Groceries",
"Home-Decoration",
];
const Home = () => {
const dispatch = useDispatch();
const { products, status } = useSelector((state) => state.product.products);
const { price, rating, discount, category } = useSelector(state => state.filters)
console.log(products);
const [searchTerm, setSearchTerm] = useState("")
const [openFilter, setOpenFilter] = useState(false);
const openFilterPanel = () => setOpenFilter(!openFilter);
useEffect(() => {
dispatch(fetchProducts())
}, [dispatch]);
useEffect(() => {
document.title = "Shoping Website";
}, []);
const searchProducts = products?.filter(product => {
if(!searchTerm.length) return product
if(!product.title) return
return product.title.toLowerCase().includes(searchTerm.toLowerCase())
})
const handleSearch = (event) => {
setSearchTerm(event.target.value)
}
const handleFilterCategory = (category) => {
const filterCategory = products?.filter(product => product.category === category)
dispatch(setCategoryFilter(filterCategory))
}
return (
<div>
<div className="flex gap-4 justify-between px-4">
<div className="hidden md:block">
<Filters categories={categories} handleFilterCategory={handleFilterCategory} />
</div>
<div className="flex flex-col items-center justify-center w-full">
<div className="flex items-center">
<SearchBar handleSearch={handleSearch} />
<div
onClick={openFilterPanel}
className={`bg-black text-white py-[10px] px-3 md:hidden block rounded cursor-pointer ml-2 mt-3 duration-1000 transition-all`}
>
<FaFilter onClick={openFilterPanel} />
</div>
{openFilter && <SidebarFilterPanel categories={categories} />}
</div>
<div className="flex flex-wrap gap-4 p-2 items-center justify-center mr-auto ml-auto w-full mt-4">
{searchProducts &&
searchProducts
?.filter((product) => product.price <= price)
?.filter((product => product.rating <= rating))
?.filter((product) => product.discountPercentage <= discount)
?.map((product) => (
<Products key={product.id} product={product} />
))}
</div>
</div>
</div>
</div>
);
};
export default Home;
filterSlice.js
import { createSlice } from "#reduxjs/toolkit";
const filterSlice = createSlice({
name: "filters",
initialState: {
price: 1500,
rating: 5,
discount: 20,
category: []
},
reducers: {
setPriceFilter: (state, action) => {
state.price = Math.max(0, action.payload);
},
setRatingFilter: (state, action) => {
state.rating = Math.max(0, action.payload)
},
setDiscountFilter: (state, action) => {
state.discount = Math.max(0, action.payload)
},
setCategoryFilter: (state, action) => {
state.category.push(action.payload)
}
},
});
export const actions = {
...filterSlice.actions,
};
export const { setPriceFilter, setRatingFilter, setDiscountFilter, setCategoryFilter } = filterSlice.actions
export default filterSlice.reducer;
productSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const baseURL = "https://dummyjson.com/products";
export const STATUSES = Object.freeze({
IDLE: "idle",
ERROR: "error",
LOADING: "loading",
});
export const fetchProducts = createAsyncThunk(
"products/fetch",
async () => {
const res = await axios.get(baseURL);
const data = await res.data;
return data;
}
);
export const getProduct = createAsyncThunk(
"product/getProduct",
async (id, { rejectWithValue }) => {
try {
const response = await axios.get(`${baseURL}/${id}`);
console.log(id);
return response.data;
} catch (error) {
return rejectWithValue(error.response);
}
}
);
const productSlice = createSlice({
name: "product",
initialState: {
products: [],
product: {},
status: STATUSES.IDLE,
},
reducers: {
},
extraReducers: (builder) => {
builder
.addCase(fetchProducts.pending, (state, action) => {
state.status = STATUSES.LOADING;
})
.addCase(fetchProducts.fulfilled, (state, action) => {
state.products = action.payload;
state.status = STATUSES.IDLE;
})
.addCase(fetchProducts.rejected, (state, action) => {
state.status = STATUSES.ERROR;
})
.addCase(getProduct.pending, (state, action) => {
state.status = STATUSES.LOADING;
})
.addCase(getProduct.fulfilled, (state, action) => {
state.status = STATUSES.IDLE;
state.product = action.payload;
});
},
});
export const { handlePriceFilter, handleSearchProducts } = productSlice.actions;
export default productSlice.reducer;
import React from "react";
import Slider from "#mui/material/Slider";
import { useDispatch, useSelector } from "react-redux";
import { setPriceFilter, setRatingFilter, setDiscountFilter, setCategoryFilter } from "../redux/features/filterSlice";
Filters
const Filters = ({ categories, handleFilterCategory }) => {
const dispatch = useDispatch();
const price = useSelector((state) => state.filters.price);
const rating = useSelector((state) => state.filters.rating);
const discount = useSelector((state) => state.filters.discount);
const category = useSelector((state) => state.filters.category);
console.log(price);
return (
<div className=" w-[200px] shadow-lg h-[500px] flex flex-col px-2 py-10">
<div className="w-[150px] ml-auto mr-auto flex flex-col gap-2">
<p className="font-bold">Price</p>
<Slider
valueLabelDisplay="auto"
aria-labelledby="range-slider"
min={0}
max={1500}
value={price}
onChange={(event, value) => dispatch(setPriceFilter(value))}
/>
</div>
<div className="px-2 mt-6">
<p className="font-bold mb-2">Catogries</p>
{categories.map((category, index) => (
<p
className="cursor-pointer text-sm my-2 hover:text-orange-500"
key={index}
onClick={() => handleFilterCategory(category)}
>
{category}
</p>
))}
</div>
<div className="w-[150px] pl-2 flex flex-col gap-2 my-2">
<p className="font-bold">Discount</p>
<Slider
valueLabelDisplay="auto"
aria-labelledby="range-slider"
min={0}
max={20}
value={discount}
onChange={(event, value) => dispatch(setDiscountFilter(value))}
/>
</div>
<div className="w-[150px] pl-2 flex flex-col gap-2">
<p className="font-bold">Rating</p>
<Slider
valueLabelDisplay="auto"
aria-labelledby="range-slider"
min={0}
max={5}
value={rating}
onChange={(event, value) => dispatch(setRatingFilter(value))}
/>
</div>
</div>
);
};
export default Filters;
You want to handle the category filter in the same way that you are handling the price and ratings filters. That is:
The Filters component accesses the current filter from the redux filters state.
const price = useSelector((state) => state.filters.price);
The Filters component handles changes to the current filter by dispatching an action whose payload is the new value of the filter.
onChange={(event, value) => dispatch(setPriceFilter(value))}
The filterSlice responds to that action and updates the redux filters state.
setPriceFilter: (state, action) => {
state.price = Math.max(0, action.payload);
},
The Products component accesses the current filter value from the redux filters state and the unfiltered list of products from the product state.
const { products, status } = useSelector((state) => state.product.products);
const { price, rating, discount, category } = useSelector(state => state.filters)
The Products component applies the filter to the array of products before displaying them.
?.filter((product) => product.price <= price)
(Steps 4 and 5 can also be implemented by using createSelector to select the filtered products list)
In contrast, what you are doing right now is dispatching an array of filtered products to your redux state, rather than dispatching the value of the filter.

i want to make hacknews userinfo react modal

i don't know how can i print user info react modal
this is my code
import React from "react";
import axios from "axios";
import { useEffect, useState } from "react";
import { Show, User } from "../api";
import UseUser from "../scroll/userInfo";
function ShowContents() {
const [storyIds, setStoryIds] = useState([]);
const [visible, setVisible] = useState(false);
const [getUser, setGetUser] = useState([]);
useEffect(() => {
Show().then((res) => {
this.res = res.data.slice(0, 10);
this.res.forEach(async (ele) => {
await axios
.get("https://hacker-news.firebaseio.com/v0/item/" + ele + ".json")
.then((res) => {
if (Array.isArray(this.res) && this.res.length === 0) {
return;
} else {
setStoryIds((value) => [
...value,
{
id: res.data.id,
title: res.data.title,
url: res.data.url,
user: res.data.by,
score: res.data.score
}
]);
}
});
});
});
}, []);
const menu = storyIds;
const menuList = menu.map((m, i) => (
<div className="box_show" key={i}>
<div className="flex">
<p className="numbers">{i + 1}</p>
<a href={m.url} className="titleFont">
{m.title}
</a>
<a href={m.url}>
<img src={`/assets/back.svg`} alt="" className="imgLink" />
</a>
</div>
<br />
<button
className="userShow"
onClick={() => {
setVisible(!visible);
}}
>
<div className="userNameShow">{m.user}</div>
</button>
</div>
));
return (
<>
{menuList}
{visible && (
<div className="modal-container" id="modal">
<div className="modal">
<div className="modal-top flex">
<p>User Info</p>
<button
className="close-btn"
onClick={() => {
setVisible(!visible);
}}
>
<img src={`/assets/close_black.svg`} alt="" />
</button>
</div>
<UseUser />
</div>
</div>
)}
</>
);
}
export default ShowContents;
The code above is my show partial code.
this is my UserInfo code
import { useState, useEffect } from "react";
import { getUser } from "../api";
const UseUser = (id) => {
// const [user, setUser] = useState({});
// useEffect(() => {
// getUser(id).then((user) => setUser(user));
// }, [id]);
return (
<>
<div className="user-detail flex">
<div className="user-profile"></div>
<div className="user-detail-info">
<p className="modal-user">user : </p>
<p className="modal-created">created : </p>
<p className="modal-karma">karma : </p>
</div>
</div>
<p className="about">about:</p>
<p className="email">Twitter:</p>);
</>
);
};
export default UseUser;
import axios from "axios";
const BASE_URL = "https://hacker-news.firebaseio.com/v0/";
export const storyUrl = `${BASE_URL}item/`;
function News() {
return axios.get(`${BASE_URL}newstories.json`);
}
function Jobs() {
return axios.get(`${BASE_URL}jobstories.json`);
}
function Top_API() {
return axios.get(`${BASE_URL}topstories.json`);
}
function Ask() {
return axios.get(`${BASE_URL}askstories.json`);
}
function Show() {
return axios.get(`${BASE_URL}showstories.json`);
}
function User() {
return axios.get(`${BASE_URL}user`);
}
export { News, Jobs, Top_API, Ask, Show, BASE_URL, User };
this is my api code
When you click the user button in the show part, I want to get information about the user. It's too difficult for me right now.
I tried to implement it using use Effect, but I don't know how to send information about the user when I press the button.
I need help.

ReactJS error TypeError: Cannot convert undefined or null to object

The affected code is (Typescript errors in local) (componet is correct, the name is as that):
{template.elements.map((element: TemplateElementModel, i) => {
const stand = roomStands?.find(
(stand: ExhibitorModel) => stand.standNumber === `${i + 1}`
);
const componet = stand ? (
<img
src={stand?.logo}
alt={`${stand?.name} logo`}
className="w-24 object-contain"
/>
) : undefined;
const handleClick = stand
? () => {
dispatch(setExhibitor(stand?.id));
dispatch(pushScreen(EventDetailScreen.stand));
}
: undefined;
That's when the code has an error but is just the new registers. When all is ok it shows the view but if has that error the view was a blank page.
I've tried a lot of solutions but i cannot detect what is the problem.
The complete code is:
import React, { useEffect, useState } from "react";
import {
TemplateElementModel,
TemplateModel,
} from "../../template/template.model";
import {
currentEventIdSelector,
roomIdSelector as eventRoomSelector,
popScreen,
pushScreen,
setExhibitor,
setRoom,
} from "../event.slice";
import { useDispatch, useSelector } from "react-redux";
import { Collections } from "../../../config/app.collections";
import DeimosModal from "../../../lib/deimos-ui/components/deimos-modal/DeimosModal";
import { EventDetailScreen } from "../enums/event-detail-screen";
import { ExhibitorModel } from "../../exhibitor/exhibitor.model";
import { RoomModel } from "../../room/room.model";
import { RootState } from "../../../app.store";
import TemplateMarker from "../../template/components/TemplateMarker";
import { TemplateTypes } from "../../template/template-types.enum";
import { useFirestoreConnect } from "react-redux-firebase";
interface EventExhibitionsPageProps {}
const EventExhibitionsPage: React.FC<EventExhibitionsPageProps> = () => {
const dispatch = useDispatch();
const [template, setTemplate] = useState<TemplateModel>();
const [roomStands, setRoomStands] = useState<ExhibitorModel[]>();
const [showRoomsModal, setShowRoomsModal] = useState(true);
const currentRoom = useSelector(eventRoomSelector);
const eventId = useSelector(currentEventIdSelector);
const rooms = useSelector(
(state: RootState) => state.firestore.data[Collections.ROOM]
);
const templates = useSelector(
(state: RootState) => state.firestore.data[Collections.TEMPLATE]
);
const stands = useSelector(
(state: RootState) => state.firestore.data[Collections.EXHIBITOR]
);
useFirestoreConnect([
{
collection: Collections.ROOM,
where: ["event", "==", eventId],
},
{
collection: Collections.TEMPLATE,
where: ["type", "==", TemplateTypes.Room],
},
{
collection: Collections.EXHIBITOR,
where: ["eventId", "==", eventId],
},
]);
useEffect(() => {
if (!currentRoom) return;
const template = templates[currentRoom?.template];
if (template) setTemplate(template);
}, [currentRoom, templates]);
useEffect(() => {
if (!currentRoom) return;
const filteredStands = Object.values<ExhibitorModel>(stands).filter(
(stand) => stand.room === currentRoom.id
);
setRoomStands(filteredStands);
}, [currentRoom, stands]);
return (
<div className="h-full w-full relative bg-no-repeat bg-center text-center">
<DeimosModal
isOpen={showRoomsModal}
hideCancelButton={!currentRoom}
closeButtonText="Salir"
cancelButtonText={`Seguir en ${currentRoom?.name}`}
handleCancel={() => {
setShowRoomsModal(false);
}}
handleClose={() => {
dispatch(popScreen());
}}
>
{!!rooms && (
<div>
<p className="font-bold">Selecciona una sala a la que entrar</p>
{Object.values<RoomModel>(rooms).map((room) => {
return (
<div
className="flex flex-row w-full my-2 p-3 bg-gray-200 rounded cursor-pointer"
onClick={() => {
dispatch(setRoom(room));
setShowRoomsModal(false);
}}
>
{room.name}
</div>
);
})}
</div>
)}
</DeimosModal>
{currentRoom && template && (
<div
className="w-full h-full flex flex-row justify-center align-center"
style={{
backgroundImage: `url(${template.background})`,
backgroundSize: "100% 100%",
}}
>
{template.elements.map((element: TemplateElementModel, i) => {
const stand = roomStands?.find(
(stand: ExhibitorModel) => stand.standNumber === `${i + 1}`
);
const componet = stand ? (
<img
src={stand?.logo}
alt={`${stand?.name} logo`}
className="w-24 object-contain"
/>
) : undefined;
const handleClick = stand
? () => {
dispatch(setExhibitor(stand?.id));
dispatch(pushScreen(EventDetailScreen.stand));
}
: undefined;
return (
<TemplateMarker
// backgroundColor="#327ba8"
width={element.w}
height={element.h}
x={element.x}
y={element.y}
key={element.id}
skewX={element.skewX ?? template.skewX}
skewY={element.skewY ?? template.skewY}
labelComponent={componet}
onClick={handleClick}
/>
);
})}
</div>
)}
</div>
);
};
export default EventExhibitionsPage;
TemplateMarker might not accept labelComponent to be undefined or null. You can add an extra check before sending TemplateMarker.
return componet && (
<TemplateMarker
// backgroundColor="#327ba8"
width={element.w}
height={element.h}
x={element.x}
y={element.y}
key={element.id}
skewX={element.skewX ?? template.skewX}
skewY={element.skewY ?? template.skewY}
labelComponent={componet}
onClick={handleClick}
/>
);

Why react loads images for many times? is it possible to save the first load?

to watch the problem you can vivsit the test site http://u100525.test-handyhost.ru/products
the problem appears if to click many times on category items, images of products start to bug becouse react loads image of one item over and over again, on every change of category - on every filter of products, so how to make one load and save somehow the loaded images?
so if i click on categories my code is filtering products array and update statement - visibleProducts then im doing visibleProducts.map((product)=>{});
and i`m getting bug problem, because every time when react renders my the component does request to the server for getting image by id and waits while the image will load, but if i click on an other category react(ProductItem) starts other request for new images then it is starting to bug they start blinking and changing ;c
im new in react and just stated to practice what i have to do guys?
is my code correct ?
here is my ProductItem component ->
import React, { useState, useEffect, memo, useCallback } from "react";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import { setModalShow, onQuickViewed, addedToCart } from "../../actions";
import Checked from "../checked";
import "./product-item.css";
import Spinner from "../spinner";
const ProductItem = ({
product,
wpApi,
addedToCart,
onQuickViewed,
setModalShow,
}) => {
const [prodImg, setProdImg] = useState("");
const [animated, setAnimated] = useState(false);
const [checked, setChecked] = useState(false);
const [itemLoading, setItemLoading] = useState(true);
const checkedFn = useCallback(() => {
setChecked(true);
setTimeout(() => {
setChecked(false);
}, 800);
},[product]);
const onModalOpen = useCallback((e, id) => {
onQuickViewed(e, id);
setModalShow(true);
}, product);
const addHandle = useCallback((e, id) => {
e.preventDefault();
addedToCart(id);
checkedFn();
},[product]);
useEffect(()=>{
setItemLoading(false);
}, [prodImg]);
useEffect(() => {
wpApi.getImageUrl(product.imageId).then((res) => {
setProdImg(res);
});
});
return (
<div className="product foo">
<div
className='product__inner'}
>
{!itemLoading? <div
className="pro__thumb"
style={{
backgroundImage:prodImg
? `url(${prodImg})`
: "assets/images/product/6.png",
}}
>
<Link
to={`/product-details/${product.id}`}
style={{ display: `block`, width: `100%`, paddingBottom: `100%` }}
>
</Link>
</div>: <Spinner/>}
<div className="product__hover__info">
<ul className="product__action">
<li>
<a
onClick={(e) => {
onModalOpen(e, product.id);
}}
title="Quick View"
className="quick-view modal-view detail-link"
href="#"
>
<span ><i class="zmdi zmdi-eye"></i></span>
</a>
</li>
<li>
<a
title="Add TO Cart"
href="#"
onClick={(e) => {
addHandle(e, product.id);
}}
>
{checked ? (
<Checked />
) : (
<span className="ti-shopping-cart"></span>
)}
</a>
</li>
</ul>
</div>
</div>
<div className="product__details">
<h2>
<Link to={`/product-details/${product.id}`}>{product.title}</Link>
</h2>
<ul className="product__price">
<li className="old__price">${product.price}</li>
</ul>
</div>
</div>
);
};
const mapStateToProps = ({ options, cart, total, showModal }) => {
return {};
};
const mapDispatchToProps = {
onQuickViewed,
setModalShow,
addedToCart,
};
export default connect(mapStateToProps, mapDispatchToProps)(memo(ProductItem));
here is my parent component Products ->
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import ProductItem from "../product-item";
import { withWpApiService } from "../hoc";
import { onQuickViewed, addedToCart, categoriesLoaded } from "../../actions";
import CategoryFilter from "../category-filter";
import Spinner from "../spinner";
import "./products.css";
const Products = ({
maxProducts,
WpApiService,
categoriesLoaded,
addedToCart,
onQuickViewed,
products,
categories,
loading,
}) => {
const [activeIndex, setActiveIndex] = useState(0);
const [activeCategory, setActiveCategory] = useState(0);
const [visibleProducts, setVisibleProducts] = useState([]);
const wpApi = new WpApiService();
useEffect(() => {
updateVisibleProducts(activeCategory, products);
}, [products]);
useEffect(() => {
wpApi.getCategories().then((res) => {
categoriesLoaded(res);
});
}, []);
const getCatId = (cat) => {
setActiveCategory(cat);
updateVisibleProducts(cat, products);
setActiveIndex(cat);
};
const updateVisibleProducts = (category, products) => {
let updatedProducts = [];
switch (category) {
case 0:
updatedProducts = products;
setVisibleProducts(updatedProducts);
break;
default:
updatedProducts = products.filter(
(product) => product.categories.indexOf(category) >= 0
);
setVisibleProducts(updatedProducts);
}
};
let currentLocation = window.location.href.split("/");
if (!loading) {
return (
<section className="htc__product__area shop__page mb--60 mt--130 bg__white">
<div className={currentLocation[3] == "" ? `container` : ""}>
<div className="htc__product__container">
<CategoryFilter
activeIndex={activeIndex}
categories={categories}
getCatId={getCatId}
/>
<div
className="product__list another-product-style"
style={{ height: "auto" }}
>
{visibleProducts
.slice(0, maxProducts ? maxProducts : products.length)
.map((prod, id) => {
return (
<ProductItem
wpApi={wpApi}
key={id}
onQuickViewed={onQuickViewed}
addedToCart={addedToCart}
product={prod}
/>
);
})}
</div>
</div>
</div>
</section>
);
} else {
return <Spinner />;
}
};
const mapStateToProps = ({ products, loading, activeCategory, categories }) => {
return {
products,
activeCategory,
categories,
loading,
};
};
const mapDispatchToProps = {
addedToCart,
categoriesLoaded,
onQuickViewed,
};
export default withWpApiService()(
connect(mapStateToProps, mapDispatchToProps)(Products)
);
and if you need, here is my CategoryFilter component ->
import React from 'react'
const CategoryFilter = ({categories, getCatId, activeIndex}) => {
return (
<div className="row mb--60">
<div className="col-md-12">
<div className="filter__menu__container">
<div className="product__menu">
{categories.map((cat) => {
return (
<button key={cat.id}
className={activeIndex === cat.id? 'is-checked' : null}
onClick={() => getCatId(cat.id)}
data-filter=".cat--4"
>
{cat.name}
</button>
);
})}
</div>
</div>
</div>
</div>
)
}
export default CategoryFilter

Categories

Resources