Server vs. klient og framtidas skjemaer i React 19

Cosden Solutions og norske Aurora Scharff om skjemaer i React 19, Tanner Linsley om "overhypa server" og forskjellen mellom serverside-rendering og serverkomponenter i ukas ForrigeUke.

Dette var uka for noen siste ord, balanse i universet og a/b-testing— og 2412 ting som skjedde i frontend-verdenen!

Fremtidens skjemaer

En av mine favoritt-React-pedagoger på YouTube, Cosden Solutions, hadde nettopp et samarbeid med norske Aurora Scharff. Her snakket de om fremtiden til skjemaer.

React trender mot serveren. React 19 introduserer nye hooks som useActionState, og du kan allerede se server-funksjoner i Next.js. Hvordan ser React-serverkode ut i forhold til react-klientkode, og hvordan påvirker dette brukeren?

La oss først se på forskjellene i kode. Et populært bibliotek for skjemaer på klientsiden er React Hook Form. Det gir deg en del funksjonalitet, som å lettere håndtere tilstand og feilmeldinger, med færre linjer kode.

Her har du et eksempel på skjema, hvor en bruker kan skrive inn et navn:

"use client"

export default function App() {
  const {
    register,
    handleSubmit,
    // 👇 Vi får error- og loading state fra hooken
    formState: { errors, isSubmitting },
  } = useForm<Inputs>()
  
  // 👇 Vi registrerer input-ene, og data sendes via onSubmit
  const onSubmit: SubmitHandler<Inputs> = (data) => submitData(data)

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
     <label>First Name</label>
      <input {...register("firstName")} />

Det er ikke noe galt i å lage et skjema på denne måten. Men det kan være nyttig for deg å vite noen svakheter ved et klientside skjema.

Et klientside skjema er avhengig av JavaScript. Det kan være flere grunner til at JavaScripten ikke er ferdig prosessert i klienten, enten av tregt nett eller en treg prosessor. Om du noen gang har begynt å skrive i et tekstfelt, for så at teksten plutselig forsvinner, kan det være at JavaScripten har brukt tid på å laste inn. Så frustrerende!

Siden et klientside skjema kan gi noen dårlige brukeropplevelser, hvordan kan skjema som kaller en serverfunksjon forbedre dette?

La oss se på koden, med den nye useActionState-hooken, som tar imot en server-funksjon:

// actions.ts
"use server" // 👈 Server-funksjoner rendres på serveren

export async submitData() {
 // ...
}

// App.tsx
// 👇 Hooken brukes i klienten, for å få med interaktivitet, som loading
"use client"

import { submitData } from "./actions.ts"

export default function App() {
 // 👇 Vi får data fra skjema, action for skjema og loading-state fra hooken
  const [state, formAction, isPending] = useActionState(submitData, {
   error: undefined,
   success: false
  })
  
  return (
    // 👇 Denne gang bruker vi action, ikke onSubmit
    <form action={formAction}>
     <label>First Name</label>
      <input name="firstName" />

Denne gangen sender vi med skjemadata via nettleserens native form-funksjon, action, som gir flere fordeler. Nå trenger vi ikke JavaScript for å sende inn dataen — det fungerer også uten. Så, om brukeren ikke har JavaScript ferdig lastet inn, vil eneste konsekvens være at loading-tilstanden ikke vises.

Er det noen svakheter ved å håndtere skjema-logikken på serveren?

En svakhet, hvert fall slik koden er nå, er at om det oppstår feil ved innsending, blir skjemaet nullstilt, og du må skrive alt på nytt av. Men dette kan du løse ved å også returnere data fra server-funksjonen, og bruke dette som en defaultValue i inputfeltet.

En annen svakhet er at du nå ikke får tilbakemelding om feil før etter innsending. Dette kan du løse med en onChange-funksjon og sjekke data løpende, men det kan bli mye kode. En løsning, som nevnes i videoen, er å ta i bruk et bibliotek som conform, som lar deg kalle serverfunksjonen løpende, så du kan få tilbakemelding om status underveis.

Så ved å ta i bruk et serverside skjema, kan du lage en løsning som både får fordelene med et skjema som er umiddelbart tilgjengelig, og du kan få responsiviteten du kjenner igjen fra et klientside skjema.

Historien pendler frem og tilbake. Som Scharff sier i videoen, har React hatt et fokus på klientside kode. Dette har ført til mye JavaScript hos klienten, som kan ta tid til å laste inn. Vi har prøvd å flytte noe av dette til serveren, som har ført til at mer kode prosesseres i serveren, men det må fortsatt skje en hydrering i klienten for interaktivitet, som også kan ta tid. Nå flytter vi det som ikke trenger hydrering, som skjemaer, også til serveren.

Se videoen her:

Er serveren overhypet?

“Serveren er overhypet”, sier Tanner Linsley.

I Syntax-podcasten med Wes Bos og Scott Tolinski sier han at helt siden create-react-app ble deprecated, har det egentlig bare vært to store React rammeverk: Remix og Next.js. Disse er server først. Og han syns det gjør utvikling unødvendig komplisert.

I forrige uke kom hans motsvar i beta: TanStack Start.

Hans nye rammeverk blir klient først, og server opt-in. TanStack Start er visst 95% TanStack Router, 5% “noe annet”.

TanStack Router gir mye av funksjonalitet som du kan finne i React Router, som å hente data før du laster inn en rute eller SSR. Men det har også et fokus på typesikker navigering, og er mer opptatt av bruk av URL-parametere som state. I tillegg er TanStack Query en stor del av TanStack Router, hvor det til og med er bygd inn en mini useQuery inn i datahåndteringen, for cachehåndtering.

Denne 5% “annet” er ting som gjør TanStack Start til et rammeverk. Dette er blant annet bundling, full-stack typesikkerhet og serverfunksjoner.

Med TanStack Start ønsker Linsey også å støtte React Server Komponenter. Som han beskriver i podcasten, har han et interessant take på det: han tenker ikke på serverkomponenter som noe mer annerledes enn annen asynkron data. Og hva er det beste biblioteket for asynkron data? TanStack Query.

Han vil hente serverkomponenter med en useQuery. Det klarer jeg ikke helt å forestille meg. I foredraget han hadde på Netlify-konferansen (som er verdt å se for powerpoint-overganger alene!), viste han hvordan du kanskje kan rendre serverkomponenter via serverfunksjoner:

Serverfunksjon håndterer visning av en komponent. Ny måte å tenke RSC på? Screenshot fra Linseys talk på Netlify-konferansen
Serverfunksjon håndterer visning av en komponent. Ny måte å tenke RSC på? Screenshot fra Linseys talk på Netlify-konferansen Vis mer

Håndtering av RSC i TanStack Start er fortsatt i tenkeboksen. Det blir spennende å se hvordan dette nye rammeverket kanskje kommer til å trekke pendelen tilbake til klient-siden igjen.

Her er videoen fra Netlify-konferansen:

Hva er forskjellen på serverside-rendret kode og React serverkomponenter?

Med snakket om klient versus server, og serverkomponenter oppi det hele, hva er egentlig forskjellen?

Serverside-rendring (SSR) og React serverkomponenter (RSC) har begge ordet “server” i seg, men dette er ulike konsepter. Kodaps Academy sier du heller bør se på forskjellene i ordene, nemlig “rendring” og “komponent”.

Imens SSR handler om hvordan du rendrer hele siden som en helhet, handler serverkomponent om at du rendrer en bit av siden, altså en komponent, på serveren. I tillegg kan serversside-rendret kode være interaktiv, mens serverkomponenter er statiske.

I videoen viser han også hvordan serverkomponenter ikke blir sendt som HTML, men som JSON-blob. Kanskje dette forklarer hvordan TanStack Start vil bruke useQuery på serverkomponenter?

Jeg anbefaler å ta en titt på videoen:

Videoen er hentet fra Tymek Zapała artikkel om de fem områdene SSR og RSC er forskjellige. Jeg har nå allerede indirekte nevnt at SSR og RSC er forskjellige med tanke på komponent-tre og hydrering, men Zapała peker også på forskjeller i bundle-størrelse, komponent-livssyklus og henting av data.

Les mer om forskjellene her.

Dette var virkelig uka for dypdykk i server vs. klient. Også må det selvfølgelig ikke være et “versus” der. Også i foredraget om TanStack Start, sier Linsey at han er på en mellomting mellom klient og server. Men å gå fra server-først til klient-først er et brudd i hva som har vært trenden — så får vi se hvordan pendelen svinger inn i fremtiden.

Det var alt for denne uka — snakkes i desember! 👋