Skip to content

Instantly share code, notes, and snippets.

@dbritto-dev
Last active June 24, 2021 16:13
Show Gist options
  • Save dbritto-dev/aebd0d8b35e079f3c92b2f7b6fc487f2 to your computer and use it in GitHub Desktop.
Save dbritto-dev/aebd0d8b35e079f3c92b2f7b6fc487f2 to your computer and use it in GitHub Desktop.
React Hooks para principiantes
// 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 />
}
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} />
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;
}
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])
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]
)
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.
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