useReact("better")

el hook de las buenas prácticas

Joel A. Villarreal Bertoldi

🏳‍🌈 ¡Hola, soy Joey!

Soy co-fundador de CoDeAr y trabajo en Mercado Libre como líder técnico. Programo hace más de 20 años. Me gustan los unicornios 🦄 (y los cactus 🌵)

cuento cosas en Twitter

@joelalejandro

desmitificando

TypeScript

en React

is the cake a lie?

hablemos de

PropTypes

Es la forma que nos ofrece React para validar el tipo de datos de las propiedades de nuestros componentes.

const Button = (props) => {
    const children = props.children;
    const type = props.type;

    return (
        <button type={type} className="ui-button">
            {children}
        </button>
    );
}

¿Cómo valido que children y type
tengan valores coherentes?

const Button = (props) => {
    const children = props.children;
    const type = props.type;

    return (
        <button type={type} className="ui-button">
            {children}
        </button>
    );
}

Button.propTypes = {
    children: PropTypes.node,
    type: PropTypes.string,
};

¿Y en TypeScript?

const Button = (props) => {
    const children = props.children;
    const type = props.type;

    return (
        <button type={type} className="ui-button">
            {children}
        </button>
    );
}

Button es un componente funcional.
(¿Por qué? Porque no utilizamos una clase que extienda de React.Component)

const Button: React.FC = (props) => {
    const children = props.children;
    const type = props.type;

    return (
        <button type={type} className="ui-button">
            {children}
        </button>
    );
}

Podemos tiparlo utilizando React.FC.

Pero ¿y las props?

const Button: React.FC<{type: string}> = (props) => {
    const children = props.children;
    const type = props.type;

    return (
        <button type={type} className="ui-button">
            {children}
        </button>
    );
}

Aprovechando el sistema de genericidad de TypeScript, podemos subtipar nuestras propiedades.

Pero ¿y "children"?

En React, props está definido como de tipo PropsWithChildren<T>.
A cualquier tipo que definamos para props, le anexará automáticamente una propiedad children.

props: React.PropsWithChildren<T>
PropsWithChildren<T> = T & {children?: React.ReactNode}
const Button: React.FC<{type: string}> = (props) => {
    const children = props.children;
    const type = props.type;

    return (
        <button type={type} className="ui-button">
            {children}
        </button>
    );
}

¿Podemos ser más específicos en la declaración del tipo de las propiedades de este componente?

¡Sí! Definimos un tipo ButtonProps con la definición exacta de qué puede recibir type como valores permitidos y pasamos este tipo como parámetro a React.FC.

type ButtonProps = {
    type: "submit" | "button"
};

const Button: React.FC<ButtonProps> = (props) => {
    const children = props.children;
    const type = props.type;

    return (
        <button type={type} className="ui-button">
            {children}
        </button>
    );
}

Pero entonces hasta acá, ¿cuál es la ventaja con respecto a PropTypes?

PropTypes

Run-time

TypeScript

Build-time

veamos un poco de

State Management

¿Cómo manejamos el estado de un árbol de componentes?

Puede ser muy tentador instalar dependencias como Redux, MobX o cualquiera de sus símiles.

¿Es absolutamente necesario?

import React from "react";

export type InputValues<T = string | number> = { [key: string]: T }

export type FormContextProps = {
    inputValues: InputValues;
}

const FormContext = React.createContext<FormContextProps>({
    inputValues: {}
});

export default FormContext;
import React from "react";
import FormContext from "./FormContext";
import useFormContext from "../../hooks/useFormContext";

const Form: React.FC = (props) => {
    const [formContext] = useFormContext();
    const { children } = props;
    return <form>
        <FormContext.Provider value={formContext}>
            {children}
        </FormContext.Provider>
    </form>
};

export default Form;

FormContext.ts

Form.tsx

import React from "react";
import useFormContext from "../../hooks/useFormContext";

export type InputProps = {
    type: string;
    value: string | number;
    name: string;
}

const Input: React.FC<InputProps> = (props) => {
    const [formContext, updateFormContext] = useFormContext();
    return <input
        onChange={({ target }) => updateFormContext(props.name, target.value)}
        value={formContext[props.name]}
        {...props} />
};

export default Input;

Input.tsx

y ya que estamos enganchades, hablamos de 

Hoooooks

Pero no de los hooks de React... si no de ¡los hooks que podemos inventar!

import React from "react";
import FormContext from "./FormContext";
import useFormContext from "../../hooks/useFormContext";

const Form: React.FC = (props) => {
    const [formContext] = useFormContext();
    const { children } = props;
    return <form>
        <FormContext.Provider value={formContext}>
            {children}
        </FormContext.Provider>
    </form>
};

export default Form;
import { useContext } from "react";
import FormContext, { FormContextProps } from "../components/form/FormContext";

const createFormContextUpdater = (
  formContext: FormContextProps
) => (
  inputName: string, inputValue: string | number
) => {
  formContext.inputValues[inputName] = inputValue;
};

const useFormContext = () => {
    const formContext = useContext(FormContext);
    return [
      formContext, 
      createFormContextUpdater(formContext)
    ] as const;
}

export default useFormContext;

import React from "react";
import useFormContext from "../../hooks/useFormContext";

export type InputProps = {
    type: string;
    value: string | number;
    name: string;
}

const Input: React.FC<InputProps> = (props) => {
    const [formContext, updateFormContext] = useFormContext();
    return <input
        onChange={({ target }) => updateFormContext(props.name, target.value)}
        value={formContext[props.name]}
        {...props} />
};

export default Input;

¿Preguntas?