Storybook tilbyr en rå, interaktiv playground for å teste ulike props, men det kommer med en pris: med det følger en tvangstrøye av et design.
Ved å kombinere React Live og litt strengkonkatenering kan du lage en løsning du har full kontroll over, samtidig som du gir brukerne dine en effektiv måte å få oversikt over en komponents muligheter.
Hva består en playground av?
Det jeg hadde lyst på var en playground, som lot meg teste ulike props og å se hvordan komponenten ville se ut, lignende det Storybook tilbyr:
Vi hadde valgt bort Storybook til fordel for fleksibilitet, og måtte derfor lage noe tilsvarende på egenhånd. Så hvordan bygger du noe sånt fra scratch?
Vi kan dele det opp i noen enklere funksjonaliteter:
- Forhåndsvisning av komponent
- Kodeeditor
- Endre komponenten via skjema
- Generere skjema utifra props
Forhåndsvisning og kodeeditor
La oss starte med React Live, som huker av for to krav. Det er et bibliotek som tilbyr både forhåndsvisning av kode og en kodeeditor. Koden som blir vist er styrt av code
-propen i LiveProvider
:
const code = `<Button variant="secondary" size="medium">Knapp</Button>`;
<LiveProvider code={code}>
<LivePreview />
<LiveEditor />
</LiveProvider>
Her er hvordan dette ser ut rendret på en side:
Når koden endres, oppdateres forhåndsvisningen. Det skjer også om en bruker endrer på teksten i editoren.
Men vi ønsker ikke å tvinge brukerne til å skrive ut alle varianter selv via editoren. Så hvordan kan vi endre koden utenfor selve kodeeditoren?
Hvordan endre på komponenten med et skjema
Siden forhåndsvisningen automatisk endrer seg når koden i LiveProvider
endrer seg, trenger vi bare å sette koden for LiveProvider
i en variabel, så vi senere kan oppdatere den:
const [code, setCode] = useState<string>("");
Vi kan så lage en variabel componentProps
for å holde styr på props. Vi lager det som et objekt, så vi kan holde styr på hvilken prop som har hvilken verdi. Her initert med variant og children:
type ComponentProps = Record<string, string>;
const [componentProps, setComponentProps] = useState<ComponentProps>({
variant: "secondary",
children: "knapp"
});
Vi kan så oppdatere code
-variabelen når componentProps
endres. Dette gjør vi via en useEffect
.
Siden LiveProvider
tar imot en streng, gjør vi om objektet til en streng med key-value-par. Så putter vi den strengen i komponentnavnet for å rendre komponenten riktig:
useEffect(() => {
const propsString = Object.entries(componentProps)
.map(([key, value]) => `${key}="${value}"`)
.join(" ");
setCode(`<Button ${propsString} />`);
}, [componentProps]);
Her er resultatet:
Vi har nå gått fra å hardkode en streng, til å danne strengen via props definert i et objekt. Resultatet er det samme, men omskrivningen vår gjør det lettere for oss å legge til noe viktig: interaktivitet.
Hvordan får vi til interaktivitet?
For å få til interaktivitet bruker vi et skjemaelement som vil oppdatere componentProps
. Vi lager en handler handlePropChange
som tar imot propnavnet vi vil oppdatere og den nye verdien.
Her legger vi handleren på en select:
// 👇 En enkel funksjon som oppdaterer en nøkkel i et objekt, vårt propnavn, med en ny verdi
const handlePropChange = (propName: string, value: string): void => {
setComponentProps({ ...componentProps, [propName]: value });
};
// ...mer kode
return (
<LiveProvider code={code}>
<form>
<label>
variant
<select
{/* 👇 Vi bruker handleren for å oppdatere prop-verdien */}
onChange={(e: ChangeEvent<HTMLSelectElement>): void =>
handlePropChange("variant", e.target.value)
}
value={componentProps.variant}
>
{/* 👇 Vi viser de tilgjengelige prop-verdiene */}
{["primary", "secondary"].map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</label>
</form>
<LivePreview />
<LiveEditor />
</LiveProvider>
);
Nå har vi fått til interaktivitet for en av propsene våre:
Men ulike komponenter vil ha ulike inputs avhengig av props. Hvordan kan vi generere skjemaelementer basert på props?
Generere skjema utifra props
En måte er å definere hvilke props en knapp har, og hvilke verdier vi ønsker å vise frem. Legg merke til at vi også definerer type
, som vi vil bruke for å switche hvilket skjema-element vi rendrer verdiene i:
interface PropRenderOption {
propName: string;
type: "select" | "textInput";
options?: string[];
}
const propRenderOptions: PropRenderOption[] = [
{
propName: "variant",
type: "select",
options: ["primary", "ghost"]
},
{
propName: "children",
type: "textInput"
}
];
Etter å ha definert typer, kan vi switche over props og rendre passende skjema-elementer, her med eksempel select og text-input:
const inputs = propRenderOptions.map((prop) => {
switch (prop.type) {
case "textInput": // 👈 Vi rendrer en passende skjema-input avhengig av typen vi har satt
return (
<div key={prop.propName}>
<label>{prop.propName}</label>
<input
// 👇 Ved endringer oppdaterer vi en prop med ny verdi
onChange={(e: ChangeEvent<HTMLInputElement>): void =>
handlePropChange(prop.propName, e.target.value)
}
type="text"
value={componentProps[prop.propName] || ""}
/>
</div>
);
case "select": // 👈 Samme handler brukes for select
return (
<div key={prop.propName}>
<label>{prop.propName}
<select
onChange={(e: ChangeEvent<HTMLSelectElement>): void =>
handlePropChange(prop.propName, e.target.value)
}
value={componentProps[prop.propName] || ""}
>
{prop.options?.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</label>
</div>
);
default:
return null;
}
});
return (
<LiveProvider code={code}>
<form>{inputs}</form>
<LivePreview />
<LiveEditor />
</LiveProvider>
);
Her er resultatet:
Avsluttende ord
En playground er et utrolig nyttig verktøy som effektivt demonstrerer mulighetene til en komponent. Med bruk av React Live og litt streng-konkatenering, har vi sett hvor langt vi kan ta funksjonaliteten.
Over har jeg vist en basal løsning for å få prinsippene frem, men her er noen forslag til videre forbedringer:
- Flytt playgroundProps ut i egen fil for oversiktlighet
- Legg også til inititialProps, for bedre startpunkt over hva komponenten kan gjøre
- Ikke rendre children som en prop, men mellom opening- og closing tag.
- Støtt sammensatte komponenter
- Finn en automagisk måte å hente ut komponenter sine props (det har dessverre ikke jeg funnet, så rop ut om du finner en løsning!)
💛 Denne playgrounden er inspirert av Enturs playground. Tusen takk til Magnus Rand som pekte meg i retning av hvordan deres var lagd, så jeg kunne lage min egen versjon.