Home > Blockchain >  Display each user details and zoomed map location with create react app and leaflet maps
Display each user details and zoomed map location with create react app and leaflet maps

Time:08-30

I am trying to create a simple app that displays a list of user cards with some user information from a JSON API and a map with markers of each user's location. That part I have managed to successfully implement.

However I would like to now display a popup with the full user's information and the zoomed location for that user on the leaflet map but I'm struggling understanding how to associate each user card button to that specific user data.

This is my App.js file where I have a function to zoom to each user location when each marker is clicked on the leaflet map and where I fetch my data from the json file and display it through my UserCard component:

import { useState, useEffect } from 'react';
import './App.css';
import UserCard from './UserCard';
import UserModal from './UserModal';
import { MapContainer, TileLayer, Marker, Popup, useMap } from "react-leaflet";
import { Icon } from "leaflet";

function Markers({ data }) {
  const map = useMap();
  return (
    data.length > 0 &&
    data.map((marker, index) => {
      return (
        <Marker
          eventHandlers={{
            click: () => {
              map.flyTo(
                [
                  marker.address.geo.lat,
                  marker.address.geo.lng
                ],
                5
              );
            }
          }}
          key={index}
          position={{
            lat: marker.address.geo.lat,
            lng: marker.address.geo.lng
          }}
          Icon={Icon}
        >
          <Popup>
            <div className="map-popup">{marker.name}</div>
          </Popup>
        </Marker>
      );
    })
  );
}


function App() {

const [users, setUsers] = useState([]);
const [data, setData] = useState([]);


useEffect(() => {
  (async () => {
    let userData;
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/users/')
      userData = await response.json();    
    
    } catch (error) {
      console.log(error);
      userData = [];
    }

    setUsers(userData);
    setData(userData);

    // Map


  })();
}, []);


  return (
    <div className="App">
      <div className="cards-container">
        {users.map((user, index) => (
          <UserCard userData={user} key={index}/>
        ))}
                        
      </div>

      <div className="map-container">
        
      <MapContainer 
        center={[47.217324, 13.097555]}
        zoom={0}
        style={{ height: "100vh" }}>
        <TileLayer
    url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
    attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
      />

      <Markers data={data}/>

      </MapContainer>
      </div>
    </div>
  );
}

export default App;

This is my UserCard component with a selection of user information which is exported and used in App.js:

import './UserCard.css';
import UserModal from './UserModal';

const UserCard = ({ userData }) => {
    return (
        <div className="card">
            <div className="card-name">{userData.name}</div>

            <div className="card-body">
                <div className="card-email"><i className="fa fa-envelope" /> {userData.email}</div>
                <div className="card-phone"><i className="fa fa-phone" /> {userData.phone}</div>
                <div className="card-website"><i className="fa fa-globe" /> {userData.website}</div>
                <div className="card-company"><i className="fa fa-briefcase" /> {userData.company.name}</div>
                <UserModal />
            </div>
        </div>
    );
};

export default UserCard;

And this is my UserModal component which is where full user data and zoomed in map location should display for each individual user when the modal button on UserCar is clicked:

import { useState, useEffect } from 'react';
import Button from 'react-bootstrap/Button';
import Modal from 'react-bootstrap/Modal';
import 'bootstrap/dist/css/bootstrap.min.css';
import UserDetails from './UserDetails';

function UserModal() {
    const [show, setShow] = useState(false);
  
    const handleClose = () => setShow(false);
    const handleShow = () => setShow(true);

    const [users, setUsers] = useState([]);

    useEffect(() => {
      (async () => {
        let userData;
        try {
          const response = await fetch('https://jsonplaceholder.typicode.com/users/')
          userData = await response.json();    
        
        } catch (error) {
          console.log(error);
          userData = [];
        }
    
        setUsers(userData);
    
        // Map
    
    
      })();
    }, []);
  
    return (
      <>
        <Button variant="primary" onClick={handleShow}>
          Full User Details
        </Button>
  
        <Modal show={show} onHide={handleClose}>
          <Modal.Header closeButton>
            <Modal.Title>User Details</Modal.Title>
          </Modal.Header>
          <Modal.Body>
          {users.map((user, id) => (
            <UserDetails userData={user} key={id}/>
           ))}
          </Modal.Body>
        </Modal>
      </>
    );
  }
  export default UserModal;

At the moment I am repeating code here from my App.js in order to get the user data on my UserModal popup but I am getting a list of all users when I click the button to open the popup on each user card and I'm not managing to associate each button to that user specific data.

Lastly this is my UserDetails component that is responsible for displaying full user data inside my UserModal component.

const UserDetails = ({ userData }) => {
    return (
        <div className="card">
            <div className="card-name">{userData.name}</div>

            <div className="card-body">
                <div className="card-username"><i className="fa fa-envelope" /> {userData.username}</div>
                <div className="card-email"><i className="fa fa-envelope" /> {userData.email}</div>
                <div className="card-address"><i className="fa fa-envelope" /> {userData.address.street}, {userData.address.suite}, {userData.address.city}, {userData.address.zipcode}, {userData.address.geo.lat}, {userData.address.geo.lng}</div>
                <div className="card-phone"><i className="fa fa-phone" /> {userData.phone}</div>
                <div className="card-website"><i className="fa fa-globe" /> {userData.website}</div>
                <div className="card-company"><i className="fa fa-briefcase" /> {userData.company.name}, {userData.company.catchPhrase}, {userData.company.bs}</div>
            </div>
        </div>
    );
};

export default UserDetails;

So, my question is how do I associate each CardModal button to open a modal with only that user data.

Demo here

CodePudding user response:

  1. To open each modal with only the relevant details just pass userData as prop to UserModal and do not fetch all user details and loop over them. You just need a single user details not all of them.

  2. You need to use a state management tool to avoid prop drilling because you need to pass each user info to the map in order to have each user's geo data to fly the map to. The easiest solution is to use react context. Create a provider that will hold the user info and a setter to be able to change the user info upon clicking to each user.

UserProvider.js with context

const Context = createContext();
const Provider = ({ children }) => {
  const [user, setUser] = useState(null);
  const userProvider = useMemo(() => ({ user, setUser }), [user, setUser]);

UserModal.js, here save the user info upon clicking to each user detail btn

const handleShow = () => {
    setShow(true);
    setUser(userData);
  };

  return (
    <>
      <Button variant="primary" onClick={handleShow}>
        Full User Details
      </Button>

App.js here consume the user info to change the map location using a custom react-leaflet component

function SetMapView() {
  const { user } = useAppContext();
  const map = useMap();

  useEffect(() => {
    if (!user) return;

    const { lat, lng } = user.address.geo;

    map.flyTo([lat, lng], 5);
  }, [user]);

  return null;
}

Demo

  • Related