TanStack React Query (formerly known as React Query) is a powerful and popular data-fetching and state management library for React applications. It provides a simple and efficient way to handle server-state in your applications, making it easier to fetch, cache, and synchronize data.
Install React Query
First, you need to install the necessary packages:
npm install @tanstack/react-query
Set Up the React Query Client
Create a file (e.g., react-query.ts
) to configure the React Query client:
// src/react-query.ts
import { QueryClient } from "@tanstack/react-query";
const queryClient = new QueryClient();
export default queryClient;
Provide the QueryClient to Your Application
Wrap your application with the QueryClientProvider
to make the client available throughout your app:
// src/main.tsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { QueryClientProvider } from "@tanstack/react-query";
import queryClient from "./react-query";
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>,
document.getElementById("root")
);
Fetch Data Using React Query
Use the useQuery
hook to fetch data and handle state. Here’s an example of how to integrate it into your StockComponent
:
import React from "react";
import { useQuery } from "@tanstack/react-query";
import StockTabs from "./StockTabs";
// Function to fetch chart data
const fetchChartData = async (symbol: string) => {
const response = await fetch(`http://localhost:3000/chart/${symbol}`);
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
};
const StockComponent = () => {
// Use the useQuery hook to fetch chart data
const { data, error, isLoading } = useQuery(
["chartData", "TCS"], // Query key
() => fetchChartData("TCS") // Fetch function
);
return (
<div className="stock-container">
<StockTabs />
{isLoading && <p>Loading chart data...</p>}
{error && <p>Error fetching chart data: {error.message}</p>}
{data && (
<div>
{/* Render your chart data here */}
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)}
</div>
);
};
export default StockComponent;
Fundamentals
useQuery
. Supports async functions to retrieve data.useMutation
for creating, updating, or deleting data. Useful for non-GET requests.Core Concepts
Advanced Features
TanStack React Query is a powerful tool for managing server-state in React applications. Its efficiency stems from its built-in caching, automatic refetching, and synchronization capabilities. Its popularity is due to its ease of use, declarative data-fetching approach, and rich feature set.
To share data fetched using useQuery
among different components, you have a few options. Here’s a detailed approach on how to achieve this in a React application using TanStack React Query:
Since the data is already fetched and cached by React Query, you can access it from any component that uses useQuery
with the same query key. To share and manipulate this data effectively, follow these steps:
Fetch and Transform Data in a Parent Component
You can fetch the data and add an ID or perform any transformation in a parent component. Then, pass the transformed data down to child components via props or a context provider.
// src/components/StockComponent.tsx
import React from "react";
import { useQuery } from "@tanstack/react-query";
import { useHistory } from "react-router-dom"; // If you use React Router
import StockTabs from "./StockTabs";
import CalendarComponent from "./CalendarComponent"; // Import your CalendarComponent
const fetchChartData = async (symbol: string) => {
const response = await fetch(`http://localhost:3000/chart/${symbol}`);
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
};
const StockComponent = () => {
const { data, error, isLoading } = useQuery(["chartData", "TCS"], () =>
fetchChartData("TCS")
);
// Adding an ID or modifying the data
const transformedData = data ? { ...data, id: "unique-id" } : null;
return (
<div className="stock-container">
<StockTabs />
{isLoading && <p>Loading chart data...</p>}
{error && <p>Error fetching chart data: {error.message}</p>}
{transformedData && (
<div>
{/* Render your chart data */}
<pre>{JSON.stringify(transformedData, null, 2)}</pre>
<CalendarComponent stockData={transformedData} />
</div>
)}
</div>
);
};
export default StockComponent;
Use Context for Global State
If you need to access the stock data in multiple places or components, consider using React Context to provide and consume the data globally.
Create a Context Provider:
// src/contexts/StockContext.tsx
import React, { createContext, useContext, ReactNode } from "react";
interface StockContextType {
stockData: any; // Adjust type if needed
}
const StockContext = createContext<StockContextType | undefined>(undefined);
export const StockProvider: React.FC<{ children: ReactNode }> = ({
children,
}) => {
const { data } = useQuery(["chartData", "TCS"], () =>
fetchChartData("TCS")
);
const stockData = data ? { ...data, id: "unique-id" } : null;
return (
<StockContext.Provider value=>
{children}
</StockContext.Provider>
);
};
export const useStockContext = () => {
const context = useContext(StockContext);
if (context === undefined) {
throw new Error("useStockContext must be used within a StockProvider");
}
return context;
};
Wrap Your Application with the Provider:
// src/main.tsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { QueryClientProvider } from "@tanstack/react-query";
import queryClient from "./react-query";
import { StockProvider } from "./contexts/StockContext";
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<StockProvider>
<App />
</StockProvider>
</QueryClientProvider>
</React.StrictMode>,
document.getElementById("root")
);
Consume the Context in Your Components:
// src/components/CalendarComponent.tsx
import React from "react";
import { useStockContext } from "../contexts/StockContext";
const CalendarComponent = () => {
const { stockData } = useStockContext();
return (
<div>
<h2>Calendar Component</h2>
{stockData && (
<div>
<p>Stock ID: {stockData.id}</p>
{/* Render other parts of stockData */}
</div>
)}
</div>
);
};
export default CalendarComponent;
Using context allows for better scalability in complex applications, as you can avoid prop drilling and centralize data management. Choose the approach that best fits your application’s needs.
Here’s a detailed approach on how to achieve this:
You will create a central context that wraps your entire application and provides access to various pieces of data fetched from different APIs.
Create the Context and Provider:
Define the Context:
// src/contexts/AppDataContext.tsx
import React, { createContext, useContext, ReactNode } from "react";
import {
useQuery,
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query";
// Define types for your data
interface StockData {
id: string;
// Add other properties
}
interface WeatherData {
temperature: number;
// Add other properties
}
interface NewsFeedData {
articles: Array<any>;
// Add other properties
}
interface AppDataContextType {
stockData: StockData | null;
weatherData: WeatherData | null;
newsFeedData: NewsFeedData | null;
}
const AppDataContext = createContext<AppDataContextType | undefined>(
undefined
);
// Fetch functions
const fetchStockData = async () => {
const response = await fetch("http://localhost:3000/stock");
if (!response.ok) throw new Error("Network response was not ok");
return response.json();
};
const fetchWeatherData = async () => {
const response = await fetch("http://localhost:3000/weather");
if (!response.ok) throw new Error("Network response was not ok");
return response.json();
};
const fetchNewsFeedData = async () => {
const response = await fetch("http://localhost:3000/news");
if (!response.ok) throw new Error("Network response was not ok");
return response.json();
};
export const AppDataProvider: React.FC<{ children: ReactNode }> = ({
children,
}) => {
const { data: stockData } = useQuery(["stockData"], fetchStockData);
const { data: weatherData } = useQuery(["weatherData"], fetchWeatherData);
const { data: newsFeedData } = useQuery(
["newsFeedData"],
fetchNewsFeedData
);
const contextValue = {
stockData: stockData || null,
weatherData: weatherData || null,
newsFeedData: newsFeedData || null,
};
return (
<AppDataContext.Provider value={contextValue}>
{children}
</AppDataContext.Provider>
);
};
export const useAppDataContext = () => {
const context = useContext(AppDataContext);
if (context === undefined) {
throw new Error(
"useAppDataContext must be used within an AppDataProvider"
);
}
return context;
};
Wrap Your Application with the Provider:
// src/main.tsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { AppDataProvider } from "./contexts/AppDataContext";
const queryClient = new QueryClient();
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<AppDataProvider>
<App />
</AppDataProvider>
</QueryClientProvider>
</React.StrictMode>,
document.getElementById("root")
);
In any component, you can access the data provided by the context. Here’s how to use the context to access the data in different components:
Example Usage in a Component:
// src/components/StockComponent.tsx
import React from "react";
import { useAppDataContext } from "../contexts/AppDataContext";
const StockComponent = () => {
const { stockData } = useAppDataContext();
return (
<div className="stock-container">
{stockData ? (
<div>
<h2>Stock Data</h2>
<pre>{JSON.stringify(stockData, null, 2)}</pre>
</div>
) : (
<p>No stock data available</p>
)}
</div>
);
};
export default StockComponent;
Example Usage in Another Component:
// src/components/WeatherComponent.tsx
import React from "react";
import { useAppDataContext } from "../contexts/AppDataContext";
const WeatherComponent = () => {
const { weatherData } = useAppDataContext();
return (
<div className="weather-container">
{weatherData ? (
<div>
<h2>Weather Data</h2>
<pre>{JSON.stringify(weatherData, null, 2)}</pre>
</div>
) : (
<p>No weather data available</p>
)}
</div>
);
};
export default WeatherComponent;
AppDataContext
) provides all the fetched data.useQuery
to fetch data within the context provider.This approach centralizes data management and avoids prop drilling, making your application more organized and scalable. It’s particularly useful in larger applications where multiple components need access to various pieces of data.
Move the API call functions to a separate file. This keeps your code modular and easier to test.
// src/api/stockApi.ts
export const fetchStockData = async () => {
const response = await fetch("http://localhost:3000/stock");
if (!response.ok) throw new Error("Network response was not ok");
return response.json();
};
// src/api/weatherApi.ts
export const fetchWeatherData = async () => {
const response = await fetch("http://localhost:3000/weather");
if (!response.ok) throw new Error("Network response was not ok");
return response.json();
};
// src/api/newsApi.ts
export const fetchNewsFeedData = async () => {
const response = await fetch("http://localhost:3000/news");
if (!response.ok) throw new Error("Network response was not ok");
return response.json();
};
Create custom hooks for fetching data using useQuery
. This separates the data fetching logic from the context provider.
// src/hooks/useStockData.ts
import { useQuery } from "@tanstack/react-query";
import { fetchStockData } from "../api/stockApi";
export const useStockData = () => {
return useQuery(["stockData"], fetchStockData);
};
// src/hooks/useWeatherData.ts
import { useQuery } from "@tanstack/react-query";
import { fetchWeatherData } from "../api/weatherApi";
export const useWeatherData = () => {
return useQuery(["weatherData"], fetchWeatherData);
};
// src/hooks/useNewsFeedData.ts
import { useQuery } from "@tanstack/react-query";
import { fetchNewsFeedData } from "../api/newsApi";
export const useNewsFeedData = () => {
return useQuery(["newsFeedData"], fetchNewsFeedData);
};
Use the custom hooks in your context provider to fetch and provide the data.
// src/contexts/AppDataContext.tsx
import React, { createContext, useContext, ReactNode } from "react";
import { useStockData } from "../hooks/useStockData";
import { useWeatherData } from "../hooks/useWeatherData";
import { useNewsFeedData } from "../hooks/useNewsFeedData";
interface StockData {
id: string;
// Add other properties
}
interface WeatherData {
temperature: number;
// Add other properties
}
interface NewsFeedData {
articles: Array<any>;
// Add other properties
}
interface AppDataContextType {
stockData: StockData | null;
weatherData: WeatherData | null;
newsFeedData: NewsFeedData | null;
}
const AppDataContext = createContext<AppDataContextType | undefined>(undefined);
export const AppDataProvider: React.FC<{ children: ReactNode }> = ({
children,
}) => {
const {
data: stockData,
error: stockError,
isLoading: stockLoading,
} = useStockData();
const {
data: weatherData,
error: weatherError,
isLoading: weatherLoading,
} = useWeatherData();
const {
data: newsFeedData,
error: newsError,
isLoading: newsLoading,
} = useNewsFeedData();
if (stockLoading || weatherLoading || newsLoading)
return <div>Loading...</div>;
if (stockError || weatherError || newsError)
return <div>Error loading data</div>;
const contextValue = {
stockData: stockData || null,
weatherData: weatherData || null,
newsFeedData: newsFeedData || null,
};
return (
<AppDataContext.Provider value={contextValue}>
{children}
</AppDataContext.Provider>
);
};
export const useAppDataContext = () => {
const context = useContext(AppDataContext);
if (context === undefined) {
throw new Error("useAppDataContext must be used within an AppDataProvider");
}
return context;
};
useQuery
.This structure maintains separation of concerns and ensures that each piece of your application is responsible for a specific part of the logic. It’s easier to manage and test each part of your application this way.