Next.js Discord

Discord Forum

Scroll to top after first invocation of server action from page with many forms.

Unanswered
Red-breasted Merganser posted this in #help-forum
Open in Discord
Red-breasted MerganserOP
Hello everyone!

I have this client 'form' (just a button, that toggles) where a user can add or remove an item from their journal.

The page this is used on renders a whole list of 'cruises' which they can add or remove from their list.

However, on first load of any particular page that has these forms on them, the first interaction results in a scroll to the top of the page (no full refresh is happening) and then if I add another from the list, it doesn't trigger the same scroll to top.

Client form:
'use client'

import { updateUsersCruises } from './actions'
import { useFormState } from 'react-dom'

import { AddCruiseButton } from './AddCruiseButton'

const initialState = {
  message: '',
}

export default function AddCruise({ cruiseId, beenOnCruise }: { cruiseId: string, beenOnCruise: boolean }) {

  const [state, formAction] = useFormState(updateUsersCruises, initialState)

  return (
    <>
      <form action={formAction}>
        <input type="hidden" name="cruiseId" value={cruiseId} />
        <AddCruiseButton beenOnCruiseInitialState={beenOnCruise} state={state} />
      </form>
    </>
  )
}



Server action:
'use server'

import { revalidatePath } from 'next/cache'
import { cookies } from 'next/headers'
import { createClient } from '@/utils/supabase/actions'

export async function updateUsersCruises(prevState: any, formData: FormData) {
  const cruiseId = formData.get('cruiseId');

  if (formData.get('action') === 'add') {
    const { data : queryReturn, error: queryReturnError } = await supabase.from('users_cruises')
    .insert({ cruise_id: cruiseId, profile_id: data?.session?.user.id }).select()
    return { message: 'added' };
  } else if (formData.get('action') === 'remove') {
    const { data : queryReturn, error: queryReturnError } = await supabase.from('users_cruises')
    .delete().eq('cruise_id', cruiseId).eq('profile_id', data?.session?.user.id)
    return { message: 'deleted' };
  }

}

tsx

12 Replies

In your client code, make your own async function which will run your action and within that you can scroll to the top. This might not be the most optimal solution as you'll no longer be using useFormState or action={formAction} however you'll still be using a server action abd getting all the benefits from doing so.
PS: Add syntax highlighting in the future by putting tsx after the three backticks
This might be happening because of the default form behaviour
try adding an onSubmit event listener and then doing e.preventDefault
Like this.
'use client'

import { updateUsersCruises } from './actions'
import { useFormState } from 'react-dom'

import { AddCruiseButton } from './AddCruiseButton'

const initialState = {
  message: '',
}

export default function AddCruise({ cruiseId, beenOnCruise }: { cruiseId: string, beenOnCruise: boolean }) {

  const [state, formAction] = useFormState(updateUsersCruises, initialState)

  const handleSubmit = async (e) => {
    e.preventDefault();
    // Your form submission logic here
  }

  return (
    <>
      <form action={formAction} onSubmit={handleSubmit}>
        <input type="hidden" name="cruiseId" value={cruiseId} />
        <AddCruiseButton beenOnCruiseInitialState={beenOnCruise} state={state} />
      </form>
    </>
  )
}
Red-breasted MerganserOP
  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault()
    const data = new FormData(event.currentTarget)
    formAction(data)
  }


Something like that, but it kills useFormState - no longer returns data. Wild goose chase though, it's possible it's just a react issue.
@Red-breasted Merganser Hmm interesting. I have no idea how to invoke the server action like that, while still passing the form data etc.
use an onSubmit event and remove the action={formAction} get your form values from e, then pass them to your server action. You'll have to adjust the logic of your server action and there won't be any need for useFormState
Red-breasted MerganserOP
Right! I just figured out my mistake in returning back to the client. 🙂
Red-breasted MerganserOP
Well, just had some time to test the refactor to use the event handler and found I have the same issue even with preventDefault. Going to require some more investigation on this one.

Note: I replaced the server action with a simple setTimeout and the problem went away. Definitely something unique to the invocation of the server action.
Red-breasted MerganserOP
Update on this!

I found three fixes:
- If I didn't have a loading.tsx, then the scroll didn't happen!?
- If I change the refer's link tag to scroll={false}, the problem didn't happen (remember, the problem happens on first invocation of a server action on the scrolled page)

So considering this, I decided to create a minimal repo to demonstrate. Then, I deployed it, and the problem no longer existed. So, it seems to only be an issue in dev mode. I will continue to monitor.