I'm trying to create a feed of things that updates as you swipe down. My logic is that I need to fetch the required data before rendering the feed, and I will fetch new data every time I hit the nth element as I swipe down the page. So I thought of having a boolean state variable to detect the first render, and then I set that to false to not run the query again.
const Feed = (props) => {
const [feedArray, setFeedArray] = useState([]);
const [firstRender, setFirstRender] = useState(true);
const FEED_QUERY = gql`
query getFeedArray {
getFeedArray
}
`;
if (firstRender) {
const { loading, error, data } = useQuery(FEED_QUERY);
if (loading) return <Loading/>;
if (error) return `Error! ${error}`;
feedArray.push(...data.getFeedArray);
setFirstRender(false);
}
return (
<RenderredComponent />
);
}
export default withApollo(withRouter(Feed));
Clearly, this results in an error (Unexpected Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
) as the useQuery
hook is only called once in the if statement.
I experimented with different ways of doing this, as I require this data before my component is rendered. I tried useEffect()
with props.client.query()
but that wouldn't work since it allows the gql query to be run synchronously and hence only updates the DOM with the data on the second render, and I can't block the render based on the loading
state. I can't place useQuery()
in useEffect()
as it throws an error as well (hook within a hook).
If I place the useQuery
hook outside the if statement, it works but is run every render, which is unnecessary extra calls. For context - renders happen often as I scroll down due to how React Swipeable Views works.
So to put it simply - how can I run the useQuery
hook only once, before the component is rendered?
Any help is appreciated, thank you!
EDIT: Screenshots below to illustrate what's happening,
The first screenshot shows when it renders, and I print the data received to the console. I hardcoded a 5-element array to be returned on repeat for this example. Then as I swipe up, the component is being re-rendered although I do NOT need to fetch more data, but it is because the useQuery
hook is being triggered. This is just going to keep fetching more data because the component will continually be re-rendered.
Also - I need to store my data in state because I will be adding to it, and there is no other way for my component to remember what was fetched the previous time.
EDIT2: The accepted answer works. But I had a misconception about how useQuery
works, I should have done some logging on the server whenever the query was called. This calls the query once and only once, despite all the DOM updates. Furthermore, it adds it to my state only once, because after the first successful render the firstRender state is set to false. Not sure if this is ideal but this is what I would've done if I thought of logging the calls on my server.
const Feed = (props) => {
const [feedArray, setFeedArray] = useState([]);
const [firstRender, setFirstRender] = useState(true);
const FEED_QUERY = gql`
query getFeedArray {
getFeedArray
}
`;
const { loading, error, data } = useQuery(FEED_QUERY);
if (loading) return <Loading/>;
if (error) return `Error! ${error}`;
if (firstRender) {
feedArray.push(...data.getFeedArray);
setFirstRender(false);
}
return (
<RenderredComponent />
);
}
export default withApollo(withRouter(Feed));
You don't need to use state. Just use query data
const Feed = props => {
const FEED_QUERY = gql`
query getFeedArray {
getFeedArray
}
`;
const { loading, error, data } = useQuery(FEED_QUERY);
if (loading) return <Loading />;
if (error) return `Error! ${error}`;
return <RenderredComponent data={data.getFeedArray} />;
};
export default withApollo(withRouter(Feed));
You can use data
wherever you want.
UPDATE
If you want to use state for some reason, you can use it with useEffect
. Add data.getFeedArray
to the useEffect
dependencies, so that when it is changed you can set the feedArray
state.
const Feed = props => {
const [feedArray, setFeedArray] = useState([]);
const FEED_QUERY = gql`
query getFeedArray {
getFeedArray
}
`;
const { loading, error, data } = useQuery(FEED_QUERY);
useEffect(() => {
setFeedArray([...data.getFeedArray])
}, [data.getFeedArray])
if (loading) return <Loading />;
if (error) return `Error! ${error}`;
return <RenderredComponent data={feedArray} />;
};
export default withApollo(withRouter(Feed));