I'm a beginner learning React and ran into a problem that I can't figure out. Basically I have an input field that filters out results from a list, and a different list should be rendered depending on the filter that is typed in. The problem is that when the results are filtered and rendered, basically the last letter of the input value is ignored. Example below.
If I type the character "s", nothing happens: Image. What should happen instead is that a paragraph tag should be rendered with the content "Too many matches, specify another filter".
Then, if I type in the letter "w", it renders the paragraph tag that should've been rendered before: Image. What should've happened instead is that a list of countries with names containing the substring "sw" should've been rendered.
Then, if I type in the letter "e", it renders the list that should've been rendered when I typed in the previous character: Image. As you can see, the names of the countries in the list contain the substring "sw", basically ignoring the last character that was inputted.
My code:
main App component:
import axios from "axios";
import Filter from "./Filter";
import "./styles.css";
import Content from "./Content";
const App = () => {
const [countries, setCountries] = useState([])
const [allCountries, setAllCountries] = useState([])
const [newFilter, setNewFilter] = useState("")
useEffect(() => {
axios
.get('https://restcountries.com/v3.1/all')
.then(response => {
setAllCountries(response.data)
})
}, [])
const handleFilterChange = (event) => {
setNewFilter(event.target.value);
if (newFilter) {
const regex = new RegExp(newFilter, "i");
const filteredCountries = () => allCountries.filter(country => country.name.common.match(regex));
setCountries(filteredCountries)
}
}
return (
<div>
<Filter value={newFilter} onChange={handleFilterChange} />
<Content countries={countries} setCountries={setCountries} />
</div>
)
}
export default App
Filter (input field) component:
import React from "react";
const Filter = (props) => {
return (
<input value={props.value} onChange={props.onChange} />
);
}
export default Filter;
Content component:
import React from "react";
const Content = ({countries, setCountries}) =>{
if (countries.length > 10){
return <p>Too many matches, specify another filter</p>;
} else if ((countries.length > 2 && countries.length < 10) || countries.length === 0){
return <ul>{countries.map((country, i) => <li key={i}>{country.name.common}</li>)}</ul>;
} else {
return <p>{countries[0].name.common}</p>;
}
}
export default Content;
Help would be appreciated!
CodePudding user response:
Your problem is here:
const handleFilterChange = (event) => {
setNewFilter(event.target.value);
if (newFilter) {
const regex = new RegExp(newFilter, "i");
const filteredCountries = () => allCountries.filter(country => country.name.common.match(regex));
setCountries(filteredCountries)
}
}
Specifically:
if (newFilter)
You need to change it to:
if( event.target.value) ...
CodePudding user response:
setNewFilter
does not update your local state variable, since in React, state is immutable.
So your if statement here:
const handleFilterChange = (event) => {
setNewFilter(event.target.value);
if (newFilter) {
...
}
}
Will evaluate to false the first time around.
Just change to
const handleFilterChange = (event) => {
setNewFilter(event.target.value);
if (event.target.value) {
...
}
}
Referencing a state variable only gives you the value from the last time useState
was called, ie. at this line:
const [newFilter, setNewFilter] = useState("")
The local variable newFilter
will not be updated until the next time the component renders, since useState
will be called again.
CodePudding user response:
import "./styles.css";
import Content from "./Content";
const App = () => {
const [countries, setCountries] = useState([])
const [allCountries, setAllCountries] = useState([])
const [newFilter, setNewFilter] = useState("")
useEffect(() => {
axios
.get('https://restcountries.com/v3.1/all')
.then(response => {
setAllCountries(response.data)
})
}, [])
const handleFilterChange = (event) => {
setNewFilter(event.target.value);
if (newFilter) { // at this point, the newFilter is not set as a new value yet. So you'd better to use event.target.value instead of newFilter.
const regex = new RegExp(newFilter, "i");
const filteredCountries = () => allCountries.filter(country => country.name.common.match(regex));
setCountries(filteredCountries)
}
}
return (
<div>
<Filter value={newFilter} onChange={handleFilterChange} />
<Content countries={countries} setCountries={setCountries} />
</div>
)
}
export default App
Or you can use useEffect
hook.
import "./styles.css";
import Content from "./Content";
const App = () => {
const [countries, setCountries] = useState([])
const [allCountries, setAllCountries] = useState([])
const [newFilter, setNewFilter] = useState("")
useEffect(() => {
axios
.get('https://restcountries.com/v3.1/all')
.then(response => {
setAllCountries(response.data)
})
}, [])
useEffect(() => {
if (newFilter) {
const regex = new RegExp(newFilter, "i");
const filteredCountries = () => allCountries.filter(country => country.name.common.match(regex));
setCountries(filteredCountries)
}
}, [newFilter])
const handleFilterChange = (event) => {
setNewFilter(event.target.value);
}
return (
<div>
<Filter value={newFilter} onChange={handleFilterChange} />
<Content countries={countries} setCountries={setCountries} />
</div>
)
}
export default App
Hope this would be helpful for you.