Last active
June 24, 2021 16:13
-
-
Save dbritto-dev/aebd0d8b35e079f3c92b2f7b6fc487f2 to your computer and use it in GitHub Desktop.
React Hooks para principiantes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // contexts/auth-context.js | |
| import React, { useReducer, useContext, createContext } from 'react' | |
| export const AuthStateContext = createContext() | |
| export const AuthDispatchContext = createContext() | |
| export const AuthProvider = ({ initialState, reducer, children }) => { | |
| const [state, dispatch] = useReducer(reducer, initialState) | |
| return ( | |
| <AuthStateContext.Provider value={state}> | |
| <AuthDispatchContext.Provider value={dispatch}> | |
| {children} | |
| </AuthDispatchContext.Provider> | |
| </AuthStateContext.Provider> | |
| ) | |
| } | |
| export const useAuthState = () => { | |
| const state = useContext(AuthStateContext) | |
| if (state === undefined) { | |
| throw new Error(`useAuthState must be within a AuthProvider`) | |
| } | |
| return state; | |
| } | |
| export const useAuthDispatch = () => { | |
| const dispatch = useContext(AuthDispatchContext) | |
| if (dispatch === undefined) { | |
| throw new Error(`useAuthDispatch must be used within an AuthProvider`) | |
| } | |
| return dispatch; | |
| } | |
| // reducers/auth-reducer.js | |
| export const authInitialState = { | |
| user: null, | |
| loading: false, | |
| error: '', | |
| } | |
| export const authReducer = (state, action) => { | |
| switch(action.type) { | |
| case 'RESET_STATE': { | |
| return authInitialState; | |
| } | |
| case 'LOGIN': { | |
| return { ...state, loading: true } | |
| } | |
| case 'LOGIN.DONE': { | |
| return { ...state, loading: false, user: action.payload } | |
| } | |
| case 'LOGIN.FAIL': { | |
| return { ...state, loading: false, error: action.payload } | |
| } | |
| case 'LOGOUT': { | |
| return { ...state, loading: true } | |
| } | |
| case 'LOGOUT.DONE': { | |
| return { ...state, loading: false, user: null } | |
| } | |
| case 'LOGOUT.FAIL': { | |
| return { ...state, loading: false, error: action.payload } | |
| } | |
| default: { | |
| throw new Error(`The ${action.type} doesn't exists.`) | |
| } | |
| } | |
| } | |
| // actions/auth-actions.js | |
| // Pollyfill for fetch suggested: unfetch | |
| // How to use: import fetch from 'unfetch' | |
| export const login = (dispatch) => async (user, password) => { | |
| try { | |
| dispatch({ type: 'LOGIN' }) | |
| const response = fetch(`http://awesome.api/user/login/`, { | |
| body: JSON.stringify({ user, password }), | |
| headers: { 'Content-Type': 'application/json' }, | |
| }) | |
| const user = response.json() | |
| dispatch({ type: 'LOGIN.DONE', payload: user }) | |
| } catch(e) { | |
| console.error(`<auth-actions:login>: ${e.message}`) | |
| dispatch({ type: 'LOGIN.FAIL', payload: e.message }) | |
| } | |
| } | |
| export const logout = (dispatch) => async () => { | |
| try { | |
| dispatch({ type: 'LOGOUT' }) | |
| const response = fetch(`http://awesome.api/user/logout`) | |
| dispatch({ type: 'LOGOUT.DONE' }) | |
| } catch(e) { | |
| console.error(`<auth-actions:logout>: ${e.message}`) | |
| dispatch({ type: 'LOGOUT.FAIL', payload: e.message }) | |
| } | |
| } | |
| // index.js | |
| import React from 'react' | |
| import { AuthProvider } from 'contexts/auth-context' | |
| import { authInitialState, authReducer } from 'reducers/auth-reducer' | |
| import { App } from 'App' | |
| ReactDOM.render( | |
| <React.StrictMode> | |
| <AuthProvider initialState={authInitialState} reducer={authReducer}> | |
| <App /> | |
| </AuthProvider> | |
| </React.StrictMode>, | |
| document.getElementById("root") | |
| ); | |
| // components/AuthenticatedApp | |
| import React from 'react' | |
| import { useAuthState, useAuthDispatch } from 'components/auth-context' | |
| import { logout } from 'actions/auth-actions' | |
| export const AuthenticatedApp = () => { | |
| const authState = useAuthState() | |
| const authDispatch = useAuthDispatch() | |
| return ( | |
| <div> | |
| <h1>{authState.user.name}</h1> | |
| <button type="button" onClick={async () => await logout(authDispatch)()}>Logout</button> | |
| </div> | |
| ) | |
| } | |
| // components/UnAuthenticatedApp | |
| import React, { useState } from 'react' | |
| import { useAuthDispatch } from 'components/auth-context' | |
| import { login } from 'actions/auth-actions' | |
| export const UnAuthenticatedApp = () => { | |
| const [user, setUser] = useState('') | |
| const [password, setPassword] = useState('') | |
| const authDispatch = useAuthDispatch() | |
| return ( | |
| <div> | |
| <input type="text" value={user} onChange={e => setUser(e.target.value)} placeholder="Insert you username" /> | |
| <input type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder="Insert your password" /> | |
| <button type="button" onClick={async () => await login(authDispatch)(user, password)}>Login</button> | |
| </div> | |
| ) | |
| } | |
| // App.js | |
| import React, {} from 'react' | |
| import { AuthenticatedApp } from 'components/AuthenticatedApp' | |
| import { UnAuthenticatedApp } from 'components/UnAuthenticatedApp' | |
| import { useAuthState } from 'contexts/auth-context.js' | |
| export const App = () => { | |
| const authState = useAuthState() | |
| return authState.user ? <AuthenticatedApp /> : <UnAuthenticatedApp /> | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React, { useCallback } from 'react' | |
| // Hook: useCallback | |
| const callbackMemoizado = useCallback(() => { | |
| // contenido de tu función de retorno (callback) | |
| }, [...dependencias]) | |
| // Usos de useCallback | |
| // Podemos usar useCallback para crear una función que se | |
| // cree unicamente cuando alguna de sus dependencias sufra algun cambio | |
| // o se actualize, debido a que pasaremos dicha función a otro componente | |
| // y no queremos que se renderize a causa de que la funcion se re-cree | |
| // como conscuencia de los eventos que ocurran en el componente, de esta manera | |
| // podremos evitar que el componente hijo al que le pasemos dicha función | |
| // se renderize innecesariamente. | |
| // Por ejemplo: imaginemos que queremos crear una función que luego usaremos | |
| // para construir una consulta que se usará posteriormentet para traer cierta información | |
| // usando como dependencia un servicio que se crea dinámicamente. | |
| const getPlaceQuery = useCallback(() => createQuery(service), [service]) | |
| <ChildComponent getPlaceQuery={getPlaceQuery} /> | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React, { useContext, createContext } from 'react' | |
| // Hook: useContext | |
| // Context API: createContext() | |
| const SomeContext = createContext() | |
| const valueToSharedAcrossAllComponents = { | |
| someData: 'Some data to share' | |
| }; | |
| const Provider = ({ children }) => { | |
| return ( | |
| <SomeContext.Provider value={valueToSharedAcrossAllComponents}> | |
| {children} | |
| </SomeContext.Provider> | |
| ) | |
| } | |
| const useSomeContext = () => { | |
| const someContext = useContext(SomeContext); | |
| if (someContext === undefined) { | |
| throw new Error(`useSomeContext debe ser usado dentro de Provider`) | |
| } | |
| return someContext; | |
| } | |
| // Usos del useContext y Context API | |
| // Podemos usar useContext y Context API para enviar cierta información | |
| // que queremos compartir en varios componentes que no se encuentran en | |
| // el mismo nivel o padre. | |
| // Por ejemplo: si queremos compartir el tema de la aplicación a todos los | |
| // componentes de la aplicación. | |
| const theme = { color: 'peru', fontFamily: 'Roboto' } | |
| const ThemeContext = createContext() | |
| const ThemeProvider = ({ theme, children }) => { | |
| return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider> | |
| } | |
| const useTheme = () => { | |
| const theme = useContext(ThemeContext); | |
| if (theme === undefined) { | |
| throw new Error(`useTheme debe ser usando dentro de ThemeProvider`) | |
| } | |
| return theme; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { useEffect } from 'react' | |
| // Hook: useEffect | |
| useEffect(() => { | |
| // Ejecuta esto: | |
| // 1. cuando el component se monta -> `componentDidMount()` | |
| // 2. cuando la `informacion` se actualiza -> `componentDidUpdate()` | |
| return () => { | |
| // Ejecuta esto: | |
| // 1. cuando la `informacion` se actualiza -> `componentDidUpdate()` | |
| // 2. cuando el componente se va a desmontar -> `componentWillUnmount()` | |
| } | |
| }, [informacion]) | |
| // Usos del useEffect | |
| // Cuando el hook `useEffect` no tiene definida una lista de dependencias | |
| // significa estara pendiente de todos los cambios que sufra el componente | |
| // internamente (a causa de alguna actualización en los estados que maneja | |
| // internamente) o externamente (a causa de alguna actualizacion en las | |
| // propiedades que se le pasa al componente cuando es utilizado). | |
| useEffect(() => { | |
| // `componentDidMunt()` + `componentDidUpdate()` | |
| return () => {} // `componentDidUpdate()` + `componentWilUnmount()` | |
| }) | |
| // Cuando el hook `useEffect` tiene definida una lista de dependencias | |
| // pero dicha lista es esta vacia, significa que solo estará pendiente | |
| // a que el componente sea montado. | |
| useEffect(() => { | |
| // `componentDidMunt()` + `componentDidUpdate()` | |
| return () => {} // `componentDidUpdate()` + `componentWilUnmount()` | |
| }, []) | |
| // ✍️ Nota: la función anonima que le pasemos a useEffect no puede ser | |
| // asíncrona, por lo que si deseamos ejecutar una función asíncrona | |
| // es necesario crear dicha función dentro y luego usarla como en el siguiente ejemplo. | |
| useEffect(() => { | |
| const getDataAsync = async () => {} | |
| getDataAsync() | |
| }, []) | |
| // Cuando el hook `useEffect` tiene definida una lista de dependencias | |
| // significa que estará pendiente de los cambios que cualquiera de las | |
| // dependencias tenga de tal manera que la mantendra sincronizada. | |
| useEffect(() => { | |
| // `componentDidMunt()` + `componentDidUpdate()` | |
| return () => {} // `componentDidUpdate()` + `componentWilUnmount()` | |
| }, [...dependencias]) | |
| // ✍️ Nota: Es importante declarar la lista de dependencias al definir el useEffect | |
| // y no usar declarar afuera y asignarle el valor al `useEffect` como en el | |
| // siguiente ejemplo: | |
| const dependencias = [primeraDependencia, segundaDependencia, terceraDependencia] ❌ | |
| useEffect(() => {}, dependencias) ❌ | |
| // crear la lista de dependencias como parte del `useEffect` | |
| useEffect(() => {}, [primeraDependencia, segundaDependencia, terceraDependencia]) ✔️ | |
| // ✨ Tip: podemos cargar información dinámicamente usando como dependencia el valor | |
| // de algun dato que necesitemos para cargar la información. Un ejemplo puede ser que | |
| // necesitemos cargar los datos del usuario según el identificador del usuario, como | |
| // haremos en el siguiente ejemplo: | |
| useEffect(() => { | |
| const getDataAsync = async () => { | |
| try { | |
| const response = await fetch(`http://awesome.api/user/${userId}/`) | |
| const data = response.json() | |
| } catch(e) { | |
| window.alert(`Error: Ha ocurrido un error al intentar cargar los datos del usuario: ${userId}`) | |
| } | |
| } | |
| getDataAsync() | |
| }, [userId]) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { useMemo } from 'react' | |
| // Hook: useMemo | |
| useMemo(() => procesosPesados(), [...dependencias]) | |
| // Usos del useMemo | |
| // Podemos usar useMemo para evitar ejecutar procesos pesados o calculos | |
| // pesados: filtrar, ordenar, fibonacci, etc. | |
| // usuarios y queremos devolver solo los usuarios cuyo estado es 3 | |
| const usuariosFiltrados = useMemo( | |
| () => usuarios.filter(usuario => usuario.status == 3), | |
| [usuarios] | |
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { useReducer } from 'react' | |
| // Hook: useReducer | |
| const [estado, disparador] = useReducer((estado, accion) => { | |
| // Codigo para calcular el nuevo estado basado en la acción | |
| // nuevoEstado = f(estado, accion) | |
| }, estadoInicial) | |
| // ✍️ Nota: el estado inicial no necesariamente tiene que ser un objecto | |
| // Usos del useReducer | |
| // Podemos usar useReducer usando conocimientos previos de redux | |
| // debido a que useReducer usa una función para calcular el nuevo | |
| // estado tal y cual lo hace redux. | |
| // Por ejemplo: imaginemos que queremos crear un contador usando | |
| // useReducer para delimitar y tener mas control sobre las acciones | |
| // permitidas para interactuar con el estado. | |
| const [state, dispatch] = useReducer((state, action) => { | |
| switch(action.type) { | |
| case 'INCREMENT': { | |
| return state + 1; | |
| } | |
| case 'DECREMENT': { | |
| return state - 1: | |
| } | |
| default: { | |
| throw new Error(`The ${action.type} is not permitted.`) | |
| } | |
| } | |
| }, 0) | |
| // Ahora podemos usar `dispatch` para trabajar sobre nuestro contador | |
| dispatch({ type: 'INCREMENT' }) | |
| console.log(state) | |
| dispatch({ type: 'INCREMENT' }) | |
| console.log(state) | |
| dispatch({ type: 'DECREMENT' }) | |
| console.log(state) | |
| // ✨ Tip: podemos reemplazar la mayoria de casos de uso de redux para | |
| // la gestion de estados locales (dentro del component) y externos mediante | |
| // el uso de useContext y Context API. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { useState } from 'react' | |
| // Hook: useState | |
| const [estado, definirEstado] = useState(estadoInicial) | |
| // Usos del useState | |
| // Podemos manejar multiples estados y utilizar dichos estados | |
| // como el estado inicial de otro estado | |
| const [firstName, setFirstName] = useState('') | |
| const [lastName, setLastName] = useState('') | |
| const [fullName, setFullName] = useState(`${lastName}, ${firstName}`) | |
| // ✍️ Nota: cuando el valor a actualizar es generado a partir del estado actual | |
| // es mejor usar una funcion para definir el siguiente estado. | |
| // Por ejemplo: cuando creamos un contador usamos el estado anterior para calcular el | |
| // siguienteestado (contador = contador + 1) por lo que la forma correcta es definir | |
| // el estado es mediante una función de tal manera de que podamos acceder de manera segura | |
| // a el último estado. | |
| const [count, setCount] = useState(0) | |
| // ✍️ Nota: no es seguro usar el estado fuera del metodo del metodo para actualizar el estado | |
| // si es que queremos calcular el nuevo estado basado en el estado actual, sobre todo | |
| // si dicha acción se ejecuta de manera asíncrona. | |
| setCount(count + 1) ❌ | |
| // ✨ Tip: podemos pasar una función como parametro el cual recibe el estado actual, | |
| // por lo tanto podemos calcular el siguiente estado de forma segura. | |
| setCount(currentCount => currentCount + 1) ✔️ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment