14

I have a search box that I can copy and paste a column from excel. I parse the input and generate an array of the entries.

I am then mapping over the entries and calling a custom hook with each item to fetch some data from my graphql endpoint.

For example:

3 entries are provided from the search box:

["D38999/26", "LJT06RE25-61PA", "D38999/46FJ61AA"]

The fetchData function receives one of these items at a time as the query parameter. Currently, this process would return 3 separate objects as such:

{query: "D38999/26", cables: Array(35)}
{query: "LJT06RE25-61PA", cables: Array(1)}
{query: "D38999/46FJ61AA", cables: Array(1)}

How do I set up a react hook to allow me to append each object into the result State as an array of objects?

My end goal would be an array of objects like this:

[
{query: "D38999/26", cables: Array(35)},
{query: "LJT06RE25-61PA", cables: Array(1)},
{query: "D38999/46FJ61AA", cables: Array(1)}
]

This is my current custom hook to resolve my API endpoint

const useCableMatch = () => {
  const [result, setResult] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);
  const client = useApolloClient();

  const fetchData = async query => {
    setIsError(false);
    setIsLoading(true);
    try {
      const { data } = await client.query({
        query: GET_VALID_CABLE,
        variables: { pn: `%${query}%` },
      });

      const response = { query, ...data };
      console.log('response:', response);

      setResult(response);
    } catch (error) {
      setIsError(true);
    }
    setIsLoading(false);
  };

  return [{ result, isLoading, isError }, fetchData];
};

Currently setResult is set up to only return a single object from response. I would like to return an array with each object generated appended to the existing array of objects.

skyboyer
  • 22,209
  • 7
  • 57
  • 64
Jeremy London
  • 143
  • 1
  • 1
  • 4
  • 2
    Maybe with deconstruction? `setResult([...result, response])` – AronNeewart Sep 02 '19 at 00:16
  • 1
    React functional components must be *pure functions of props*. Just like any state has to be wrapped in a call to `useState`, any side effects *must* be wrapped in a call to `useEffect`. Or just use a class component. – Jared Smith Sep 02 '19 at 00:16

3 Answers3

31

Assuming that response can be added directly to result array, you can:

setResult(result => [...result, response]);

This will append the new response from the previous result state and by using array spread operator.

Joseph D.
  • 11,804
  • 3
  • 34
  • 67
  • 3
    This worked! I'm curious why `setResult(result => [...result, response]);` But this does not.. `setResult([...result, response]);` – Jeremy London Sep 02 '19 at 19:12
  • @JeremyLondon for setting the state is asynchronous. That notation is making sure to use the previous state of `result`. – Joseph D. Sep 03 '19 at 01:37
  • 1
    upvoted. also if you update some value deep in `result` and need a rerender you can just do `setResult( result => [...result] )`. consider it a nudge to cause a render – Fakeer May 28 '20 at 05:27
  • 1
    Thanks for this. I was struggling with why my child component would not update when I was using `.push()` to add to the existing state array and trying to set that in its state. It would work but my child component depending on the array was not immediately re-rendering as expected. (It would on a subsequent render or a force render). – Rob Oct 06 '20 at 15:09
  • 1
    Well hell I learned something new today. Thanks for that. – Jonathan E. Emmett Apr 27 '22 at 18:18
2

You could just pass the entire array into the hook and return the result as an array. You should also use useEffect for async logic. I rewrote your code to process all 3 fields at once:

const useCableMatch = (searchInput) => {
  const [result, setResult] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);
  const client = useApolloClient();

  useEffect((searchInput) => {
     setIsError(false);
     setIsLoading(true);
     let response
     for(let field of searchInput){
        try {
           const { data } = await client.query({
              query: GET_VALID_CABLE,
              variables: { pn: `%${field}%` },
           });

           response = { ...data };
         } catch (error) {
             setIsError(true);
         }
     }
     setResult(current => {query, ...current, ...response);
     setIsLoading(false);
   };
  }, [searchInput])

  return [{ result, isLoading, isError }];
};
Travis James
  • 1,879
  • 9
  • 22
0

From React docs Updating Arrays in State, we can do this

setResult([...result, response])
milkice
  • 455
  • 6
  • 10