Poznajemy coś takiego, jak funkcja jako children prop. Sam tego nie znałem i dopiero na przykładzie analizy cudzego kodu zauważyłem, że tak można. Zaczynajmy!

Ok, zobaczmy nad czym pracujemy:

import { useReducer, ChangeEvent } from 'react'

const initState = { count: 0, text: '' }

const enum REDUCER_ACTION_TYPE {
    INCREMENT,
    DECREMENT,
    NEW_INPUT,
}

type ReducerAction = {
    type: REDUCER_ACTION_TYPE,
    payload?: string,
}

To będzie jakiś reducer. Tak:

const reducer = (state: typeof initState, action: ReducerAction): typeof initState => {
    switch (action.type) {
        case REDUCER_ACTION_TYPE.INCREMENT:
            return { ...state, count: state.count + 1 }
        case REDUCER_ACTION_TYPE.DECREMENT:
            return { ...state, count: state.count - 1 }
        case REDUCER_ACTION_TYPE.NEW_INPUT:
            return { ...state, text: action.payload ?? "" }
        default:
            throw new Error()
    }
}

Już to omawialiśmy, teraz zobaczmy na RFC:

const CounterRed = () => {
    const [state, dispatch] = useReducer(reducer, initState)

    const increment = () => dispatch({ type: REDUCER_ACTION_TYPE.INCREMENT })
    const decrement = () => dispatch({ type: REDUCER_ACTION_TYPE.DECREMENT })
    const handleTextInput = (e: ChangeEvent<HTMLInputElement>) => {
        dispatch({
            type: REDUCER_ACTION_TYPE.NEW_INPUT,
            payload: e.target.value
        })
    }

    return (
        <>
            <h1>Current count: {state.count}</h1>
            <div>
                <button onClick={increment}>+</button>
                <button onClick={decrement}>-</button>
            </div>
            <input type="text" onChange={handleTextInput} />
            <h2>{state.text}</h2>
        </>
    )
}
export default CounterRed;

Chyba wszystko jasne, jak nie to analizujemy do skutku + czytamy poprzednie lekcje. Teraz jak ten komponent wygląda w App.tsx:

<CounterRed/>

Tylko tak może wyglądać, bo nie pozwalamy mu mieć żadnych propsów. Żadnych, także dzieci, musi być self-closing (robimy w TS). W sumie nie musi, bo nie doprecyzowaliśmy typu propsów, ale teraz to zrobimy.

I zrobimy coś naprawdę dziwnego:

type ChildrenType = {
    children: (num: number) => JSX.Element
}

const CounterRed = ({ children }: ChildrenType) => {

O co tu chodzi? Children jako funkcja? Można tak? Ok, zmieńmy sobie JSX:

 return (
        <>
            <h1>{children(state.count)}</h1>
            <div>
                <button onClick={increment}>+</button>
                <button onClick={decrement}>-</button>
            </div>
            <input type="text" onChange={handleTextInput} />
            <h2>{state.text}</h2>
        </>
    )
}
export default CounterRed;

Teraz pozostaje nam tę funkcję jako dziecko przekazać:

<CounterRed>{(num: number) => <>Current Count: {num}</>}</CounterRed>

Funkcja zgadza się z interfejsem, zaś typ state.count zgadza się z typem num, wszystko oplecione fragmentem <></> oraz nawiasami wąsatymi {}, zatem działa, działa bez problemu.

Można tak, pytanie jest jak często będziemy coś takiego w kodzie widzieli. Warto wiedzieć, że można. Więcej Reacta niedługo!