Sean McQuaid
Twitter - SeanMcQuaidCode
GitHub - seanmcquaid
const PostsPage = () => {
const [isLoading, setIsLoading] = useState(true);
const [posts, setPosts] = useState<Post[]>([]);
const [error, setError] = useState<unknown | null>(null);
useEffect(() => {
setIsLoading(true);
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(data => {
setPosts(data as Post[]);
})
.catch(err => {
setError(err);
})
.finally(() => {
setIsLoading(false);
});
}, []);
return (
<div>
<h1>Hello Connect.tech!</h1>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
};const getPosts = (): ThunkAction<void, RootState, unknown, UnknownAction> => async dispatch => {
dispatch({ type: 'GET_POSTS_REQUEST' });
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await response.json();
dispatch({ type: 'GET_POSTS_SUCCESS', payload: data as Post[] });
} catch (err) {
dispatch({ type: 'GET_POSTS_FAILURE', payload: err });
}
};
const PostsPage = () => {
const dispatch = useAppDispatch();
const posts = useAppSelector(state => state.posts);
useEffect(() => {
dispatch(getPosts());
}, [dispatch]);
return (
<div>
<h1>Hello Connect.tech!</h1>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
};Good news!!!
import { useQuery } from '@tanstack/react-query';
const PostsList = () => {
const { data, isError, isLoading } = useQuery({
queryKey: ['getPosts'],
queryFn: () =>
fetch('https://jsonplaceholder.typicode.com/posts').then(
res => res.json() as Promise<Post[]>,
),
});
return <ul>{data?.map(post => <li key={post.id}>{post.title}</li>)}</ul>;
};import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
const queryClient = useQueryClient();
const { data } = useQuery({
queryKey: ['getPosts'],
queryFn: () =>
fetch('https://jsonplaceholder.typicode.com/posts').then(
res => res.json() as Promise<Post[]>,
),
});
const { mutate } = useMutation({
mutationKey: ['createPost'],
mutationFn: (body: { title: string; body: string }) =>
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body,
}).then(res => res.json()),
onSuccess: () => {
queryClient.invalidateQueries(['getPosts']);
},
onError: () => {
console.log('Do something on error here!');
},
});import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
const postsApi = createApi({
reducerPath: 'postsApi',
baseQuery: fetchBaseQuery({
baseUrl: 'https://jsonplaceholder.typicode.com/',
}),
tagTypes: ['Posts'],
endpoints: builder => ({
getPosts: builder.query<Post[], void>({
query: () => ({ url: 'posts' }),
providesTags: result =>
result
? result.map(({ id }) => ({ type: 'Posts', id }))
: [{ type: 'Posts', id: 'LIST' }],
}),
createPost: builder.mutation<
Post,
{
title: string;
body: string;
}
>({
query: body => ({
url: 'posts',
method: 'POST',
body,
}),
invalidatesTags: ['Posts'],
}),
}),
});
export const { useGetPostsQuery, useCreatePostMutation } = postsApi;
export default postsApi;import { combineReducers, configureStore } from '@reduxjs/toolkit';
const rootReducer = combineReducers({
[appSlice.name]: appSlice.reducer,
[postsApi.reducerPath]: postsApi.reducer,
});
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware().concat(postsApi.middleware),
});
export default store;import { useGetPostsQuery } from '@/store/postsApi';
const PostsList = () => {
const { data, isError, isLoading } = useGetPostsQuery();
return <ul>{data?.map(post => <li key={post.id}>{post.title}</li>)}</ul>;
};// Use this in a component
import { useCreatePostMutation } from '@/store/postsApi';
const [mutate, { isError, isLoading, isSuccess, data }] = useCreatePostMutation();import { createSlice } from '@reduxjs/toolkit';
import { authApi, User } from '../../app/services/auth';
interface AuthState {
user: User | null;
token: string | null;
}
const initialState: AuthState = {
user: null,
token: null,
};
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {},
extraReducers: builder => {
builder.addMatcher(
authApi.endpoints.login.matchFulfilled,
(state, { payload }) => {
state.token = payload.token;
state.user = payload.user;
},
);
},
});
export default authSlice;async function getData() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
next: { tags: ['posts'] },
});
return res.json() as Promise<Post[]>;
}
const PostsList = async () => {
const posts = await getData();
return <ul>{posts?.map(post => <li key={post.id}>{post.title}</li>)}</ul>;
};
export default PostsList;// loading.tsx in the same directory
const Loading = () => {
return <LoadingSpinner/>;
}
export default Loading;// error.tsx in the same directory
'use client' // Error components must be Client Components
import { useEffect } from 'react'
const Error = ({
error,
reset,
}: {
error: Error;
reset: () => void;
}) => {
useEffect(() => {
// Log the error to an error reporting service
console.error(error)
}, [error])
return (
<div>
<h2>Something went wrong!</h2>
<button
onClick={
// Attempt to recover by trying to re-render the segment
() => reset()
}
>
Try again
</button>
</div>
)
}
export default Error;// next.config.js
module.exports = {
experimental: {
serverActions: true,
},
};'use server';
import { revalidateTag } from 'next/cache';
const deletePost = async (formData: FormData) => {
const id = formData.get('id');
await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
method: 'DELETE'
});
revalidateTag('posts');
}
export default deletePost;'use client'
import deletePost from './deletePost';
import { experimental_useFormStatus as useFormStatus } from 'react-dom'
const ClientComponent = () => {
const { pending } = useFormStatus();
return (
<form action={deletePost}>
<input type="text" name="id" />
<button type="submit" disabled={pending}>Delete Post</button>
</form>
)
};
export default ClientComponent;// React Query
import { useQuery } from '@tanstack/react-query';
const PostsList = () => {
const { data } = useQuery({
queryKey: ['getPosts'],
queryFn: () =>
fetch('https://jsonplaceholder.typicode.com/posts').then(
res => res.json() as Promise<Post[]>,
),
});
return <ul>{data?.map(post => <li key={post.id}>{post.title}</li>)}</ul>;
};
export default PostsList;
// RTK Query
import { useGetPostsQuery } from '@/store/postsApi';
const PostsList = () => {
const { data } = useGetPostsQuery();
return <ul>{data?.map(post => <li key={post.id}>{post.title}</li>)}</ul>;
};
export default PostsList;// Next.js + React Server Components
async function getData() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
next: { tags: ['posts'] },
});
return res.json() as Promise<Post[]>;
}
const PostsList = async () => {
const posts = await getData();
return <ul>{posts?.map(post => <li key={post.id}>{post.title}</li>)}</ul>;
};
export default PostsList;