I am trying to filter through some data to display the results based on the category chosen. I believe I am approaching it correct. But I also don't think I am filtering my data correct. Can I be guided as to what approach to take to display only movies based on category chosen. My regular text input is filtering but I can seem to get my mind to understand what I am doing wrong using select. The categories are a nested array in a json file. See example...
{
"id": 2,
"title": "Crocodile Dundee",
"year": "1986",
"runtime": "97",
"genres": ["Adventure", "Comedy"],
"director": "Peter Faiman",
"actors": "Paul Hogan, Linda Kozlowski, John Meillon, David Gulpilil",
"plot": "An American reporter goes to the Australian outback to meet an eccentric crocodile poacher and invites him to New York City.",
"posterUrl": "/images/crocodiledundee.jpg"
}
My App js where I have the state and function to filter
function App() {
const [movies, setMovies] = useState("");
const [searchTerm, setSearchTerm] = useState("");
const [genre, setGenre] = useState("");
const searchMovie = (e) => {
e.preventDefault();
const newFilter = movies.filter((value)=>{
return value.title.toLowerCase().includes(searchTerm.toLowerCase())
});
setMovies(newFilter);
}
const changeCategory = (e) =>{
const id = e.target.value;
const result = movies.filter((currData)=>{
const category = currData.genres.map(rating =>( rating.genre ))
return category == id
});
console.log( result)
setGenre(result)
}
useEffect(() => {
const loadMovieInfoInfo = async () => {
try {
const response = await axios.get(" http://localhost:5000/movies");
setMovies(response.data);
} catch (err) {
console.log(err);
}
};
loadMovieInfoInfo();
}, []);
This is my component where I am trying to filter and render the data.
const MovieList = ({ movies, searchTerm, setSearchTerm, searchMovie, genre, changeCategory}) => {
return (
<div className="container">
<div className="filterMain">
<h3>Search or filter to find your favorite movie</h3>
<form onClick={searchMovie}>
<select
value={genre}
onChange={changeCategory}
name=""
id="genreOptions"
>
<option value="comedy">Comedy</option>
<option value="fantasy">Fantasy</option>
<option value="crime">Crime</option>
<option value="drama">Drama</option>
<option value="music">Music</option>
<option value="adventure">Adventure</option>
<option value="history">History</option>
<option value="thriller">Thriller</option>
<option value="animation">Animation</option>
<option value="family">Family</option>
<option value="mystry">Mystry</option>
<option value="biography">Biography</option>
<option value="film-noir">Film-Noir</option>
<option value="romance">Romance</option>
<option value="scifi">Sci-Fi</option>
<option value="war">War</option>
<option value="western">Western</option>
<option value="horror">Horror</option>
<option value="musical">Musical</option>
<option value="sport">Sport</option>
</select>
<></>
<input
type="text"
name=""
id="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search"
/>
<button>Look for Movies</button>
</form>
</div>
<div className="main">
{movies &&
movies.slice(0,20).map((movie, index) => (
<>
<div key={index}>
<div className="content">
<Link to={`/movielist/${movie.id}`}>
<img key={movie.id} src={`${movie.posterUrl}`} alt="" />
</Link>
<div className="innerContent">
<h1 id="movieTitle">
<span className="desc">Title: </span>
{movie.title}
</h1>
<h3 id="moviePlot">
<span className="desc">Year: </span>
{movie.year}
</h3>
<h3 id="moviePlot">
<span className="desc">Director: </span>
{movie.director}
</h3>
<h3 id="moviePlot">
{" "}
<span className="desc">Actors: </span>
{movie.actors}
</h3>
<h3 id="moviePlot">
<span className="desc">Plot: </span>
{movie.plot}
</h3>
<h3 id="moviePlot">
<span className="desc">Genre: </span>
{movie.genres.map(rating =>(" / " rating " " ))}
</h3>
</div>
</div>
</div>
CodePudding user response:
You are setting your genre in changeCategory
function to a filtered array of movies. You should set your genre directly to option's value which is e.target.value
.
Other than that you are not setting your movies
to genre filtered array, so you would not be displaying filtered movies. Also, you can use includes
method to filter your movies with genre (Capitalizing your options' values are necessary to match the element in genre array).
const changeCategory = (e) => {
setGenre(e.target.value)
const filteredMovies = movies.filter((movie) => movie.genres.includes(e.target.value))
setMovies(filteredMovies)
}
CodePudding user response:
There are two goals that we will achieve here:
- Select Movies by Genre
- Search Movies
Ok, let's do it.
Setup
Please make sure you have two .json
files which are movies.json & genreOptions.json.
# ./movies.json
[
{
"title": "The Godfather",
"year": "1972",
"runtime": "175 min",
"genre": ["Crime", "Drama"],
"directors": "Francis Ford Coppola",
"actors": ["Marlon Brando", "Al Pacino", "James Caan", "Diane Keaton"],
"plot": "The aging patriarch of an organized crime dynasty in postwar New York City transfers control of his clandestine empire to his reluctant youngest son.",
"posterUrl": "/M/MV5BM2MyNjYxNmUtYTAwNi00MTYxLWJmNWYtYzZlODY3ZTk3OTFlXkEyXkFqcGdeQXVyNzkwMjQ5NzM@._V1_UY209_CR3,0,140,209_AL_.jpg"
},
...
]
# ./genreOptions.json
[
{
"value": "",
"label": "All Genre"
},
{
"value": "action",
"label": "Action"
},
{
"value": "adventure",
"label": "Adventure"
},
...
]
and import those json data into our component:
import { useEffect, useMemo, useState } from "react";
import allMovies from "./movies.json";
import genreOptions from "./genreOptions.json";
Select Movies by Genre
Here, we will create a select option based on genre values inside our genreOption.json file. First create genre state that would become our selected genre placeholder.
export default function App() {
const [genre, setGenre] = useState("");
...
}
Next, we create our select options tag element. At select
DOM tag, we pass genre state into the value attribute and at onChage event handler we pass an arrow function that will update the genre state by using genre setter (setGenre) when an onchange event occurs. Here, the arrow function will run on every selected option changes.
We render the option tag by iterating the genreOptions and passing genreOption value and label to it.
return (
<select
value={genre}
onChange={(e) => setGenre(e.target.value)}
>
{genreOptions.map((option, i) => {
return (
<option value={option.value} key={i}>
{option.label}
</option>
);
})}
</select>
)
Next, we create a memoized movies constant, that depends on the genre value. When genre changes, the allMovies filtering process will be re-run again resulting in an updated filtered movies array that based on the selected genre.
const movies = useMemo(() => {
return allMovies.filter((movie) => {
// return all movies when options All genre selected (genre value is "")
if (genre === "") {
return allMovies;
}
// if not an empty string, create an array of lowering case genre
const movieGenre = movie.genre.map((val) => val.toLowerCase());
// return movie if the genre is included in movieGenre
return movieGenre.includes(genre);
});
}, [genre]);
At the moment, we render the movie details without styling to see whether the select options event management has run as expected. Open your browser and check your select options. It should working if you follow the steps correctly.
...
return (
<select>
..
</select>
<div>
{movies.map((movie, index) => {
return (
<div key={index}>
<h1>{movie.title}</h1>
Year:
<span>{movie.year}</span>
Runtime:
<span>{movie.runtime}</span>
Genre:
<span>{movie.genre.join(", ")}</span>
Director:
<span>{movie.directors}</span>
Actors:
<span>{movie.actors.join(", ")}</span>
<p>{movie.plot}</p>
</div>
);
})}
</div>
)
}
Search Movies
Now it is time to create our Search Input where we can search the movie by typing in keywords in our search input. We use searchTerm state to keep our keywords query. Pass searchTerm state to input value attribute and pass setSearchTerm into input onChange handler. By doing this the onChange handler will call the setSearchTerm on every keystroke and the searchTerm value will update as the user types.
function App() {
const [genre, setGenre] = useState("");
const [searchTerm, setSearchTem] = useState("");
...
return (
<div>
<select>
..
</select>
<input
name="searchMovie"
placeholder="Search Movie"
value={searchTerm}
onChange={(e) => setSearchTem(e.target.value)}
/>
<div>
...
</div>
</div>
)
}
Then we add this useEffect block. This will make us sure that the searchTerm changes when user types. Setter setGenre("") will be called when the searchTerm is not empty.
useEffect(() => {
if (searchTerm !== "") {
setGenre("");
}
}, [searchTerm]);
Next, update your memoized movies function. Add searchTerm deps and the else block where we define searchFiels that become filter reference.
const movies = useMemo(() => {
if (genre === "") {
if (searchTerm === "") {
return allMovies;
} else {
// search input filter
return allMovies.filter((movie) => {
// define a string by combining all fields that will become search reference
const searchFields =
`${movie.title.toLowerCase()} `
`${movie.year} `
`${movie.directors.toLowerCase()}`
`${movie.actors.join("").toLowerCase()}`
`${movie.plot.toLowerCase()}`;
return searchFields.includes(searchTerm.toLowerCase());
});
}
}
// select option filter
return allMovies.filter((movie) => {
const movieGenre = movie.genre.map((val) => val.toLowerCase());
return movieGenre.includes(genre);
});
}, [genre, searchTerm]);
Now, open your browser and do types in the search input. The rendered movies should displaying the movies the contain the typed keyword.
import { useEffect, useState, useMemo } from "react";
import allMovies from "./movies.json";
import genreOptions from "./genreOptions.json";
export default function App() {
const [genre, setGenre] = useState("");
const [searchTerm, setSearchTem] = useState("");
const movies = useMemo(() => {
if (genre === "") {
if (searchTerm === "") {
return allMovies;
} else {
return allMovies.filter((movie) => {
const searchFields =
`${movie.title.toLowerCase()} `
`${movie.year} `
`${movie.directors.toLowerCase()}`
`${movie.actors.join("").toLowerCase()}`
`${movie.plot.toLowerCase()}`;
return searchFields.includes(searchTerm.toLowerCase());
});
}
}
return allMovies.filter((movie) => {
const movieGenre = movie.genre.map((val) => val.toLowerCase());
return movieGenre.includes(genre);
});
}, [genre, searchTerm]);
useEffect(() => {
if (searchTerm !== "") {
setGenre("");
}
}, [searchTerm]);
return (
<div className="p-6">
<h1 className="text-xl font-bold my-6">
Top 100 Greatest Movies of All Time
</h1>
<form className="flex flex-col w-72">
<select
className="px-2 py-1 border w-40"
value={genre}
onChange={(e) => setGenre(e.target.value)}
>
{genreOptions.map((option, i) => {
return (
<option className="py-2" value={option.value} key={i}>
{option.label}
</option>
);
})}
</select>
<input
className="border p-1 px-3 my-3"
name="searchMovie"
placeholder="Search Movie"
value={searchTerm}
onChange={(e) => setSearchTem(e.target.value)}
/>
</form>
<hr className="mb-6 mt-3" />
<div className="lg:grid lg:grid-cols-2 lg:gap-4">
{movies.map((movie, index) => {
return (
<div className="flex border rounded py-4 my-4 lg:my-0">
<div className="flex-none w-48 flex justify-center items-center">
<img
src={`https://m.media-amazon.com/images${movie.posterUrl}`}
alt=""
/>
</div>
<div key={index} className="grow flex flex-col">
<h1 className="text-base font-bold mt-2">{movie.title}</h1>
<span className="text-sm mt-2">
Year:
<span className="m-2">{movie.year}</span>
Runtime:
<span className="m-2">{movie.runtime}</span>
</span>
<span className="text-sm mt-2">
Genre:
<span className="m-2">{movie.genre.join(", ")}</span>
</span>
<span className="text-sm mt-2">
Director:
<span className="m-2">{movie.directors}</span>
</span>
<span className="text-sm mt-2">
Actors:
<span className="m-2">{movie.actors.join(", ")}</span>
</span>
<p className="text-sm mt-2">{movie.plot}</p>
</div>
</div>
);
})}
</div>
</div>
);
}