11

As we know a react component is re-rendered when it's props or state changes.

Now i'm using useQuery from react-apollo package like below:

import { gql, useQuery } from '@apollo/client';

const getBookQuery = gql`
  {
    books {
      name
    }
  }
`;

function BookList() {
    const { loading, error, data} = useQuery(getBookQuery);

    if(loading) return <p>Loading....</p>
    if(error) return <p>Ops! Something went wrong</p>

    return (
      <>
        <ul>
          {data.books.map(book => (
            <li key={book.name}>{book.name}</li>
          ))}
        </ul>
      </>
    )
}

export default BookList;

When i run the code above, we first get Loading... in DOM which is then updated to list containing query data (once it arrives). But how does react know to re-render my component once data is received from query.

Are these data, loading and error properties mapped to component props and they are updating? If so, why doesn't chrome dev tools show any props for this BookList component?

enter image description here

Can someone explain how is this useQuery custom hook working here?

D_S_X
  • 1,351
  • 5
  • 17
  • 35
  • Documentations are for these purposes right? https://www.apollographql.com/docs/react/data/queries/ – Leigh Cheri Feb 07 '21 at 16:32
  • You can't see the props, but you can see the hooks, below the props. Apollo has a very good developer tool btw. You can use that, too – Leigh Cheri Feb 07 '21 at 16:35

3 Answers3

30

A good way of figuring out (roughly) what is happening in useQuery is to consider how you'd do it yourself, e.g.

const MyComponent = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(async () => {
    try {
      setLoading(true);
      const data = await GraphQL.request(getBookQuery);
      setData(data);
    } catch (ex) {
      setError(ex);
    } finally {
      setLoading(false);
    }
  }, []);

  if(loading) return <p>Loading....</p>
  if(error) return <p>Ops! Something went wrong</p>

  return (
    <>
      <ul>
        {data.books.map(book => (
          <li key={book.name}>{book.name}</li>
        ))}
      </ul>
    </>
  );
};

In the above you can see your component has state (not props) data, loading and error which causes your component to re-render.

You can then imagine this logic was wrapped in your own useQuery hook:

const useQuery = (query, variables) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(async () => {
    try {
      setLoading(true);
      const data = await GraphQL.request(query, variables);
      setData(data);
    } catch (ex) {
      setError(ex);
    } finally {
      setLoading(false);
    }
  }, []);

  return { data, loading, error };
}

const MyComponent = () => {
  const { data, loading, error } = useQuery(getBookQuery);

  if(loading) return <p>Loading....</p>
  if(error) return <p>Ops! Something went wrong</p>

  return (
    <>
      <ul>
        {data.books.map(book => (
          <li key={book.name}>{book.name}</li>
        ))}
      </ul>
    </>
  );
};

So ultimately your component is re-rendering because it does have data, loading and error held in MyComponent state, it's just abstracted away.

Richard Scarrott
  • 6,638
  • 1
  • 35
  • 46
  • 3
    Ah so basically a hook's state variable also works as a state (or prop?) for component where hook was used? If hook's state changes component render also gets triggered. Tested it using a very basic custom hook and seems like that is actually the case `https://codesandbox.io/s/modest-sea-ghqjm`. – D_S_X Feb 07 '21 at 18:39
  • 5
    Yeah, there's actually no such thing as a 'hooks' state but only a components state, a custom hook merely defines state associated with the component it's called from. The fact useState is called within a different function (useQuery) is largely meaningless, it's all related to the component from which it was called in. – Richard Scarrott Feb 07 '21 at 22:04
  • 5
    Fantastic answer! – MEMark Oct 17 '21 at 19:17
  • 3
    Such a good answer ! Thanks! – noob Mama Jan 14 '22 at 03:15
  • 1
    What a straight-forward explanation. Thank you for the post. – goldenriver4422 Mar 03 '23 at 09:24
1

Hooks can have their own state which when changed can trigger a rerender. E.g loading, error, data.

I created a sample implementation of the useQuery hook of apollo in this sandbox https://codesandbox.io/s/determined-wave-dd1wd?file=/src/BookList2.js

In the fake implementation it's visible that the hook has state and whenever the state changes the component re-renders.

Also created another component BookList2 where instead of using the hook I pasted the fake implementation of the hook in the component - that way maybe it'll be clearer how the re-render is triggered

TLDR: Check https://codesandbox.io/s/determined-wave-dd1wd?file=/src/BookList2.js - see that hooks can have state which when changed can cause rerenders

Besnik Korça
  • 939
  • 3
  • 9
1

Another thing to consider is the default configuration of you useQuery() hook which is provided by the QueryClient. For example rerendering on window focus is a default setting, which causes the hook to refetch and therefore rerender on every window focus (for example when clicking on devtools and click back into the DOM. You can manipulate this when creating the queryClient.

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      //other query settings
      refetchOnWindowFocus: false,
    },
  },
});

There are other settings to have in the back of your head.

DrGregoryHouse
  • 510
  • 5
  • 12