Damas y Caballeros, bienvenidos a la clase de Portals
Primeros pasos con React
Cómo aprender React.js
Cuándo usar React.js
Cambios en React 18: ReactDOM.createRoot
Instalación con Create React App
Fundamentos de React: maquetación
JSX: componentes vs. elementos (y props vs. atributos)
Componentes de TODO Machine
CSS en React
Fundamentos de React: interacción
Manejo de eventos
Manejo del estado
Contando y buscando TODOs
Completando y eliminando TODOs
Fundamentos de React: escalabilidad
Organización de archivos y carpetas
Persistencia de datos con Local Storage
Custom Hook para Local Storage
Manejo de efectos
React Context: estado compartido
useContext
Modales y formularios
Portales: teletransportación de componentes
Formulario para crear TODOs
Retos
Reto: loading skeletons
Reto: icon component
Próximos pasos
Deploy con GitHub Pages
Toma el Curso de React.js: Patrones de Render y Composición
Aún no tienes acceso a esta clase
Crea una cuenta y continúa viendo este curso
Aportes 51
Preguntas 19
Damas y Caballeros, bienvenidos a la clase de Portals
Primeramente, para que el botón se mantuviera visible lo que hice fue agregarle un z-index en el css del componente CreateTodoButton de la siguiente forma:
.CreateTodoButton {
z-index: 1;
}
Y luego para poder cerrarlo lo que hice fue enviarle ademas de setOpenModal el atributo openModal al componente CreateTodoButton para poder crear un condicional
<CreateTodoButton setOpenModal={setOpenModal} openModal={openModal} />
function CreateTodoButton({ setOpenModal, openModal }) {
const onClickButton = () => {
if (openModal) {
setOpenModal(false);
} else {
setOpenModal(true);
}
};
return (
<button className="CreateTodoButton" onClick={() => onClickButton()}>
+
</button>
);
}
para tener en cuenta en react 18
para hacer render del root se usa
import ReactDOM from 'react-dom/client';
pero para hacer render del modal se usa el anterior
import ReactDOM from "react-dom";
espero les sirva el aporte 😃
Me da risa la emoción de Juan. Se emociona cuando aparece el modal y luego nos manda el reto 😂 😂 😂.
¡A por el reto!
Al igual que los compañeros use el useContext para practicar su uso y en el css agregué el z-index:
function CreateTodoButton() {
const { openModal, setOpenModal } = React.useContext(TodoContext);
const onClickButton = () => {
setOpenModal(!openModal);
};
return (
<button className="CreateTodoButton" onClick={onClickButton}>
+
</button>
);
}
.CreateTodoButton{
z-index: 1;
}
Los portales en React sirven para teletransportar componentes a un nodo de HTML distinto al nodo principal de la aplicación.
Para los que hagan el curso con React 18, si les sale en el navegador el siguiente error ->
"“react_dom_client__WEBPACK_IMPORTED_MODULE_1__.createPortal is not a function”",
si en su archivo modal.js importaron ReactDOM de 'react-dom/cliente, lo cambian por ’ react-dom ’
Aqui estan los estilos CSS
.ModalBackground {
background: rgba(32, 35, 41, .8);
position: fixed;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
display: flex;
justify-content: center;
align-items: center;
color: white;
}
Buenas, yo quise seguir manteniendo la técnica del useContext
ya que estamos usando el Provider para enviar las props y state
y me esta convenciendo de que es la forma más limpia de mantener controlado y de fácil mantención el pasaje de props y estados.
function CreateTodoButton(){
const {
setOpenModal,
openModal,
} = React.useContext(TodoContext);
const handleClick = () => {
setOpenModal(!openModal);
}
return (
<button
className="CreateTodoButton"
onClick={handleClick}
>
+
</button>
);
}
Luego, para mantener por arriba al createTodoButton
con la propiedad css z-index
tanto en css del Modal y del CreateTodoButton se soluciona sencillamente.
Hola, yo use el react hook useContext
CSS:
z-index: 1;
JS Toggle:
const { openModal, setOpenModal } = useContext(TodoContext)
const onClickButton = ()=>{
!openModal ? setOpenModal(true) : setOpenModal(false)
}
El caso de uso que se me ocurre para los portales es, teniendo un sitio web estático, de puro HTML, CSS, poder usar React no en una, sino en algunas partes (el root y los portales), y conservar gran parte del HTML original, y sin tener que reconstruir todo el sitio con React.
Si vienes de Vue, esta herramienta es similar a los Slots.
Esta es mi solución para el reto para cerrar el modal
en el css solo use un z-index: 999; para que el boton esté sobre cualquier otro elemento
En JS hice los siguiente
import React, { useContext } from 'react';
import { TodoContext } from '../../Context/TodoContext';
import './CreateTodoButton.css';
export const CreateTodoButton = () => {
const { openModal, setOpenModal } = useContext(TodoContext);
const handleClick = () => {
setOpenModal(!openModal);
};
return (
<>
<button
className="CreateTodoButton"
type="submit"
onClick={() => handleClick()}
>
{openModal ? 'x' : '+'}
</button>
</>
);
};
Una API para renderizar componentes fuera de la jerarquía DOM de su aplicación.
Incluso podrías renderizar cosas en una ventana nueva! 😎
.
.
Perfecto para ocasiones donde los estilos CSS restringen los elementos. Por ejemplo, problemas de apilamiento (z-index) y desbordamiento (overflow).
.
En lugar de retornar un elemento en el método render de un componente, retorna un portal.
El componente <Afuera/> se renderizará como descendiente directo de document.body 👍🏻
.
import React from "react";
import './CreateTodoButton.css'
function CreateTodoButton({setOpenModal, openModal}){
const onClickButton = () => {
setOpenModal(!openModal)
}
return(
<button className="CreateTodoButton"
onClick={onClickButton}>+</button>
)
}
export { CreateTodoButton }
.CreateTodoButton {
z-index: 1;
}
Solución al reto
Modal
import React from "react";
import "./createTodoButton.css";
const CreateTodoButton = ({ setOpenModal, openModal }) => {
const onClick = () => setOpenModal(!openModal);
return (
<button
className="create-todo-button"
type="button"
onClick={onClick}
>
+
</button>
);
};
export default CreateTodoButton;
css boton de crear
.create-todo-button {
background-color: #61dafa;
box-shadow: 0px 5px 25px rgba(97, 218, 250, 0.5);
border: none;
border-radius: 50%;
cursor: pointer;
font-size: 50px;
position: fixed;
bottom: 24px;
right: 24px;
font-weight: bold;
color: #fafafa;
display: flex;
justify-content: center;
align-items: center;
height: 64px;
width: 64px;
transform: rotate(0);
transition: 0.3s ease;
z-index: 999;
}
Componente Boton
import React from "react";
import "./createTodoButton.css";
const CreateTodoButton = ({ setOpenModal, openModal }) => {
const onClick = () => setOpenModal(!openModal);
return (
<button
className="create-todo-button"
type="button"
onClick={onClick}
>
+
</button>
);
};
export default CreateTodoButton;
Para el reto de cerrar el modal simplemente pase el estado actual osea openModal a buttom y lo pase por parametro a su actualizador setOpenModal y lo niegue para que me devolviera el valor contrario.
function CreateTaskButton(props) {
const onClickButton = () => {
props.setOpenModal(!props.openModal);
}
return (
<button
className="CreateTaskButton"
onClick={onClickButton}
>
+
</button>
);
}
export { CreateTaskButton };```
Aquí mi sencilla solución:
en AppIU.js lo primero que hice fue pasarle openModal también
<CreateTodoButton
setOpenModal={setOpenModal}
openModal={openModal}
/>
luego en CreateTodoButton
function CreateTodoButton({ openModal , setOpenModal }) {
const onClickCreateTodoBotton = () => {
setOpenModal(!openModal);
};
return (
<button
className="CreateTodoButton"
onClick={onClickCreateTodoBotton}
>
+
</button>
);
}
Mi solucion al reto fue, en CreateTodoButton.css, agregue un z-index: 1 y para hacer que el modal se abra o cierre le pase al componente la propiedad openModal y agregue lo siguiente en CreateTodoButton
const onClickButton = (msg) => {
props.setOpenModal(!props.openModal);
};
Lo que hice fue darle a button un z-index de 1, después pararle las propiedades setOpenModal={setOpenModal} openModal={openModal} a el componente <CreateTodoButton /> y en su index.js decirle que en el evento onclick utilizara props.setOpenModal(!props.openModal) para que vaya alternando entre true o false dependiendo del click.
Juan Dc Ama lo que hace, sin duda.
Esta fue mi solución:
function CreateTodoButton (){
const {setOpenModal, openModal} = React.useContext(TodoContext);
const buttonFunction = () => {
if (openModal){
setOpenModal(false)
} else{
setOpenModal(true)
}
};
return (
<button className='button' onClick={()=> buttonFunction()}>
Add Task
</button>
)
}
juan: es hora de terminar nuestra aplicacion 😃 😃 😃 😃
yo: esta toda rota mi app juan no mms xd
Para importar las props use useContext y arrow function para simplificar onClick sin perder legibilidad
const { openModal, setOpenModal } = React.useContext(TodoContext);
const onClickButton = () => setOpenModal(!openModal)
Solo agregar z-index: 1;
a las clases del boton, y listo
Reto completado 🌭
CreateTodoButton --> index.js
function CreateTodoButton(props) {
const onClickButton = () => {
props.openModal ? props.setOpenModal(false) : props.setOpenModal(true)
}
return (
<button className="CreateTodoButton"
onClick={onClickButton}
>
+
</button>
);
}
CreateTodoButton --> css, solo le agrego
z-index: 1;
CreateTodoButton.js
import React from 'react';
import './CreateTodoButton.css';
import {TodoContext} from "../TodoContext";
function CreateTodoButton() {
const {
openModal,
setOpenModal,
} = React.useContext(TodoContext);
const onClickButton = () => {
(!openModal)
? setOpenModal(true)
: setOpenModal(false)
}
return (
<button
className={ "CreateTodoButton "+ (openModal ? 'active' : '' )}
onClick={onClickButton}
>+</button>
)
}
export {CreateTodoButton};
CreateTodoButton.css
.CreateTodoButton {
background-color: #61DAFA;
box-shadow: 0px 5px 25px rgba(97, 218, 250, 0.5);
border: none;
border-radius: 50%;
cursor: pointer;
font-size: 50px;
position: fixed;
bottom: 24px;
right: 24px;
font-weight: bold;
color: #FAFAFA;
display: flex;
justify-content: center;
align-items: center;
height: 64px;
width: 64px;
transform: rotate(0);
transition: .3s ease;
z-index: 1;
}
.CreateTodoButton:hover, .CreateTodoButton.active {
transform: rotate(224deg);
}
Hola, para hacer que el signo de más se vea como una tachita cuando se abra el modal, tienes que agregar este CSS al final del archivo CreateTodoButton.css
.CloseTodoButton {
transform: rotate(45deg);
}
Ahora solo basta con agregar esta clase cuando el modal esté abierto, supongo que hay varias maneras de hacerlo pero yo lo hice así (Archivo CreateTodoButton/index.js
)
import React from 'react';
import './CreateTodoButton.css';
import { TodoContext } from '../TodoContext';
function CreateTodoButton(props) {
const { openModal, setOpenModal } = React.useContext(TodoContext);
let classNameButton = "CreateTodoButton";
classNameButton += openModal ? " CloseTodoButton": "";
return (
<button
className={classNameButton}
onClick={() => setOpenModal(!openModal)}
>
+
</button>
);
}
export { CreateTodoButton };
Ahí mismo podrás encontrar como abrir y cerrar el modal de la manera más sencilla posible.
Mi solución al reto del toggle sería así:
1- Cambiar la propiedad z-index de la clase “CreateTodoButton” en el archivo .css
z-index: 10; /*o cualquier valor superior al del <div id="modal"></div>
2- Pasar “openModal” como props al componente “CreateTodoButton” en el archivo de AppUI.js
<CreateTodoButton
setOpenModal={setOpenModal}
openModal={openModal}
/>
3- Recibir la propiedad “openModal” en la definición del componente “CreateTodoButton” y asignar la negación del valor de “openModal” al momento de llamar a la función “setOpenModal”
const CreateTodoButton = ({ openModal, setOpenModal }) => {
const onClickButtom = () => {
setOpenModal(!openModal)
}
...
Los React Portals te permiten comunicar el nodo principal donde se renderiza nuestra aplicación con otro nodo generado en HTML. Ambos se pueden comunicar entre sí recibiendo cambios en el estado y en las props.
Personalmente prefiero pasarle un componente como prop a los modales. Eg:
import React from "react";
import ReactDOM from "react-dom";
function Modal({ component }) {
return ReactDOM.createPortal(
<>{component}</>,
document.getElementById('modal')
)
}
export { Modal }
Y usarlo de la siguiente forma:
<Modal component={ <UnFormConMuchosInputs /> } />
A mi gusto queda más legible y reutilizable 😄
Dentro de AppUI.js
agregue el estado del modal,
<CreateTodoButton
openModal={openModal}
setOpenModal={setOpenModal}
/>
para recibirlo en props dentro de CreateTodoButton/index.js
const onClickButton = () => {
props.setOpenModal(!props.openModal)
}
donde cambio su valor al contrario del que tiene permitiendo el toogle
Lo que yo hice fue: En AppUI.js agregue esta otra propiedad:
<CreateTodoButton
setOpenModal={setOpenModal}
openModal={openModal}
/>
Luego en el index.js del createButton agregue estas líneas,
const onClickButton =()=> {
props.openModal ? props.setOpenModal(false) : props.setOpenModal(true);
}
Para que alterne entre el true y el false,
Y para que el botón se viera, fui al css del mismo y le agregué un z-index: 10;
Para que se superponga a todo el contenido.
Me pareció mejor cerrar el modal cuando el usuario haga click en cualquier parte de la pantalla menos del contenido del modal.
import { TodoContext } from 'context/todoContext';
import { useContext } from 'react';
import ReactDOM from 'react-dom'
import './Modal.css'
function Modal({ children }) {
const { openModal, setOpenModal } = useContext(TodoContext)
return ReactDOM.createPortal(
openModal &&
<div className="modal-wrapper" onClick={()=> setOpenModal(false)}>
<div className="modal-content" onClick={(event)=> event.stopPropagation()}>
{children}
</div>
</div>,
document.getElementById('modal') as Element
);
}
export default Modal;
/*Modal.css*/
.modal-wrapper{
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.8);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content{
width: 100%;
max-width: 500px;
background: #fff;
border-radius: 5px;
padding: 20px;
box-shadow: 0 0 10px rgba(0,0,0,0.5);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
Para que no se cierre el modal al hacer click en el contenido, evitamos la propagación del evento al padre, con el codigo event.stopPropagation()
MI solución a los retos, con el button como todo buen programador z-index: 999; XD
Y al Modal
function CreateTodoButton({ openModal, setOpenModal }) {
const onClickButton = () => {
!openModal ? setOpenModal(true) : setOpenModal(false);
};
}
Estuvo genial la clase:DDD
Usando useContext
/CreateTodoButton/CreateTodoButton.jsx
import React from 'react'
import "./CreateTodoButton.css"
import { TodoContext} from './../context/todoContext'
function CreateTodoButton() {
const { openModal,setOpenModal}=React.useContext(TodoContext)
const onClickButton = () => {
setOpenModal(true)
}
return (
<button onClick={()=>onClickButton()} className='CreateTodoButton'>+</button>
)
}
export {CreateTodoButton}
Modal/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css'
import { TodoContext} from './../App/context/todoContext'
const Modal = ({ children }) => {
const { openModal,setOpenModal}=React.useContext(TodoContext)
const onClickButton = () => {
setOpenModal(false)
}
return ReactDOM.createPortal(
<div className='modal'>
<div className="modal-content">
<button className="close" onClick={()=>onClickButton()}>×</button>
{children}
</div></div> ,
document.getElementById('modal')
);
}
export {Modal}
Modal/index.css
.modal {
position: fixed;
z-index: 1;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
Con CSS para el button utilizé z-index: 2 y en el modal z-index: 1
Para abrir / cerrar el modal utilize el useContext:
const { openModal } = React.useContext(TaskContext);
const onClickButton = () =>
{
props.setOpenModal( !openModal );
};
Este curso es una joya, me encanta todo lo que he aprendido
Mi solución fue abusar del poderoso useContext
import React from 'react';
import { TodoContext } from '../TodoContext';
import './CreateTodoButton.css'
function CreateTodoButton() {
const { openModal, setOpenModal} = React.useContext(TodoContext);
const onClickButton = () =>{
setOpenModal(!openModal);
}
return (
<button
className="CreateTodoButton"
onClick={onClickButton}
>+</button>
)
}
export { CreateTodoButton }
Mi solución fue pasar el estado de openModal y usar un ternario.
function CreateTodoButton({ openModal, setOpenModal }) {
const onClickButton = () => {
(openModal)
? setOpenModal(false)
: setOpenModal(true)
};
. . .
}
Para el css solo use el z-index
.CreateTodoButton {
. . .
z-index: 2;
}
Mi solución al reto:
function CreateTodoButton({ setOpenModal }) {
const [isActive, setIsActive] = useState(false);
const handleClick = () => {
setOpenModal((openModal) => !openModal);
setIsActive(!isActive);
};
return (
<button
className={`CreateTodoButton ${isActive && "OpenModal"}`}
onClick={handleClick}
>
+
</button>
);
}
isActive
se encarga de manejar el estado de si el modal está abierto o no, si lo está, agrega una clase OpenModal
al button.
En CreateTodoButton.css
agregue una clase la cual, lo único que hace, es darle un z-index
de 1.
.OpenModal {
z-index: 1;
}
Mi solucion
function Modal({children, setOpenModal}) {
//la funcion handleClick cierra el modal
function handleClick() {
setOpenModal(false)
}
return ReactDom.createPortal(
<div className="ModalBackground">
{children}
<span onClick={handleClick}>
X
</span>
</div>,
document.getElementById('modal')
);
}
Una posible solución para cerrar el Modal, puede ser la siguiente: Al hacer click en cualquier lugar afuera del componente “Portaleado”, se cierra la modal.
.
Al componente padre (Modal) se le añade el evento de al hacer click, cerrar modal.
.
Modal/index.js
export const Modal = ({ children, setOpenModal }) => {
const handleClick = () => setOpenModal(false);
return ReactDOM.createPortal(
<Wrapper onClick={handleClick}>{children}</Wrapper>,
document.getElementById('modal')
);
};
Para impedir que al hacer click dentro del componente “portaleado” se cierre, se usa un evento stopPropagation()
.
AppUI.js
<Modal setOpenModal={setOpenModal}>
<div onClick={(e) => e.stopPropagation()}>Transporteee</div>
</Modal>
Mi solución:
setOpenModal(!openModal);
Yo lo hice de esta manera, para cerrar el modal puse el ícono “X”. Dentro del evento onClick mando a llamar la siguiente función :
const { setOpenModal } = React.useContext(TodoContext);
const closeModalButton = () => {
setOpenModal(false);
}
Y por lo pronto mi modal se ve de la siguiente manera:
Así esta bien para mí
Mi solucion al reto de abrir y cerrar el modal es de la siguiente manera:
export const CreateTodoButton = ({setOpenModal, openModal}) => {
const handleClick = () => {
setOpenModal(!openModal);
}
return (
<button
className="btn-create"
onClick={handleClick}
></button>
);
}
coloque un z-index en el css del boton
z-index: 1;
y modifique el renombre el onClickButton para llamarlo onToggleButton y
en setOpenModal agregue la negación de su valor actual.
const onToggleButton = () => {
props.setOpenModal(!props.openModal);
}
El uso de modales en los portales es de uso comun
Pregunta de examen:
¿Cómo creamos un portal en React?
¿Quieres ver más aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesión.