I got a component that fetches data from a GraphQL route using Apollo.
When I load the page, it console logs the data twice.
This is the component :
const LAUNCHES_QUERY = gql`
query LaunchesQuery {
launches {
flight_number
mission_name
launch_date_local
launch_success
}
}
`
const Launches = () => {
const { loading, error, data } = useQuery(LAUNCHES_QUERY)
const getLaunches = () => {
if (loading) return <div>Loading..</div>
if (error) console.log(error)
console.log(data)
}
return (
<div>
<h1 className='display-4 my-3'>Launches</h1>
{getLaunches()}
</div>
)
}
What am I doing wrong?
useQuery is an async operation, the first render will be while fetching the data and next render will be when data is fetched. Therefore, it is logging the twice.
One is from start of the useQuery, and another one is from once useQuery is done fetching.
You can console.log loading to see.
Related
I am working on React app which fetches large data (Thousands of records) and then render it. Until api is fetching data, UI keep blocked and does not display anything. I am creating chunks in size of 10 for fetching api using Promise.allSettled and combining them all.
useEffect(() => {
fetchBatchedData()
},[])
fetchBatchedData is an async function and sets data in redux store only, I don`t need that data in UI at loading time. Until I get all the data, UI display nothing. How can I fetch data in background without blocking component rendering?
You could use a useState like this:
const [data, setData] = useState();
useEffect(() => {
const loadData = () => {
const tempData = fetchBatchedData();
setData(tempData)
}
},[])
if(!data){
return(
<h1>Loading...</h1>
)
} else {
return(
<h1>Got the data!</h1>
)
}
Maybe we can defer rendering the data? There is something called requestIdleCallback here.
Something like this maybe?
import { useState, useEffect } from 'react';
function RenderDeferred({ children, timeout }) {
const [render, setRender] = useState(false);
useEffect(() => {
if (render) setRender(false);
const id = requestIdleCallback(() => setRender(true), { timeout: idleTimeout });
return () => cancelIdleCallback(id);
}, [idleTimeout]);
if (!render) return null;
return children;
}
And then use it like this:
<RenderDeferred timeout={3000}>
<YourComponent />
</RenderDeferred>
For a project, where i've implemented authentication by running a GraphQL query inside a AuthenticationProvider from a context, I noticed the query is fetching data twice.
const AuthenticationProvider: FC = props => {
const {
loading,
data
} = useQuery(MeQuery)
if (loading) return null
return <AuthenticationContext.Provider value={{user: data?.me || null}} {...props} />
}
However the query runs perfect, I still wanted to know why it fetches the data twice. I did some googling, and came across this issue, where this answer was provided. I tried the same thing, with the skip option, based if the data is loaded.
const [skip, setSkip] = useState(false)
const {
loading,
data
} = useQuery(MeQuery, { skip })
useEffect(() => {
if (!loading && data?.me) {
setSkip(true)
}
}, [loading, data])
// ...
But when logging in, it stopped working.
const useLoginMutation = () => useMutation(LOGIN_QUERY, { update: (cache, { data }) => {
if (!data) {
return null
}
cache.writeQuery({ query: MeQuery, data: { me: data.login } })
}
})
The cache still get's updated with the right values, but doesn't retrieve the user anymore (null).
const { user } = useContext(AuthenticationContext)
What am I missing here? It seems the query did run and fetched the correct data.
You don't need to use context when you are using apollo useQuery. If you make a query first, then the data fetched will be stored in the cache. You can directly access the data from the cache for the second you run the query. Since useQuery has default fetchPolicy cache-first. Mean its check in the cache first, if no query made before it makes a network request.
If you want to avoid network loading. You can make a top-level component AuthWrapper.
const useUserQuery = () => useQuery(ME_QUERY);
const AuthWrapper = ({children}) => {
const {loading, data} = useUserQuery();
if(loading) return null
return children;
}
export GetUsetInThisComponent = ({}) => {
// Since we have fetched the user in AuthWrapper, the user will be fetched from the cache.
const {data} = useUserQuery();
// No you can access user from data?.user
// Rest of the application logic
}
// Wrap the component like this to avoid loading in the children components
<AuthWrapper>
<GetUserInThisComponent />
</AuthWrapper>
The question is simple. How to fetch data in your React blog and stay DRY? Let's say that you have just two components in your blog - PostsList and SinglePost, in both components you must fetch data, activate isLoading state, etc. There will be chunks of the same code in both components.
I investigated the situation a little bit, checking React-blog demo apps of big headless CMS providers, like Prismic or Sanity.io, and they all just repeat fetch functions in both PostsList and SinglePost.
Does anybody have any idea? You can point me to some good resources?
You can achieve this by using High Order Components. You can use them for reusing component logic. Let me show you an example of how to handle the isLoading with a HOC:
HOC:
import React, { useState } from 'react'
const hocLoading = (WrappedComponent, loadingMessage) => {
return props => {
const [ loading, setLoading ] = useState(true)
const setLoadingState = isComponentLoading => {
setLoading(isComponentLoading)
}
return(
<>
{loading && <p>{loadingMessage}</p>} //message showed when loading
<WrappedComponent {...props} setLoading={setLoadingState} />
</>
)
}
}
export default hocLoading
As you can see this HOC is receiving the WrappedComponent and a message that you can set depending on your component. Then you will have to wrap every component where you want to show the loading feedback with the HOC and you can use the setLoading prop to stop showing the loading feedback:
const Component = props => {
const { setLoading } = props
useEffect(() => {
const loadUsers = async () => {
await fetchData() // fetching data
setLoading(false) // this function comes from the HOC to set loading false
}
loadUsers()
},[ ])
return (
<div className="App">
{usuarios.data.map(x => <p key={x.id}>{x.title}</p>)}
</div>
);
}
export default hocLoading(Component, "Data is loading") //component wrapped
// with the HOC and setting feedback message
This way you avoid repeating this process for every component. Regarding the data fetching you can create a Hook or a function that receives dynamic params so you can just call something like fetchData(url). Here is an example of a dynamic function for making request using axios:
const baseUrl = "" //your BASE URL
async function request(url,method,data){
try {
const response = await axios({
method,
url: `${baseUrl}${url}`,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
data: data ? data : undefined
})
return response
} catch (e) {
// handle error
}
}
I'm having a website where I show different kinds of projects to the user. I'm using Hasura, NextJS and Apollo to make this happen. The problem I have now is that every time I come back to the home page (where I show my projects) the query is getting fired again and has to load all over again. I want to save the data in my cache, but I'm not sure how to do that.
This is how I do it now:
I'm calling the query, when its done loading, I return my Home component with the data.
const ProjectList = () => {
const { loading, error, data } = useQuery(GET_PROJECTS);
if (loading) {
return <div>Loading...</div>;
}
if (error) {
console.error(error);
return <div>Error!</div>;
}
return <Home projects={data.projects} />;
};
export default withApollo({ ssr: true })(ProjectList);
In the home component I know can use the data:
const Home = ({ projects }) => {
// rest code...
}
I create my apolloClient as the following:
export default function createApolloClient(initialState, headers) {
const ssrMode = typeof window === 'undefined';
let link;
if (ssrMode) {
link = createHttpLink(headers); // executed on server
} else {
link = createWSLink(); // executed on client
}
return new ApolloClient({
ssrMode,
link,
cache: new InMemoryCache().restore(initialState),
});
}
Is there a way I can save the data in the cache, so I don't have to wait for the loading state every time I come back to the home page?
You can use the prebuild feature of nextjs. For this you need to call getStaticProps in your ProjectList.jsx file. There you have to import your apollo client as well as your query, then initialize the client and do the query:
export async function getStaticProps() {
const apollo = require("../../../apollo/apolloClient"); // import client
const GET_PROJECTS = require("../../../apollo/graphql").GET_PROJECTS; // import query
const client = apollo.initializeApollo(); //initialize client
const { data, error } = await client.query({
query: GET_PROJECTS
});
if (!data || error) {
return {
notFound: true
};
}
return {
props: { projects: data.projects } // will be passed to the page component as props
};
}
I've got a simple React component:
const Page = ({ data }) => {
return (
<header>
{data.length !== 0 ?
<>
{data((d) =>
// render data
)}
</>
:
<>Loading...</>
}
</header>
)
}
I'm getting the data using Next.js recommended getServerSideProps:
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`someurl`)
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
Now, for the love of God, I can't figure out why <>Loading...</> is never rendering. The page is blank and then everything pops up. Why does it happen and how do I fix that? of course data.length IS 0 before it's fetched...
Note I'm using dynamic routing and do not want to go with getStaticProps.
getServerSideProps always runs on server side also for client side navigation.
When you return data from getServerSideProps (if the fetch method is executed without errors) it will have always return a value.
In your example <Loading /> will be visible only if data returned from fetch has 0 length and will never be visible during fetch.
Here the docs https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering
It's obvious that user should not wait a few seconds in which nothing happens (because getServerSideProps is not finished loading) when he clicks a link. He should see some action is happening, for example:
Loading spinner
Data template (boxes for images, text and so on), youtube example.
But for now it's not possible with getServerSideProps, because page is rendered only after getServerSideProps request is complete.
There is exist future request on next.js about this, so i hope it will be implemented.
you need to use isFallback method provided by there next/router. have a look on this code try to look for isfallback https://github.com/vercel/next-site/blob/master/pages/docs/%5B%5B...slug%5D%5D.js.
Edit:
`export async function getServerSideProps() {
Fetch data from external API
const res = await fetch(someurl)
const data = await res.json()
Pass data to the page via props
return {
props: res ? {
data,
id,
url,
} : {}
};
}
`
and in your component
const router = useRouter();
const { isFallback } = router
if (isFallback) {
return <Loading />
}
else {
return (
// render data
)
}