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 thecallback
. 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.