skip to Main Content

I’m trying to make an input filtered based list that only shows results when there are 10 or less objects, but the array.length is always lagging one step behind the input.

    const [countriesToShow, setCountriesToShow] = useState([])
    const [newSearch, setSearch] = useState('')
    const [showSearched, setShowShearched] = useState(true)
    const [notificationMessage, setNotificationMessage] = useState(null)

    useEffect(() => {
      console.log(countriesToShow.length)
    },[countriesToShow])
  
    const handleSearchChange = (event) => {
      setCountriesToShow(countries.filter(country =>
        country.name.common.toLowerCase().includes(event.target.value.toLowerCase())))
      setSearch(event.target.value)
      if (event.target.value.trim().length === 0) {
        setNotificationMessage(null)
        setShowShearched(false)
      }
      else if (countriesToShow.length <= 10) {
        setNotificationMessage(null)
        setShowShearched(true)
        console.log(countriesToShow.length)
      }
      else {
        setNotificationMessage('list too long')
        setShowShearched(false)
        console.log(countriesToShow.length)
      }
    }

I managed to get the console print the right length with the help of Effect Hook but I’m stumped about how to implement the same to the ‘else if (countriesToShow.length <= 10)’ as it still lags behind the input.

2

Answers


  1. When you call handleSearchChange and then call setCountriesToShow to udpate that state:

    setCountriesToShow(
      countries.filter(country =>
        country
          .name
          .common
          .toLowerCase()
          .includes(event.target.value.toLowerCase())
      )
    )
    

    You are triggering a re-render. The newly updated state value will only become available then, in the upcoming re-render, that’s why it’s lagging behind.

    If you want to use that value below, you need to store it in a variable first:

    const handleSearchChange = (event) => {
      const newCountriesToShow = countries.filter(country =>
        country
          .name
          .common
          .toLowerCase()
          .includes(event.target.value.toLowerCase())
      )
    
      setCountriesToShow(newCountriesToShow)
    
      setSearch(event.target.value)
    
      if (event.target.value.trim().length === 0) {
        setNotificationMessage(null)
        setShowShearched(false)
      } else if (newCountriesToShow.length <= 10) {
        setNotificationMessage(null)
        setShowShearched(true)
      } else {
        setNotificationMessage('list too long')
        setShowShearched(false)
      }
    }
    
    Login or Signup to reply.
  2. The answer to your question

    Why is array.length method one step behind the input in handleChange event handler

    This is because whenever you call setCountriesToShow you aren’t updating the countriesToShow variable. This variable gets reinitialized on the next tick because the assignment to the result of the useState function is executed again when your component is run.

    As an analogy you could consider this code:

    const _state = {
        value: 1
    }
    
    const useState = () => [_state.value, (newValue) => {_state.value = newValue}];
    
    const myFunction = () => {
        const [state, setState] = useState();
    
        setState(2);
        console.log(state); // Logs 1
    }
    
    

    this is basically how react works. So the local state variable never gets changed. When myFunction is rerun however the local state value will be reinitialized to the newest value

    How to fix it

    You could of course make a temporary variable like newCountriesToShow and assign that to the new value and then call setCountriesToShow(newCountriesToShow) then inside of your function use newCountriesToShow this could work but is an anti pattern. You shouldn’t do this unless you absolutely have to. See Danziger’s Answer for more details on this approach.

    What React wants you to do here is to use derived values (useMemo or top level code). In general try to use as few useStates as possible (I’m not suggesting simply combining fields into an object when there is no other benefit but that is the exception)

    const [newSearch, setSearch] = useState("");
    
    useEffect(() => {
      console.log(countriesToShow.length);
    }, [countriesToShow]);
    
    const countriesToShow = countries.filter((country) =>
      country.name.common
        .toLowerCase()
        .includes(newSearch.toLowerCase())
    );
    
    const notificationMessage = countriesToShow.length > 10 ? "list too long" : null;
    const showSearched = notificationMessage === null && newSearch.length !== 0;
    
    const handleSearchChange = (event) => {
      setSearch(event.target.value);
    };
    

    Any code that you want to run when notificationMessage or showSearched changes should be in a useEffect

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search