To quote the useEffect documentation:
The function passed to useEffect will run after the render is committed to the screen. Think of effects as an escape hatch from React’s purely functional world into the imperative world.
The problem is that useEffect will trigger after a render. This will result in the following chain of events. Assume loading is just set to false and there where no errors fetching the data.
When the component is now being rendered both the if (loading) and if (error) guard clauses will be skipped, because the data successfully loaded. However state is not yet updated because the useEffect callback will trigger after the current render. At this point when you call renderBlogs(state) state will still be undefined. This will trow the error you describe.
I might be missing some context, but from what is shown in the question there is no reason to use useEffect. Instead use data directly.
The example provided by Apollo for useQuery provides a good starting point:
import { gql, useQuery } from '@apollo/client';
const GET_GREETING = gql`
query GetGreeting($language: String!) {
greeting(language: $language) {
message
}
}
`;
function Hello() {
const { loading, error, data } = useQuery(GET_GREETING, {
variables: { language: 'english' },
});
if (loading) return <p>Loading ...</p>;
return <h1>Hello {data.greeting.message}!</h1>;
}
Applying the example to your scenario it might look very similar.
const { loading, error, data } = useQuery(FETCH_BLOGS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error</p>;
return renderBlogs(data.blogs);
If you need the setter you might want to provide a bit more context to the question. However a common reason to have a setter might be if you want to apply some sort of filter. A simple solution would be to have a computed blogs state.
const filteredBlogs = data.blogs.filter(blog => blog.title.includes(search));
Here search would be the state of some <input value={search} onChange={handleSearchChange} /> element. To improve performance you could also opt to use useMemo here.
const filteredBlogs = useMemo(() => (
data.blogs.filter(blog => blog.title.includes(search))
), [data.blogs, search]);
If you really need to use useState for some reason, you could try the onCompleted option of useQuery instead of useEffect.
const [blogs, setBlogs] = useState();
const { loading, error } = useQuery(FETCH_BLOGS, {
onCompleted: data => setBlogs(data.blogs),
});