blnks
blnks

Reputation: 1161

Cannot pass react query result into child component

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

Answers (2)

Chad S.
Chad S.

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.

If your API supports filtering

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} />;
};

If you are filtering your results locally

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

Taghi Khavari
Taghi Khavari

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

Related Questions