Skip to content

Crea Aplicaciones Web Impresionantes con Material UI, Guía para Principiantes

Material UI es una biblioteca de componentes de interfaz de usuario para React que sigue los diseños de Material Design de Google. Proporciona componentes estilizados y listos para usar para acelerar el desarrollo de aplicaciones web y móviles. Material UI permite a los desarrolladores crear interfaces de usuario atractivas y funcionales con facilidad, mientras mantiene una consistencia en la apariencia y la experiencia de usuario.

Documentación oficial aquí: Material UI

En este tutorial estaremos trabajando en la versión 5 de MUI.

Material Design

Es un sistema de diseño de Google que se centra en proporcionar una experiencia de usuario coherente y atractiva en todos los dispositivos. Fue lanzado en 2014 y es un enfoque en la creación de una estética visual y de interacción sólida y estructurada.

Material Design utiliza elementos visuales sólidos y sombras para proporcionar una sensación de profundidad en la interfaz de usuario, y se basa en una escala de grises y en una paleta de colores limitada para proporcionar una apariencia uniforme y coherente.

La filosofía de Material Design se basa en la idea de que el software debe ser tangible, y que la interacción con él debe ser intuitiva y natural.

Ventajas de Material UI (MUI)

  • Material UI sigue los diseños de Material Design de Google, lo que garantiza una experiencia de usuario coherente y atractiva.
  • La biblioteca ofrece una amplia variedad de componentes estilizados y listos para usar, lo que ahorra tiempo y esfuerzo en el desarrollo.
  • Material UI permite a los desarrolladores personalizar fácilmente los componentes para satisfacer sus necesidades específicas.
  • Material UI es compatible con React, lo que significa que los desarrolladores pueden integrar fácilmente la biblioteca en sus proyectos existentes.
  • Material UI ofrece una documentación completa y detallada para ayudar a los desarrolladores a aprovechar al máximo la biblioteca.
  • Material UI cuenta con una comunidad activa y en constante crecimiento, lo que significa que los desarrolladores pueden obtener soporte y soluciones a problemas de manera eficiente.

En este curso de Material UI aprenderás:

Cómo instalar y configurar Material UI en tu proyecto de React.js y crear interfaces de usuario atractivas y funcionales. Este curso te enseñará los conceptos clave de Material UI, incluyendo CSSBaseline, Container, Typography, Grid, ThemeProvider, y muchos más.

Practica el uso de componentes como Navbar, Drawer, Button, Card, y explora componentes avanzados como AppBar, Alert, Snackbar, entre otros.

Además, para finalizar el curso, pondrás en práctica tus conocimientos creando una emocionante aplicación web de clima (Weather App) desde cero. Podrás buscar ciudades y obtener el clima actual al instante. No te pierdas la oportunidad de crear una experiencia única para tus usuarios.

¡Domina Material UI en proyectos de React.js para crear interfaces impresionantes y funcionales!

Github

Encuentra todo el código del curso aquí: código del curso MUI

Ayúdame a seguir creando contenido 😍

Tienes varias jugosas alternativas:

Muchas gracias por su tremendo apoyo 😊

Ramas

Instalación de MUI

Nuevo proyecto de react:

sh
npm create vite@latest .
npm i

Instalación de Material UI:

sh
npm install @mui/material @emotion/react @emotion/styled
npm install @fontsource/roboto
npm install @mui/icons-material

npm install @mui/lab #LoadingButton etc

Material UI utiliza Emotion como su motor de estilos predeterminado. Emotion es una biblioteca de estilos CSS-in-JS que se puede utilizar con React, React Native, Vue, Angular, Ember, Svelte, Preact, Inferno, etc.

Ejemplo rápido

jsx
import { Button } from "@mui/material";
jsx
<Button variant="contained">Hello World</Button>

CssBaseline

CssBaseline: Material UI proporciona un componente CssBaseline opcional. Corrige algunas incoherencias entre navegadores y dispositivos al tiempo que proporciona restablecimientos que se adaptan mejor a la interfaz de usuario de Material que las hojas de estilo globales alternativas como normalize.css.

CssBaseline incluye una serie de reglas CSS que establecen valores para propiedades como la tipografía, los márgenes y los paddings, entre otros. Estas reglas son aplicadas a toda la aplicación y se aseguran de que todos los componentes tengan una apariencia consistente.

main.jsx

jsx
import { CssBaseline } from "@mui/material";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <CssBaseline />
    <App />
  </React.StrictMode>
);

Sin embargo, es posible que esté migrando progresivamente un sitio web a MUI, y usar un restablecimiento global podría no ser una opción. Es posible aplicar la línea de base solo a los niños usando el ScopedCssBaseline componente.

jsx
import { ScopedCssBaseline } from "@mui/material";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <ScopedCssBaseline>
      <App />
    </ScopedCssBaseline>
  </React.StrictMode>
);

Container

jsx
import { Container } from "@mui/material";
jsx
<Container>
  <h1>Material UI</h1>
  <Button variant="contained">mi primer componente</Button>
</Container>

maxWidth: Determina el ancho máximo del contenedor. El ancho del contenedor crece con el tamaño de la pantalla. Establézcalo en falso para deshabilitar maxWidth.

The sx prop: es un atajo para definir estilos personalizados que tienen acceso al tema.

jsx
<Container
  maxWidth="xs"
  sx={{
    border: 2,
    boxShadow: 1,
    pb: 2,
  }}
>
  <h1>Material UI</h1>
  <Button variant="contained">mi primer componente</Button>
</Container>

Typography

Recordar que instalamos: npm install @fontsource/roboto y en nuestro main.jsx importamos:

jsx
import "@fontsource/roboto/300.css";
import "@fontsource/roboto/400.css";
import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css";

Ejemplo:

jsx
<Typography variant="h1">Soy un h1</Typography>
<Typography variant="h2">Soy un h2</Typography>
<Typography variant="h3">Soy un h3</Typography>

Changing the semantic element

La variante está asociada con un elemento semántico HTML predeterminado. Puede cambiar el elemento semántico predeterminado utilizando el component prop.

jsx
<Typography
  variant="h1"
  component="h2"
>
  Soy un h1
</Typography>

System props

Este componente es compatible con todos system props, por ejemplo podemos aplicar un margin top:

jsx
<Typography
  variant="h1"
  component="h2"
  mb={2}
  color="primary"
  align="center"
  boxShadow={2}
  pb={2}
>
  Soy un h1
</Typography>

Box

  • El componente Box sirve como un componente contenedor para la mayoría de las necesidades de la utilidad CSS.
  • box
  • The sx prop: es un atajo para definir estilos personalizados que tienen acceso al tema.
  • properties
  • example
jsx
<Box
  sx={{
    border: 2,
    borderColor: "peru",
    p: 2,
    bgcolor: "#111",
    color: "white",
  }}
>
  <Typography align="center">
    Lorem ipsum dolor sit amet consectetur adipisicing elit. Earum saepe quas
    laudantium, enim praesentium maiores beatae impedit vero dolorum
    dignissimos, assumenda ipsam? Similique, mollitia commodi ducimus aliquid
    voluptate molestias laborum!
  </Typography>
</Box>

Theming

Vamos a cambiar la personalización de nuestro tema por defecto.

jsx
import CssBaseline from "@mui/material/CssBaseline";
import { createTheme, ThemeProvider } from "@mui/material/styles";

const theme = createTheme({
  palette: {
    primary: {
      main: "#000e35",
    },
    secondary: {
      main: "#f50057",
    },
  },
});

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <CssBaseline />
    <ThemeProvider theme={theme}>
      <App />
    </ThemeProvider>
  </React.StrictMode>
);

Button & Icons

jsx
import Container from "@mui/material/Container";
import Button from "@mui/material/Button";
import AirplanemodeActiveIcon from "@mui/icons-material/AirplanemodeActive";
import AndroidIcon from "@mui/icons-material/Android";

export default function App() {
  return (
    <Container>
      <Button
        variant="contained"
        color="info"
        startIcon={<AirplanemodeActiveIcon />}
      >
        Botón personalizado
      </Button>

      <Button
        variant="contained"
        color="secondary"
        endIcon={<AndroidIcon />}
      >
        Botón 2
      </Button>
    </Container>
  );
}

Grid

Se basa en 12 columnas al igual que Bootstrap. De esta manera podemos crear un layout responsive a través de Flexbox.

jsx
import Grid from "@mui/material/Grid";
import Container from "@mui/material/Container";

export default function App() {
  return (
    <Container>
      <Grid
        container
        spacing={2}
      >
        <Grid
          item
          xs={12}
          sm={6}
          md={4}
        >
          <p>
            Lorem, ipsum dolor sit amet consectetur adipisicing elit. Qui
            aspernatur perferendis ipsum veniam, nostrum eos quaerat sequi sed,
            quisquam minima provident, ad magnam saepe impedit voluptatem
            ratione quas molestias! Nisi.
          </p>
        </Grid>
        <Grid
          item
          xs={12}
          sm={6}
          md={4}
        >
          <p>
            Lorem, ipsum dolor sit amet consectetur adipisicing elit. Qui
            aspernatur perferendis ipsum veniam, nostrum eos quaerat sequi sed,
            quisquam minima provident, ad magnam saepe impedit voluptatem
            ratione quas molestias! Nisi.
          </p>
        </Grid>
        <Grid
          item
          xs={12}
          sm={12}
          md={4}
        >
          <p>
            Lorem, ipsum dolor sit amet consectetur adipisicing elit. Qui
            aspernatur perferendis ipsum veniam, nostrum eos quaerat sequi sed,
            quisquam minima provident, ad magnam saepe impedit voluptatem
            ratione quas molestias! Nisi.
          </p>
        </Grid>
      </Grid>
    </Container>
  );
}

MiniPráctica Card Product

Paper es un componente en Material-UI que proporciona un contenedor elevado y sombreado. Se utiliza para encerrar contenido y darle un aspecto elevado en la página. Paper es un componente versátil que puede ser utilizado para crear tarjetas, dialogos, menús desplegables, entre otros.

Los estilos y la apariencia de Paper pueden ser personalizados utilizando los props de Material-UI o a través de CSS. También es posible anidar varios componentes Paper para crear diseños más complejos.

Ejemplo de uso:

styled()

styled() es una función en Material-UI que permite crear componentes personalizados con estilos.

Es una forma de agregar estilos a componentes personalizados con una sintaxis similar a los componentes CSS.

La función styled() se utiliza en conjunto con el paquete @emotion/styled, que proporciona una forma sencilla de aplicar estilos CSS en JavaScript.

jsx
import { styled } from "@mui/material/styles";
import { Button, Paper } from "@mui/material";
import { Box } from "@mui/system";

const Img = styled("img")({
  width: 200,
  height: "100%",
  objectFit: "cover",
  objectPosition: "center",
});

export default function Product() {
  return (
    <Paper
      sx={{
        display: "flex",
        alignItems: "center",
        gap: 2,
        overflow: "hidden",
        mt: 5,
      }}
    >
      <Img
        src="https://via.placeholder.com/200"
        alt="random"
      />
      <Box sx={{ flexGrow: 1 }}>
        <h2>Product Name</h2>
        <p>Product Description</p>
        <Button variant="contained">Add cart</Button>
      </Box>
      <Box
        component="p"
        sx={{ mr: 2 }}
      >
        $19.99
      </Box>
    </Paper>
  );
}

Card

jsx
import {
  Button,
  Card,
  CardActionArea,
  CardActions,
  CardContent,
  CardMedia,
} from "@mui/material";

export default function MyCard() {
  return (
    <Card
      sx={{
        transition: "0.2s",
        "&:hover": {
          transform: "scale(1.05)",
        },
      }}
    >
      <CardActionArea>
        <CardMedia
          component="img"
          image="https://via.placeholder.com/1000x200"
          height="200"
          alt="Card Image"
        />
        <CardContent>
          <h2>Card Title</h2>
          <p>
            Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis
            deserunt optio exercitationem, fugit enim saepe iusto magnam ipsam
            est cumque hic deleniti sequi neque soluta quas! Accusamus voluptate
            alias optio.
          </p>
        </CardContent>
      </CardActionArea>

      <CardActions>
        <Button variant="contained">Add</Button>
        <Button>Remove</Button>
      </CardActions>
    </Card>
  );
}

List

aria-label

El atributo aria-label es un atributo ARIA (Accessible Rich Internet Applications) que proporciona una descripción textual para un elemento.

En este caso, el valor "main mailbox folders" proporciona una descripción de la sección de navegación que contiene enlaces a las carpetas principales de correo. Esto puede ser útil para los usuarios que utilizan lectores de pantalla o tecnologías de asistencia para mejorar la accesibilidad.

NavListDrawer.jsx

jsx
import {
  Divider,
  List,
  ListItem,
  ListItemButton,
  ListItemIcon,
  ListItemText,
} from "@mui/material";
import { Box } from "@mui/system";

import InboxIcon from "@mui/icons-material/Inbox";
import DraftsIcon from "@mui/icons-material/Drafts";

export default function NavListDrawer() {
  return (
    <Box sx={{ width: 250, bgcolor: "lightsalmon" }}>
      <nav aria-label="main mailbox folders">
        <List>
          <ListItem disablePadding>
            <ListItemButton>
              <ListItemIcon>
                <InboxIcon />
              </ListItemIcon>
              <ListItemText primary="Inbox" />
            </ListItemButton>
          </ListItem>
          <ListItem disablePadding>
            <ListItemButton>
              <ListItemIcon>
                <DraftsIcon />
              </ListItemIcon>
              <ListItemText primary="Draft" />
            </ListItemButton>
          </ListItem>
        </List>
      </nav>
      <Divider />
      <nav aria-label="secondary trash spam">
        <List>
          <ListItem disablePadding>
            <ListItemButton
              component="a"
              href="#trash"
            >
              <ListItemText primary="Trash" />
            </ListItemButton>
          </ListItem>
          <ListItem disablePadding>
            <ListItemButton
              component="a"
              href="#spam"
            >
              <ListItemText primary="Spam" />
            </ListItemButton>
          </ListItem>
        </List>
      </nav>
    </Box>
  );
}
jsx
import {
  Divider,
  List,
  ListItem,
  ListItemButton,
  ListItemIcon,
  ListItemText,
} from "@mui/material";
import { Box } from "@mui/system";

import InboxIcon from "@mui/icons-material/Inbox";
import DraftsIcon from "@mui/icons-material/Drafts";

const mainList = [
  {
    text: "Inbox",
    icon: <InboxIcon />,
    href: "#inbox",
  },
  {
    text: "Drafts",
    icon: <DraftsIcon />,
    href: "#drafts",
  },
];

const secondaryList = [
  {
    text: "Trash",
    href: "#trash",
  },
  {
    text: "Spam",
    href: "#spam",
  },
];

export default function NavListDrawer() {
  return (
    <Box sx={{ width: 250, bgcolor: "lightsalmon" }}>
      <nav aria-label="main mailbox folders">
        <List>
          {mainList.map((item) => (
            <ListItem
              disablePadding
              key={item.text}
            >
              <ListItemButton
                href={item.href}
                component="a"
              >
                <ListItemIcon>{item.icon}</ListItemIcon>
                <ListItemText primary={item.text} />
              </ListItemButton>
            </ListItem>
          ))}
        </List>
      </nav>
      <Divider />
      <nav aria-label="secondary trash spam">
        <List>
          {secondaryList.map((item) => (
            <ListItem
              disablePadding
              key={item.text}
            >
              <ListItemButton
                component="a"
                href={item.href}
              >
                <ListItemText primary={item.text} />
              </ListItemButton>
            </ListItem>
          ))}
        </List>
      </nav>
    </Box>
  );
}

Drawer

role="presentation"

role="presentation" es un atributo de HTML que se utiliza en elementos de HTML para especificar su rol en un documento o aplicación. Este atributo especifica que el elemento no tiene un rol semántico específico y que su uso se limita a la presentación visual.

En el caso de Material-UI, se utiliza role="presentation" en un elemento para indicar que el elemento es solo para la presentación y no debe tener un comportamiento específico en el contexto de la accesibilidad, como ser un enlace o un elemento de menú. Esto ayuda a mejorar la accesibilidad de la aplicación y a evitar errores en el comportamiento de la accesibilidad.

App.jsx

jsx
import { Container, Button, Drawer } from "@mui/material";
import { useState } from "react";
import NavListDrawer from "./components/NavListDrawer";

export default function App() {
  const [open, setOpen] = useState(false);

  return (
    <Container sx={{ display: "grid", gap: 4 }}>
      <Button
        variant="contained"
        onClick={() => setOpen(true)}
      >
        Open Drawer
      </Button>

      <Drawer
        anchor="left"
        open={open}
        onClose={() => setOpen(false)}
      >
        <NavListDrawer onClick={() => setOpen(false)} />
      </Drawer>
    </Container>
  );
}

NavListDrawer.jsx

jsx
export default function NavListDrawer({ onClick }) {
  return (
    <Box
      sx={{ width: 250 }}
      onClick={onClick}
    >
      ...
    </Box>
  );
}

AppBar

Ayúdame a seguir creando contenido 😍

Tienes varias jugosas alternativas:

Muchas gracias por su tremendo apoyo 😊

AppBar: El atributo position en AppBar se utiliza para especificar la posición del AppBar. El valor "static" indica que la barra de aplicaciones se mantendrá en su posición original en la página, sin flotar o estar fijada en la parte superior de la página. Esto significa que la barra de aplicaciones no se moverá mientras se desplaza por la página.

Los otros valores posibles para position son "fixed" y "sticky", que fijan la barra de aplicaciones en la parte superior de la página mientras se desplaza por ella.

Toolbar: proporciona un área de contenido para elementos como títulos, botones, menús, formularios y otros elementos. Además tiene configurado un display: flex.

IconButton: El atributo edge en IconButton se utiliza para especificar la ubicación de un botón con icono en un AppBar. En este caso se ubicará en la esquina superior izquierda.

jsx
import { AppBar, Button, IconButton, Toolbar, Typography } from "@mui/material";

import MenuIcon from "@mui/icons-material/Menu";

export default function Navbar() {
  return (
    <AppBar position="static">
      <Toolbar>
        <IconButton
          color="inherit"
          size="large"
          edge="start"
          aria-label="menu"
        >
          <MenuIcon />
        </IconButton>
        <Typography
          variant="h6"
          sx={{ flexGrow: 1 }}
        >
          News
        </Typography>
        <Button color="inherit">Home</Button>
        <Button color="inherit">Login</Button>
      </Toolbar>
    </AppBar>
  );
}

Ejemplo con React Router

jsx
import { AppBar, Button, Container, Toolbar, Typography } from "@mui/material";
import AddToDriveIcon from "@mui/icons-material/AddToDrive";
import { NavLink } from "react-router-dom";

const Navbar = () => {
  return (
    <AppBar position="static">
      <Container maxWidth="md">
        <Toolbar>
          <AddToDriveIcon />
          <Typography
            variant="h6"
            sx={{ ml: 1, flexGrow: 1 }}
          >
            MyCompany
          </Typography>
          <Button
            color="inherit"
            component={NavLink}
            to="/"
            sx={{ pt: 1 }}
          >
            Home
          </Button>
          <Button
            color="inherit"
            component={NavLink}
            to="/contact"
            sx={{ pt: 1 }}
          >
            Contact
          </Button>
        </Toolbar>
      </Container>
    </AppBar>
  );
};

export default Navbar;

isActive

jsx
<NavLink
  to="/"
  style={{ textDecoration: "none" }}
>
  {({ isActive }) => (
    <Button
      color="inherit"
      sx={{
        pt: 1,
        color: isActive ? "salmon" : "white",
      }}
    >
      Home
    </Button>
  )}
</NavLink>
jsx
<Button
  component={NavLink}
  to="/"
  sx={{ pt: 1 }}
  style={({ isActive }) => (isActive ? { color: "black" } : { color: "white" })}
>
  Home
</Button>

AppBar + Drawer

jsx
import {
  AppBar,
  Button,
  Drawer,
  IconButton,
  Toolbar,
  Typography,
} from "@mui/material";

import MenuIcon from "@mui/icons-material/Menu";
import NavListDrawer from "./NavListDrawer";
import { useState } from "react";

export default function Navbar() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <AppBar position="static">
        <Toolbar>
          <IconButton
            color="inherit"
            size="large"
            edge="start"
            aria-label="menu"
            onClick={() => setOpen(true)}
          >
            <MenuIcon />
          </IconButton>
          <Typography
            variant="h6"
            sx={{ flexGrow: 1 }}
          >
            News
          </Typography>
          <Button color="inherit">Home</Button>
          <Button color="inherit">Login</Button>
        </Toolbar>
      </AppBar>

      <Drawer
        anchor="left"
        open={open}
        onClose={() => setOpen(false)}
      >
        <NavListDrawer onClick={() => setOpen(false)} />
      </Drawer>
    </>
  );
}

AppBar + Drawer + Responsive

NavbarDrawerResponsive.jsx

jsx
import {
  AppBar,
  Button,
  Drawer,
  IconButton,
  Toolbar,
  Typography,
} from "@mui/material";

import MenuIcon from "@mui/icons-material/Menu";

import { useState } from "react";
import NavListDrawerResponsive from "./NavListDrawerResponsive";
import { Box } from "@mui/system";

const navLinks = [
  { title: "Home", path: "#" },
  { title: "Login", path: "#login" },
  { title: "Register", path: "#register" },
];

export default function Navbar() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <AppBar position="static">
        <Toolbar>
          <IconButton
            color="inherit"
            size="large"
            edge="start"
            aria-label="menu"
            onClick={() => setOpen(true)}
            sx={{ display: { xs: "block", sm: "none" } }}
          >
            <MenuIcon />
          </IconButton>
          <Typography
            variant="h6"
            sx={{ flexGrow: 1 }}
          >
            News
          </Typography>
          <Box sx={{ display: { xs: "none", sm: "block" } }}>
            {navLinks.map((link) => (
              <Button
                key={link.title}
                sx={{ color: "#fff" }}
                href={link.path}
              >
                {link.title}
              </Button>
            ))}
          </Box>
        </Toolbar>
      </AppBar>

      <Drawer
        anchor="left"
        open={open}
        onClose={() => setOpen(false)}
        sx={{ display: { xs: "block", sm: "none" } }}
      >
        <NavListDrawerResponsive
          onClick={() => setOpen(false)}
          navLinks={navLinks}
        />
      </Drawer>
    </>
  );
}

NavListDrawerResponsive.jsx

jsx
import {
  Divider,
  List,
  ListItem,
  ListItemButton,
  ListItemText,
} from "@mui/material";
import { Box } from "@mui/system";

export default function NavListDrawerResponsive({ onClick, navLinks }) {
  return (
    <Box
      sx={{ width: 250 }}
      onClick={onClick}
    >
      <nav aria-label="main mailbox folders">
        <List>
          {navLinks.map((item) => (
            <ListItem
              disablePadding
              key={item.title}
            >
              <ListItemButton
                href={item.path}
                component="a"
              >
                {/* <ListItemIcon>{item.icon}</ListItemIcon> */}
                <ListItemText primary={item.title} />
              </ListItemButton>
            </ListItem>
          ))}
        </List>
      </nav>
      <Divider />
    </Box>
  );
}

AppBar + React Router 6

sh
npm install react-router-dom@6

BrowserRouter

BrowserRouter es un componente que se utiliza en React para permitir la navegación por una aplicación web utilizando la [API de Historial HTML5]. Cuando se utiliza BrowserRouter, se establece una correspondencia entre las rutas definidas en la aplicación y las URL que se utilizan en el navegador.

main.jsx

jsx
import { BrowserRouter } from "react-router-dom";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <ThemeProvider theme={theme}>
      <BrowserRouter>
        <CssBaseline />
        <App />
      </BrowserRouter>
    </ThemeProvider>
  </React.StrictMode>
);

Routes & Route

El componente Routes es el componente principal que se utiliza para definir un conjunto de rutas que la aplicación puede manejar. Este componente se utiliza para definir las rutas principales y secundarias de la aplicación. Dentro de Routes, se pueden definir varias instancias del componente Route para especificar qué componentes deben renderizarse para cada ruta.

El componente Route se utiliza para definir una ruta específica dentro de la aplicación. Para definir una ruta, se utiliza la prop path para especificar la URL que se va a asociar con el componente que se va a renderizar. El componente Route también acepta una prop element que especifica el componente que se debe renderizar cuando se accede a la ruta especificada.

App.jsx

jsx
import { Route, Routes } from "react-router-dom";
jsx
<Routes>
  <Route
    path="/"
    element={<Home />}
  />
  <Route
    path="/login"
    element={<Login />}
  />
  <Route
    path="/register"
    element={<Register />}
  />
</Routes>

El componente NavLink es un componente proporcionado por la librería "react-router-dom" que se utiliza para crear enlaces de navegación en una aplicación de React.

Navbar.jsx

jsx
import { NavLink } from "react-router-dom";
jsx
// navegationLinks.map
<Button
  color="inherit"
  key={item.title}
  component={NavLink}
  to={item.path}
>
  {item.title}
</Button>

// Drawer
<NavListDrawer
  navegationLinks={navegationLinks}
  component={NavLink}
  setOpen={setOpen}
/>

NavListDrawer.jsx

jsx
<ListItemButton
  component={component}
  to={item.path}
  onClick={() => setOpen(false)}
>

Alert

jsx
<Alert severity="error">This is an error alert — check it out!</Alert>
<Alert severity="warning">This is a warning alert — check it out!</Alert>
<Alert severity="info">This is an info alert — check it out!</Alert>
<Alert severity="success">This is a success alert — check it out!</Alert>

Separación con grid

jsx
<Box sx={{ display: "grid", gap: "1rem" }}>
  <Alert severity="error">Esta es una alerta</Alert>
  <Alert severity="warning">This is a warning alert — check it out!</Alert>
  <Alert severity="info">This is an info alert — check it out!</Alert>
  <Alert severity="success">This is a success alert — check it out!</Alert>
</Box>

Con título

jsx
<Alert severity="error">
  <AlertTitle>Error</AlertTitle>
  Esta es una alerta
</Alert>

Actions

jsx
import { Alert, Button, Collapse } from "@mui/material";
import { Box } from "@mui/system";
import { useState } from "react";

export default function Home() {
  const [open, setOpen] = useState(true);

  return (
    <>
      <h1>Home</h1>
      <Box sx={{ display: "grid", gap: "1rem" }}>
        <Alert
          severity="warning"
          action={<Button color="inherit">Cerrar</Button>}
        >
          This is a warning alert — check it out!
        </Alert>
        <Collapse in={open}>
          <Alert
            severity="info"
            onClose={() => {
              setOpen(false);
            }}
          >
            This is an info alert — check it out!
          </Alert>
        </Collapse>
        <Alert
          severity="warning"
          action={<Button color="inherit">Cerrar</Button>}
        >
          This is a warning alert — check it out!
        </Alert>
      </Box>
    </>
  );
}

Dos botones

jsx
<Alert
  severity="warning"
  action={
    <>
      <Button color="primary">Update</Button>
      <Button color="error">Delete</Button>
    </>
  }
>
  This is a warning alert — check it out!
</Alert>

Icon

jsx
import { Alert } from "@mui/material";
import AutorenewIcon from "@mui/icons-material/Autorenew";

export default function Home() {
  return (
    <>
      <h1>Home</h1>
      <Alert
        severity="warning"
        icon={<AutorenewIcon fontSize="inherit" />}
      >
        This is a warning alert — check it out!
      </Alert>
    </>
  );
}

Variante: outlined & filled

jsx
<Alert
  severity="warning"
  variant="outlined"
>
  This is a warning alert — check it out!
</Alert>z

Snackbars

Ayúdame a seguir creando contenido 😍

Tienes varias jugosas alternativas:

Muchas gracias por su tremendo apoyo 😊

jsx
import { Alert, Button, Snackbar } from "@mui/material";
import { useState } from "react";

export default function Home() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <h1>Home</h1>

      <Button
        onClick={() => setOpen(true)}
        variant="contained"
      >
        Open
      </Button>

      <Snackbar
        open={open}
        autoHideDuration={1000}
        onClose={() => setOpen(false)}
        anchorOrigin={{ vertical: "top", horizontal: "right" }}
      >
        <Alert
          severity="warning"
          variant="filled"
        >
          This is a warning alert — check it out!
        </Alert>
      </Snackbar>
    </>
  );
}

notistack

bash
npm i notistack
jsx
import { SnackbarProvider } from "notistack";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <CssBaseline />
    <ThemeProvider theme={theme}>
      <SnackbarProvider
        maxSnack={3}
        autoHideDuration={2000}
      >
        <App />
      </SnackbarProvider>
    </ThemeProvider>
  </React.StrictMode>
);

En el componente donde se quiere usar:

jsx
import { useSnackbar } from "notistack";
jsx
const { enqueueSnackbar } = useSnackbar();

const handleClick = () => {
  enqueueSnackbar("Product added to cart", {
    variant: "success",
    anchorOrigin: {
      vertical: "top",
      horizontal: "right",
    },
  });
};

// anchorOrigin:
// Type: { horizontal: left | center | right, vertical: top | bottom }
// Default: { horizontal: left, vertical: bottom }

// variant:
// Type: default | error | success | warning | info
// Default: default
jsx
<Button
  variant="contained"
  onClick={handleClick}
>
  Add cart
</Button>

mui/lab

sh
npm install @mui/lab
jsx
import LoadingButton from "@mui/lab/LoadingButton";
jsx
const { enqueueSnackbar } = useSnackbar();
const [loading, setLoading] = useState(false);

const handleClick = () => {
  setLoading(true);

  setTimeout(() => {
    setLoading(false);
    enqueueSnackbar("Product added to cart", {
      variant: "success",
      anchorOrigin: {
        vertical: "top",
        horizontal: "right",
      },
    });
  }, 2000);
};
jsx
<LoadingButton
  variant="contained"
  onClick={handleClick}
  loading={loading}
>
  Add cart
</LoadingButton>

Text Field

jsx
<TextField
  id="outlined-basic"
  label="Outlined"
  variant="outlined"
/>

Formulario:

jsx
import { Box, Button, TextField } from "@mui/material";
import { useState } from "react";

export default function Register() {
  const [email, setEmail] = useState("");
  const [error, setError] = useState({
    error: false,
    message: "",
  });

  const emailValidation = (email) => {
    // expresion regular para validar email
    const regex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
    return regex.test(email);
  };

  const onSubmit = (e) => {
    e.preventDefault();
    if (!emailValidation(email)) {
      setError({
        error: true,
        message: "El email no es valido",
      });
      return;
    }
    console.log(email);
    setError({
      error: false,
      message: "",
    });
  };

  return (
    <>
      <h1>Register</h1>
      <Box
        component="form"
        onSubmit={onSubmit}
        autoComplete="off"
      >
        <TextField
          label="Email"
          variant="outlined"
          id="email"
          type="email"
          fullWidth
          required
          error={error.error}
          helperText={error.message}
          onChange={(e) => setEmail(e.target.value)}
          value={email}
        />
        <Button
          variant="outlined"
          type="submit"
          sx={{ mt: 2 }}
        >
          Submit
        </Button>
      </Box>
    </>
  );
}

Práctica Weather App

Install

sh
npm create vite@latest .
npm i
sh
npm install @mui/material @emotion/react @emotion/styled @fontsource/roboto @mui/icons-material @mui/lab notistack

main.jsx

js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

import "@fontsource/roboto/300.css";
import "@fontsource/roboto/400.css";
import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css";
import "./index.css";

import { CssBaseline } from "@mui/material";
import { SnackbarProvider } from "notistack";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <CssBaseline />
    <SnackbarProvider>
      <App />
    </SnackbarProvider>
  </React.StrictMode>
);

.env.local

sh
VITE_API_KEY=YOUR_API_KEY
js
console.log(import.meta.env.VITE_API_KEY);

API

sh
https://api.weatherapi.com/v1/current.json?key=tuApiKey&lang=es&q=london
js
const API_WEATHER = `http://api.weatherapi.com/v1/current.json?key=${
  import.meta.env.VITE_API_KEY
}&lang=es&q=`;

Code

App.jsx

jsx
import { LoadingButton } from "@mui/lab";
import { Box, Container, TextField, Typography } from "@mui/material";
import { useState } from "react";

const API_WEATHER = `http://api.weatherapi.com/v1/current.json?key=${
  import.meta.env.VITE_API_KEY
}&lang=es&q=`;

export default function App() {
  const [city, setCity] = useState("");
  const [error, setError] = useState({
    error: false,
    message: "",
  });
  const [loading, setLoading] = useState(false);

  const [weather, setWeather] = useState({
    city: "",
    country: "",
    temperature: 0,
    condition: "",
    conditionText: "",
    icon: "",
  });

  const onSubmit = async (e) => {
    e.preventDefault();
    setError({ error: false, message: "" });
    setLoading(true);

    try {
      if (!city.trim()) throw { message: "El campo ciudad es obligatorio" };

      const res = await fetch(API_WEATHER + city);
      const data = await res.json();

      if (data.error) {
        throw { message: data.error.message };
      }

      console.log(data);

      setWeather({
        city: data.location.name,
        country: data.location.country,
        temperature: data.current.temp_c,
        condition: data.current.condition.code,
        conditionText: data.current.condition.text,
        icon: data.current.condition.icon,
      });
    } catch (error) {
      console.log(error);
      setError({ error: true, message: error.message });
    } finally {
      setLoading(false);
    }
  };

  return (
    <Container
      maxWidth="xs"
      sx={{ mt: 2 }}
    >
      <Typography
        variant="h3"
        component="h1"
        align="center"
        gutterBottom
      >
        Weather App
      </Typography>
      <Box
        sx={{ display: "grid", gap: 2 }}
        component="form"
        autoComplete="off"
        onSubmit={onSubmit}
      >
        <TextField
          id="city"
          label="Ciudad"
          variant="outlined"
          size="small"
          required
          value={city}
          onChange={(e) => setCity(e.target.value)}
          error={error.error}
          helperText={error.message}
        />

        <LoadingButton
          type="submit"
          variant="contained"
          loading={loading}
          loadingIndicator="Buscando..."
        >
          Buscar
        </LoadingButton>
      </Box>

      {weather.city && (
        <Box
          sx={{
            mt: 2,
            display: "grid",
            gap: 2,
            textAlign: "center",
          }}
        >
          <Typography
            variant="h4"
            component="h2"
          >
            {weather.city}, {weather.country}
          </Typography>
          <Box
            component="img"
            alt={weather.conditionText}
            src={weather.icon}
            sx={{ margin: "0 auto" }}
          />
          <Typography
            variant="h5"
            component="h3"
          >
            {weather.temperature} °C
          </Typography>
          <Typography
            variant="h6"
            component="h4"
          >
            {weather.conditionText}
          </Typography>
        </Box>
      )}

      <Typography
        textAlign="center"
        sx={{ mt: 2, fontSize: "10px" }}
      >
        Powered by:{" "}
        <a
          href="https://www.weatherapi.com/"
          title="Weather API"
        >
          WeatherAPI.com
        </a>
      </Typography>
    </Container>
  );
}

⭐ Apoya el canal ⭐

Si quieres demostrar todo tu amor 😊, puedes comprar el curso en 👉🏽 Udemy: React + Firebase by bluuweb.

💀 Skeleton