remove option from second select if already selected in reactjs - reactjs

I have two select input to select years (years,years_1) , when i change years to 2020 i want to remove 2020 from years_1 but when i change years to 2021 then in years_1 2020 must add back and 2021 will remove (or disable also work).
useEffect(() => {
const currentYear = new Date().getFullYear();
const yearsArray = [];
for (let i = currentYear; i > currentYear - 3; i--) {
yearsArray.push(i.toString());
yearsArray.sort();
}
setYears(yearsArray);
setYears_1(yearsArray);
setCommonYear(yearsArray);
}, []);
there setYears,setYears_1,setCommonYear are useStates to store year array.
if (years !== null && years_1 !== null) {
if (years.includes(value)) {
setYears_1((prevState) => prevState.filter((item) => item !== value));
} else {
setYears_1(commonYear);
}
}
this is what i have tried
<select
name="year"
value={turnoverModels?.year}
onChange={handleChangeTurnoverYear}
>
<option value="NA" selected>
NA
</option>
{years.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
<select
name="year"
value={turnoverModels?.year}
onChange={handleChangeTurnoverYear}
>
<option value="NA" selected>
NA
</option>
{years_1.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>

Added state to store the values of both the select dropdowns. Then we can use the useEffect() hook to find when the value of first select changes and update the years_1 array.
Demo : Codesandbox
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [years, setYears] = useState([]);
const [years_1, setYears_1] = useState([]);
const [select1, setSelect1] = useState(null);
const [select2, setSelect2] = useState(null);
useEffect(() => {
const yearsArray = getYears();
setYears(yearsArray);
setYears_1(yearsArray);
}, []);
// When the first year changes, update the years_1 array
useEffect(() => {
if (!select1) return;
const filtered = getYears().filter((y) => y !== select1);
setYears_1(filtered);
}, [select1, years_1]);
const getYears = () => {
const currentYear = new Date().getFullYear();
const yearsArray = [];
for (let i = currentYear; i > currentYear - 3; i--) {
yearsArray.push(i.toString());
yearsArray.sort();
}
return yearsArray;
};
return (
<div className="App">
<select
name="year"
value={select1}
onChange={(e) => setSelect1(e.target.value)}
>
<option value="NA" selected>
NA
</option>
{years.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
<select
name="year"
value={select2}
onChange={(e) => setSelect2(e.target.value)}
>
<option value="NA" selected>
NA
</option>
{years_1.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
</div>
);
}

setYears_1 again when select in years array:
Example: https://codesandbox.io/s/winter-sky-hbm8ec?file=/src/App.js
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [years, setYears] = useState([]);
const [years_1, setYears_1] = useState([]);
const [select1, setSelect1] = useState(null);
const [select2, setSelect2] = useState(null);
useEffect(() => {
const yearsArray = getYears();
setYears(yearsArray);
setYears_1(yearsArray);
}, []);
useEffect(() => {
if (!select1) return;
const filtered = getYears().filter((y) => y !== select1);
setYears_1(filtered);
}, [select1, years_1]);
const getYears = () => {
const currentYear = new Date().getFullYear();
const yearsArray = [];
for (let i = currentYear; i > currentYear - 3; i--) {
yearsArray.push(i.toString());
yearsArray.sort();
}
return yearsArray;
};
return (
<div className="App">
<select
name="year"
value={select1}
onChange={(e) => setSelect1(e.target.value)}
>
<option value="NA" selected>
NA
</option>
{years.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
<select
name="year"
value={select2}
onChange={(e) => setSelect2(e.target.value)}
>
<option value="NA" selected>
NA
</option>
{years_1.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
</div>
);
}

Related

How to have second dropdown menu values be dependent on value of first dropdown menu in React render function

In the render function of a React component, I have the following code
<select name="env" id="env">
<option value="Dev">Dev</option>
<option value="Staging">Staging</option>
<option value="Prod">Prod</option>
</select>
<select name="region" id="region">
<option value="option1">op1</option>
<option value="option2">op2</option>
<option value="option3">op3</option>
<option value="option4">op4</option>
</select>
If Dev option is selected in first dropdown, there should only be op1 choice available in the 2nd dropdown. Similarly if Staging is selected, then op1, op2 and op3 are the available choices. If Prod is selected, then all 4 choices are available. How can I achieve this in React?
You should render different region options by env.
const regionsByEnv = {
Dev: ["option1"],
Staging: ["option1", "option2", "option3"],
Prod: ["option1, option2, option3, option4"],
};
const YourComponent = () => {
const [env, setEnv] = useState("");
const regionOptions = useMemo(() => {
if (!env) {
return regionsByEnv.Prod.map((option) => (
<option value={option} key={option}>
{option}
</option>
));
}
return regionsByEnv[env].map((option) => (
<option value={option} key={option}>
{option}
</option>
));
}, [env]);
return (
<>
<select
value={env}
onChange={(e) => setEnv(e.target.value)}
name="env"
id="env"
>
<option value="Dev">Dev</option>
<option value="Staging">Staging</option>
<option value="Prod">Prod</option>
</select>
<select name="region" id="region">
{regionOptions}
</select>
</>
);
};
You should filter option for select region base on selected option of select env. So you can create a state for handling value of select env
export default function App() {
const [select1, setSelect1] = useState("Dev");
const filterOption2 = allOption2.filter((i) => {
return (
(select1 === "Dev" && i.value === "option1") ||
(select1 === "Staging" && i.value !== "option4") ||
select1 === "Prod"
);
});
return (
<div className="App">
<select name="env" id="env" onChange={(e) => setSelect1(e.target.value)}>
<option value="Dev">Dev</option>
<option value="Staging">Staging</option>
<option value="Prod">Prod</option>
</select>
<select name="region" id="region">
{filterOption2.map((op) => (
<option key={select1 + op.value} value={op.value}>
{op.name}
</option>
))}
</select>
</div>
);
}
const allOption2 = [
{
name: "op1",
value: "option1"
},
{
name: "op2",
value: "option2"
},
{
name: "op3",
value: "option3"
},
{
name: "op4",
value: "option4"
}
];
You can check in my codesandbox

Pagination issue in React.js

I've created Pagination component but have got issues while implementing it into my Characters component.
I'm getting prev and next buttons, but instead of page numbers I'm gettin NaN.
Please advise where is my mistake?
Is there an issue with Pagination props?
Pagination.js
import React, { useState } from "react";
export const Pagination = ({
data,
RenderComponent,
title,
pageLimit,
dataLimit,
}) => {
const [pages] = useState(Math.round(data.length / dataLimit));
const [currentPage, setCurrentPage] = useState(1);
function goToNextPage() {
setCurrentPage((page) => page + 1);
}
function goToPreviousPage() {
setCurrentPage((page) => page - 1);
}
const changePage = (event) => {
console.log(event.target);
const pageNumber = Number(event.target.textContent);
setCurrentPage(pageNumber);
};
const getPaginatedData = () => {
const startIndex = currentPage * dataLimit - dataLimit;
const endIndex = startIndex + dataLimit;
return data.slice(startIndex, endIndex);
};
const getPaginationGroup = () => {
let start = Math.floor((currentPage - 1) / pageLimit) * pageLimit;
return new Array(pageLimit).fill().map((_, idx) => start + idx + 1);
};
return (
<div>
<h1>{title}</h1>
<div>
{getPaginatedData().map((d, idx) => (
<RenderComponent key={idx} data={d} />
))}
</div>
<div className="pagination">
<button
onClick={goToPreviousPage}
className={`prev ${currentPage === 1 ? "disabled" : ""}`}
>
prev
</button>
{/* show page numbers */}
{getPaginationGroup().map((item, index) => (
<button
key={index}
onClick={changePage}
className={`paginationItem ${
currentPage === item ? "active" : null
}`}
>
<span>{item}</span>
</button>
))}
{/* next button */}
<button
onClick={goToNextPage}
className={`next ${currentPage === pages ? "disabled" : ""}`}
>
next
</button>
</div>
</div>
);
};
The file where I want to implement the Pagination component.
Characters.js
import { useState, useEffect } from "react";
import { CHARACTERS_PAGE_URL } from "../../api/rickNMortyApi";
import { Loading } from "../../components/Loading/Loading";
import { Character } from "./Character";
import { Pagination } from "../Pagination/Pagination";
export const Characters = () => {
const [characters, setCharacters] = useState();
useEffect(() => {
try {
fetch(CHARACTERS_PAGE_URL)
.then((res) => res.json())
.then(({ results }) => {
if (results && Array.isArray(results)) {
setCharacters(results);
}
})
.catch((err) => console.log(err));
} catch (e) {
console.log(e);
}
}, []);
if (!characters) {
return <Loading />;
}
return (
<div className="p-4 font-mono text-green-500 ">
<div className="flex flex-row">
<div className="m-4 ">
<label>Species</label>
<select name="species" id="species">
<option value="all">all</option>
<option value="human">human</option>
<option value="alien">alien</option>
</select>
</div>
<div className="m-4">
<label>Status</label>
<select name="status" id="status">
<option value="all">all</option>
<option value="alive">alive</option>
<option value="dead">dead</option>
<option value="unknown">unknown</option>
</select>
</div>
<div className="m-4">
<label>Gender</label>
<select name="gender" id="gender">
<option value="all">all</option>
<option value="female">female</option>
<option value="male">male</option>
<option value="genderless">genderless</option>
<option value="unknown">unknown</option>
</select>
</div>
</div>
<h1 className="text-4xl">Characters</h1>
<Pagination data={characters} />
<div className="grid grid-flow-col grid-cols-5 grid-rows-4 gap-4">
{characters.map((character) => (
<div key={character.id}>
<Character character={character} />
</div>
))}
</div>
</div>
);
};
I've added
and now I can see prev, 1, 2, 3, 4,5, next
but charcters do not change if I click on the page number
When I try to add dataLimit={20} as a props for Pagination I'm getting an arrow below:
Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Characters filter by property value in React.js

I'm a trying to build an app based on Rick and Morty api.
I want to filter characters by species.
I've created separate components for each species value
For example:
Human.js
export const Human = ({ match }) => {
const { species } = match.params;
const [characterFilters, setCharacterFilters] = useState([]);
useEffect(() => {
try {
fetch('https://rickandmortyapi.com/api/character?species=human')
.then((res) => res.json())
.then((res) => setCharacterFilters(res))
.catch((err) => console.log(err));
} catch (e) {
console.log(e);
}
}, []);
const { name, image } = characterFilters;
return (
<div>
<h2>Name: {name}</h2>
<div>
<img alt={name} src={image}></img>
</div>
</div>
);
};
In the characters file I've set the conditions based on select propery chosen
Characters.js
export const Characters = () => {
const [characters, setCharacters] = useState();
useEffect(() => {
try {
fetch(CHARACTERS_PAGE_URL)
.then((res) => res.json())
.then(({ results }) => {
if (results && Array.isArray(results)) {
setCharacters(results);
}
})
.catch((err) => console.log(err));
} catch (e) {
console.log(e);
}
}, []);
const handleSpeciesChange = (e) => {
console.log("[e.target.value]", e.target.value);
switch (e.target.value) {
case "human":
return <Human />;
break;
case "alien":
return <Alien />;
break;
case "unknown":
return <Unknown />;
break;
default:
return;
}
};
const handleStatusChange = (e) => {
switch (e.target.value) {
case "alive":
return <Human />;
break;
case "dead":
return <Alien />;
break;
case "unknown":
return <Unknown />;
break;
default:
return;
}
};
if (!characters) {
return <Loading />;
}
return (
<div className="p-4 font-mono text-green-500 ">
<div className="flex flex-row">
<div className="m-4 ">
<label>Species</label>
<select name="species" id="species" onChange={handleSpeciesChange}>
<option value="all">all</option>
<option value="human">human</option>
<option value="alien">alien</option>
<option value="unknown">unknown</option>
</select>
</div>
<div className="m-4">
<label>Status</label>
<select name="status" id="status" onChange={handleStatusChange}>
<option value="all">all</option>
<option value="alive">alive</option>
<option value="dead">dead</option>
<option value="unknown">unknown</option>
</select>
</div>
<div className="m-4">
<label>Gender</label>
<select name="gender" id="gender">
<option value="all">all</option>
<option value="female">female</option>
<option value="male">male</option>
<option value="genderless">genderless</option>
<option value="unknown">unknown</option>
</select>
</div>
</div>
<h1 className="text-4xl">Characters</h1>
<Pagination data={characters} pageLimit={5} />
<div className="grid grid-flow-col grid-cols-5 grid-rows-4 gap-4">
{characters.map((character) => (
<div key={character.id}>
<Character character={character} />
</div>
))}
</div>
</div>
);
};
But I'm not getting filtered characters.
I'm new to React.
Please help me to understand, what am I doing wrong here?
Is there a possibility to created a common filter component that I would be able to apply for different properties filters?
You cannot return jsx from a callback
Make filters get stored and only render these values from the array:
export const Characters = () => {
const [characters, setCharacters] = useState();
const [ speciesFilter, setSpeciesFilter ] = useState('');
const [ status, setStatus] = useState('');
useEffect(() => {
try {
fetch(CHARACTERS_PAGE_URL)
.then((res) => res.json())
.then(({ results }) => {
if (results && Array.isArray(results)) {
setCharacters(results);
}
})
.catch((err) => console.log(err));
} catch (e) {
console.log(e);
}
}, []);
const handleSpeciesChange = (e) => {
console.log("[e.target.value]", e.target.value);
setSpeciesFilter(e.target.value)
};
const handleStatusChange = (e) => {
setStatus(e.target.value);
};
if (!characters) {
return <Loading />;
}
let filteredCharacters = characters;
if (speciesFilter) {
filteredCharacters = filteredCharacters.filter(c => c.species === speciesFilter);
}
if (status ) {
filteredCharacters = filteredCharacters.filter(c => c.status === status);
}
return (
<div className="p-4 font-mono text-green-500 ">
<div className="flex flex-row">
<div className="m-4 ">
<label>Species</label>
<select name="species" id="species" onChange={handleSpeciesChange}>
<option value="all">all</option>
<option value="human">human</option>
<option value="alien">alien</option>
<option value="unknown">unknown</option>
</select>
</div>
<div className="m-4">
<label>Status</label>
<select name="status" id="status" onChange={handleStatusChange}>
<option value="all">all</option>
<option value="alive">alive</option>
<option value="dead">dead</option>
<option value="unknown">unknown</option>
</select>
</div>
<div className="m-4">
<label>Gender</label>
<select name="gender" id="gender">
<option value="all">all</option>
<option value="female">female</option>
<option value="male">male</option>
<option value="genderless">genderless</option>
<option value="unknown">unknown</option>
</select>
</div>
</div>
<h1 className="text-4xl">Characters</h1>
<Pagination data={characters} pageLimit={5} />
<div className="grid grid-flow-col grid-cols-5 grid-rows-4 gap-4">
{filteredCharacters.map((character) => (
<div key={character.id}>
<Character character={character} />
</div>
))}
</div>
</div>
);
};
The above example uses a filter which can be improved
what am I doing wrong here?
You are returning JSX from the callback, for example,
onChange={handleStatusChange}
Here you pass the function which will be passed the event for you to handle, the problem is, returning a component from this function doesn't do anything, it doesn't tell React to render the component you returned.
Instead, you can store the value from there in state, and do something accordingly, for example, filter the array using that value
Is there a possibility to created a common filter component that I
would be able to apply for different properties filters?
That can be done, by passing the characters to that component and the filter values
export const FilterCharacters = ({ characters, status, specie }) => {
let filteredCharacters = characters;
if (specie ) {
filteredCharacters = filteredCharacters.filter(c => c.species === specie);
}
if (status ) {
filteredCharacters = filteredCharacters.filter(c => c.status === status);
}
return filteredCharacters.map((character) => (
<div key={character.id}>
<Character character={character} />
</div>
))
}
As for your Human and specific species components, it calls the API for filtered values, but I assume you already got all values initially, so you could only filter from there, if that's not the case, you could make Human more generic by passing the species and status, and querying the API with that values

How to set the initial value of a select in React?

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>
);
}

handling select options in React Hooks

I am trying to get the text value from a dropdown select using {useState} in React Hooks. I just get the value (number) rather than the text.
I've copied the bits of code below which control the select dropdown. What am I missing here?
const [addrtype, setAddrType] = useState('Home')
function handleAddrTypeChange(e) {
setAddrType(e.target.value);
console.log(addrtype)
}
<select
defaultValue={addrtype}
onChange={handleAddrTypeChange}
className="browser-default custom-select">
<option selected value="1">Home</option>
<option value="2">Marketing</option>
<option value="3">Work</option>
<option value="3">Head Office</option>
</select>
import React, { useState, Component } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import './style.css';
const App = () => {
const [addrtype, setAddrtype] = useState(["Work", "Home", "school"])
const Add = addrtype.map(Add => Add
)
const handleAddrTypeChange = (e) => console.log((addrtype[e.target.value]))
return (
< select
onChange={e => handleAddrTypeChange(e)}
className="browser-default custom-select" >
{
Add.map((address, key) => <option value={key}>{address}</option>)
}
</select >)
}
render(<App />, document.getElementById('root'));
Working example
https://stackblitz.com/edit/react-select-hook
If you want text then access text instead of value. event.target.text.
Check the reference here. http://output.jsbin.com/vumune/4/
Just change the option value
<option selected value="Home">Home</option>
<option value="Marketing">Marketing</option>
<option value="Work">Work</option>
<option value="Head Office">Head Office</option>
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [addrtype, setAddrtype] = useState(["Normal", "Admin"])
const Add = addrtype.map(Add => Add)
const handleAddrTypeChange = (e) => {
console.clear();
console.log((addrtype[e.target.value]));
setRole(addrtype[e.target.value])
}
const [role, setRole] = useState('Normal')
const handleSubmit = (event) => {
event.preventDefault();
console.log
(`
Name: ${name}
Email: ${email}
Role: ${role}
`);
};
const UserForm = (
<form onSubmit={handleSubmit}>
<label htmlFor="name">Name</label>
<input
value={name}
placeholder="Name"
required
onChange={(event) => setName(event.target.value)}
></input>
<label htmlFor="email">Email</label>
<input
value={email}
placeholder="Email"
required
onChange={(event) => setEmail(event.target.value)}
></input>
<label for="role">Choose a Role:</label>
< select
onChange={e => handleAddrTypeChange(e)}
className="browser-default custom-select" >
{
Add.map((address, key) => <option key={key} value={key}>{address}
</option>)
}
</select >
<div class="wrapper">
<button type="submit" className="button">Create User</button>
</div>
</form >

Resources