Reputation: 1161
In my first Next.js
project, I have an article component which is rendered at the server side. I'd like to fetch articls' tags from the client side because otherwise, I get too many DOM elements. So here is what I came up with:
const ArticlesPage = () => {
const [tags, setTags] = useState([])
const { isLoading, isError, data, error } = useQuery('tags', getTags, {
onSuccess: () => setTags(data)
}
//...
})
console.log('tags are:', tags)
return (
<>
...
{!isLoading && !isError &&
<TagsComponent tags={tags} />
}
{isLoading &&
<div> Loading tags...</div>
}
{isError &&
<div> Error fetching tags</div>
}
</>
The problem is that tags are rendered on the articles page whimsically, that is when I refresh page they do not show up but when I refocus on page, the tags are displayed.
I don't see any Loading
or Error
being rendered either. So I'm confused what is going on here?
How can I fix this?
Upvotes: 1
Views: 2049
Reputation: 6633
As mentioned in the other answer, you should not be using setState to store data that comes back from the API. Doing so will make your cache management very difficult to maintain and reason about. Instead you should use state to store the filter options for the result, and then use that state to filter the data.
Assuming your API takes a payload to filter the response, we can store the payload we will send in a 'filter' state variable and use it for our query key.
They key function getTags
should accept {queryKey: [,filter]}
as its argument and should pass the filter to the api call. Then you can control it in your TagsComponent via the filter
and setFilter
props.
const ArticlesPage = () => {
const [filter, setFilter] = useState({});
const { isLoading, isError, data: tags, error } = useQuery(
["tags", filter],
getTags,
);
console.log("tags are:", tags);
if(isError) return <div>Error fetching tags</div>;
if(isLoading) return <div>Loading tags...</div>;
return <TagsComponent
tags={tags}
filter={filter}
setFilter={setFilter} />;
};
Since you cannot rely on the API to filter your results you'll need to filter the results once the response has returned. Again assuming you have some filter
state that the user specifies, and a function filterTags(filter, tags) => filteredTags
which will filter tags based on that state we can put them together in this way:
const ArticlesPage = () => {
const [filter, setFilter] = useState({});
const { isLoading, isError, data: tags, error } = useQuery("tags", getTags);
const filteredTags = useMemo(() => filterTags(filter, tags), [tags, filter]);
if(isError) return <div>Error fetching tags</div>;
if(isLoading) return <div>Loading tags...</div>;
return <TagsComponent
tags={filteredTags}
filter={filter}
setFilter={setFilter} />;
};
Upvotes: 5
Reputation: 6582
You don't need to create another state just to keep track of the data, you should use the data
property directly like this:
const ArticlesPage = () => {
const { isLoading, isError, data: tags, error } = useQuery("tags", getTags);
console.log("tags are:", tags);
return (
<>
...
{!isLoading && !isError && <TagsComponent tags={tags} />}
{isLoading && <div> Loading tags...</div>}
{isError && <div> Error fetching tags</div>}
</>
);
};
Upvotes: 1