pmndrs/suspend-react

How would you use suspend-react when fetching through a hook?

vbylen opened this issue · 4 comments

For example with a usePostData hook:

const Post = ({ postId }) => {
  const { title } = usePostData(postId)
  return (
  	<Text>{title}</Text>
  )
}

Any insights would be greatly appreciated, thanks!

what does usePostData do? where does it come from?

@drcmda In my project I have it setup like this:

import { useEffect, useState } from 'react'
import { supabase } from '../../lib/initSupabase'
import { useStore } from '../store/Root'

const usePostData = (post_id) => {

  const { postsStore, usersStore } = useStore()

  const [loading, setLoading] = useState(true)
  const [post, setPost] = useState(null)
  const [author, setAuthor] = useState(null)
  const [recipient, setRecipient] = useState(null)

  const fetchPostData = async () => {
    if (!post_id)
      return {
        post: null,
        author: null,
        recipient: null
      }

    try {
      let response = {
        post: null,
        author: null,
        recipient: null
      }
      const postData = await supabase
        .from('posts')
        .select(`*, author:author_id(*), recipient:recipient_id(*) `)
        .eq('post_id', post_id)

      if ((postData.data && postData.data.length > 0) || !postData.error) {
        response.post = {
          caption: postData.data[0]?.caption,
          image_url: postData.data[0]?.image_url,
          post_id: postData.data[0].post_id,
          posted_at: postData.data[0].posted_at,
          author_id: postData.data[0].author_id,
          title: postData.data[0].title,
          text: postData.data[0].text,
        }
        response.author = {
          avatar_url: postData.data[0]?.author?.avatar_url,
          bio: postData.data[0]?.author?.bio,
          confirmed_at: postData.data[0]?.author?.confirmed_at,
          email: postData.data[0]?.author?.email,
          firstname: postData.data[0]?.author?.firstname,
          id: postData.data[0]?.author?.id,
          lastname: postData.data[0]?.author?.lastname,
          phone: postData.data[0]?.author?.phone,
          tag: postData.data[0]?.author?.tag,
          website: postData.data[0]?.author?.website,
        }
        response.recipient = {
          avatar_url: postData.data[0]?.recipient?.avatar_url,
          bio: postData.data[0]?.recipient?.bio,
          confirmed_at: postData.data[0]?.recipient?.confirmed_at,
          email: postData.data[0]?.recipient?.email,
          firstname: postData.data[0]?.recipient?.firstname,
          id: postData.data[0]?.recipient?.id,
          lastname: postData.data[0]?.recipient?.lastname,
          phone: postData.data[0]?.recipient?.phone,
          tag: postData.data[0]?.recipient?.tag,
          website: postData.data[0]?.recipient?.website,
        }
      } else {
        console.error(
          `[fetchPostData_Response]
                            ${postData.error || 'No Data'}`
        )
        return {
          post: null,
          author: null,
          recipient: null
        }
      }
      return response
    } catch (err) {
      console.error('[fetchPostData]', err)
      return {
        post: null,
        author: null,
        recipient: null
      }
    }
  }
  const fetchPost = async () => {
    const postData = await fetchPostData()
    if (postData?.post) setPost(postData.post)
    if (postData?.author) setAuthor(postData.author)
    if (postData?.recipient) setAuthor(postData.recipient)
    setLoading(false)
  }

  useEffect(() => {
    if (post_id) fetchPost()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [post_id])

  useEffect(() => {
    if (post_id) {
      const postData = postsStore.posts.find(
        (postToFind) => postToFind.post_id === post_id
      )

      if (postData) {
        setPost(postData)
        const userData = usersStore.users.find(
          (userToFind) => userToFind.id === postData.author_id
        )

        if (userData) setAuthor(userData)

        const recipientData = usersStore.users.find(
          (userToFind) => userToFind.id === postData.recipient_id
        )

        if (recipientData) setRecipient(recipientData)
      }
    }
  }, [post_id])

  if (!post_id)
    return {
      fetchPost,
      loading: false,
      post: null,
      author: null,
      recipient: null
    }

  return {
    fetchPost,
    loading,
    post,
    author,
    recipient
  }
}

export default usePostData

error handling and loading state move to the parental level, so that burden goes away. let supabase crash and it's going to get caught. you don't need to mock, check or validate, the data will be there.

import suspend from 'suspend-react'
import { ErrorBoundary } from 'react-error-boundary'
import { supabase } from '../../lib/initSupabase'
import { useStore } from '../store/Root'

export function usePostData(post_id) {
  const { postsStore, usersStore } = useStore()
  return suspend(async () => {
    const postData = await supabase
      .from('posts')
      .select('*, author:author_id(*), recipient:recipient_id(*)')
      .eq('post_id', post_id)
    return postData.data[0]
  }, [post_id])
}
function Post({ postId }) {
  const { title, author: { name }, recipient: { phone } } = usePostData(postId)
  return <div>{title}, {name}, {phone}</div>
}

function App() {
  <Suspense fallback={<div>loading ...</div>}>
    <ErrorBoundary fallback={<div>error ...</div>}>
      <Post postId={1} />
    </ErrorBoundary>
  </Suspense>

Super clean, thanks!