RTK-query: how to have the fixedCacheKey approach also for useLazyQuery? - reactjs

reading the documentation I saw that mutations can share their result using the fixedCacheKey option:
RTK Query provides an option to share results across mutation hook instances using the fixedCacheKey option. Any useMutation hooks with the same fixedCacheKey string will share results between each other when any of the trigger functions are called. This should be a unique string shared between each mutation hook instance you wish to share results.
I have a GET api call that I need to invoke using the trigger method of the useLazyQuery hook, but I need to exploit its booleans (isSuccess, isError, etc...) in many places. Is it possible to have the same behaviour for the useLazyQuery hooks ?

In RTK-query, queries instances follow this logic in cache (from documentation):
When you perform a query, RTK Query automatically serializes the request parameters and creates an internal queryCacheKey for the request. Any future request that produces the same queryCacheKey will be de-duped against the original, and will share updates if a refetch is trigged on the query from any subscribed component.
Given that the only way, in different components, to get the same cache entry is to pass the same query parameters. If you have many useLazy in many components, those hooks don't point any cache entry until you fire them (with some parameters).
This is what I can understand from the official docs

I ended up implementing a custom hook to handle this scenario
// This is the custom hook that wrap the useLazy logic
const useCustomHook = () => {
const [trigger] = useLazyGetItemQuery();
const result = apiSlice.endpoints.getItem.useQueryState(/* argument if any */);
const handleOnClick = async () => {
// The second argument for the trigger is the preferCacheValue boolean
const { data, error } = await trigger(/* argument if any */, true);
// Here where I will implement the success/error logic ...
}
return { onClick: handleOnClick, result}
}
Setting the preferCacheValue at true forced the RTQK to return the cached value if present for the lazy I'm using. That allow me both to avoid triggering many API calls.
Once implemented the hook above I can do the following:
// Component that will make the API call
function Header() {
const { onClick } = useCustomHook();
return (<Button onClick={onClick}>Click me</Button>);
}
// Component that will use the booleans
function Body() {
const { result: { isFetching } } = useCustomHook()
return (<Spin spinning={isFetching}>
<MyJSX />
</Spin>);
}
With this approach I was able both to centralize the trigger logic (e.g. which parameters I have to pass to my trigger function) and exploiting the booleans of that RTKQ hooks in many components.

Related

How to use RTK Query data for derived state?

I am building a small app that tracks progress of mountain summit peak lists for hikers. I am using RTK Query for the first time for the majority of state management. I need to create a piece of derived state, a "list counts" object, which contains key value pairs of peak list IDs and the number of peaks completed on that list. Data returned from two RTK Query hook calls is needed to calculate it and this derived data is needed in multiple components:
const listCounts = {
nh4k: 6,
ne100: 5,
usHigh: 3,
// more data
}
I have two query hooks from RTK Query which work to fetch the necessary data from the backend. My current working solution is a custom hook which calls each hook generated by RTK Query and returns listCounts:
const useListCounts = () => {
const { data: peakLists } = useGetPeakListsQuery();
const { data: logEntries } = useGetLogEntriesQuery();
// logic to create listCounts object
return listCounts;
}
Is this appropriate use of a custom hook? I don't have a ton of experience using custom hooks but most other examples I have seen return some value from a built in React hook such as useState, useReducer etc.
Another solution I have considered is to create a separate function, call each RTK query hook in the component directly and then pass the data to the function in the component:
// in util function file:
const calculateListCounts = (peakLists, logEntries) => {
// logic to create listCounts object
return listCounts;
}
// In component file:
import { calculateListCounts } from "..."
const MyComponent = () => {
const { data: peakLists } = useGetPeakListsQuery();
const { data: logEntries } = useGetLogEntriesQuery();
const listCounts = calculateListCounts(peakLists, logEntries);
// return JSX
}
Is there any reason to prefer either option above, or is there some way to access RTK query cache outside of a React component or custom hook so that I can calculate and return listCounts with a single function call?
I have several other similar situations where some piece of derived state calculated using data returned from RTK Query hooks is needed across multiple components and want to make sure I'm following the correct pattern.

await useFetch and then passing data to another react hook

I have a simple useFetch() react hook that makes an api call to fetch data, I then want to pass that data to a custom usePagination() hook that processes the data and returns some of it to display.
something like this
const [data] = useFetch(`http://localhost:whatever`);
const {postDisplay} = usePagination(data);
The problem is that data is undefined until it finishes fetching, and usePagination crashes.
and being that it is a hook you can't call conditionally.
(I guess I can make useFetch async and await it, but it doesn't feel like that's the solution here)
Is there any easy ways around this?
You can handle the condition inside the usePagination hook itself. If it receives undefined as the argument, it should still call all the other hooks it could have (like useMemo or useState), but bail out of all of them. Eg.:
function usePagination(data) {
const postDisplay = useMemo(() => {
if (!data) return null
// business as usual here
}, [data])
return {postDisplay}
}
If your hook doesn't call any other hooks, you can just return early:
function usePagination(data) {
if (!data) return {postDisplay: null}
// business as usual here
return {postDisplay}
}
And inside your component, you handle the case when postDisplay is null as well.

How to reduce API calls in a react functional component?

Overview
The below functional component renders the list of users in a dropdown. In the edit mode, we can change the current user to another user from the list of users.
Expected
The given code is fetching details thrice. I need to reduce it to one.
function UsersListView(props){
const { edit } = props // this is true for this case.
const refreshUsers = useRef(true);
const [error,users,refersh]=useFetchData('/profiles')
useEffect(() => {
if(edit && refreshUsers.current){
const fetchData = async () => {
await refresh();
}
fetchData();
refreshUsers.current = false;
}
},[edit, refresh])
return (
...... JSX CODE TO RENDER USERS
)
}
In the above code, we are making 3 API calls.
In the initial render
When we refresh the data => We fetch the data again. => API called
As refresh is in the dependency of useEffect() hook a re-render occurs then calling useFetchData() => API called.
My attempt
Initially refershUsers.current=false is under await refresh() which got me 8 API calls.
The above-mentioned code still gives me 3 API calls.
I tried moving const [error,users,refersh]=useFetchData('/profiles') inside useEffect() which throws an error. Hooks can't be used inside callbacks.
Queries
Is there a way to reduce the API calls to 1.
Why were the API calls reduced from 8 to 3 when I moved the line refershUsers.current=false outside
Try using a separate useEffect for each dependency. React allows you to use multiple useEffect within the same component.
one of them could be with an empty dependency array for the initial fetch. and then update state or make additional fetch calls as needed in another useEffect

ReactJS: useEffect update metadata on every request of aws IVS chaannel

I would like to perform the continuous process as this below link to update the metadata of the aws IVS stream.
I have managed with the useEffect hook but it is not updating the value and throws the undefined error.
https://codepen.io/amazon-ivs/pen/XWmjEKN?editors=0011
function AppComponent(props) {
const [metaData, setMetaData] = useState(null)
useEffect(() => {
const data = props.metadata
const initData = async () => {
setMetaData(data)
}
initData()
}, [metaData])
return (
<div>
<h2>{metaData.question}</h2>
</div>
)
}
Ok, I am a bit confused about this code snippet.
if you assume that you receive in your props metadata then probably you don't need state at all, and your code should look like below
function AppComponent(props) {
return (
<div>
<h2>{props.metadata.question}</h2>
</div>
)
}
Regarding your code overall, you have created metaData with initial value null, so metaData.question will throw error on the initial run.
It doesn't make no sense to run setMetaData asynchronously. Also you have subscribed to metaData change in your hook, but only place where you update it is your hook, it will cause also infinite loop.
It would be better if you will provide more information about your issue and make this question not so wide
If you use only some properties from the object you are passing then you probably don't need to pass whole object as a prop, but it would be better to pass props that you use, as any change on the object, you are passing will cause rerendering of the component. Also you can try to use React.memo to avoid unnecessary rerendering cycles.

does `useCallback` have something like a `useRef.current` inside it that will ensure that calls always use the latest version of the callback?

I have a situation where I am loading several datasets; the user can choose how many to load. All the datasets are rendered on the same chart. The datasets load individually and asynchronously.
The code is something along the lines of
export const DatasetGraph = ({userSelections}) => {
const [datasets, setDatasets] = useState({});
// when a selection changes, update the data
useEffect(() => {
// while loading a dataset, it is visible but has no data
setDatasets(userSelections.map(selec => ({ dsname: [] })));
// trigger asynchronous loads for each new dataset
userSelections.forEach((dsname) => fetchDataset(dsname));
}, [userSelections]);
const fetchDataset = async (dsname) => {
response = await fetch(url + dsname);
// QUESTION: after the await, will this call the most recent version of
// the callback? Will it use the most recent datasets object or an old
// one saved in the useCallback closure?
updateDataset(dsname, response);
};
// when a fetch returns, update the empty placeholder with the real data
const updateDataset = useCallback(
// For this toy example, we could use a setState function to always
// retrieve the latest `datasets`. However, this callback may use other
// state which is really what this question is about.
(dsname, response) => setDatasets({ ...datasets, dsname: response }),
[datasets]
);
return <Graph data={datasets}></Graph>;
};
I have not yet tried just having each dataset be a React component that doesn't render anything to the DOM, which can then manage its own loading state. That might actually be easier.
useCallback uses the dependencies array to detect changes
The useCallback method uses the dependencies array you pass to it in order to memoize the value of your function. Your function will be recreated every time but not assigned to updateDataset unless one of the dependencies has changed.
You should be wary of using useCallback unless a component below your function is expensive to rerender, otherwise, useCallback won't have much of a positive effect on your application's performance if any positive effect at all.
It works the same way that useMemo does to ensure that your data, in the case of useCallback it is a function, is only updated on your variable when something it depends on has changed.

Resources