0

Working on a project of mine and ran into an issue. So I am adding "sorting" to my products. So for instance, you can sort "hoodies" based on color, brand, size etc. Fortunately, the API I am using accepts these values, such as: brand: <brand> or base_colour: <color>.

So far, I have managed to get the key & value to the API, but it is acting a bit strange.

Whenever I sort the products, it doesn't instantly apply. So for instance, if I want to sort the "brand" to "bike", nothing happens. But if I then try to sort the "color" to "black". Then the brand changes, but not the color. So it is "delayed" by one.

Through my debugging of this issue, I am 80% sure my useEffect is the thief here.

Here is a picture that might help:

enter image description here

As you can see, the key and value of attribute_1046 gets sent to my API fetch js file, but it doesn't get added to the parameters. But whenever I change the brand (Meaning, I have 2 sortings). Then the attribute_1046 gets added to the parameters.

Here is my API Fetch code:

  // Receiving the Key and Value when a user interacts with the "sorting" dropdowns.
  let facetValue = props.facetValue;
  let facetKey = props.facetKey;

  // Setting initial parameters
  const [params, setParams] = useState({
    store: "US",
    offset: props.offset,
    categoryId: props.categoryId,
    limit: props.limit,
    country: "US",
    sort: "freshness",
    currency: "USD",
    sizeSchema: "US",
    lang: "en-US",
  });

  useEffect(() => {
    // if FacetKey is not undefined.
    if (facetKey) {
      console.log(`${facetKey}: ${facetValue}`);
      setParams({
        [facetKey]: facetValue,
        ...params,
      });
    }
    console.log(params);

    const options = {
      method: "GET",
      url: "https://asos2.p.rapidapi.com/products/v2/list",
      params: params,
      headers: {
        "x-rapidapi-key": "",
        "x-rapidapi-host": "",
      },
    };
    axios
      .request(options)
      .then(function (response) {
        setProducts(response.data.products);
        props.items(response.data.itemCount);
        props.facets(response.data.facets);
      })
      .catch(function (error) {
        console.error(error);
      });
    // Is it my dependencies that are incorrect?
  }, [props.limit, facetKey]);

Not sure if this file is needed as well, but here the user sort the products:

  const [facets, setFacets] = useState();
  const [facetValue, setFacetValue] = useState();
  const [facetKey, setFacetKey] = useState();

        <BottomBar>
          <div className={classes.filter_grid_container}>
            {facets != null
              ? facets.map((filter) => (
                  <div className={classes.item}>
                    {filter.name != "Price Range" ? (
                      <Dropdown
                        name={filter.name}
                        values={filter.facetValues}
                        facetKey={filter.id}
                        facetValue={(facetValue) => setFacetValue(facetValue)}
                        facetItemKey={(facetKey) => setFacetKey(facetKey)}
                      />
                    ) : (
                      <DropdownPrice />
                    )}
                  </div>
                ))
              : null}
          </div>
        </BottomBar>
      </StyledApp>
      <div className={classes.grid_container}>
        <FetchAPI
          limit={limit}
          offset={offset}
          items={(itemCount) => setItemCount(itemCount)}
          categoryId={params.id}
          facets={(facets) => setFacets(facets)}
          facetValue={facetValue}
          facetKey={facetKey}
        />
      </div>
simon sjöö
  • 365
  • 2
  • 3
  • 17
  • setState is asynchronous, and a state value is constant throughout a single render, so logging right after calling setState will always show the state value in the *current* render, the updated value won't be available until the next render cycle. see: [React setState not updating state](https://stackoverflow.com/questions/41446560/react-setstate-not-updating-state) – pilchard May 21 '21 at 11:50

2 Answers2

2

Actually issue is function returned by useState is asynchronous and you are updating params in useEffect and immedially access it to pass in request of axios api.

Issue:-

    if (facetKey) {
      console.log(`${facetKey}: ${facetValue}`);
      setParams({
        [facetKey]: facetValue,  // updating params here
        ...params,
      });
    }
    console.log(params);

    const options = {
      method: "GET",
      url: "https://asos2.p.rapidapi.com/products/v2/list",
      params: params,  // using it here so this will always return previous value
      headers: {
        "x-rapidapi-key": "",
        "x-rapidapi-host": "",
      },
    };

Solution:-

    let requestParam = {};
    if (facetKey) {
      requestParam = {[facetKey]: facetValue, ...params}   // create const with expected value
      setParams({...requestParam});
    }

    const options = {
      method: "GET",
      url: "https://asos2.p.rapidapi.com/products/v2/list",
      params: requestParam,   // pass new const here in options
      headers: {
        "x-rapidapi-key": "",
        "x-rapidapi-host": "",
      },
    };
Priyank Kachhela
  • 2,517
  • 8
  • 16
  • Ah okay, I see. Gave this a try but getting that `requestParams` is not defined. Did you not get that error or? Posted on pastebin: `7nPbf5hW` – simon sjöö May 21 '21 at 12:27
  • My bad, I had declated `requestParam` in if block and was trying to access it outside if block. I have update my answer and by default I have declared `requestParam` with empty object if you are required to pass default `params` value in option then you can declare it like `let requestParam = {...params}`. – Priyank Kachhela May 21 '21 at 12:34
0

The problem is occurring because inside your useEffect, whenever you call setParams, it doesn't occur in synchronous mode. setParams is async in nature, so in case you want to call an api on change on params, you can do following.

Also, as a suggestion, if you could check the way setParams is being called, this way it would be less error prone as it will not batch updates. It is generally better to use the functional convention of setState whenever your next state depends on your previous state.


// Receiving the Key and Value when a user interacts with the "sorting" dropdowns.
const facetValue = props.facetValue;
const facetKey = props.facetKey;

// Setting initial parameters
const [params, setParams] = useState({
  store: "US",
  offset: props.offset,
  categoryId: props.categoryId,
  limit: props.limit,
  country: "US",
  sort: "freshness",
  currency: "USD",
  sizeSchema: "US",
  lang: "en-US",
});

useEffect(() => {
  // if FacetKey is not undefined.
  if (facetKey) {
    console.log(`${facetKey}: ${facetValue}`);
    setParams((currParams) => { ...currParams, [facetKey]: facetValue });
  }

  // Is it my dependencies that are incorrect?
}, [props.limit, facetKey]);



useEffect(() => {
  // you can move your api call to a function itself which takes params
  const options = {
    method: "GET",
    url: "https://asos2.p.rapidapi.com/products/v2/list",
    params: params,
    headers: {
      "x-rapidapi-key": "",
      "x-rapidapi-host": "",
    },
  };
  axios
    .request(options)
    .then(function (response) {
      setProducts(response.data.products);
      // assuming here that props.items is more like props.setItems as you are setting it's value
      props.items(response.data.itemCount);
      // assuming here that props.facets is more like props.setFacets as you are setting it's value
      props.facets(response.data.facets);
    })
    .catch(function (error) {
      console.error(error);
    });
}, [params]);
jaybhatt
  • 550
  • 2
  • 7