Poznajemy pierwszy generyczny komponent w React z TypeScriptem. Kontynuacja lekcji poprzednich. Do dzieła!

Ok, tworzymy List.tsx w components i piszemy:

import { ReactNode } from 'react'

interface ListProps<T> {
    items: T[],
    render: (item: T) => ReactNode
}

Jak widać jeden z propów musi być funkcją. Już to znamy. Ok, teraz jak ten RFC wygląda:

const List = <T,>({ items, render }: ListProps<T>) => {
    return (
        <ul>
            {items.map((item, i) => (
                <li key={i}>
                    {render(item)}
                </li>
            ))}
        </ul>
    )
}
export default List;

Ten przecinek to nie błąd. Zauważmy, że zarówno TypeScript jak i React ma swoje zastosowanie dla znaków < oraz >, do tematu zaraz wrócimy.

Użycie tego RFC (to z internetu przykład):

<List items={["☕ Coffee", "🌮 Tacos", "💻 Code"]} render={(item: string) => <span className="bold">{item}</span>} />

Jak już ułoży się nam w głowie jak to działa (pamiętajmy o imporcie w App.tsx) to kilka uwag. Po pierwsze, możemy tak ten problem z przecinkiem rozwiązać:

const List = <T extends {}>({ items, render }: ListProps<T>) => {
    return (
        <ul>
            {items.map((item, i) => (
                <li key={i}>
                    {render(item)}
                </li>
            ))}
        </ul>
    )
}
export default List;

Generalnie extends {} (nie mylić z extends object) oznacza, że wartości null i undefined nie przechodzą. Ale samo słówko extends tutaj już naprowadza Reacta, że robimy generyczny typ, a nie żaden JSX i nie potrzeba tego przecinka, React wie o co chodzi.

Bo JSX też można przypisywać do zmiennych/stałych i dlatego ten zapis jest problematyczny, React nie ma możliwości wiedzieć, o co nam chodzi.

Oczywiście teraz nie mamy takiego problemu:

function List<T>({ items, render }: ListProps<T>) {
    return (
        <ul>
            {items.map((item, i) => (
                <li key={i}>
                    {render(item)}
                </li>
            ))}
        </ul>
    )
}
export default List;

No i jeszcze taka mała rzecz, a mianowicie takie funkcje render nadają się do przekazywania dynamic components w React:

<List items={["☕ Coffee", "🌮 Tacos", "💻 Code"]} render={(item: string) => <Counter/>} />

Tutaj akurat trochę bez sensu (item jest nieużyty) 3 razy dostaniemy countera, ale chodzi mi o koncepcję. Render może też zwracać JSX.Element, nie ma problemu.

Mówię o tym, bo wcześniej miałem mega przygody, aby jakoś ogarnąć używanie dynamic components w React + TS. I ciągle coś mnie prześladowało (no dobra, spędziłem nad tym może 10 minut) aż w końcu ogarnąłem, że można to tak robić, czyli przez funkcję.

Więcej Reacta niedługo, do następnego razu!