Krasjer sida di, kan det være Google Translate og Chrome-utvidelsers feil

Hvorfor Chrome-utvidelser krasjer nettsida di, typesikkerhet i React Query med Zod og PDF-laging i React i ukas ForrigeUke!

Marcus Haaland viser deg hvorfor Google Translate og andre Chrome-utvidelser kan kræsje appen din. 📸: kode24 / Bekk
Marcus Haaland viser deg hvorfor Google Translate og andre Chrome-utvidelser kan kræsje appen din. 📸: kode24 / Bekk Vis mer

Dette var uka for manglende galvanisering, miskommunikasjon og actionfylte (fil)typer — og 278 andre ting som skjedde i frontend-verdenen!

Hvorfor Google Translate (og andre utvidelser) kræsjer nettsiden din

Om du har en bug som forsvinner når du besøker en inkognitofane, går min hovedmistanke mot en Chrome-utvidelse. Det er frustrerende når noe utenfor koden vår forårsaker trøbbel, og det er gjerne noe vi utforsker først etter noen timer debugging. Mange av disse problemene oppstår på grunn av hvordan React er optimalisert.

Martijn Hols beskriver hvordan Google Translate og andre utvidelser påvirker applikasjoner. Google Translate fjerner og dytter inn DOM-elementer i applikasjonen din. Det gjør den ved å bytte ut tekstnoder med et “font”-element:

<h3>Hallo ForrigeUke!</h3> // Original
<h3><font>Hello LastWeek!</font></h3> // Oversatt

Å endre DOM-en utenfra er et problem i React, siden React bruker “Virtual DOM”. Dette innebærer at React holder rede på referanser på alle elementer og oppdaterer bare de som ikke endrer seg. Når utvidelser endrer elementer i DOM-en uten å si ifra til React, blir det kluss.

Det er i hvert fall tre feil som kan skje som følge av DOM-endringen:

  • Når Google Translate dytter inn et nytt element, vet ikke React om dette elementet. Dermed vil ikke state-endringer for det forrige elementet komme med i den nye UI-en
  • Ettersom font-elementet legger seg oppå elementer, kan elementer som knapper få mindre klikkflate
  • Siden Reacts referanse til en tekstnode blir fjernet, kan siden kræsje når React ønsker å endre rekkefølge, som ved betinget visning

Det fins overraskende nok ikke en god løsning på alle problemene oversettelses-utvidelsen kan bringe med seg:

  • En løsning for sidekræsj er å wrappe tekst i et span-element, så selv om teksten fjernes, bevares referansen til span-elementet. Denne løsningen fikser ikke de andre to problemene
  • En annen løsning er å blokkere bruk av oversettelse-utvidelse, men det stenger ute brukere som ikke kan språket
  • En tredje løsning er å implementere oversettelser i applikasjonen selv, men dette er ressurskrevende

Det er heller ikke enighet i hvem som har ansvaret for å sørge for at utvidelser ikke brekker applikasjoner. En person som jobbet med oversettelser mente at DOM-en ikke kun er noe applikasjonen skal kunne endre, så applikasjonsutviklere må tilrettelegge for andre utenfor. Dette passer ikke biblioteker som React inn i.

Hols mener at det ikke er verdt å ofre ytelsesforbedringen Virtual DOM gir for å tillate at eksterne skal tukle med applikasjonen. Det er utvidelses-utviklere som gjør noe eksternt fra applikasjonen, så de må også sørge for at de ikke lager noe som knekker dem.

Typesikkerhet i React Query med Zod

Når du skal type et kall i frontend, er en naiv løsning å lage en type for hva du forventer fra backend:

interface FetchedData {
  id: number;
  name: string;
}

async function fetchData(): Promise<FetchedData> {
  const response = await fetch('<https://api.example.com/data>');
  return response.json();
}

Men om noen endrer et felt i backend, er det ikke noe automatikk i at typen i frontend endres, som kan føre til feil. For dette fins det flere løsninger, med varierende krav og innsats.

  • En løsning er å dele typer mellom frontend og backend, men det krever at TypeScript brukes på begge sider av stacken og at koden er i et monorepo.
  • En annen løsning ved ulike teknologier, er å bruke et verktøy som lager typer for deg, som swagger-codegen.

Disse løsningene kan kreve litt setup, så Noah Falk foreslår heller å bruke valideringsbiblioteket Zod.

Et Zod-skjema ligner på en TypeScript-type:

import { useQuery } from 'react-query';
import { z } from 'zod';

const FetchedDataSchema = z.object({
  id: z.number(),
  name: z.string(),
});

type FetchedData = z.infer<typeof FetchedDataSchema>;

Fordelen med å bruke Zod sammen med TypeScript er at du kan validere dataene i runtime. Det gjør at om du ikke får de ventede dataene fra backend, kan du kaste en feil.

Her er et eksempel med Zod sammen med TanStack Query. Her henter vi data med en fetch, og om dataene ikke stemmer overens med skjemaet, vil Zod kaste en feil som vi får tilgang til i error:

async function fetchData(): Promise<FetchedData> {
  const response = await fetch('<https://api.example.com/data>');
  const data = await response.json();
  return FetchedDataSchema.parse(data);
}

function DataFetchingComponent() {
  const { data, isLoading, error } = useQuery('data', fetchData);

  if (isLoading) {
    return <div>Loading...</div>;
  }
  
  if (error) {
    return <div>Error</div>;
  }

  return (
    <div>
      {`ID: ${data.id}, Name: ${data.name}`}
    </div>
  );
}

Før kodebasen din florerer av Zod-skjemaer, skal du vite at du bare bør bruke det når du trenger det. Du trenger ikke å kaste et skjema på en funksjon hvor du sender med en hardkodet verdi, for da vet du allerede hvilken verdi du får. Zod er heller beregnet på data du er usikker på, som fra et backend-kall du ikke har helt kontroll på eller et skjemafelt som brukeren skriver inn.

Nå er det lettere å lage PDF-er i React

Mange bedrifter ønsker å vise en PDF til kundene sine, som en kvittering eller en annen bestillingsbekreftelse. Lenge har Puppeteer vært et populært valg for dette, men Colby Fayock presenterer to mye enklere løsninger — inkludert en spesifikk for React.

html2pdf er et bibliotek som er latterlig lett å bruke. Med bare to kodelinjer kan du laste ned en PDF:

<div id="kvittering">
  Kvitteringsinnhold
</div>

const html2pdf = await require('html2pdf.js');
html2pdf(document.getElementById('kvittering'));

Selv om det er enkelt å bruke, har det dessverre en stor begrensning: det omformer UI-et til et bilde, som så blir lagt i en PDF. Siden det er et bilde, blir ikke tekstinnholdet søkbart, og dermed heller ikke tilgjengelig for skjermlesere.

Heldigvis fins et annet alternativ: react-pdf. Det krever litt mer enn to linjer, men til gjengjeld får du en søkbar tekst. Mens html2pdf lager et bilde av det eksisterende templatet, må du selv bygge PDF-en med react-pdf. Dette gjør du med visse grunnelementer, som Document, Page og Text.

Fra videoen og artikkelen forteller Fayock om hvordan du kan style PDF-en med StyleSheet, som ligner hvordan du styler i React Native. Men om denne syntaksen er uvant for deg, er det en i kommentarfeltet som tipser om biblioteket react-pdf-tailwind, som lar deg style PDF-en med Tailwind:

import { Page, Document, Text } from "@react-pdf/renderer";
import { createTw } from "react-pdf-tailwind";

const tw = createTw({
  theme: {
    fontFamily: {
      sans: ["Comic Sans"],
    },
    extend: {
      colors: {
        custom: "#bada55",
      },
    },
  },
});

function MyPdf() {
  return (
    <Document>
      <Page>
        <Text style={tw("p-20 bg-gray-100")}>Hello</Text>
        <Text style={tw("p-20 bg-red-100")}>Forrige</Text>
        <Text style={tw("p-20 bg-blue-100")}>Uke</Text>
      </Page>
    </Document>
  );
}

En måte å få ut filen på, er å lage et endepunkt med Next for å hente ut filen:

// app/pdf/route.tsx
export async function GET() {
  const stream = await renderToStream(<MyPdf />);
  // Liten hack for TS
  return new NextResponse(stream as unknown as ReadableStream);
}

Da kan også du få en slik lekker PDF:

image: Krasjer sida di, kan det være Google Translate og Chrome-utvidelsers feil

En begrensning med react-pdf er at du ikke bare kan dytte innholdet ditt inn i en PDF-generator, men heller må gjenskape templaten. Det var en annen person i kommentarfeltet som foreslo å bruke react-pdf-html, som lover å transformere direkte fra React til PDF. Dette fikk jeg dessverre ikke til å fungere, så om du klarer det, rop ut!

Det var alt for denne gang. Ha en finfin uke!