React master/detail pattern

This post was inspired by this Tweet by Sebastian Markbåge, member of the core React team.

I had not used the term "master/detail" before but knew immediately what Seb was referring to in his post because I have used that pattern many times.

What is master/detail anyway?#

A master/detail view includes a List component to display a list of items and a Detail component to display details for a selected item.

Examples#

Below are 2 instances of the same component with 1 tiny difference that alters the behavior of the User Details section.

🐞 Broken version#

As you change users, the details section becomes stale and does not match the selection in the list.

User List

  • Leanne Graham
  • Ervin Howell
  • Clementine Bauch
  • Patricia Lebsack
  • Chelsey Dietrich
  • Mrs. Dennis Schulist
  • Kurtis Weissnat
  • Nicholas Runolfsdottir V
  • Glenna Reichert
  • Clementina DuBuque

User Details

⬅️ Select a user

✅ Working version#

This version ensures the details section is reset immediately upon selection in the list, allowing additional data to load and then be displayed to match the selection.

User List

  • Leanne Graham
  • Ervin Howell
  • Clementine Bauch
  • Patricia Lebsack
  • Chelsey Dietrich
  • Mrs. Dennis Schulist
  • Kurtis Weissnat
  • Nicholas Runolfsdottir V
  • Glenna Reichert
  • Clementina DuBuque

User Details

⬅️ Select a user

What changed?#

🐞 Code snippet for broken version#

const UserList = () => {
  const [users, setUsers] = useState([]);
  const [selectedUser, setSelectedUser] = useState(null);

  useEffect(() => {
    (async () => {
      const resp = await fetch('https://jsonplaceholder.typicode.com/users');
      const json = await resp.json();
      setUsers(json);
    })();
  }, []);

  return (
    <div>
      <div>
        <h2>User List</h2>
        <ul>
          {users.map(u => (
            <li key={u.id} onClick={() => setSelectedUser(u)}>{u.name}</li>
          ))}
        </ul>
      </div>
      <div>
        <h2>User Details</h2>
        {/* 🐞 Pay attention to this line! 🐞 */}
        {selectedUser ? <UserDetails user={selectedUser} />}
      </div>
    </div>
  )
};
const UserDetails = ({ user }) => {
  const [userDetails, setUserDetails] = useState(null);

  useEffect(() => {
    (async () => {
      const resp = await fetch(`https://jsonplaceholder.typicode.com/users/${user.id}`);
      const json = await resp.json();
      setUserDetails(json);
    })();
  }, [user]);

  if (!userDetails) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      {userDetails && (
        <>
          <ul>
            <li>Name: {userDetails.name}</li>
            <li>Company: {userDetails.company.name}</li>
          </ul>
        </>
      )}
    </div>
  );
};

✅ Code snippet for working version#

{/* ✅ Pay attention to this line! ✅ */}
{selectedUser ? <UserDetails key={u.id} user={selectedUser} />}

🔑 The key take away#

By adding the key prop to the <UserDetails> component, we are telling React:

When the user id changes, mount a new component

In this case, we want this because as the selected list item changes we do not want to preserve any of the old state of the previous selection. In this simple example, there is not much local state but more realistic scenarios may have more state. Imagine use cases like editing user names and other fields. You certaintly want those to reset whenever the selected user changes.

Questions about this post? Find me on Twitter.

Join the Newsletter

I write about software development of all kinds (mostly front-end).

I won't send you spam. Unsubscribe at any time.