Access data already fetched with react query in other component - reactjs

I'm new with React Query, and I have a question, I've being looking on the documentation but I can't find how should I access the data that is already fetched with useQuery() from another component.
I'm fetching const query = useQuery('todos', fetchFunction) from HomeComponent and I want to access that data that is already fetched in the TodosComponent. Is ok to fetch the data again with const query = useQuery('todos', fetchFunction) or is there anything like redux that the data is in something like Store so I can access it from any place ?
;)

It is definitely best to just call useQuery again, because it's the only thing that creates a subscription, so your component will re-render correctly if new data comes in. You can do it imperatively with queryClient.getQueryData('todos'), but it doesn't create a subscription.
Note that useQuery will not always trigger a fetch of the data. You can customize staleTime to tell react-query how long a resource is considered fresh, and as long as it's fresh, data will come from the internal cache only. If you set staleTime: Infinity, there will only be one fetch, and all other invocations will only read from the cache (apart from manual invalidations and garbage collection).
It's also best to extract useQuery calls to a custom hook:
const useTodos = () => useQuery('todos', fetchFunction)
HomeComponent:
const { data } = useTodos()
TodosComponent:
const { data } = useTodos()

You can create custom hook with useQueryClient
import { useQueryClient } from "react-query";
export const useGetFetchQuery = (name) => {
const queryClient = useQueryClient();
return queryClient.getQueryData(name);
};
And in component just write like this
const data = useGetFetchQuery("todos");

You can use options { refetchOnMount: false } to use it in many other components, it will render only once and make only one api call.

Related

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

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.

How to use Apollo Client's useQuery and useMutation Hooks with Ant Design Forms

In our web application, we use React with Apollo Graphql. As it is recommended in the apollo docs, we want to use the useQuery and useMutation hooks to communicate with the backend server.
Our app works roughly like this: We import a excel-file, parse it in the frontend, send the resulting javascript objects via graphql to the backend and save them in the mongodb database.
In the frontend, we have some forms containing the excel content. After we make some changes, we can save and overwrite the data in the database. In the end, a new excel can be exported from the database data.
import { Form } from "antd";
export default () => {
const [form] = Form.useForm();
const [exelId, setExcelId] = useState("");
const [saveData] = useMutation(SAVEDATA, {
onCompleted: ({ dataFromDB }) => {
form.setFieldsValue(dataFromDB);
},
});
const { data: _ } = useQuery(GETDATA, {
skip: !exelId,
onCompleted: ({ dataFromDB }) => {
form.setFieldsValue(dataFromDB);
},
});
const onFinish = (formValues) => {
saveData({ variables: { formValues } });
};
return (
<Form form={form} onFinish={onFinish}>
<ExcelUpload saveData={saveData} setExcelId={setExcelId} />
<SomeForms />
<Button type="primary">Save</Button>
</Form>
);
};
The problem with our current code is that it causes unnecessary network traffic: After uploading a excel-file, I start a mutation, which returns the required data and sets an "excelId" state. This trigger then the query which fetches the same data. However, I need this query if i reload the page or want to load different data from the database.
I think there are two ways to solve this:
1. Don't use the useMutation hook for filling the form:
That would be easy to implement as I don't have to distinguish between fetching data after an excel-upload or fetching existing data from the data base. But I still need a mutation AND a query.
2. Don't start a query in the excel-uploading process, as the data is received via the mutation:
I think that is complicated to implement with Apollo Client hooks and not the intended way how to use the useQuery hook.
Is there some kind of best practices, how to use the Apollo Client hooks in similar scenarios?
I thought that using those hooks should simplify things, e. g. using the useQuery hook can serve as state. However, using ant design form we also have this form state, which we have to manually set and read with "form.setFieldsValue()" and "form.getFieldsValue()". Is there a good way to combine those with useQuery?

What is the best way to update my react state when data in database changes?

I am workin on a project with React and I need to update react state with react hook when new data is inserted to my database. I am using contextAPI but as far as I am concerned, the way my components are structured I can't update that state with the data I got back from my database. Here is the more detail with my code:
I have a component in which data exists with useState
const [questions, setQuestions] = useState([]);
useEffect(() => {
(async () => {
const resp = await axios.get(
`${process.env.REACT_APP_BACKEND_URL}/product/${productId}/question`
);
setQuestions(resp.data);
})();
}, []);
And I have another component called PostQuestion which sends post request to my backend and insert new question to my database. They are completely unaware of each other's existence. we can pass down the props to the children and nested children by using contextAPI. but PostQuestion component is not the child of that component where all the questions data exist. That's how I understand contextAPI. But both of them are children of another component. But I don't want to place my
const [questions, setQuestions] = useState([]);
in that component which is the parent of those two components. What can I do to update that questions state inside my PostQuestion component?
Here is my PostQuestion component code
const postQuestionHandler = async (e) => {
e.preventDefault();
try {
const resp = await axios({
url: `${process.env.REACT_APP_BACKEND_URL}/product/${productId}/question`,
method: "POST",
data: {
question: questionInput.question.value,
userId,
},
});
if (resp.status === 200) {
setShowForm(false);
}
} catch (err) {
alert(err);
}
};
return <SecondaryBtn disabled={questionInput.question.error}>
Submit Your Question
</SecondaryBtn>
Summary
I have 2 components which don't have parent to child relationship. I want to update the state in component 1 by calling setState when I got back new data in component 2. I don't want to have state in those two components parent because it is already cluttered enough.
If I use Redux, there will be no such problem. Perhaps there is a way to do it in contextAPI too.
I think Context API is the best way.
You can create a context with React.createContext, and then a component that will be encapsulating your context provider. So, a PostsContext and then a PostsProvider that would store the posts state and pass both the state and the setState to the PostsContext.Provider.
Then, to make it simpler, create a usePosts hook and on those children that need to consume the context, simply do const [posts, setPosts] = usePosts().
Alright, so you'd have:
A Context;
A Provider component of your own, that would use the Context;
A hook that would consume the Context with useContext;
Just to make it clearer:
A context:
const PostsContext = React.createContext();
A provider:
function PostsProvider(props) {
const [posts, setPosts] = React.useState([]);
return <PostsContext.Provider value={{ posts, setPosts }} {...props} />
}
A hook:
function usePosts() {
const context = React.useContext(PostsContext);
function postQuestionHandler(newPost) {
// ...
context.setPosts((posts) => [...posts, post]);
}
return [context.posts, postQuestionHandler]
}
And then you can use the PostsProvider to encapsulate the children, and those components can access the context using the usePosts hook.
That would be a complete cenario, you can divide the logic some other ways, like not creating a custom hook nor a custom Provider. I, personally, would prefer to just lift the state in this case, but as you said your parent component is already handling too much, perhaps that's a good option.
Multiple solutions:
You could poll in an interval.
Store the state in a parent component (at some point they have so share one) and pass it down.
Utilize a global state management tool like redux, or the context API of react. I would recommend the context API as it's built in.

Use external data in XState FSM

I'm trying to shim XState into an existing state management system (in a React app) and I'm trying to figure out how to represent the state that is already captured in the legacy state management without duplication.
import {useLegacyState} from 'legacy-state-system'
import {useMachine} from '#xstate/react'
import {MyMachine} from '../machine'
const MyComponent = () => {
const [data, setData] = useLegacyState();
const [state, send] = useMachine(MyMachine)
.....JSX etc....
}
For some of the data there is no overlap, but in at least one case (selecting an item on screen, causes the app to send({type: "SELECT_ITEM", itemId: "xyz"}) and fire setData("XYZ")), both legacy and new systems care about the item. XState is being used for UI State Management but the legacy system has side effects that depends on its internal state, so I can't only have data in XState.
My understanding of XState is that I should represent itemId as continuous data in XState's context, but that duplicates the data and I'm concerned that presents a maintenance issue since all developers forever will need to know to update both simultaneously. Is there a way for XState Context to take a value from a runtime-evaluated function? I know that there's assign if I want to push values into Context but that's susceptible to the same maintenance issue so I'm looking for a way to pull values from legacy-state-manager when I call state.context.itemId.
What about wrapping useMachine and using that instead?
import { useMachine as useXStateMachine } from '#xstate/react'
export const useMachine = (machine, options) => {
const [data, setData] = useLegacyState();
const [state, send] = useXStateMachine(machine)
const context = new Proxy({}, {
get: (_, prop) => {
try {
return state.context[prop] || data[prop]
} catch (_) {
return data[prop]
}
}
})
return [{...state, context}, send]
}
The view or the react layer gets updated every time the data store changes and renders it. Typically in a MVC architecture, these logics are built into the controller, where multiple data stores are combined and the resulting data is returned to the UI. In a hook based approach like how you have used, you an create services, that wraps the datastore logics, within it and return only the data required at the UI level.
import {useCustomService} from './services';
const MyComponent = () => {
const [uiData, updateUI] = useCustomService();
}

React Context API - persist data on page refresh

Let's say we have a context provider set up, along with some initial data property values.
Somewhere along the line, let's say a consumer then modifies those properties.
On page reload, those changes are lost. What is the best way to persist the data so we can retain those data modifications? Any method other than simply local storage?
Yeah, if you want the data to persist across reloads, your options are going to be storing that info server-side (via an api call) or in browser storage (local storage, session storage, cookies). The option you'll want to use depends on what level of persistence you're looking to achieve. Regardless of storage choice, it would likely look something along the lines of
const MyContext = React.createContext(defaultValue);
class Parent extends React.Component {
setValue = (value) => {
this.setState({ value });
}
state = {
setValue: this.setValue,
value: localStorage.getItem("parentValueKey")
}
componentDidUpdate(prevProps, prevState) {
if (this.state.value !== prevState.value) {
// Whatever storage mechanism you end up deciding to use.
localStorage.setItem("parentValueKey", this.state.value)
}
}
render() {
return (
<MyContext.Provider value={this.state}>
{this.props.children}
</MyContext.Provider>
)
}
}
Context doesn't persist in the way you want. Here's a sample of what I've done, using stateless functional with React hooks.
import React, {useState, useEffect} from 'react'
export function sample(){
// useState React hook
const [data, setData] = useState({})
const [moreData, setMoreData] = useState([])
// useState React hook
useEffect(() => {
setData({test: "sample", user: "some person"})
setMoreData(["test", "string"])
}, [])
return data, moreData
}
export const AppContext = React.createContext()
export const AppProvider = props => (
<AppContext.Provider value={{ ...sample() }}>
{props.children}
</AppContext.Provider>
)
Understand from the start that this isa workaround, not a permanent solution. Persisting data is the job of a database, not the client. However, if you need persisted data for development, this is one way. Notice first that I'm using React hooks. This is a fully supported feature as of 16.8. The useEffect() replaces the lifecycle methods found in class declarations like that of TLadd above. He's using componentDidUpdate to persist. The most up-to-date way of doing this is useEffect. When the app is refreshed this method will be called and set some hard-coded data in context.
To use the provider:
import React from 'react'
import Component from './path/to/component'
import { AppProvider } from './path/to/context'
const App = () => {
return (
<AppProvider>
<Component />
</AppProvider>
)
}
When you refresh, data and moreData will still have whatever default values you assign to them.
I am assuming that you are already familiar with setting context and setting up the context provider.
One of the things you can do is to store the value in the browser's Cookie or any storage available to you, and then, in your Context Provider, retrieve the value, if you can find it, you set it, if not, set the default. If the provider file is a class based component, you would like to retrieve this value in the constructor(), otherwise if it is functional, you can use useLayoutEffect() to set it.

Resources