I am working on an application where I am taking data(array of sectors) from firestore database and adding this array to select box options. My problem is that I can't properly display each item in a separate option, instead my entire array is displayed in one line in the select box option.
a screenshot of what my application looks like with the described problem.
Parts of the code responsible for getting an array from the database and displaying it in the select box options:
const [allSectors, setAllSectors] = useState([]);
useEffect(() => {
getSectors();
}, []);
const getSectors = async () => {
const data = await UserDataService.getAllSectorsData();
setAllSectors(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
};
const FormSelect = () => {
return allSectors.map((sector) => (
<option key={sector.id} value={sector.id}>
{sector.Manufacturing}
</option>
));
};
return (
<>
<Form onSubmit={handleSubmit}>
<Form.Group className="mb-3" controlId="formUserSector">
<InputGroup>
<InputGroup.Text id="formUserSector">Sectors: </InputGroup.Text>
<Form.Select size="sm" onChange={(e) => setSectors(e.target.value)}>
{FormSelect()}
</Form.Select>
</InputGroup>
</Form.Group>
</Form>
</>
)
Not sure which UI library you are using. The first thing I noticed is that you use <option> tag without surrounding it with <select> tag
<option key={sector.id} value={sector.id}>
{sector.Manufacturing}
</option>
I'm assuming you are trying to implement HTML <select> dropdown. I think if you use only tags without surrounding it with it will just return strings, which is what happening in your example.
Try changing your FormSelect function to something like this:
const FormSelect = () => {
return (
<select>
{allSectors.map((sector) => (
<option key={sector.id} value={sector.id}>
{item}
</option>
))}
</select>
);
};
EDIT 1
From your comments, I noticed that your sector.Manufacturing is an array, but it should be a string. When you put an array inside option like this <option>{array}</option> your get all the array values squashed into one <option> tag.
try also looping over sector.Manufacturing rather than allSector. Hard to solve without seeing the full code, but it might look something like this:
const FormSelect = () => {
return (
<select>
{allSectors.map((sector) => (
<option key={sector.id} value={sector.id}>
{sector.Manufacturing.map((item) => {
return item;
})}
</option>
))}
</select>
);
};
EDIT 2
Try this:
const FormSelect = () => {
return (
<select>
{allSectors.map((sector) =>
sector.Manufacturing.map((item, i) => {
return (
<option key={i} value={item}>
{item}
</option>
);
})
)}
</select>
);
};
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>
There are two goals that we will achieve here:
Select Movies by Genre
Search Movies
Ok, let's do it.
Spoiler: Top 100 Greatest Movies of All Time app
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 once the 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 keywords in our search input. We use searchTerm state to keep our keywords query. Pass searchTerm state to input value attribute and pass setSearchTerm setter into input's onChange handler. By doing this the onChange handler will call the setSearchTerm on every keystroke and the searchTerm value will have an updated value 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. The code in this block will re-run once the searchTerm value changes. It happen when the user types the search keywords in the input element. Here, setter setGenre("") will be called only when the searchTerm is not empty. This will reset our select option and All Genre will be the selected option.
useEffect(() => {
if (searchTerm !== "") {
setGenre("");
}
}, [searchTerm]);
Next, update your memoized movies function. Add searchTerm deps and the else block where we define searchFields that becomes filter reference. This function will return allMovies when the searchTerm value is an empty string and shallow copy of allMovies contains movies that pass the implemented test.
const movies = useMemo(() => {
if (genre === "") {
if (searchTerm === "") {
return allMovies;
} else {
// search input filter will return new array filled with movie that pass the test
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()}`;
// implemented test
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 type in the search input. The rendered movies should display the movies that contain the typed keyword.
Finally we add tailwind CSS styling and add image to our card. Here is the complete code.
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
key={index}
className="md:flex border rounded p-4 my-4 lg:my-0 text-ellipsis overflow-hidden"
>
<div className="flex-none md:w-48 flex justify-center items-center">
<img
src={`https://m.media-amazon.com/images${movie.posterUrl}`}
alt=""
/>
</div>
<div className="lg: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>
);
}
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)
}
I have a simple app for displaying data. The state changes from a select. However I want the default select option to be united kingdom. Currently the option defaults to Afghanistan as it's the first in the alphabet.
export default function CountrySelect() {
const [country, setCountry] = useState('GBR');
const countries = useFetch('https://covid19.mathdro.id/api/countries');
if (!countries) return null;
const countryArr = Object.entries(countries.countries).map(([key, value]) => {
return {
name: `${key}`,
code: `${value}`
};
});
return (
<div>
<h2>Showing: {country}</h2>
<select
onChange={(event) => setCountry(event.target.value)}
defaultValue={country}>
{countryArr.map((country) => (
<option value={country.code} key={country.name}>
{country.name}
</option>
))}
</select>
<Info url={`https://covid19.mathdro.id/api/countries/${country}`}></Info>
</div>
);
}
To clarify the country state is 'GBR' and data from 'GBR' or United Kingdom is displayed. It's the tag which I'm having the issue with.
If you can not use defaultValue, just add simple condions
const [countryState, setCountry] = useState('GBR');
{countryArr.map(country => {
if (country.name !== countryState) {
return (
<option
value={country.code}
label={country.name}
key={country.name}
>
{country.name}
</option>
);
} else {
return (
<option
value={country.code}
label={country.name}
key={country.name}
selected="selected"
>
{country.name}
</option>
);
}
})}
From the code, it seems to be HTML select and not react-select. There is no default value attribute to the default select. You can add the selected attribute to the required option here, like:
const [selectedCountry, setCountry] = useState({ code: <Countrycode of UK>});
// ..
// ..
<select
onChange={(event) => setCountry(event.target.value)}
{countryArr.map((country) => (
<option selected={selectedCountry.code === country.code} value={country.code} key={country.name}>
{country.name}
</option>
))}
</select>
Set the object property that you want to be the default option. Since you want the United Kingdom to be selected , set the defaultValue to the code that you get from the API.
Sandbox for reference : https://codesandbox.io/s/react-hooks-t1c2x
export default function CountrySelect() {
const [selectedCountry, setCountry] = useState({
code: "GB"
});
const countries = useFetch("https://covid19.mathdro.id/api/countries", {});
if (!countries) return null;
const countryArr = Object.entries(countries.countries).map(([key, value]) => {
return {
name: `${key}`,
code: `${value}`
};
});
return (
<div>
<select
onChange={event => setCountry(event.target.value)}
defaultValue={selectedCountry.code}
>
{countryArr.map(country => (
<option value={country.code} key={country.name}>
{country.name}
</option>
))}
</select>
</div>
);
}
I am using this.state.something in option tag to make it selected default.
but it is showing me error at this.state.editusertype
<select name="usertype" className="form-control" value={this.state.editusertype}>
<option value="">Select</option>
<option value="Employee" {this.state.editusertype == "Employee" ? "selected" : ""} >Employee</option>
<option value="Manager" {this.state.editusertype == "Manager" ? "selected" : ""}>Manager</option>
</select>
If you want to have a default option, set a default value for your select tag. It should correspond with a matching option. See sandbox: https://codesandbox.io/s/young-pine-zmpvr
class App extends React.Component {
state = {
editusertype: "Manager"
};
handleOnChange = event => {
this.setState({
editusertype: event.target.value
});
};
render() {
return (
<div>
<select
name="usertype"
className="form-control"
onChange={this.handleOnChange}
value={this.state.editusertype}
>
<option value="">Select</option>
<option value="Employee">Employee</option>
<option value="Manager">Manager</option>
</select>
</div>
);
}
}
handleOnChange() is used to change the value upon selection of a different item.
I recommend you use react-select.
const options = [
{ value: '', label: 'Select' },
{ value: 'Employee', label: 'Employee' },
{ value: 'Manager', label: 'Manager' }
];
return (
<Select
name="usertype"
className="form-control"
options={options}
value={this.state.editusertype}
/>
);
I am trying to show the first option different based on an attribute I pass.
In the search page, I want the user to be able to select "Any" option.
In the edit profile page, "Any" option should not be shown because that is only for search.
Edit profile page
<option value="0" disabled selected>
Select Religion
</option>
Search page
<option value="Any" selected>
Any Religion
</option>
Search profile page has this code:
import React, { Component } from 'react'
..
import ReligionInput from '../edit-profile/religion-input'
..
..
<ReligionInput value={religion} change={this.changeReligion} any="Y" />
...
And ReligionInput has a check for any == Y or not but its not working and always has the Any option. What is wrong with the code below ?
import religionOptions from './religion-options'
const InputReligion = ({ value, change, any }) => (
<div className="edit_religion_div">
<Select
placeholder="Select option"
value={value}
valueChange={e => change('religion', e)}
className="edit_religion my2"
>
{any} == 'Y' ?
`<option value="Any" selected>
Any Religion
</option>` :
`<option value="0" disabled selected>
Select Religion
</option>`
{religionOptions.map((e, key) => {
return <option key={key} value={e.value}>{e.name}</option>;
})}
</Select>
</div>
)
InputReligion.propTypes = {
value: PropTypes.string.isRequired,
change: PropTypes.func.isRequired,
}
All other options come from religionOptions.
A dirty way of doing it would be.
<option value={ any=='Y' ? '0' : 'Any' } selected>{ any=='Y' ? 'select' : 'any religion' }</option>
Yeah, not really a fan of those conditions either.
A cleaner and more readable approach would be:
const InputReligion = (props) => {
let defaultOption = null;
if (props.any == 'Y' ) {
defaultOption = <option value='Any' selected>Any religion</option>
} else {
defaultOption = <option value='0' selected disabled>Select Religion</option>
}
return (
<div className="edit_religion_div">
<select placeholder="Select Option"
className="edit_religion my2"
value={ props.value }
valueChange={ e => change('religion', e)}
>
{ defaultOption }
{
religion.map( (e, key) => {
return <option key={ key } value={ e.value }>{ e.name }</option>
})
}
</select>
</div>
)
}