React router – what is the best practise to navigate from object list to single object [closed]

In path /user, there is a list of users

enter image description here

When I click the name button, it will go to /user/:id, which will use UserProfile component

App.js

import "./styles.css";
import { Route, useHistory, withRouter } from "react-router-dom";

import Users from "./Users";
import UserProfile from "./UserProfile";

function App() {
  let history = useHistory();

  function handleClick() {
    history.push("/user");
  }

  return (
    <div className="App">
      <button
        onClick={() => {
          handleClick();
        }}
      >
        Click to change path
      </button>
      <Route exact path="/user" component={Users} />
      <Route path="/user/:id" component={UserProfile} />
    </div>
  );
}

export default withRouter(App);

Users.js

const users = [
  { id: 1, name: "Tom" },
  { id: 2, name: "Ken" }
];

export default function Users() {
  return (
    <div className="App">
      <h1>User List</h1>
      {users.map((user) => {
        return (
          <div>
            <button>{user.name}</button>
          </div>
        );
      })}
    </div>
  );
}

UserProfile.jsx

// how to get the user object/id here?????
export default function UserProfile() {
  return (
    <div className="App">
      <h1>User Profile</h1>
      <p>I am user xxx</p>
    </div>
  );
}

On this stage, I am thinking the best practise to access to single user component.

Any suggestion?

Codesandbox
https://codesandbox.io/s/shy-butterfly-6ezgi?file=/src/Users.jsx:0-607

Answer

First order of business, I changed your paths to /users and /users/{id}. Google has a guide on resource names conventions. See also: “REST API Resource Naming Conventions – User or Users”

Note: I added the following hooks to optimize rendering:

  • useCallback
  • useEffect
  • useMemo
  • useState

I changed your router to render the component rather than tell it what the component is. This way I can reference path names and parameter from the route. View the userRenderer below:

src/App.jsx

import React, { useCallback, useMemo } from "react";
import { Route, useHistory, withRouter } from "react-router-dom";
import Users from "./components/Users";
import UserProfile from "./components/UserProfile";

const styles = {
  display: "flex",
  flexDirection: "column",
  justifyContent: "center",
  alignItems: "center"
};

const userRenderer = ({ match }) => (
  <UserProfile id={parseInt(match.params.id, 10)} />
);

const App = () => {
  const history = useHistory();

  const handleClick = useCallback(() => history.push("/users"), [history]);

  return useMemo(
    () => (
      <div className="App" style={styles}>
        <button
          onClick={() => {
            handleClick();
          }}
        >
          Go to Users
        </button>
        <Route exact path="/users" component={Users} />
        <Route path="/users/:id" render={userRenderer} />
      </div>
    ),
    [handleClick]
  );
};

export default withRouter(App);

With the render, we can parse the path variable :id and pass it to the UserProfile as a prop.

src/components/UserProfile.jsx

import React, { useEffect, useMemo, useState } from "react";
import { fetchUser } from "../services/UserService";

const styles = {
  display: "flex",
  flexDirection: "column",
  justifyContent: "center",
  alignItems: "center"
};

const UserProfile = (props) => {
  const { id } = props;
  const [user, setUser] = useState({});

  useEffect(() => {
    fetchUser(id).then((data) => {
      setUser(data);
    });
  }, [id]);

  return useMemo(
    () => (
      <div style={styles}>
        <h1>User Profile</h1>
        <p>I am user {user?.name}</p>
      </div>
    ),
    [user]
  );
};

export default UserProfile;

This way the UserProfile can internally fetch a user by its ID using the UserService. You will want to change the Promise.resolve to a native fetch() or axios.get call.

src/services/UserService.js

const fetchUsers = () =>
  Promise.resolve([
    { id: 1, name: "Tom" },
    { id: 2, name: "Ken" }
  ]);

const fetchUser = (id) =>
  fetchUsers().then((users) => users.find(({ id: _id }) => _id === id));

export { fetchUser, fetchUsers };

I created a forked sandbox that can be viewed here:

https://codesandbox.io/s/flamboyant-currying-lhes2

I reorganized your files into the following directory structure:

+ src/
  + components/
    - UserButton.jsx
    - UserProfile.jsx
    - Users.jsx
  + services/
    - UserService.js
  - App.jsx
  - index.jsx