Elevando el estado - Ejemplo
Supongamos que tenemos un modal que puede abrirse y cerrarse. El componente sería más o menos así:
const Modal = () => {const [isOpen, setIsOpen] = useState(false)const open = () => setIsOpen(true)const close = () => setIsOpen(false)return (isOpen &&<div className='modal'><button onClick={close}>X</button></div>)}
Modal
tiene un estado isOpen
que determina si el componente se renderiza o no. A su vez, tiene las funciones open
y close
que controlan el cambio de estado.
El problema que tenemos es que un modal no tiene forma de mostrarse a si mismo. No tiene ningún botón ni nada que permita ejecutar la función open
, porque para eso, ya tendría que estar mostrándose.
Entonces, necesitamos que algo más, es decir, otro componente, pueda abrir nuestro modal. Supongamos que nuestra aplicación tiene la siguiente estructura:
App||.--Container--.| |Modal Button
En nuestro caso, queremos que el componente Button
pueda abrir nuestro modal. Como ya mencionamos, el estado tiene que encontrarse en un único lugar, es decir, tiene que haber una única fuente de la verdad, y si queremos que distintas funciones compartan un estado y las acciones que lo modifican, tenemos que nuclearlo en un único componente y compartir las funciones que cada uno necesitan.
Para eso, tenemos que elevar el estado del componente Modal
. A dónde lo elevamos? Al ancestro común entre los componentes que necesitan compartir el estado más inmediato. En este caso, tanto Modal
como Button
son hermanos, por lo que su ancestro común más inmediato es Container
. Por lo tanto, tenemos que elevar el estado de Modal
a Container
.
Cómo hacemos esto? Primero tenemos que extraer el estado y las funciones que lo modifican, y cambiarlo de componente. En nuestro caso:
const Container = () => {const [isOpen, setIsOpen] = useState(false)const open = () => setIsOpen(true)const close = () => setIsOpen(false)return (<><Button /><Modal /></>)}
const Modal = () => (isOpen &&<div className='modal'><button onClick={close}>X</button></div>)
Container
posee ahora el estado isOpen
y las funciones open
y close
. Podemos renombrarlos a algo un poco más significativo:
const Container = () => {const [isModalOpen, setIsModalOpen] = useState(false)const openModal = () => setIsModalOpen(true)const closeModal = () => setIsModalOpen(false)return (<><Button /><Modal /></>)}
Cómo hacemos ahora para compartir este estado y estas funciones? Mediante props. Empecemos por compartirle el estado isModalOpen
a nuestro componente Modal
:
const Container = () => {const [isModalOpen, setIsModalOpen] = useState(false)const openModal = () => setIsModalOpen(true)const closeModal = () => setIsModalOpen(false)return (<><Button /><Modal isOpen={isModalOpen} /></>)}
Ahora Modal
tiene un prop
isOpen
que va a depender del estado isModalOpen
. Dentro de Modal
tenemos que obtenerlo de los props
.
const Modal = ({isOpen}) => (isOpen &&<div className='modal'><button onClick={close}>X</button></div>)
El siguiente paso es pasarle la función closeModal
al componente Modal
. Esto también lo hacemos mediante props
:
const Container = () => {const [isModalOpen, setIsModalOpen] = useState(false)const openModal = () => setIsModalOpen(true)const closeModal = () => setIsModalOpen(false)return (<><Button /><ModalisOpen={isModalOpen}onClose={closeModal}/></>)}
Es importante destacar que lo que estamos pasándole a Modal
en el prop onClose
es una referencia a la función closeModal
. Entonces, dentro de nuestro Modal
, podemos utilizarla así:
const Modal = ({isOpen, onClose}) => (isOpen &&<div className='modal'><button onClick={onClose}>X</button></div>)
Dentro del prop onClose
está la referencia a la función closeModal
. Cuando se haga click en el botón y se ejecute el callback que tiene asignado, React encuentra que tiene una referencia a otra función, va a buscar esa función y luego la ejecuta. Por lo tanto, al hacer click en el botón en el componente Modal
estamos ejecutando la función closeModal
del componente Container
.
Ahora sólo nos queda pasar de la misma forma a la función openModal
a nuestro componente Button
:
const Container = () => {const [isModalOpen, setIsModalOpen] = useState(false)const openModal = () => setIsModalOpen(true)const closeModal = () => setIsModalOpen(false)return (<><Button onClick={openModal} /><ModalisOpen={isModalOpen}onClose={closeModal}/></>)}
y en el componente Button
, usar dicha prop
con la función asiganada para algo:
const Button = ({onClick}) => (<button onClick={onClick} />)
Si bien onClick={onClick}
puede prestarse a confusión, es importante entender qué significa cada cosa. El primer onClick
representa un evento del elemento button
, el segundo es un prop del componente Button
que contiene la función que se le asignó cuando se creó el componente.
📄 Resumiendo
- Teníamos dos componentes,
Modal
yButton
, que necesitaban compartir ciertas acciones que modificasen un estado en particular. - Para esto buscamos el ancestro común más inmediato, el componente
Container
, de los cualesModal
yButton
son componentes hijos. - "Elevamos" el estado y las acciones que modifican dicho estado a dicho componente.
- Pasamos el estado y las funciones a cada componente que lo necesita mediante
props
. - Dentro de cada componente, utilizamos esos
props
(que incluyen referencias a funciones del componente padre) para asignarlos a los elementos que corresponden.
Hecho esto, es importante destacar un par de cosas:
- Hay una única "fuente de la verdad" del estado
isModalOpen
, que reside en el componenteContainer
. - El flujo de la información es de arriba hacia abajo,
Container
pasa el estado y las funciones a sus componentes hijos, pero no a la inversa. Container
en ningún momento hace uso de dicho estado o de dichas funciones, simplemente se encarga de compartirlos entre sus componentes hijos (esto no es un requisito, puede que tenga que hacer uso de ellos dependiendo de la ocasión).