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

With react query #2

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
5 changes: 4 additions & 1 deletion src/components/GlobalLoader.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React from 'react'
import { useIsFetching } from 'react-query'
import { Loader } from './styled'

export default function GlobalLoader() {
const isFetching = useIsFetching()

return (
<Loader
css={`
Expand All @@ -12,7 +15,7 @@ export default function GlobalLoader() {
transition: 0.3s ease;
`}
style={{
opacity: 1,
opacity: isFetching ? 1 : 0,
}}
/>
)
Expand Down
40 changes: 26 additions & 14 deletions src/hooks/useCreatePost.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import React from 'react'
import axios from 'axios'
import { useMutation } from 'react-query'

import { queryCache } from '../'

export default function useCreatePost() {
const [state, setState] = React.useReducer((_, action) => action, {
isIdle: true,
})
return useMutation(
(values) => axios.post('/api/posts', values).then((res) => res.data),
{
onMutate: (values) => {
queryCache.cancelQueries('posts')

const mutate = React.useCallback(async (values) => {
setState({ isLoading: true })
try {
const data = axios.post('/api/posts', values).then((res) => res.data)
setState({ isSuccess: true, data })
} catch (error) {
setState({ isError: true, error })
}
}, [])
const oldPosts = queryCache.getQueryData('posts')

return [mutate, state]
queryCache.setQueryData('posts', (old) => {
return [
...old,
{
...values,
id: Date.now(),
isPreview: true,
},
]
})

return () => queryCache.setQueryData('posts', oldPosts)
},
onError: (error, values, rollback) => rollback(),
onSuccess: () => queryCache.invalidateQueries('posts'),
}
)
}
31 changes: 17 additions & 14 deletions src/hooks/useDeletePost.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import React from 'react'
import axios from 'axios'
import { useMutation } from 'react-query'

import { queryCache } from '../'

export default function useDeletePost() {
const [state, setState] = React.useReducer((_, action) => action, {
isIdle: true,
})
return useMutation(
(postId) => axios.delete(`/api/posts/${postId}`).then((res) => res.data),
{
onError: (error, variables, rollback) => {
rollback && rollback()
},
onSuccess: (data, postId) => {
const previousPosts = queryCache.getQueryData('posts')

const mutate = React.useCallback(async (postId) => {
setState({ isLoading: true })
try {
await axios.delete(`/api/posts/${postId}`).then((res) => res.data)
setState({ isSuccess: true })
} catch (error) {
setState({ isError: true, error })
}
}, [])
const optimisticPosts = previousPosts.filter((d) => d.id !== postId)

return [mutate, state]
queryCache.setQueryData('posts', optimisticPosts)
queryCache.invalidateQueries('posts')
},
}
)
}
37 changes: 14 additions & 23 deletions src/hooks/usePost.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
import React from 'react'
import axios from 'axios'
import { useQuery } from 'react-query'

export const fetchPost = (postId) =>
import { queryCache } from '../'

export const fetchPost = (_, postId) =>
axios.get(`/api/posts/${postId}`).then((res) => res.data)

export default function usePost(postId) {
const [state, setState] = React.useReducer((_, action) => action, {
isLoading: true,
export const prefetchPost = (postId) => {
queryCache.prefetchQuery(['posts', String(postId)], fetchPost, {
staleTime: 5000,
})
}

const fetch = React.useCallback(async () => {
setState({ isLoading: true })
try {
const data = await fetchPost(postId)
setState({ isSuccess: true, data })
} catch (error) {
setState({ isError: true, error })
}
}, [postId])

React.useEffect(() => {
fetch()
}, [fetch])

return {
...state,
fetch,
}
export default function usePost(postId) {
return useQuery(['posts', postId], fetchPost, {
placeholderData: () =>
queryCache.getQueryData('posts')?.find((d) => d.id == postId),
staleTime: 2000,
})
}
31 changes: 10 additions & 21 deletions src/hooks/usePosts.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
import React from 'react'
import axios from 'axios'
import { useQuery } from 'react-query'

export default function usePosts() {
const [state, setState] = React.useReducer((_, action) => action, {
isLoading: true,
})
import { queryCache } from '../'

const fetch = async () => {
setState({ isLoading: true })
try {
const data = await axios.get('/api/posts').then((res) => res.data)
setState({ isSuccess: true, data })
} catch (error) {
setState({ isError: true, error })
}
}
const fetchPosts = () => axios.get('/api/posts').then((res) => res.data)

React.useEffect(() => {
fetch()
}, [])
export const prefetchPost = (postId) => {
queryCache.prefetchQuery(['posts', String(postId)], fetchPosts, {
staleTime: 5000,
})
}

return {
...state,
fetch,
}
export default function usePosts() {
return useQuery('posts', fetchPosts)
}
37 changes: 21 additions & 16 deletions src/hooks/useSavePost.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
import React from 'react'
import axios from 'axios'
import { useMutation } from 'react-query'

import { queryCache } from '../'

export default function useSavePost() {
const [state, setState] = React.useReducer((_, action) => action, {
isIdle: true,
})
return useMutation(
(values) =>
axios.patch(`/api/posts/${values.id}`, values).then((res) => res.data),
{
onMutate: (values) => {
queryCache.cancelQueries('posts')

const mutate = React.useCallback(async (values) => {
setState({ isLoading: true })
try {
const data = await axios
.patch(`/api/posts/${values.id}`, values)
.then((res) => res.data)
setState({ isSuccess: true, data })
} catch (error) {
setState({ isError: true, error })
}
}, [])
const oldPost = queryCache.getQueryData(['posts', values.id])

return [mutate, state]
queryCache.setQueryData(['posts', values.id], values)

return () => queryCache.setQueryData(['posts', values.id], oldPost)
},
onError: (error, values, rollback) => rollback(),
onSuccess: (data, variables) => {
queryCache.invalidateQueries('posts')
queryCache.invalidateQueries(['posts', variables.id])
},
}
)
}
67 changes: 46 additions & 21 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
import React from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { ReactQueryCacheProvider, QueryCache } from 'react-query'
import { ReactQueryDevtools } from 'react-query-devtools'
import { hydrate, dehydrate } from 'react-query/hydration'
//

import { Wrapper, Main } from './components/styled'
import Sidebar from './components/Sidebar'
import GlobalLoader from './components/GlobalLoader'

import Admin from './screens/admin'
import AdminPost from './screens/admin/Post'
import Blog from './screens/blog'
import BlogPost from './screens/blog/Post'

export const queryCache = new QueryCache()

function restoreCache() {
if (typeof localStorage !== 'undefined') {
let cache = localStorage.getItem('queryCache_1')
if (cache) {
hydrate(queryCache, JSON.parse(cache))
}

queryCache.subscribe((cache) => {
localStorage.setItem('queryCache_1', JSON.stringify(dehydrate(cache)))
})
}
}

restoreCache()

function SafeHydrate({ children }) {
return (
<div suppressHydrationWarning>
Expand All @@ -21,27 +42,31 @@ function SafeHydrate({ children }) {
export default function App() {
return (
<SafeHydrate>
<BrowserRouter>
<Wrapper>
<Sidebar />
<Main>
<Routes>
<Route
path="/"
element={
<>
<h1>Welcome!</h1>
</>
}
/>
<Route path="/admin" element={<Admin />} />
<Route path="/admin/:postId" element={<AdminPost />} />
<Route path="/blog" element={<Blog />} />
<Route path="/blog/:postId" element={<BlogPost />} />
</Routes>
</Main>
</Wrapper>
</BrowserRouter>
<ReactQueryCacheProvider queryCache={queryCache}>
<BrowserRouter>
<Wrapper>
<Sidebar />
<Main>
<Routes>
<Route
path="/"
element={
<>
<h1>Welcome!</h1>
</>
}
/>
<Route path="/admin" element={<Admin />} />
<Route path="/admin/:postId" element={<AdminPost />} />
<Route path="/blog" element={<Blog />} />
<Route path="/blog/:postId" element={<BlogPost />} />
</Routes>
</Main>
</Wrapper>
<GlobalLoader />
<ReactQueryDevtools />
</BrowserRouter>
</ReactQueryCacheProvider>
</SafeHydrate>
)
}
3 changes: 1 addition & 2 deletions src/screens/admin/Post.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ export default function Post() {
const [deletePost, deletePostInfo] = useDeletePost()

const onSubmit = async (values) => {
await savePost(values)
postQuery.fetch()
savePost(values)
}

const onDelete = async () => {
Expand Down
22 changes: 14 additions & 8 deletions src/screens/admin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,13 @@ import PostForm from '../../components/PostForm'
import { Loader } from '../../components/styled'

import usePosts from '../../hooks/usePosts'
import { prefetchPost } from '../../hooks/usePost'
import useCreatePost from '../../hooks/useCreatePost'

export default function Posts() {
const postsQuery = usePosts()
const [createPost, createPostInfo] = useCreatePost()

const onSubmit = async (values) => {
await createPost(values)
postsQuery.fetch()
}

return (
<section>
<div>
Expand All @@ -29,8 +25,18 @@ export default function Posts() {
<h3>Posts</h3>
<ul>
{postsQuery.data.map((post) => (
<li key={post.id}>
<Link to={`./${post.id}`}>{post.title}</Link>
<li
key={post.id}
style={
post.isPreview && { opacity: 0.3, pointerEvents: 'none' }
}
>
<Link
to={`./${post.id}`}
onMouseEnter={() => prefetchPost(post.id)}
>
{post.title}
</Link>
</li>
))}
</ul>
Expand All @@ -44,7 +50,7 @@ export default function Posts() {
<h3>Create New Post</h3>
<div>
<PostForm
onSubmit={onSubmit}
onSubmit={createPost}
clearOnSubmit
submitText={
createPostInfo.isLoading
Expand Down