Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace fetch to use custom React Query implementation with AxiosProvider #431

Merged
merged 12 commits into from
Aug 17, 2024
23 changes: 23 additions & 0 deletions api/AxiosProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client';

import React, { createContext, useContext } from 'react';
import { AxiosInstance } from 'axios';
import axiosInstanceDefault from './axiosInstance';

const AxiosContext = createContext<AxiosInstance | null>(null);

export const useAxios = () => {
const axiosInstance = useContext(AxiosContext);
if (!axiosInstance) {
throw new Error('useAxios must be used within an AxiosProvider');
}
return axiosInstance;
};

interface AxiosProviderProps {
children: React.ReactNode;
}

export const AxiosProvider = ({ children }: AxiosProviderProps) => {
return <AxiosContext.Provider value={axiosInstanceDefault}>{children}</AxiosContext.Provider>;
};
10 changes: 10 additions & 0 deletions api/axiosInstance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import axios from 'axios';

const axiosInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});

export default axiosInstance;
23 changes: 23 additions & 0 deletions api/endpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL;

if (!API_BASE_URL) {
throw new Error('NEXT_PUBLIC_API_BASE_URL is not defined in the environment variables');
}

const ENDPOINTS = {
BLOGS: {
GET_ALL: (limit: number, skip: number) => `${API_BASE_URL}/posts?limit=${limit}&skip=${skip}`,
CREATE: `${API_BASE_URL}/posts`,
},
USERS: {
GET_ALL: `${API_BASE_URL}/users`,
},
CONTACT: {
SUBMIT: `${API_BASE_URL}/contact`,
},
NEWSLETTER: {
SUBSCRIBE: `${API_BASE_URL}/newsletter-subscribers`,
},
};

export default ENDPOINTS;
24 changes: 24 additions & 0 deletions api/mutations/blogs/useAddNewPost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use client';

import { useMutation } from '@tanstack/react-query';
import { useAxios } from '../../AxiosProvider';
import ENDPOINTS from '../../endpoints';

type NewPost = {
title: string;
body: string;
userId: number;
};

const useAddNewPost = () => {
const axios = useAxios();

return useMutation({
mutationFn: async (newPost: NewPost) => {
const response = await axios.post(ENDPOINTS.BLOGS.CREATE, newPost);
return response.data;
},
});
};

export default useAddNewPost;
21 changes: 21 additions & 0 deletions api/mutations/contact/useSubmitContactform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useMutation } from '@tanstack/react-query';
import ENDPOINTS from '../../endpoints';
import { useAxios } from '../../AxiosProvider';

export interface ContactFormData {
name: string;
email: string;
message: string;
cfTurnstileResponse: string;
}

export const useSubmitContactForm = () => {
const axios = useAxios();

return useMutation({
mutationFn: async (formData: ContactFormData) => {
const response = await axios.post(ENDPOINTS.CONTACT.SUBMIT, formData);
return response.data.message;
},
});
};
20 changes: 20 additions & 0 deletions api/mutations/newsletter/useSubmitNewsletterForm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useMutation } from '@tanstack/react-query';
import ENDPOINTS from '../../endpoints';
import { useAxios } from '../../AxiosProvider';

export interface NewsletterFormData {
first_name: string;
email: string;
'cf-turnstile-response': string;
}

export const useSubmitNewsletterForm = () => {
const axios = useAxios();

return useMutation({
mutationFn: async (formData: NewsletterFormData) => {
const response = await axios.post(ENDPOINTS.NEWSLETTER.SUBSCRIBE, formData);
return response.data.message;
},
});
};
17 changes: 17 additions & 0 deletions api/queries/blogs/getBlogs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useQuery } from '@tanstack/react-query';
import fetchBlogPosts from '../../../app/action';
import QUERY_KEYS from '../../queryKeys';

interface GetBlogsParams {
pageTitle: string;
blogCardsNumber: number;
}

const useGetBlogs = ({ pageTitle, blogCardsNumber }: GetBlogsParams) => {
return useQuery({
queryKey: [...QUERY_KEYS.BLOGS.ALL, pageTitle, blogCardsNumber],
queryFn: () => fetchBlogPosts(0, blogCardsNumber),
});
};

export default useGetBlogs;
32 changes: 32 additions & 0 deletions api/queries/blogs/getInfiniteBlogs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useInfiniteQuery } from '@tanstack/react-query';
import QUERY_KEYS from '../../queryKeys';
import fetchBlogPosts from '../../../app/action';
import { BlogCardProps } from '../../../components/reusable-components/blog-card/BlogCard';

interface GetInfiniteBlogsParams {
pageTitle: string;
blogCardsNumber: number;
}
const useGetInfiniteBlogs = ({ pageTitle, blogCardsNumber }: GetInfiniteBlogsParams) => {
return useInfiniteQuery<
BlogCardProps[],
Error,
BlogCardProps[],
[string, string, string, string],
number
>({
queryKey: [
QUERY_KEYS.BLOGS.INFINITE[0],
QUERY_KEYS.BLOGS.INFINITE[1],
pageTitle,
blogCardsNumber.toString(),
],
queryFn: ({ pageParam }) => fetchBlogPosts(pageParam, blogCardsNumber),
initialPageParam: 0,
getNextPageParam: (lastPage, allPages) => {
return lastPage.length === blogCardsNumber ? allPages.length * blogCardsNumber : undefined;
},
});
};

export default useGetInfiniteBlogs;
17 changes: 17 additions & 0 deletions api/queries/users/getUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useAxios } from '../../AxiosProvider';
import ENDPOINTS from '../../endpoints';
import QUERY_KEYS from '../../queryKeys';

const useGetUsers = () => {
const axios = useAxios();

return {
queryKey: QUERY_KEYS.USERS.ALL,
queryFn: async () => {
const { data } = await axios.get(ENDPOINTS.USERS.GET_ALL);
return data;
},
};
};

export default useGetUsers;
11 changes: 11 additions & 0 deletions api/queryKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const QUERY_KEYS = {
BLOGS: {
ALL: ['blogPosts'],
INFINITE: ['infiniteBlogPosts', 'infinite'] as const,
},
USERS: {
ALL: ['users'],
},
} as const;

export default QUERY_KEYS;
38 changes: 19 additions & 19 deletions app/action.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
'use server';

import BlogCard, { BlogCardProps } from '../components/reusable-components/blog-card/BlogCard';
import axios from 'axios';
import axiosInstance from '../api/axiosInstance';
import { BlogCardProps } from '../components/reusable-components/blog-card/BlogCard';

const fetchBlogPosts = async (nextPosts: number, pageTitle: string, blogCardsNumber: number) => {
const fetchBlogPosts = async (
nextPosts: number,
blogCardsNumber: number
): Promise<BlogCardProps[]> => {
try {
const response = await fetch(
`https://dummyjson.com/posts?limit=${blogCardsNumber}&skip=${nextPosts}`
);

const data = await response.json();

return data?.posts.map((post: BlogCardProps) => {
return (
<BlogCard
key={post?.id}
id={post?.id}
title={post?.title}
body={post?.body}
pageTitle={pageTitle}
/>
);
const { data } = await axiosInstance.get('/posts', {
params: {
limit: blogCardsNumber,
skip: nextPosts,
},
});
return data.posts;
} catch (error: any) {
throw new Error(error);
if (axios.isAxiosError(error)) {
throw new Error(
error.response?.data?.message || 'An error occurred while fetching blog posts'
);
}
throw new Error('An unexpected error occurred');
}
};

Expand Down
52 changes: 0 additions & 52 deletions app/addNewPost.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion app/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import BlogList from '../../components/module-components/blog-list/BlogList';
import Tab from '../../components/reusable-components/tab/Tab';
// import AddNewPost from '../addNewPost';
// import AddNewPost from '../../components/module-components/blog/addNewPost';

const Blog = () => {
return (
Expand Down
13 changes: 0 additions & 13 deletions app/getUsers.tsx

This file was deleted.

25 changes: 15 additions & 10 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import React, { Suspense } from 'react';
import Head from 'next/head';
import Script from 'next/script';
import './styles/main.scss';
import { ToastContainer } from 'react-toastify';
import Loading from './loading';
import Footer from '../components/reusable-components/footer/Footer';
import Navigation from '../components/reusable-components/navigation/Navigation';
import ReactQueryProvider from '../utils/providers/ReactQueryProvider';
import { ThemeProvider } from './context/themeContext';
import styles from './page.module.scss';
import { AuthProvider } from './context/authContext';
import { AxiosProvider } from '../api/AxiosProvider';

const montserrat = Montserrat({ subsets: ['latin'], weight: ['400', '500', '700'] });

Expand Down Expand Up @@ -50,17 +52,20 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => {
/>
</noscript>
)}
<ToastContainer />
<ThemeProvider>
<ReactQueryProvider>
<AuthProvider>
<Navigation />
<main className={styles.main}>
<Suspense fallback={<Loading />}>{children}</Suspense>
<SpeedInsights />
</main>
<Footer />
</AuthProvider>
</ReactQueryProvider>
<AxiosProvider>
<ReactQueryProvider>
<AuthProvider>
<Navigation />
<main className={styles.main}>
<Suspense fallback={<Loading />}>{children}</Suspense>
<SpeedInsights />
</main>
<Footer />
</AuthProvider>
</ReactQueryProvider>
</AxiosProvider>
</ThemeProvider>
</body>
</html>
Expand Down
Loading
Loading