How to refetch queries from sibling component with react-query - javascript

I was wondering how to refetch a query from another sibling component with react-query.
Lets say I have Component A
import {useQuery} from "react-query";
function ComponentA(){
const getData = async () => data //returns api data
const {data, refetch} = useQuery(["queryA"], async () => await getData())
return(
<div>
{data}
</div>
)}
And in Component B
import {useQuery, QueryClient} from "react-query";
function ComponentB(){
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
},
},
})
const refreshQueryFromComponentA = async () => {
await queryClient.refetchQueries(["queryA"], { active: true })
}
return(
<div>
<button onClick={refreshQueryFromComponentA}/>
</div>
)}
And in Page.js
import ComponentA from "./componentA";
import ComponentB from "./componentB";
function Page(){
return(
<div>
<ComponentA/>
<ComponentB/>
</div>
)}
When I call the refreshQueryFromComponentA function in ComponentB I do not see the query refresh in ComponentA or a new call to the backend in the network tab. I also use the correct query-key but I am only able to refetch the query in ComponentA with the refetch() function which comes from the useQuery function.
I think it's possible what I'm trying to do, since react-query uses context and should be available throughout the whole application. But maybe I'm using the wrong method or misinterpreted it.
Thanks in advance everyone!

There needs to be one QueryClient at the top of your app. The QueryClient holds the queryCache, which stores your data. If you create a new one in componentB, it won’t share anything with componentA. Also, make sure to add it to the QueryClientProvider and retrieve it via useQueryClient(). The client also needs to be stable, so don’t create a new one each render. This is from the first page of the docs:
import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
2
3 const queryClient = new QueryClient()
4
5 export default function App() {
6 return (
7 <QueryClientProvider client={queryClient}>
8 <Example />
9 </QueryClientProvider>
10 )
11 }

Related

How to use SSR data fetching in nextjs with apollo client?

I am try to use apollo-client with nextjs. Here I want to fetch data in getServerSideProps. Suppose I have 2 components and one page-
section.tsx this is component-1
const Section = () => {
return (
<div>
Section
</div>
);
};
export default Section;
mobile.tsx this is component 2
const Mobile = () => {
return (
<div>
Mobile
</div>
);
};
export default Mobile;
Now I call this two component into home page.
index.tsx-
const Home: NextPage = () => {
return (
<Container disableGutters maxWidth="xxl">
<Section />
<Mobile />
</Container>
);
};
export default Home;
export const getServerSideProps: GetServerSideProps = async () => {
const { data } = await client.query({ query: GET_USER_LIST })
return { props: {} }
}
Here you can see that in getServerSideProps I already fetch my data.
My question is How can I directly access this data form Section component and Mobile component without passing props. I don't want to pass props, because if my component tree will be more longer, then it will be difficult to manage props.
From appollo docs, I alreay know that apollo client do the same with redux state manager. So please tell me how can I access this data from any component that already fetched in getServerSideProps. Is it possible?. If not then how can what is the solutions.
How about using context api if you want to avoid prop drilling? By putting data into context, you can access it from any child component. Get the data from the SSR and put it into the context.
Below is the example
import React, {createContext, useContext} from "react";
export default function Home({data}) {
return <DataContext.Provider value={{data}}>
<div>
<Section/>
<Mobile/>
</div>
</DataContext.Provider>
}
export async function getServerSideProps() {
const data = 'hello world' //Get from api
return {
props: {data},
}
}
function Section() {
return <div>
Section
</div>
}
function Mobile() {
const context = useContext(DataContext);
return <div>
Mobile {context.data}
</div>
}
const DataContext = createContext({});
Now, as long as your tree structure grows within the DataContext provider, each child node will have access to data in the context.
Hope this helps.

In React Router V6, can I still get RouteComponentProps (or access to history and location) in class components? [duplicate]

The version of react-router-dom is v6 and I'm having trouble with passing values to another component using Navigate.
I want to pass selected rows to another page called Report. But, I'm not sure I'm using the right syntax for navigate method and I don't know how to get that state in the Report component.
Material-ui Table: I'm trying to use redirectToReport(rowData) in onClick parameter.
function TableRows(props){
return (
<MaterialTable
title="Leads"
columns={[
...
]}
data = {props.leads}
options={{
selection: true,
filtering: true,
sorting: true
}}
actions = {[{
position: "toolbarOnSelect",
tooltip: 'Generate a report based on selected leads.',
icon: 'addchart',
onClick: (event, rowData) => {
console.log("Row Data: " , rowData)
props.redirect(rowData)
}
}]}
/>
)}
LeadTable component
export default function LeadTable(props) {
let navigate = useNavigate();
const [leads, setLeads] = useState([]);
const [loading, setLoading] = useState(true);
async function fetchUrl(url) {
const response = await fetch(url);
const json = await response.json();
setLeads(json[0]);
setLoading(false);
}
useEffect(() => {
fetchUrl("http://localhost:5000/api/leads");
}, []);
function redirectToReport(rowData) {
navigate('/app/report', { state: rowData }); // ??? I'm not sure if this is the right way
}
return(
<div>
<TableRows leads={leads} redirect={redirectToReport}></TableRows>
</div>
)}
Report component
export default function ReportPage(state) {
return (
<div>
{ console.log(state) // This doesn't show anything. How to use the state that were passed from Table component here?}
<div className = "Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);}
version 6 react-router-dom
I know the question got answered but I feel this might be helpful example for those who want to use functional components and they are in search of passing data between components using react-router-dom v6.
Let's suppose we have two functional components, first component A, second component B. The component A wants to share data to component B.
usage of hooks: (useLocation,useNavigate)
import {Link, useNavigate} from 'react-router-dom';
function ComponentA(props) {
const navigate = useNavigate();
const toComponentB=()=>{
navigate('/componentB',{state:{id:1,name:'sabaoon'}});
}
return (
<>
<div> <a onClick={()=>{toComponentB()}}>Component B<a/></div>
</>
);
}
export default ComponentA;
Now we will get the data in Component B.
import {useLocation} from 'react-router-dom';
function ComponentB() {
const location = useLocation();
return (
<>
<div>{location.state.name}</div>
</>
)
}
export default ComponentB;
Note: you can use HOC if you are using class components as hooks won't work in class components.
Your navigate('/app/report', { state: rowData }); looks correct to me.
react-router-v6
If you need state, use navigate('success', { state }).
navigate
interface NavigateFunction {
(
to: To,
options?: { replace?: boolean; state?: any }
): void;
(delta: number): void;
}
Your ReportPage needs to be rendered under the same Router that the component doing the push is under.
Route props are no longer passed to rendered components, as they are now passed as JSX literals. To access route state it must be done so via the useLocation hook.
function ReportPage(props) {
const { state } = useLocation();
console.log(state);
return (
<div>
<div className="Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);
}
If the component isn't able to use React hooks then you still access the route state via a custom withRouter Higher Order Component. Here's an example simple withRouter HOC to pass the location as a prop.
import { useLocation, /* other hooks */ } from 'react-router-dom';
const withRouter = WrappedComponent => props => {
const location = useLocation();
// other hooks
return (
<WrappedComponent
{...props}
{...{ location, /* other hooks */ }}
/>
);
};
Then access via props as was done in pre-RRDv6.
class ReportPage extends Component {
...
render() {
console.log(this.props.location.state);
return (
<div>
<div className="Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);
}
}
2 things (just a suggestion):
Rather than a ternary use &&
{location && <div>{location.state.name}</div>}
Why are you checking location and rendering location.state.name? I would use the check on the data you are fetching or make sure the data returns null or your value.
On Sabaoon Bedar's Answer, you can check if there is any data or not before showing it :
Instead of this <div>{location.state.name}</div>
Do this { location != null ? <div>{location.state.name}</div> : ""}
if you want to send data with usenavigate in functional component you can use like that
navigate(`/take-quiz/${id}`, { state: { quiz } });
and you can get it with uselocation hook like this
const location = useLocation();
location.state.quiz there is your data
But you cannot get this data in props it;s tricky part ;)!!
on SABAOON BEDAR answer,
from component A: navigate('/', {state:"whatever"}
in component B: console.log(location.state) //output = whatever

React not getting API results on first render

I'm running into a problem in development where the page finishes loading before the data gets sent from the API. I've tried using asynchronous functions but that doesn't help even though I'm sure it should. I think I might be doing it wrong. Below is an example of a page in my app where I am experiencing this issue:
import React, {useEffect, useState} from 'react';
import { useRouter } from 'next/router';
import axios from 'axios';
import Link from 'next/link';
import { Card,
Button
} from 'react-bootstrap';
export default function SingleTour() {
const [tour, setTour]= useState({});
const [tourShows, setTourShows] = useState({});
const router = useRouter();
const {slug} = router.query;
useEffect( () => {
let enpoints = [
`http://localhost:3000/tours/${slug}`,
`http://localhost:3000/listshows/${slug}`
]
axios.all(
enpoints.map((endpoint) =>
axios.get(endpoint)))
.then(response => {
console.log(response)
setTour(response[0].data)
setTourShows(response[1].data)
})
.catch(error => console.log(error))
}, [slug])
console.log(tour);
return (
<div className='container'>
<div>
<h1></h1>
</div>
<h3>Shows</h3>
<div className='card-display'>
{tourShows.data ? (
tourShows.data.map(({attributes, id}) => (
<Link href={`/shows/${id}`} passHref key={id}>
<Card border="secondary" style={{ width: '18rem', margin: '1rem'}}>
<Card.Body>
<Card.Title>Show {id}</Card.Title>
<Card.Text>{attributes.date}</Card.Text>
<Card.Text>{attributes.location}</Card.Text>
<Card.Text>Head Count {attributes.headcount}</Card.Text>
</Card.Body>
</Card>
</Link>
))
) : 'LOADING ...'}
</div>
</div>
)
}
Any help is greatly appreciated. I am also using Next JS if that makes a difference.
If you use useEffect hook it is expected that you will have a render before the hook fires to fetch the data, that is the way useEffect works.
If you want to fetch your data inside the next app you have to use getServerSideProps instead, fetch the data there and pass that as a prop to the component. See the docs here: https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props
This is the way React works. useEffect will attempt to fetch the data and React will continue doing it's business, render the component. You can put an if statement at the beginning of the return statement, for instance checking the length of the tourShows.data, it the length is 0 return nothing, otherwise return as you do now.

React router / react query does not cancel/make request on navigation - React/ typescript

In my app, I am using react-router v5 and react/typescript I have a component that uses the react-query and fetches some data. At the moment it only fetches the data when the component is rendered the first time, When navigating the request does not get cancelled and navigating back it does not make a new request. This component takes in an id parameter which fetches the data based on the id, so it needs to either refresh the component or maybe I need to add the method into the useEffect hook?
Routing component
import React from 'react';
import { BrowserRouter, Route, Switch} from 'react-router-dom';
import { QueryClient, QueryClientProvider } from 'react-query';
import { RouteComponentProps } from "react-router-dom";
import Component1 from '../Component1';
import Component2 from '../Component2';
const queryClient = new QueryClient()
const Routing: React.FunctionComponent = () => {
return (
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<Switch>
<Route exact path="/" component={Component1} />
<Route path="/details/:id" render={(props: RouteComponentProps<any>) => <Component2 {...props}/>} />
<Route component={NotFound} />
</Switch>
</BrowserRouter>
</QueryClientProvider>
)
}
export default Routing;
Component2 (id)
import React from 'react';
import { useQuery } from 'react-query';
import { RouteComponentProps, useLocation } from "react-router-dom";
interface stateType {
model: { pathname: string },
start: { pathname: string | Date }
}
const Component2: React.FunctionComponent<RouteComponentProps<any>> = (props) => {
const { state } = useLocation<stateType>();
let alertInnerId = props.match.params.id;
const fetchChart = async () => {
const res = await fetch(`/detail/${id}`);
return res.json();
};
const { data, status } = useQuery('planeInfo', fetchPlane, {
staleTime: 5000,
});
return (
<>
{status === 'error' && (
<div className="mt-5">Error fetching data!</div>
)}
{status === 'loading' && (
<div className="mt-5">Loading data ...
</div>
)}
{status === 'success' && (
{data.map(inner => {
return (
<p>{inner.id}</p>
)
})}
)}
</div>
</>
)
}
export default Component2;
In the Component 1 I am programmatically navigating:
onClick={() => history.push(`/detail/${id}}`, { model: plane.model, start: formattedStartDateTime })}>
Either way by programmatically or normal, its still the same.
[...] and navigating back it does not make a new request.
First of all, according to your code, as per the staleTime option that is set as an option on useQuery itself, the cache should invalidate every five seconds. So each time the useQuery hook is mounted (such as on route change), if five seconds have passed, a new request should be made. Your code does appear to be incomplete though as you're referencing id which appears to be undefined.
In any case, since you are requesting details of a resource with an ID, you should consider using a query key like: [planeInfo, id] instead of planeInfo alone. From the documentation:
Since query keys uniquely describe the data they are fetching, they
should include any variables you use in your query function that
change. For example:
function Todos({ todoId }) {
const result = useQuery(['todos', todoId], () =>
fetchTodoById(todoId))
}
To handle canceling the request on navigation:
You can't wrap the useQuery hook from React Query in a useEffect hook, but rather you can use use the return function of useEffect to clean up your useQuery request, effectively canceling the request when the component unmounts. With useQuery there are two ways (possibly more) to cancel a request:
use the remove method exposed on the returned object of useQuery
use the QueryClient method: cancelQueries
(see: useQuery reference here)
see: QueryClient reference here and specifically cancelQueries
Using remove with useEffect
(I've only kept the relevant bits of your code)
const Component2: React.FunctionComponent <RouteComponentProps<any>> = (props) => {
const fetchChart = async() => {
const res = await fetch(`/detail/${id}`);
return res.json();
};
const {
data,
status,
/** access the remove method **/
remove
} = useQuery('planeInfo', fetchPlane, {
staleTime: 5000,
});
useEffect(() => {
/** when this component unmounts, call it **/
return () => remove()
/** make sure to set an empty deps array **/
}, [])
/** the rest of your component **/
}
Calling remove like this will cancel any ongoing request, but as its name suggests, it also removes the query from the cache. Depending on whether you need to keep the data in cache or not, this may or may not be a viable strategy. If you need to keep the data, you can instead use the canceQueries method.
Using cancelQueries with useEffect
Much like before except here you need to export your queryClient instance from the routing component file (as you have it defined there) and then you're importing that instance of QueryClient into Component2 and calling cancelQueries on the cache key from useEffect:
import { queryClient } from "./routing-component"
const Component2: React.FunctionComponent <RouteComponentProps<any>> = (props) => {
const fetchChart = async() => {
const res = await fetch(`/detail/${id}`);
return res.json();
};
const {
data,
status,
} = useQuery(['planeInfo', id], fetchPlane, {
staleTime: 5000,
});
useEffect(() => {
/** when this component unmounts, call it **/
return () => queryClient.cancelQueries(['planeInfo', id], {exact: true, fetching: true})
}, [])
/** the rest of your component **/
}
Here you see that I've implemented the query key as I suggested before, with the id as well. You can see why having a more precise reference to the cached object can be beneficial. I'm also using two query filters: exact and fetching. Setting exact to true will make sure React Query doesn't use pattern matching and cancel a broader set of queries. You can decide whether or not this is necessary for your implementation needs. Setting fetching to true will make sure React Query includes and cancels and queries that are currently fetching data.
Just note that by depending on useEffect, it is in some cases possible for it's parent component to unmount due to factors other than the user navigating away from the page (such as a modal). In such cases, you should move your useQuery up in the component tree into a component that will only unmount when a user navigates, and then pass the result of useQuery into the child component as props, to avoid premature cancellations.
Alternatively you could use Axios instead of fetch. With Axios you can cancel a request using a global cancel token, and combine executing that cancellation with React Router's useLocation (example here). You could of course also combine useLocation listening to route changes with QueryClient.cancelQueries. There are in fact, many possible approaches to your question.

React Context Does Not Propagate Changes to Other Consumers After Successful Provider Update

This is my first React Context implementation. I am using Gatsby and in my layout.js I added Context (with objects and handler function) that successfully gets passed to Consumer:
import AppContext from "./AppContext"
const Layout = ({ children }) => {
let doAuthenticate = () => {
authState = {
isAuth: authState.isAuth === true ? false : true,
}
}
let authState = {
isAuth: false,
doAuthenticate: doAuthenticate
}
return (
<>
<AppContext.Provider value={authState}>
<main>{children}</main>
</AppContext.Provider>
</>
)
I successfully execute function in Consumer:
<AppContext.Consumer>
{ value =>
<button onClick={value.doAuthenticate}Sign in</button>
}
</AppContext.Consumer>
I also see the value in doAuthenticate successfully gets updated.
However, another Consumer that is listening to Provider does not update the value. Why?
When you use Gatsby, each instance of the Page will we wrapped with the Layout component and hence you will see that instead of creating one Context that is shared between pages, you end up creating multiple contexts.
Now multiple contexts cannot communicate with each other
The solution here is to make use of wrapRootElement api in gatsby-ssr.js and gatsby-browser.js to wrap all your pages with a single layout component
import React from "react";
import Layout from "path/to/Layout";
const wrapRootElement = ({ element }) => {
return (
<Layout>
{element}
</Layout>
)
}
export { wrapRootElement };

Categories