next.js swr I do not want to execute it only at the time of initial drawing - reactjs

Development Enviroment
・ next.js
・ typescript
・ swr
it uses swr to communicate with swr.
I want to run it only when the value of the query is changed.
but is also executed during the initial drawing.
what can I do to prevent it from running during the initial drawing?
export const useUserQuery = (query: string) => {
const fetcher = (url) => {
Axios.get(url).then((res) => {
return res;
});
const path = `users/?query=${query}`;
const { data, error } = useSWR<IUser>(path);
return {
data: data,
loading: !error && !data,
Error: error,
};
};
};

UPDATED:
use initalData and disable revalidateOnMount then it should work as you expect.
see more in the docs: https://github.com/vercel/swr

It seems that you what to refeatch on variable change.
If you try this, it won't work, because even if token changes, SWR will still use the same key and return the wrong data.
useSWR('/api/user', url => fetchWithToken(url, token))
Instead, you can use an array as the key parameter, which contains multiple arguments of fetcher:
const { data: user } = useSWR(['/api/user', token], fetchWithToken)
See more in docs

Related

Prevent useSWR from fetching until mutate() is called

I have a React application which uses SWR + Axios for data fetching (https://swr.vercel.app/docs/data-fetching). The issue is that my custom hook which uses useSwr is fetching all data initially whenever the hook is initialized. My goal is to fetch only when I call mutate. Currently the initial fetch is happening even without calling mutate. Any suggestion on how to achieve my goal here?
My application is wrapped in SWRConfig:
<SWRConfig
value={{
fetcher,
}}
>
<App/>
</SWRConfig>
The fetcher is described as so:
const dataFetch = (url) => axios.get(url).then((res) => res.data);
function fetcher(...urls: string[]) {
if (urls.length > 1) {
return Promise.all(urls.map(dataFetch));
}
return dataFetch(urls);
}
My custom hook using useSwr
import useSWR, { useSWRConfig } from "swr";
export function useCars(registrationPlates: number[]): ICars {
const { mutate } = useSWRConfig();
const { data: carData} = useSWR<Car[]>(
carsToFetchUrls(registrationPlates), // returns string array with urls to fetch
{
revalidateOnFocus: false,
revalidateOnMount: false,
revalidateOnReconnect: false,
refreshWhenOffline: false,
refreshWhenHidden: false,
refreshInterval: 0,
}
);
const getCar = (
carRegistrationPlate: number,
): Car => {
console.log(carData) // carData contains data from fetch even before calling mutate()
void mutate();
...
}
Usage: (this will be located in some component that wants to use the useCars hook)
const { getCar } = useCars(carsRegistrationPlates);
You can use conditional fetching in the useSWR call to prevent it from making a request.
From useSWR Conditional Fetching docs:
Use null or pass a function as key to conditionally fetch data. If the
function throws or returns a falsy value, SWR will not start the
request.
export function useCars(registrationPlates: number[], shouldFetch): ICars {
const { data: carData} = useSWR<Car[]>(
shouldFetch ? carsToFetchUrls(registrationPlates) : null,
{ // Options here }
);
// ...
return { carData, /**/ }
}
You can then use it as follows to avoid making the initial request.
const [shouldFetch, setShouldFetch] = useState(false);
const { carData } = useCars(carsRegistrationPlates, shouldFetch);
Then, when you want the make the request simply set shouldFetch to true.
setShouldFetch(true)
Here's a possible way of implementing what you are hoping to achieve.
I've used a similar approach in one of my production app.
Start by creating a custom swr hook as so
const useCars = (registrationPlates: number[]) => {
const fetcher = (_: string) => {
console.log("swr-key=", _);
return dataFetch(registrationPlates);
};
const { data, error, isValidating, revalidate, mutate } = useSWR(`api/car/registration/${JSON.stringify(registrationPlates)}`, fetcher, {
revalidateOnFocus: false,
});
return {
data,
error,
isLoading: !data && !error,
isValidating,
revalidate,
mutate,
};
};
export { useCars };
Now, you can call this hook from any other component as
const { data, error, isLoading, isValidating, revalidate, mutate } = useCars(carsRegistrationPlates);
You now control what you want returned by what you pass to useCars above.
Notice what is passed to the first argument to useSwr in our custom swr hook, this is the key swr uses to cache values and if this remains unchanged then swr will transparently returned the cached value.
Also, with this custom hook you are getting states such as loading, error etc. so you can take appropriate action for each of these states in your consuming component.

Redux toolkit RTK query mutation not getting returning data

Hi I recently learned the new react toolkit with the rtk query tool, and I am trying to put in a login system together using the createApi from the rtk package.
After giving it a test on the login button pressed, I see the network request going through without any issue(status code 200), and I get a response object providing user, token, however, when I try to get the returning data using useLoginMutation I get an undefined value.
below is the code for my endpoint which is injected in a base api:
export const apiLogin = theiaBaseApi.injectEndpoints({
endpoints: (build) => ({
loginUser: build.mutation<UserReadonly, loginValuesType | string>({
query: (values: loginValuesType, redirect?: string) => {
const { username, password } = values;
const header = gettingSomeHeaderHere
return {
url: "login",
method: "GET",
headers,
crossDomain: true,
responseType: "json",
};
},
}),
}),
});
export const { useLoginUserMutation } = apiLogin
then inside my React component I destructure the mutation result such like below:
const [login, {data, isLoading}] = useLoginUserMutation();
const submitLogin = () => {
// pass in username password from the form
login({username, password});
}
Suppose if I console log out data and isLoading I assume that I will see data: {user: "abc", token: "xyz"}, because under network tab of my inspect window I can see the response of this network request, but instead I am seeing data: undefined
Does any have experience on solving this?
Oh I found the reason, it was a very careless mistake. I had to wrap the reducer to my store, which was what I was missing
In my case the issue was that I was trying to access the UseMutationResult object inside onClick callback. And the object was not updating inside the callback, even though in the component the values were accurate.
If I put the log outside it's working just fine.
here is an example for better understanding (inside handleAddPost the mutationResult is not updating)
Here is a code sample (in case link is not working):
const Component = () => {
const [addPost, mutationResult] = useAddPostMutation();
...
const handleAddPost = async () => {
...
console.log("INSIDE CALLBACK isLoading and other data is not updating:");
console.log(JSON.parse(JSON.stringify(mutationResult)))
...
};
// in the example this is wrapped in an useEffect to limit the number of logs
console.log(mutationResult.data,"OUTSIDE CALLBACK isLoading and other data is working:")
console.log(JSON.parse(JSON.stringify(mutationResult)))
return (
...
<Button
...
onClick={handleAddPost}
>
Add Post
</Button>
...

react apollo - clear data object before refetch

When using refetch of useQuery hook the data object is still defined. And when using infinite scrolling, only the first page will be refetched.
Is it possible to clear the data object before calling the refech so that we can start fresh?
const { data, loading, error, fetchMore, refetch } = useQuery(GET_ALL_ITEMS, {variables});
getNextPage = async () => { // merges results for infinite scrolling
await fetchMore({ variables,
updateQuery: (previousResult, { fetchMoreResult }) => {
const oldEntries = previousResult.items;
const newEntries = fetchMoreResult.items;
fetchMoreResult.items = [...oldEntries, ...newEntries];
return fetchMoreResult;
},
)
}
Can I do something like refresh = () => { data = null; refetch(); } but without directly mutating state?
I couldn't find a way to clear data but found a different approach in Apollo Client v3 docs.
Basically it's to ignore loaded data when loading or in case of error:
const { data: loadedData, loading, error, fetchMore, refetch } = useQuery(
GET_ALL_ITEMS,
{
variables,
notifyOnNetworkStatusChange: true, // important for adequate loading state
}
);
const data = (loading || error) ? undefined : loadedData;
Note, that in order to make loading work for refetch, you need to pass notifyOnNetworkStatusChange: true option.
It's also possible to get networkStatus from useQuery result and check it like this:
networkStatus === NetworkStatus.refetch
but it seems to work for me with loading and error only.

Unexpected result from apollo react's useQuery method

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>

How to access data from custom react hook

Preface: I'm fairly new to React (Coming over from Angular). I know things a similar but different.
I have referenced the following SO threads to no avail in my situation:
React not displaying data after successful fetch
Objects are not valid as a React child. If you meant to render a collection of children, use an array instead
Currently, I'm trying to get my data to display from an API I developed. I'm used to the Angular approach which would call for a ngFor in the template for most data showcase situations.
I'm having trouble wrapping my mind around what I have to do here in order to display my data. The data is expected to be an array of objects which I would then parse to display.
I also receive the following error: Error: Objects are not valid as a React child (found: object with keys {data}). If you meant to render a collection of children, use an array instead.
I've searched high and low for a solution but sadly, nothing I've seen has worked for me. (All of the answers on SO are using the class-based version of React, of which I am not).
You can see my data output in the following screenshot:
I am also including my custom hook code and the component that is supposed to render the data:
CUSTOM DATA FETCH HOOK
interface Drone{
id: number;
name: string;
model: string;
price: number;
}
export function useGetData(urlpath:string) {
const [droneData, setData] = useState<any>()
async function handleDataFetch(path:string){
const result = await fetch(`https://drone-collections-api-jc.herokuapp.com${path}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'x-access-token': 'Bearer API-TOKEN'
}
})
const response = await result.json();
setData(response)
}
useEffect( () => {
handleDataFetch(urlpath)
})
return droneData
}
THE DRONE COMPONENT
import { useGetData } from '../../custom-hooks'
export const Drones = () => {
let data = useGetData('/drones')
console.log(data)
// const DisplayDrone = ( ) => {
// return (
// Array.prototype.map( data => {
// <div>{ data.name }</div>
// })
// )
// }
return (
<div>
<h1>Hello Drones</h1>
</div>
)
}
Also, for more context, the current code can be found at this repo: https://github.com/carter3689/testing-drone-frontend
Please, help me understand what I'm missing. Many Thanks!
There are several locations that needed to be fixed
In fetchData.tsx
export function useGetData(urlpath: string) {
const [droneData, setData] = useState<any>([]);
async function handleDataFetch(path: string) {
const result = await fetch(`https://jsonplaceholder.typicode.com/posts`, {
...
});
const response = await result.json();
setData(response);
}
useEffect(() => {
handleDataFetch(urlpath);
}, []);
Explanation:
you need a "blank" array for looping through. I guess that the error causes by the fact that at the start, before the data is fetched, there is nothing to loop through. It's same as doing undefined.map(), which is obviously fail.
You need a dependencies array for useEffect. Right now your code will do an infinite loop since everytime it get data, it update the state, thus re-run the useEffect and repeat. Add dependencies array limit when that useEffect will run
In Drones.tsx
return (
<div>
{data.map(item => <div>{item.name}</div>}
</div>
)
Not much to say here. I don't use Angular so I'm not sure why you use Array.prototype.map, but in React you can loop through your variable directly. I also have a CodeSandbox link for your project (I use public API)

Resources