Home > Software design >  React useState hook not updating correctly
React useState hook not updating correctly

Time:08-22

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.

  • Related