I’m trying to implement a like dislike gauge similar to the one on youtube. I get my data from a js file that has a redux store. I use dispatch to slice and fetch data from the movie.js file that contains the data.
movie.js
const movies = [
{
id: '1',
title: 'Oceans 8',
category: 'Comedy',
likes: 4,
dislikes: 1
}, {
id: '2',
title: 'Midnight Sun',
category: 'Comedy',
likes: 2,
dislikes: 0
}, {
id: '3',
title: 'Les indestructibles 2',
category: 'Animation',
likes: 3,
dislikes: 1
},
]
export const movies$ = new Promise((resolve, reject) => setTimeout(resolve, 100, movies))
Reducer.js
onst initialState = {
allMovies: [],
filteredMovies: [],
};
const Reducer = (state = initialState, action) => {
switch (action.type) {
case 'initial':
return {
...state,
allMovies: action.data,
};
case 'remove':
const removedMovie = state.allMovies.filter((movie) => movie.id !== action.id)
return {
...state,
allMovies: removedMovie
};
case 'likes':
return state.map((movie) => {
if (movie.id === action.id) {
if (movie.dislikesActive) {
return {
...movie,
likes: movie.likes + 1,
dislikes: movie.dislikes - 1,
likesActive: !movie.likesActive,
dislikesActive: movie.likesActive
};
}
return {
...movie,
likes: movie.likes + 1,
likesActive: !movie.likesActive,
dislikesActive: movie.likesActive
};
}
return movie;
});
case 'dislikes':
return state.map((movie) => {
if (movie.id === action.id) {
if (movie.likesActive) {
return {
...movie,
dislikes: movie.dislikes + 1,
likes: movie.likes -1,
dislikesActive: !movie.dislikesActive,
likesActive: movie.dislikesActive
};
}
return {
...movie,
dislikes: movie.dislikes + 1,
dislikesActive: !movie.dislikesActive,
likesActive: movie.dislikesActive
};
}
return movie;
});
case 'filter':
const filteredMovies = state.allMovies.filter(movie => movie.category === action.category);
if (action.category) {
return {
...state,
filteredMovies
};
} else {
return {
...state
}
}
default:
return state;
}
};
export default Reducer;
store.js
import Reducer from './Redux/Reducer';
export default configureStore({
reducer: {
Reducer
},
});
In my component I get the likes and dislikes data and linked them to their respective buttons.
Listing.jsx
import React, {useEffect, useReducer, useState} from "react";
import IconButton from '@mui/material/IconButton';
import DeleteIcon from '@mui/icons-material/Delete';
import ThumbUpOutlinedIcon from '@mui/icons-material/ThumbUpOutlined';
import ThumbDownOutlinedIcon from '@mui/icons-material/ThumbDownOutlined';
import { red } from '@mui/material/colors';
import Reducer from "../../Redux/Reducer";
import Category from "../Category/Category";
import Paginations from "../Pagination/Paginations";
import {movies$} from "../../Services/movies";
import "../../Styles/Components/MoviesCards/Listing.css"
const titleStyle = {
textAlign: "left",
fontSize: "5vw",
fontFamily: "Mont",
color: "#283D47",
};
const Listing = () => {
const [state, dispatch] = useReducer(Reducer, {allMovies: [], filteredMovies: []});
const [activePage, setCurrentPage] = useState(1);
const [moviesPerPage, setMoviesPerPage] = useState(4);
const filteredCategory = Array.from(new Set(state.allMovies.map(movie => movie.category)));
const indexOfLastMovie = activePage * moviesPerPage;
const indexOfFirstMovie = indexOfLastMovie - moviesPerPage;
const currentMovies = state.filteredMovies && state.filteredMovies.length !== 0 ?
state.filteredMovies.slice( indexOfFirstMovie, indexOfLastMovie ) :
state.allMovies.slice( indexOfFirstMovie, indexOfLastMovie );
const handlePageChange = (pageNumber) => {
setCurrentPage(pageNumber)
};
useEffect(() => {
const fetchMovies = async () => {
return movies$;
};
fetchMovies().then(data => dispatch({type:'initial', data}));
}, []);
return (
<>
<Category filteredCategory={filteredCategory} dispatch={dispatch} />
<div className="cardsComponent">
{state && currentMovies.map(movie => {
return (
<div className="movieCard">
<div key={movie.id}>
<div className="topContainer">
<IconButton onClick={() => dispatch({type:'remove', id: movie.id})} value={2} aria-label="delete" size="large">
<DeleteIcon fontSize="inherit" sx={{ color: red[500] }}/>
</IconButton>
</div>
<div className="citiesPricesContainer">
<div className="bottomContainer">
<span className="movieName">{movie.title}</span>
</div>
<div className="bottomContainer">
<span className="currentPrice">{movie.category}</span>
</div>
<div className="bottomContainer">
<IconButton active={movie.likesActive} onClick={() => dispatch({ type:'likes', id: movie.id})} value={2}>
<ThumbUpOutlinedIcon fontSize="inherit" />
</IconButton>
<p>{movie.likes}</p>
<IconButton active={movie.dislikesActive} onClick={() => dispatch({ type:'dislikes', id: movie.id})} value={2}>
<ThumbDownOutlinedIcon fontSize="inherit" />
</IconButton>
<p>{movie.dislikes}</p>
</div>
</div>
</div>
</div>
)
})
}
</div>
<div className="footer">
<Paginations state={state.filteredMovies && state.filteredMovies.length !== 0 ? state.filteredMovies : state.allMovies} activePage={activePage} handlePageChange={handlePageChange} handleNumberMovies={setMoviesPerPage}/>
</div>
</>
)
}
export default Listing;
I don’t understand why after clicking one of these buttons, a blank page appears and nothing happens. Did I initialize my store wrong?
Thank you.
>Solution :
It does not work because you are not handling dislikes and likes actions properly inside reducer. Looking at you reducer initial state it is obvious that structure of reducer state is like this:
{
allMovies: [],
filteredMovies: [],
}
Meaning that each reducer method, after processing inputs, should return structure like this. But inside your likes and dislikes methods you are doing like this:
case 'likes':
return state.map((movie) => ...)
And this means you are trying to return array as final structure of your state, which is invalid since you declared it differently in initial state. Also state.map will throw you an error because state is an object with allMovies and filteredMovies, so you can not call map on object.
You need to rewrite to something like this:
case 'likes':
return { ...state, allMovies: state.allMovies.map((movie) => ...)
case 'dislikes':
return { ...state, allMovies: state.allMovies.map((movie) => ...)
Of course, if like/dislike action is related to allMovies part of the state. But anyway you will get the point.