skip to Main Content

So there may be some obvious answer I’m unaware of, I’m not overly experienced with GraphQL so apologies in advance if it’s a stupid question.

Basically, I had a component, within which I grabbed some data from GraphQL, then passed that data to a function that filters the array, then passed the filtered results onto a child component, approximately like this

ChildComponent.tsx

const { loading, error, data } = useQuery(SOME_RANDOM_QUERY, {variables: { id: Id, number: Value1 }})

const filteredItems: Item[] = data && getFilteredItems(data.items.nodes)
.
.
.
<SomeComponent itemsArray={filteredItems} />

And this worked. However, then it turned out I need the GraphQL data in another, sibling component, so I have to lift up this query. And I did:

ParentComponent.tsx

const queryResults = useQuery(SOME_RANDOM_QUERY, {variables: { id: Id, number: Value1 }})
.
.
.
<ChildComponent itemsList={queryResults?.data?.items?.nodes} />

ChildComponent.tsx

const ChildComponent: React.FC<{
  itemsList: Item[]
}> = ({ itemsList }) => {

const filteredItems: Item[] = itemsList && getFilteredItems(itemsList)
    .
    .
    .
    <SomeComponent itemsArray={filteredItems} />

And now this doesn’t work, it’s throwing an error, saying it "Cannot read properties of undefined" within the SomeComponent. Which makes me think the code runs before the query results are back somehow.

Any idea what’s causing this, and why it worked when the query was in the same file, but now when it’s passed through props, it suddenly doesn’t work?

2

Answers


  1. Since graphql is basically an async API call (i.e., the component will get rendered whether or not the API call has ended), the child component gets rendered even while the data is not received. This means that undefined gets passed until the data is retrieved.

    You can add a loading component in the meantime:

    if(loading){
      return <loadingComponent/>
    }
    
    return <SomeComponent itemsArray={filteredItems}/>
    
    Login or Signup to reply.
  2. Several things here:

    1. You’re correct that rendering happens before data is returned from useQuery – that’s why the loading variable exists. You should not render content that depends on the data being returned from the query while loading is true as data will typically be undefined.
    2. If you’re going to filter or do other operations on the data before rendering it’s good practice to use useMemo to memoize the operation.

    With the above in mind:

    const Parent = () => {
      …
      const { loading, error, data } = useQuery(SOME_RANDOM_QUERY, {variables: { id: Id, number: Value1 }})
      const filteredItems = useMemo(()=> data ? getFilteredItems(data.items.nodes) : [])
      
      if (loading) return <Loading /> // typically a spinner
      else if (error) return <Error error={error} /> // some error handling component
      else return (
        <>
          <Child1 itemList={filteredItems] />
          <Child2 itemList={filteredItems] />
        </>
      )
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search