Render Patterns and Composition
ÍNDICE
- Composición de Componentes
- Render Props y Render Functions
- React.Children y React.cloneElement
- High Order Components
- Render props vs Render Functions vs React Hooks
Escribir código limpio y fácil de mantener es muy importante, pero es difícil llegar a hacerlo. Sin embargo, existen patrones creados por el equipo de Facebook para poder hacer este trabajo más fácil
React prefiere el código aburrido en lugar del código elegante
- Links de interés:
Composición de Componentes
Piensa en esto: El elemento padre va a estar controlando que va a ir dentro el componente hijo, en lugar de que lo haga el componente hijo, así, podemos evitar un hell de props:
import { TodoCounter } from 'path/to/TodoCounter';
import { TodoSearch } from 'path/to/TodoSearch';
import { TodoList } from 'path/to/TodoList';
const TodoApp = () => {
<TodoList>
<TodoCounter />
<TodoSearch />
</TodoList>;
};
Render Props y Render Functions
-
Render Props
- Son funciones que se le envian a un componente a través de propiedades y dependiendo de lógica que nosotros hagamos, podemos renderizar algo.
- Las render props nos permiten ser más específicos sobre que vamos a renderizar, cuando y donde vamos a renderizar cada parte del contenido de nuestros componentes.
-
Render Functions
- Son funciones que se crean en la propiedad children de un componente y son similares a las funciones de las render props.
- Ejemplo con Render Props
<TodoList
onError={(error) => <TodoError error={error} />}
onLoading={() => <TodoLoading />}
onEmptyTodos={() => <EmptyTodos />}
render={(todo) => <RenderTodos {...todo} />}
/>
- Ejemplo con Render Functions
<TodoList>
{(todos) => {
<RenderTodos {...todos} />;
}}
</TodoList>
-
Ejemplo de Mezclar Render Props y Render Functions
- Punto de entrada
<TodoList
error={error}
loading={loading}
searchedTodos={searchedTodos}
onError={() => <TodoError />}
onLoading={() => <TodoLoading />}
onEmptyTodos={() => <TodoEmpty />}>
{(todo) => (
<TodoItem
key={todo.text}
text={todo.text}
completed={todo.completed}
onComplete={() => {
completeTodos(todo.text);
}}
onDelete={() => {
deleteTodos(todo.text);
}}
/>
)}
</TodoList>
- Todo List
export const TodoList = (props) => {
const { error, loading, searchedTodos, onError, onLoading, onEmptyTodos, children, render } = props;
const renderFunction = children || render;
return (
<section>
{error && onError()}
{loading && onLoading()}
{!loading && !searchedTodos.length && onEmptyTodos()}
{searchedTodos.map(renderFunction)}
</section>
);
};
React.Children y React.cloneElement
- React.Children:
React.Children.map(children, function[(thisArg)])
React.Children proporciona utilidades para lidiar con la estructura de datos opaca de this.props.children.
- React.cloneElement:
React.cloneElement(element, [config], [...children]);
Clona y retorna un elemento React usando element como punto de partida. config debe contener todas las nuevas props, key, o ref. El elemento resultante tendrá las props del elemento original con las nuevas props combinadas superficialmente. Los nuevos hijos reemplazarán los hijos existentes.
key y ref del elemento original serán preservadas si key y ref no están presentes en la configuración.
High Order Components
High Order Funcion => Son funciones que retornan otras funciones
Los HOC son componentes que retornan otros componentes, así:
function withApi(WrapperComponent) {
const apiData = fetchApi('url');
return function WrapperComponentWithApi(props) {
return <WrapperComponent data={apiData.json} />;
};
}
Al usar HOC vamos a pensar en lo siguiente:
- Primero, la función padre es nuestro HOC y se pueden combinar varios HOCs.
- Al llamar la ejecución de este HOC hay un orden de propiedades que se deben enviar. Primero se envía el argumento del HOC padre y luego se va en escalera enviando propiedades de la siguiente forma:
const AppWithWathever = withWathever(App)('Hola')('Pepe')('some')(789);
- La forma anterior, es la forma en la que se llama a los HOCs
- Si un HOC renderiza un componente, y este recibe props, estas props se las envian al momento de renderizar el HOC, así:
<AppWithWathever mensaje="Yo soy el aventurero" />
- Ejemplo de uso:
function App({ saludo, name, mensaje }) {
return (
<h1>
{saludo} {name}
<p>{mensaje}</p>
</h1>
);
}
function withWathever(WrapperComponent) {
return function ComponenteDeVerdad(saludo) {
return function ComponentePro(nombre) {
return function WrapperComponentWithSaludo(props) {
return <WrapperComponent name={nombre} saludo={saludo} {...props} />;
};
};
};
}
const AppWithWathever = withWathever(App)('Hola')('Pepe');
ReactDOM.render(<AppWithWathever mensaje="Yo soy el aventurero" />, document.getElementById('root'));
Otro Ejemplo de uso:
- HOC
export function withChangeStorage(WrappedComponent) {
return function WrappedComponentWithStorageListener(props) {
const [hasChanges, setHasChanges] = useState(false);
window.addEventListener('storage', (change) => {
if (change.key === 'TODOS_V1') {
setHasChanges(true);
}
});
return <WrappedComponent {...props} hasChanges={hasChanges} setHasChanges={setHasChanges} />;
};
}
- Componente que usa el HOC
import { withChangeStorage } from './withChangeStorage';
import './changeAlert.css';
function ChangeAlert({ hasChanges, setHasChanges, sincronizeTodos }) {
return (
<>
{hasChanges && (
<div className="alerta">
<h3>You have unsaved changes.</h3>
<button
onClick={() => {
setHasChanges(false);
sincronizeTodos();
}}>
Refresh the page
</button>
</div>
)}
</>
);
}
const ChangeAlertWithStorage = withChangeStorage(ChangeAlert);
export { ChangeAlertWithStorage };
Render props vs Render Functions vs React Hooks
-
Para enviar componentes, el claro ganador son las render props, por encima de los React Hooks. Es muy importante anañizar nuestro componente y ver qué se necesita y qué no
-
Para enviar data o información, lo mejor es usar React hooks, ya que el código es totalmente vertical y fácil de leer