Search code examples
reactjssetstateuse-state

Setting state of a 'Loading' boolean in react doesn't update


I have a page which allows a user to submit a url from which data is scraped. The user is subsequently presented with the filtered data.

Because the scraping takes some time I would like to implement a loader. While the loader class will (hopefully) be relatively straight forward, it's the state for loading which I'm having issues with. The state itself is never updated. Although other state values are such as setFilters.

Body.js

const [searchState, setSearchState] = useState({
  searchCriteria: "https://en.wikipedia.org/wiki/2020_Central_Vietnam_floods",
  headers:[],
  references: []
});

const [filterState, setFilters] = useState({
  languageFilter: ""
});

const [loadingState, setLoadingState] = useState({
  loading: false
});

The above are all passed into Search with a context

<>
  <SearchContext.Provider value={{searchState, setSearchState,filterState, setFilters, loadingState, setLoadingState}} >
      <Search />
      <DonateButton />
      <WikiHeaderGroup />
  </SearchContext.Provider>
</>

And then I have a handleSubmit inside the Search component.

Search.js

import React, {useContext} from "react";
import {SearchContext} from "../../contexts/SearchContext"
import "../../App.css"

export function Search (){

  const {searchState, setSearchState, filterState, setFilters, loadingState, setLoadingState} = useContext(SearchContext);

  const handleSubmit = (event) => {
      setFilters({languageFilter:""})
      setLoadingState({loading:true})
      console.log("Loading State : " + loadingState.loading)

      event.preventDefault();
      event.persist();            //persists the event object into the function

      const fetchReferences = async () => {

        fetch('http://127.0.0.1:8080/search/', {
          method: 'POST',
          body: JSON.stringify({
            url: searchState.searchCriteria
            }),
          headers: {"Content-type": "application/json; charset=UTF-8"}

        }).then(response => {
                console.log(response)
                return response.json()

        }).then(json => {
          console.log(json)
          setSearchState({
            headers:json.headers,
            references:json.references
          })
          setLoadingState({loading:false})
          console.log("Loading State : " + loadingState.loading)
    });}

      fetchReferences();
  }

    return (
      <div className="search container">
        <div className="input-group input-group-sm mb-3 center">
          <div className="input-group-prepend">
            <span className="input-group-text" id="inputGroup-sizing-sm">Wikipedia URL:</span>
          </div>

          <form onSubmit={(event) => handleSubmit(event)}>
          <input
            type="text"
            id="searchBox"
            className="form-control center"
            aria-label="Sizing example input"
            aria-describedby="inputGroup-sizing-sm"
            value={searchState.searchCriteria}
            onChange={(event) => setSearchState({searchCriteria:event.target.value, resultId:0})}
            placeholder="Add a url" />
          </form>
        </div>
      </div>
    );

}
export default Search;

Solution

  • don't use object for booleans, just

    const [loadingState, setLoadingState] = useState(false);
    ....
    setLoadingState(true)
    

    btw looks like a closure problem. you see loadingState always false cause the closure. take a look at this Be Aware of Stale Closures when Using React Hooks

    A way to solve it is using refs

    const loadingStateRef = useRef(loadingState);
    //then inside the function u can access 
    latestValue.current