18 Δεκ, 2022
Αναπτυξη ενος React-Router Dialog
Τί είναι ένα Dialog;
Σύμφωνα με το Material Design, τα Dialogs (γνωστά επίσης ως Modals), μπορούν να απαιτούν μια πράξη, να παραθέτουν πληροφορία ή να βοηθούν το χρήστη να ολοκληρώσει ένα task.
Τα Dialogs μας επιτρέπουν, να επικεντρώνουμε την προσοχή μας σε ένα περιεχόμενο δίχως να χάνουμε την background πληροφορία που συσχετίζεται μαζί του.
Παράδειγμα του Instagram
Σκεφτείτε ότι βρίσκεστε στο Instagram σκρολάροντας σε μια λίστα από posts και κλικάρετε σε ένα post για να δείτε περισσότερες πληροφορίες για αυτό. Παραδοσιακά, αυτό θα μπορούσε να υλοποιηθεί απλώς με ένα navigation σε μια άλλη σελίδα, αλλά αυτό συνήθως δεν είναι πολύ user-friendly.
Αυτό που μπορεί να γίνει αντ'αυτού, είναι να δείξουμε ένα Dialog με τις λεπτομέρειες του post, χωρίς να φύγουμε από τη σελίδα διατηρώντας τη λίστα των posts στο background. Έτσι, μετά ο χρήστης μπορεί απλώς να κλείσει το post και να συνεχίσει με τη λίστα, κλικάροντας σε ένα άλλο post κλπ, κλπ.
Δείτε τι κάνει το Instagram με τα posts του και πόσο user-friendly φαίνεται:
Μια Αφελής Προσπάθεια
Θα μπορούσαμε να θεωρήσουμε ότι μπορούμε να υλοποιήσουμε την παραπάνω λειτουργικότητα με το να γράψουμε κάτι σαν και αυτό:
const App = () => {
const [selectedPost, setSelectedPost] = useState(null);
return <div>
<ListOfPosts>
{posts.map(post => <Post post={post} onClick={() => setSelectedPost(post)}/>)}
</ListOfPosts>
<PostDetailsDialog selectedPost={selectedPost}>
<div/>
}
Φαίνεται αρκετά απλό έτσι;
Ή... μήπως όχι;
Ο κώδικας παραπάνω σίγουρα θα μπορούσε γενικά να λειτουργήσει, όμως μας ξεφεύγει ένα πολύ σημαντικό πράγμα.
Τι θα γίνει με το page url; Με μια υλοποίηση όπως την παραπάνω, το url της σελίδας δεν αλλάζει κάθε φορά που ένα Post εμφανίζεται.
Και από μια UI/UX προοπτική, είναι σίγουρα για το καλύτερο εάν το page-url ακολουθεί το user-journey της εφαρμογής μας.
Επιπλέον, αλλάζοντας το page url όταν το PostDetailsDialog
εμφανίζεται και κρατώντας ένα unique url για κάθε post, επιτρέπουμε στον χρήστη να κάνει εύκολα share ένα post απλώς μοιράζοντας το σύνδεσμο που βρίσκεται εκείνη τη στιγμή ο browser!
Τώρα, κοιτάξτε πώς το Instagram συμπεριφέρεται, όταν κάνουμε share το URL ενός post: https://www.instagram.com/p/CfeYvQOD1z-/
To post εμφανίζεται σε μια νέα σελίδα χωρίς να βρίσκεται μέσα σε Dialog και χωρίς να δείχνει τη λίστα των posts στο background. Απλώς επικεντρώνεται σε αυτό το post.
Απαιτήσεις
Αυτές είναι οι απαιτήσεις που πρέπει να τηρηθούν ώστε να επιτύχουμε την προαναφερθείσα λειτουργικότητα:
- Εμφάνισε ένα Dialog για κάθε post που κλικάρεται
- Το page url πρέπει να αλλάζει και πρέπει να είναι unique για κάθε post
- Όταν ο χρήστης επισκέπτεται ένα Post από ένα shared URL, το post πρέπει να εμφανίζεται κανονικά μέσα σε μια νέα σελίδα (χωρίς να βρίσκεται μέσα σε Dialog)
Μπορούμε να χρησιμοποιήσουμε το React Router με ένα Dialog component ώστε να ικανοποιήσουμε τις απαιτήσεις.
Κώδικας & Υλοποίηση
Μπορείτε να βρείτε τον πλήρη κώδικα εδώ: https://codesandbox.io/s/react-router-modal-gallery-qs44n1
Τι θα επιτύχουμε (παρατηρήστε πώς αλλάζει το URL)
Και όταν επισκεπτόμαστε την εικόνα από ένα shared URL το post ανοίγει σε νέα σελίδα αυτούσιο:
Ώρα για κώδικα!
Το κλειδί εδώ είναι ότι θα χρησιμοποιήσουμε το location
prop του Routes
component που παρέχεται από το react-router-dom
.
Βασικά, χρησιμοποιώντας το location
prop μπορούμε να "παρακάμψουμε" το πραγματικό location που βρίσκεται η εφαρμογή μας εκείνη τη στιγμή.
Το backgroundLocation
(μπορείτε να ονομάσετε την μεταβλητή όπως επιθυμείτε) έρχεται από το location state της εφαρμογής μας.
Έπειτα τσεκάρουμε εάν το backgroundLocation
έχει οριστεί και εάν ναι, σημαίνει ότι πρέπει να δείξουμε ένα Dialog και να διατηρήσουμε το background location, οπότε εφαρμόζουμε το location
prop στο Routes
component.
Ειδάλλως, εάν το backgroundLocation
δεν έχει οριστεί, τότε το location
prop απλώς παίρνει την default τιμή του location και η εφαρμογή μας ακολουθεί το "normal" flow που σημαίνει ότι το Post μας θα γίνει render όπως είναι σε μια σελίδα και όχι σε ένα Dialog.
// App.js
import { Box, Container, Divider, Typography } from "@mui/material";
import { Outlet, Route, Routes, useLocation } from "react-router-dom";
import About from "./About";
import ImageList from "./ImageList";
import ImageView from "./ImageView";
import ImageModal from "./ImageModal";
import Home from "./Home";
const Layout = () => (
<Container maxWidth="md">
<Box mb={2}>
<Typography variant="h4">Application Demo</Typography>
<Divider />
</Box>
<Outlet />
</Container>
);
const App = () => {
const location = useLocation();
const { state } = location;
return (
<div>
<Routes location={state?.backgroundLocation || location}>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/images" element={<ImageList />} />
<Route path="/images/:id" element={<ImageView />} />
</Route>
</Routes>
{/* Show the modal when a `backgroundLocation` is set */}
{state?.backgroundLocation && (
<Routes>
<Route path="/images/:id" element={<ImageModal />} />
</Routes>
)}
</div>
);
};
export default App;
Δείτε πώς μπορούμε να ορίσουμε το state μαζί με το url σε ένα Link
component, όταν ένας χρήστης κλικάρει σε μια εικόνα.
Είναι σαν να λέμε στην εφαρμογή μας: "Ο χρήστης κλίκαρε σε μια εικόνα, οπότε πρέπει να δείξουμε την εικόνα μέσα σε ένα Dialog διατηρώντας το background location".
// ImageList.js
import { Box, Grid, Stack, Typography } from "@mui/material";
import React, { useState } from "react";
import { Link, useLocation } from "react-router-dom";
import { IMAGES } from "./images";
const ImageBox = ({ image, location }) => {
const [hovered, setHovered] = useState(false);
const { id, src } = image;
return (
<Box width="100%" position="relative">
<Link to={`/images/${id}`} state={{ backgroundLocation: location }}>
<Box
onMouseEnter={() => setHovered(true)}
component="img"
src={src}
alt="post"
width="100%"
height="250px"
/>
{hovered && (
<Box
onMouseLeave={() => setHovered(false)}
position="absolute"
top={0}
left={0}
bottom={0}
right={0}
width="100%"
height="250px"
bgcolor="rgba(0, 0, 0, 0.3)"
// sx={{ opacity: 0.6 }}
/>
)}
</Link>
</Box>
);
};
const ImageList = () => {
let location = useLocation();
return (
<Grid container spacing={2} alignItems="center" justifyContent="center">
{IMAGES.map(({ title, src, id }, index) => (
<Grid key={id} item sm={4}>
<ImageBox image={{ title, src, id }} location={location} />
</Grid>
))}
</Grid>
);
};
export default ImageList;
// ImageModal.js
import { useNavigate, useParams } from "react-router-dom";
import ImageView from "./ImageView";
import { getImage } from "./images";
import { Button, Dialog, DialogActions, DialogContent } from "@mui/material";
const ImageModal = () => {
let navigate = useNavigate();
let { id } = useParams();
let image = getImage(id);
if (!image) return null;
const handleClose = () => {
navigate(-1);
};
return (
<Dialog open onClose={handleClose}>
<DialogContent>
<ImageView />
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Close</Button>
</DialogActions>
</Dialog>
);
};
export default ImageModal;
// ImageView.js
import { ArrowBackIos } from "@mui/icons-material";
import { Button, Link, Stack, Typography, Box } from "@mui/material";
import React from "react";
import { useLocation, useParams, Link as RouterLink } from "react-router-dom";
import { getImage } from "./images";
const ImageView = () => {
const { state } = useLocation();
const { id } = useParams();
const image = getImage(id);
const { src, title } = image;
const isInsideModal = state?.backgroundLocation;
return (
<div>
{!isInsideModal && (
<Link component={RouterLink} to="/images" underline="none">
<Button startIcon={<ArrowBackIos />} variant="text">
Back to Images
</Button>
</Link>
)}
<Box display="flex" justifyContent="center">
<Stack direction="column" spacing={1}>
<Typography variant="h5" fontWeight={600}>
{title}
</Typography>
<Box component="img" sx={{ width: "400px" }} src={src} alt="dog" />
</Stack>
</Box>
</div>
);
};
export default ImageView;
Τέλος
Ελπίζω να βρήκατε το άρθρο χρήσιμο.
Τα λέμε σύντομα! 🙂
Εγγραφή
Εγγραφειτε στην λιστα
Εγγραφείτε με το e-mail σας για να σας στέλνω το υλικό μου. Δεν θα είναι spam, σας το υπόσχομαι! Μπορείτε να καταργήσετε την εγγραφή σας όποτε θέλετε.