Gå til hovedinnhold
KOMPONENTER

Multi select

npmv6.1.0

MultiSelect lar brukeren velge ett eller flere elementer fra en liste med valg

npm install @entur/dropdown
@import '@entur/dropdown/dist/styles.css';
Fargemodus

Migrering fra v4 -> v5

Varianter

MultiSelect

MultiSelect er en komponent som lar brukeren velge ett eller flere elementer fra en liste. Komponenten viser frem en liste med valg bassert på items-prop-en, der items kan være en liste med string eller objekter med value, label og icons - eller en blanding av disse. For å dekke situasjoner der valgalternaltiver kommmer fra et API eller lignende støtter items også synkrone og asynkrone funksjoner som returnerer samme type liste som items aksepterer.

Når brukeren velger noe dukker valget opp som en Chip i inputfeltet. MultiSelect tilbyr også et søkefelt for filtrering av valg, filtreringen her kan overstyres gjennom itemFilter-prop-en – se Egendefinert filtreringsfunksjon for mer info.

Kom i gang

MultiSelect har tre påkrevde props: label, items og selectedItems. I tillegg har den også onChange som ikke er påkrevd, men som er nødvendig for å gjøre komponenten interaktiv.

label er feltets navn og beskriver hva du velger. items tar inn listen med valgalternaltiver, se Liste med valgalternaltiver for mer info. selectedItems og onChange jobber sammen for å holde styr på state for valgte elementer. selectedItems skal være av typen NormalizedDropdownItemType[] (Obs: tom liste, ikke null hvis ingen er valgt) og onChange skal være en funksjon som tar inn alle valgte elementer og oppdaterer verdien på selectedItems til denne listen. Se Standard MultiSelect-eksemplet for å se dette i praksis.

// Definition of NormalizedDropdownItemType
type NormalizedDropdownItemType<ValueType = string> = 
      { label: string, value: ValueType, icons?: React.ComponentType<any>[] }

Siden NormalizedDropdownItemType er en generisk type så kan value være hvilken som helst type. TypeScript vil da også gi deg riktig type ut i onChange. Den er satt til standard som string da dette er vanligste praksis.

Liste med valgalternaltiver

Listen med valgalternaltiver sendes inn gjennom items-prop-en. items har typen PotentiallyAsyncDropdownItems som vil si den støtter tre typer input: liste med DropdownItemType, synkrone funksjoner og asynkrone funksjoner – de to siste kan du lese mer om under Hente valgalternaltiver fra nettverkskall.

DropdownItemType er enten en string eller et objekt med label, value og valgfritt icons – se under.

// Definition of DropdownItemType
type DropdownItemType<ValueType = string> = 
      string |
      { label: string, value: ValueType, icons?: React.ComponentType<any>[] }

Disse kan også blandes slik at følgende også vil være et gyldig items-input:

// Valid mixed type items input
const dropdownItems = [
  "choice1",
  { label: "Second choice", value: "choice2" },
  { lable: "Third choice", value: "choice3", icons: [FunnyIcon1, FunnyIcon2] }
]

Egendefinert filtreringsfunksjon

Som standard er søkefiltreringen bassert på en enkel regex som sjekker om input-et er en del av valgalternaltivet, ønsker du å endre denne kan du skrive din egen filtreringsfunksjon til MultiSelect gjennom prop-en itemFilter. Dette kan være nyttig hvis du f.eks ønsker å implementere fuzzy search eller ignorere noen tegn fra input-et når du filtrerer. itemFilter tar inn en funksjon med item (valgalternaltivet som skal sjekkes mot filtreringen) og inputValue (det bruker for øyeblikket har skrevet inn i inputfeltet) som input. Funksjonen skal returnere true eller false avhengig av om gjeldende item skal være med i listen over valg eller ikke.

Se eksempel på dette under MultiSelect med egendefingert filtreringsfunksjon-eksempelet.

Hente valgalternaltiver fra nettverkskall

Hvis listen din med valg skal hentes fra et API eller lignende er det mulig å sende inn en memoisert funksjon til items i stedet for en liste med valg. Denne funksjonen kan enten være synkron eller asynkron og må enten returnere en liste med DropdownItemType direkte eller gjennom et Promise. OBS: du må huske å memoisere funksjonen ved å bruke React.useCallback, ellers vil den kjøre funksjonen din oftere enn nødvendig!

items-funksjonen kan også bruke inputValue -verdien som sendes inn å gjøre f.eks en query mot et API-et. I disse tilfellene kan prop-en debounceTimeout være nyttig hvis du ønsker å øke eller minke tiden det ventes før et nytt kall mot funksjonen skal kjøres etter brukeres slutter å skrive.

For å avbryte utdaterte kall og hindre 'state update of unmounted component' kan du bruke abortControllerRef som sendes inn som andre argument til funksjonen. abortControllerRef.current.signal inneholder et signal som sier fra når asynkrone kall bør avbrytes og er støttet ut av boksen i f.eks fetch og axios. Les mer om AbortController på Mozilla sine sider og se eksempel på alt i bruk under MultiSelect med valg fra nettverkskall.

Oppsummerings-chip når mange elementer er valgt

For å unngå at skjemaelementet blir for stort vil chip-ene som viser hva som er valgt samles til én felles oppsummerings-chip når antallet overstiger verdien satt i maxChips – denne er som standard satt til 10. Se et eksempel på dette under Endre når oppsummerings-chip vises

Eksempler

Standard MultiSelect

Her finner du en MultiSelect med standard-props.

Fargemodus
() => {
    const [selected, setSelected] = React.useState([]);
    return (
      <MultiSelect
        label="Velg by"
        items={cities}
        selectedItems={selected}
        onChange={setSelected}
      />
    );
  }

MultiSelect med egendefinert filtreringsfunksjon

I dette eksempelet brukes en fuzzyMatch-implementasjon til å utføre en mer tilgivende filtrering av de tilgjengelige valgene.

Fargemodus
() => {
    const [selected, setSelected] = React.useState([]);
    return (
      <MultiSelect
        label="Fuzzy search filter"
        items={cities}
        selectedItems={selected}
        onChange={setSelected}
        itemFilter={(item, inputValue) =>
          fuzzyMatch(inputValue, item.label) > 0.5
        }
      />
    );
  }

MultiSelect med valg fra nettverkskall

Her har vi en eksempelfunksjon fetchItems som bruker abortControllerRef-argumentet og henter inn data fra et test-API. Denne funksjonen settes så inn i items-prop-en og vi får data fra det eksterne API-et listet opp når data-en er mottatt.

Fargemodus
() => {
    // Bruk useCallback for å unngå at funksjonen kjøres unødvendig ofte
    const fetchItems = React.useCallback(async () => {
      try {
        const response = await fetch(
          'https://dummyjson.com/products/categories'
        );

        const data = await response.json();

        if (data.message !== undefined) {
          return [{ label: data.message, value: data.message }];
        }

        return data.map(item => ({
          label: item.name || item, // Fallback to item if it's a string
          value: item.slug || item, // Fallback to item if it's a string
        }));
      } catch (error) {
        console.error('Noe gikk galt:', error);
        return [];
      }
    }, []);

    const [selected, setSelected] = React.useState([]);

    return (
      <MultiSelect
        label="External API List"
        items={fetchItems}
        selectedItems={selected}
        onChange={setSelected}
      />
    );
  }

MultiSelect med valg fra nettverkskall bassert på tekstinput

Her har vi en eksempelfunksjon fetchItems som henter inn data fra et test-API. fetchItems tar inn nåværende inputValue og abortControllerRef og bruker det til å gjøre en query mot API-et. Denne funksjonen settes så inn i items-prop-en og vi får data fra det eksterne API-et listet opp med query bassert på det som skrives inn.

Fargemodus
() => {
    // Bruk useCallback for å unngå at funksjonen kjøres unødvendig ofte
    const fetchItems = React.useCallback(async (inputValue, abortControllerRef) => {
      try {
        const response = await fetch(
          `https://dummyjson.com/products/search?q=${inputValue}&limit=15&select=title`,
          {
            // Bruk signalet fra abortControllerRef for å avbryte utdaterte kall
            signal: abortControllerRef.current.signal,
          }
        );

        const data = await response.json();

        if (data.message !== undefined) {
          return [{ label: data.message, value: data.message }];
        }

        const processedData = data.products.map(item => ({
          label: item.title,
          value: item.id,
        }));

        return processedData;
      } catch (error) {
        // AbortError må sendes videre til komponenten for å håndtere cleanup riktig
        if (error && error.name === 'AbortError') throw error;

        console.error('Noe gikk galt:', error);
        return [];
      }
    }, []);

    const [selected, setSelected] = React.useState([]);

    return (
      <MultiSelect
        label="Ekstern API-liste"
        items={fetchItems}
        selectedItems={selected}
        onChange={setSelected}
      />
    );
  }

Slett tekstinput etter valg

For å slette tekstinput-et etter at et element er valgt kan du burke prop-en ´clearInputOnSelect´.

Fargemodus
() => {
    const [selected, setSelected] = React.useState([]);
    return (
      <MultiSelect
        label="Reset input on select"
        items={cities}
        selectedItems={selected}
        onChange={setSelected}
        clearInputOnSelect
      />
    );
  }

Endre når oppsummerings-chip vises

Du kan endre hva som er maks antall valgte-elementer-chips som vises i inputfeltet med prop-en maxChips.

Fargemodus
() => {
    const [selected, setSelected] = React.useState([
      { label: 'Oslo', value: 'Oslo' },
      { label: 'Bergen', value: 'Bergen' },
      { label: 'Kristiansand', value: 'Kristiansand' },
      { label: 'Kristiansund', value: 'Kristiansund' },
    ]);
    return (
      <MultiSelect
        label="Summery chip on 4 selected"
        items={cities}
        selectedItems={selected}
        onChange={setSelected}
        maxChips={3}
      />
    );
  }

Props

MultiSelect

import { MultiSelect } from '@entur/dropdown';

Denne komponenten har ingen props

Rediger denne siden på GitHub