> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aftersell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks

> Ændr hvordan Aftersell Cart opfører sig: transformér linjer, berig dem med Storefront-data, form abonnementsmuligheder og styr add to cart.

Hvor [events](/da/aftersell/cart/sdk-events) lader dig *reagere* på kurven, og [actions](/da/aftersell/cart/sdk-actions) lader dig *ændre* den, ændrer **hooks**, hvordan kurven selv opfører sig: hvordan linjer renderes, hvilke data de bærer, og hvad der sker ved add to cart.

Hooks ligger under `window.aftersell.cart.hooks`.

<Note>
  Et hook ændrer, hvad kunden **ser**; en action ændrer, hvad der **er i deres kurv**. At skjule en gratis gave-linje med en transform lader den blive i kurven og i totalen. At fjerne den med [`removeItem`](/da/aftersell/cart/sdk-actions#removeitemkey) tager den ud for alvor.
</Note>

<Note>
  Hooks er opsætningskald, så de er sikre at registrere helt øverst i dit script uden behov for at vente på `ready()`. Registrér dem i din kurvs **Initialization**-script (se [Brugerdefinerede scripts](/da/aftersell/cart/custom-scripts)).
</Note>

<div id="how-registration-works">
  ## Sådan fungerer registrering
</div>

Hvert hook er en `register*`-metode. Du kalder den med din funktion; den returnerer en **afregistreringsfunktion**, du kan kalde for at fjerne din.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
const off = window.aftersell.cart.hooks.registerSkipAddToCartRule((form) =>
  form.hasAttribute('data-skip-aftersell')
);

// later: off();
```

Registrering er **additiv**, så din funktion kører sammen med alle andre. Det er vigtigt, fordi dit script sjældent er det eneste på siden: en abonnementsapp, en bundle-app og selve temaet kan alle registrere mod det samme hook. Ingen af dem kan erstatte dit, og intet, du registrerer, kan lydløst blive droppet af det, der indlæses efter dig.

| Hook                                                                                      | Hvad det gør                                                                                                       | Med flere registreringer                            |
| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------- |
| [`registerLineTransform`](#registerlinetransform)                                         | Skjul eller omdøb enkelte linjer.                                                                                  | Alle kører, i registreringsrækkefølge.              |
| [`registerLineComparator`](#registerlinecomparator)                                       | Omarrangér de renderede linjer.                                                                                    | Sammensættes som tie-breakers.                      |
| [`registerCartEnricher`](#registercartenricher)                                           | Tilknyt ekstra Storefront-data til hver linje.                                                                     | Alle kører; hvert `id` er sit eget namespace.       |
| [`registerSubscriptionOptionsTransform`](#registersubscriptionoptionstransform)           | Skjul eller omdøb en linjes selling plans.                                                                         | Alle kører; patches flettes pr. plan, pr. felt.     |
| [`registerDefaultSubscriptionOptionSelector`](#registerdefaultsubscriptionoptionselector) | Vælg hvilken plan der er forudvalgt.                                                                               | Første svar, der ikke er `null`, vinder.            |
| [`registerSkipAddToCartRule`](#registerskipaddtocartrule)                                 | Lad specifikke formularer omgå kurven. Se [Add to cart-interception](/da/aftersell/cart/add-to-cart-interception). | Enhver regel, der returnerer `true`, springer over. |

Et hook, der kaster en fejl, eller som ikke er en funktion, springes over; resten kører stadig, og kurven fortsætter. Én defekt integration kan ikke tage add to cart, abonnementsvælgeren eller sorteringen ned.

Bagsiden er, at et defekt hook fra dig fejler **lydløst**: intet når browserkonsollen. Se [Fejlfinding](/da/aftersell/cart/sdk-overview#debugging) for, hvor de fejl kommer til syne.

***

<div id="registerlinetransform">
  ## registerLineTransform
</div>

`registerLineTransform(fn)` kører for hver kurvlinje, før den renderes. Brug det til at skjule en linje eller ændre, hvordan den fremstår, uden at røre, hvad der faktisk er i kundens kurv.

Funktionen modtager en skrivebeskyttet linje plus settere. Den returnerer en afregistreringsfunktion.

| Setter                            | Effekt                                                                                                                                     |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `setHidden(bool)`                 | Skjul linjen i draweren. Den bliver i kurven og i totalen.                                                                                 |
| `setTitle(string)`                | Ændr den viste titel.                                                                                                                      |
| `setVariantTitle(string \| null)` | Ændr den viste variantlabel.                                                                                                               |
| `setInternalProperties(obj)`      | Flet render-only properties ind. Gemmes aldrig i Shopify. Bruges til at [gruppere bundle-linjer](/da/aftersell/cart/sdk-use-case-bundles). |

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// Hide free gift lines from the drawer. The cart total is unaffected.
const off = window.aftersell.cart.hooks.registerLineTransform((line) => {
  if (line.finalLinePrice === 0) {
    line.setHidden(true);
  }
  if (line.sellingPlan) {
    line.setVariantTitle(`Delivered ${line.sellingPlan.name.toLowerCase()}`);
  }
});

// later: off();
```

<Warning>
  En transform ændrer kun, hvad der renderes. Den kan ikke ændre pris, antal eller linjeidentitet. Brug [actions](/da/aftersell/cart/sdk-actions) til det.
</Warning>

**Brug det til:** at skjule gave-ved-køb- eller app-indsatte linjer, omdøbe abonnementslinjer, tagge nedsatte varer, skjule bundle-komponenter, som kunden ikke bør håndtere enkeltvis.

`setInternalProperties` er setteren bag bundle-gruppering: at stemple de kanoniske bundle-properties på hver linje er måden, du får en tredjepartsapps separate kurvlinjer til at rendere som én vare. Se [Gruppér bundle-linjer fra en anden app](/da/aftersell/cart/sdk-use-case-bundles).

<div id="registerlinecomparator">
  ## registerLineComparator
</div>

En comparator i samme form, som `Array.prototype.sort` forventer. Den kører efter skjul og omdøbning, så den ser de transformerede linjer.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// Subscriptions first, then everything else.
window.aftersell.cart.hooks.registerLineComparator((lineA, lineB) => {
  return (lineB.sellingPlan ? 1 : 0) - (lineA.sellingPlan ? 1 : 0);
});
```

Comparators **sammensættes som tie-breakers**: den første, der returnerer en værdi forskellig fra nul, afgør det par, og resten konsulteres kun ved uafgjort. Returnér `0` for par, du ikke har en mening om. Det er det, der overlader beslutningen til den næste comparator i stedet for at påtvinge den en rækkefølge.

**Brug det til:** at flyde abonnementer eller varer af høj værdi til toppen, synke gratis gaver og tilkøb til bunden, holde et sponsoreret produkt først.

<div id="registercartenricher">
  ## registerCartEnricher
</div>

`registerCartEnricher(registration)` henter ekstra produkt- eller variantdata fra Shopify Storefront API'et og tilknytter dem til hver matchende kurvlinje på `line.metadata[id]`. Brug det til at vise metafields, tags eller alt andet, Storefront API'et eksponerer, uden at der kræves kodeændringer fra Aftersell.

| Felt       | Type                                 | Beskrivelse                                                                                                                   |
| ---------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `id`       | `string`                             | Namespace for resultatet; det lander på `line.metadata[id]`. Skal være unikt; en anden registrering med samme `id` ignoreres. |
| `onType`   | `'Product'` eller `'ProductVariant'` | Hvilken node fragmentet målretter. Også join-nøglen (produkt-ID vs. variant-ID).                                              |
| `fragment` | `string`                             | En GraphQL-feltudvælgelse (uden ydre klammer), der splejses ind i Storefront-forespørgslen. Klammer skal balancere.           |

Returnerer en **afregistreringsfunktion**.

Hver gang kurven indlæses eller ændres, henter Aftersell dit fragment for hvert produkt eller hver variant i kurven og tilknytter resultatet. Hentningen er ikke-blokerende: kurven renderes straks og genudsender `cart_updated`, når dataene lander. Et langsomt eller fejlende fragment forsinker eller ødelægger aldrig kurven.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerCartEnricher({
  id: 'pricing',
  onType: 'ProductVariant',
  fragment: `
    anchorPrice: metafield(namespace: "custom", key: "anchor_price") { value }
    subscriberPrice: metafield(namespace: "custom", key: "subscriber_price") { value }
  `,
});

// Read it once the data arrives.
window.aftersell.cart.events.on('cart_updated', (state) => {
  state.items.forEach((line) => {
    const anchor = line.metadata.pricing?.anchorPrice;
    if (anchor) console.log(line.title, 'anchor price', anchor.value);
  });
});
```

Fordi berigelse er asynkron, skal du altid beskytte læsningen, da `line.metadata.pricing` er `undefined`, indtil den første hentning resolver, og `metadata` selv som standard er `{}`.

**Brug det til:** at trække et metafield på hver linje (et leveringsestimat, en ingrediensliste, et "sendes separat"-flag, en loyalitetsmultiplikator) og rendere det via en [Custom code-blok](/da/aftersell/cart/custom-code-blocks). Se [visning af metafield-data på kurvlinjer](/da/aftersell/cart/sdk-use-case-metafields).

<Note>
  Flere enrichers sameksisterer gnidningsfrit, da hvert `id` er sit eget namespace, så deres data kolliderer aldrig.
</Note>

<Warning>
  Berigede værdier returneres, som de er, fra Storefront API'et og er **ikke** saneret. Render dem som tekst, ikke som rå HTML.
</Warning>

<div id="registersubscriptionoptionstransform">
  ## registerSubscriptionOptionsTransform
</div>

Skjul eller omdøb de selling plans, der tilbydes på en linje. Din funktion modtager skrivebeskyttede muligheder plus settere og returnerer intet.

| Setter            | Effekt                   |
| ----------------- | ------------------------ |
| `setHidden(bool)` | Skjul planen i vælgeren. |
| `setName(string)` | Ændr det viste plannavn. |

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerSubscriptionOptionsTransform((options, context) => {
  // context: { productId, variantId }
  options.forEach((option) => {
    if (option.discountPercent === 0) option.setHidden(true);
    option.setName(option.name.replace('Every ', ''));
  });
});
```

**Settere, ikke en returneret liste, så flere scripts kan sameksistere.** Hvis dette hook returnerede et array, ville en transform, der kun bekymrede sig om én plan, naturligt skrive `options.filter(...)` og lydløst slette alle andre apps' planer på vejen ud. Med settere kan du kun beskrive dine egne redigeringer: patches flettes pr. plan og pr. felt, og den sidste skriver vinder en reel konflikt på samme felt af samme plan. En transform, der kaster en fejl, bidrager med intet, og de andre gælder stadig.

Hver transform ser de *oprindelige* muligheder, ikke et halvt-patchet billede, så registreringsrækkefølgen ændrer ikke, hvad du læser.

<Note>
  Planrækkefølgen forbliver, som Shopify returnerede den, så en transform kan ikke omarrangere. For at styre, hvilken plan der tilbydes først (og hvilken engangs-opgraderingsknappen abonnerer på), skal du bruge [`registerDefaultSubscriptionOptionSelector`](#registerdefaultsubscriptionoptionselector), som rykker sit valg frem forrest.
</Note>

Du kan heller ikke *tilføje* en plan eller ændre en pris: `discountPercent` har ingen setter, fordi en plan, Shopify ikke vil honorere ved checkout, blot ville være et brudt løfte i vælgeren.

<div id="registerdefaultsubscriptionoptionselector">
  ## registerDefaultSubscriptionOptionSelector
</div>

Vælg, hvilken plan der er forudvalgt på en linje. Returnér et plan-`id`, eller `null` for at give afkald.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerDefaultSubscriptionOptionSelector((options) => {
  const best = options
    .slice()
    .sort((optionA, optionB) => optionB.discountPercent - optionA.discountPercent)[0];
  return best ? best.id : null;
});
```

Den **første selector, der returnerer id'et på en tilgængelig plan, vinder**, så returnér `null` for de linjer, du ikke bekymrer dig om, i stedet for at gætte. Det overlader beslutningen til den næste selector i stedet for at tilsidesætte den. Et id, der ikke matcher nogen plan på linjen, behandles på samme måde som `null` og giver også afkald, så et forældet id ikke kan tømme vælgeren.

Din funktion modtager `(options, context)`, den samme `context`, som options-transformen får.

<div id="registerskipaddtocartrule">
  ## registerSkipAddToCartRule
</div>

Returnér `true` for at lade en specifik produktformular tilføje til kurven normalt og omgå Aftersell helt. Det er nyttigt for en formular, der har brug for sin egen redirect eller håndtering.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerSkipAddToCartRule((form) =>
  form.hasAttribute('data-skip-aftersell')
);
```

**Ethvert `true` springer over**, så hold din regel snæver, så den matcher de specifikke formularer, du ejer, og returnér `false` for alt andet. Regler evalueres i registreringsrækkefølge og stopper ved det første `true`, så læg ikke bivirkninger i en: om din overhovedet kører, afhænger af, hvad der blev registreret før den.

<Tip>
  Hvis du styrer formularens markup, behøver du slet ikke et hook: tilføj klassen **`aftersell-cart-skip-atc`** til `<form>`-elementet, så lader Aftersell den være. Brug dette hook, når du ikke kan redigere markuppen, eller når beslutningen afhænger af noget, kun din kode ved.
</Tip>

**Brug det til:** en forudbestillings- eller tilbudsformular, der har brug for sin egen redirect, en abonnementsapps brugerdefinerede flow, en "buy it now"-knap, der skal gå direkte til checkout. For i stedet at slå interception fra for hele siden skal du bruge [`skip_add_to_cart_interceptor`](/da/aftersell/cart/sdk-configure#skip_add_to_cart_interceptor), men foretræk dette hook, som er afgrænset til de formularer, du navngiver.

<div id="where-to-go-next">
  ## Hvor skal du hen nu
</div>

* **[Cart-objekt](/da/aftersell/cart/sdk-cart-object)**: formen på den linje, en transform modtager.
* **[Events](/da/aftersell/cart/sdk-events)**: alt hvad du kan abonnere på.
* **[Actions](/da/aftersell/cart/sdk-actions)**: læsning og ændring af kurven.
* **[Use cases](/da/aftersell/cart/sdk-use-cases)**: komplette løsninger på almindelige ønsker.
