dai-shi / waku

⛩️ The minimal React framework
https://waku.gg
MIT License
4.11k stars 108 forks source link

`useRefetch` should be awaitable? #735

Closed himself65 closed 3 weeks ago

himself65 commented 3 weeks ago

Very simple logic using server action + useOptimistic + startTransition

https://github.com/dai-shi/waku/assets/14026360/9fb42a8e-0e2a-470b-bfd1-956b9c6aa127

I found that there's a flicking because refetch is actually an async network, but startTransition cannot await it

"use client"
import { startTransition, useOptimistic } from 'react'
import { unvote, vote } from '../../actions/index.js'
import { useRefetch } from 'waku/client'

export function BannerVote (
  props: {
    totalVotes: number
  }
) {
  const [optimisticVotes, setOptimisticVotes] = useOptimistic<number, number>(
    props.totalVotes, (current, value) => {
      return value + current
    }
  )

  const refetch = useRefetch()
  return (
    <div
      className="flex flex-row items-center justify-center gap-1"
    >
      <span>{optimisticVotes}</span>
      <button onClick={() => {
        startTransition(async () => {
          setOptimisticVotes(+1)
          await vote()
          await refetch('')
        })
      }}>
        +1
      </button>
      <button onClick={() => {
        startTransition(async () => {
          setOptimisticVotes(-1)
          await unvote()
          await refetch('')
        })
      }}>
        -1
      </button>
    </div>
  )
}

Server Actions

'use server'
import postgres from 'postgres'

const { PGHOST, PGDATABASE, PGUSER, PGPASSWORD } = process.env

const conn = postgres({
  host: PGHOST!,
  database: PGDATABASE!,
  username: PGUSER!,
  password: PGPASSWORD!,
  port: 5432,
  ssl: 'require'
})

export async function vote () {
  await conn`
    UPDATE blogs
    SET upvote_number = upvote_number + 1
    WHERE id = 1
  `
}

export async function unvote () {
  await conn`
    UPDATE blogs
    SET downvote_number = downvote_number + 1
    WHERE id = 1
  `
}

export async function getTotalVotes (): Promise<number> {
  const result = await conn`
    SELECT upvote_number, downvote_number
    FROM blogs
    WHERE id = 1
  `
  return result[0]!.upvote_number - result[0]!.downvote_number
}
himself65 commented 3 weeks ago

Please see preview in https://github.com/himself65/intro/pull/17

dai-shi commented 3 weeks ago

Something like #736? Does it work for you?

himself65 commented 3 weeks ago

let me try

dai-shi commented 3 weeks ago
        startTransition(async () => {
          setOptimisticVotes(+1)
          await vote()
          await refetch('')
        })

On second thought, it's not because of async refetch. It's because of await vote(). Let me draft another PR.

dai-shi commented 3 weeks ago

737 should be the right fix if it works for you.

himself65 commented 3 weeks ago

It's because of await vote()

What do you mean by await vote?

dai-shi commented 3 weeks ago

With the line await vote(), it loses a transition. Not because of vote(), but await Promise.resolve() would reproduce.

dai-shi commented 3 weeks ago

Let me merge #737. I will re-investigate it if it doesn't fix your issue.

himself65 commented 2 weeks ago

With the line await vote(), it loses a transition.

Doesn't startTransition support async function?

himself65 commented 2 weeks ago

It's working now

dai-shi commented 2 weeks ago

With the line await vote(), it loses a transition.

Doesn't startTransition support async function?

Yes and no. After await it's not in the transition.