Search code examples
reactjsgraphqlreact-apolloreact-apollo-hooks

Component not rendering when using children as function


I am using react-apollo to query the graphQL server and able to successfully hydrate the client with the data. As there will be more than a single place I will be querying for the data I am trying to create a container (refactor) to encapsulate the useQuery hook so that it can be used in one place.

First Try ( working as expected )

const HomeContainer = () => {
  const { data, error, loading } = useQuery(GET_DATA_QUERY, {
    variables: DATA_VARIABLES
  });
  const [transformedData, setTransformedData] = useState();

  useEffect(() => {
    if(!!data) {
      const transformedData = someTransformationFunc(data);

      setTransformedData(...{transformedData});
    }
  }, [data]);

  if (loading) {
    return <div>Loading data ...</div>;
  }

  if (error) {
    return <p>Error loading data</p>;
  }
  if (!data) {
    return <p>Not found</p>;
  }

  return <Home transformedData={transformedData} />;
};

I wanted to encapsulate the ceremony around different stages of the query to a new container ( loading, error state) so that I can reduce code duplication.

First stab at refactoring

  • The Query container gets passed in the query, variables and the callback. This takes the responsibility of returning different nodes based on the state of the query ( loading, error or when no data comes back ).

const HomeContainer = () => {
  const {data, error, loading} = useQuery(GET_DATA_QUERY, {
    variables: DATA_VARIABLES
  });
  const [transformedData, setTransformedData] = useState();

  const callback = (data) => {
    const transformedData = someTransformationFunc(data);

    setTransformedData(...{
      transformedData
    });
  };

  return ( 
     <QueryContainer 
        query={GET_DATA_QUERY}
        variables={DATA_VARIABLES}
        callback ={callback} 
     >
        <Home transformedData={transformedData} />
     </QueryContainer>
  )
};

const QueryContainer = ({callback, query, variables, children }) => {
  const {data, error, loading } = useQuery(query, {
    variables: variables
  });

  // Once data is updated invoke the callback
  // The transformation of the raw data is handled by the child
  useEffect(() => {
    if (!!data) {
      callback(data);
    }
  }, [data]);

  if (loading) {
    return <div > Loading data... < /div>;
  }

  if (error) {
    return <p > Error loading data < /p>;
  }
  if (!data) {
    return <p > Not found < /p>;
  }

  return children;
};

QueryContainer is using useEffect and invokes the callback when data comes back. I felt this is a bit messy and defeats the purpose of encapsulating in the parent and using the callback to talk and update the child.

Third Try ( Using children as function )

Got rid of the callback and passing the data as the first argument to the children function.

const HomeContainer = () => {
    return (
        <QueryContainer
            query={GET_DATA_QUERY}
            variables={DATA_VARIABLES}
        >   
           {(data) => {
               const transformedData = someTransformationFunc(data);

                return <Home transformedData={transformedData} />;
           }}

        </QueryContainer>
    )
};

const QueryContainer = ({ query, variables, children }) => {
    const { data, error, loading } = useQuery(query, {
        variables: variables
    });

    if (loading) {
        return <div>Loading data ...</div>;
    }

    if (error) {
        return <p>Error loading data</p>;
    }
    if (!data) {
        return <p>Not found</p>;
    }

    return children(data);
};

I expected this to work as nothing really changed and the new render when the data is updated calls the children as a function with data as argument. But when I navigate to that route I see a black screen ( no errors and I can see the correct data logged into the console ) If I click the link again I can see the component committed to the DOM.

Not really sure what is going on here and wondering if someone can throw light as to what is going on here.


Solution

  • The code snippets that I have added above is working as expected in isolation.

    https://codesandbox.io/s/weathered-currying-4ohh3

    The problem was with some other component down the hierarchy tree that was causing the component not to re render.

    The 2nd implementation is working as expected as the component is getting rendered again dud to the callback being invoked from the parent.