Stand out in a React interview by rendering a list like a pro

Andrew Lee - Nov 13 '22 - - Dev Community

We often get asked to render a list in a React interview. In this article we are going to look at a basic implementation and come up with four ways we can improve it to stand out from the rest.

Standard Implementation

Let's look at a basic implementation where we render a list based on an array of items. Try to think of at least three different ways we can improve the following implementation before reading further.

import { useState, useEffect } from "react";

const App = () => {
  const [posts, setPosts] = useState([]);
  const [currentPost, setCurrentPost] = useState(undefined);

  useEffect(() => {
    const initialize = async () => {
      const res = await fetch("https://jsonplaceholder.typicode.com/posts");
      const json = await res.json();
      setPosts(json);
    };
    initialize();
  }, []);

  const onPostClick = (post) => {
    setCurrentPost(post);
  };

  return (
    <div>
      {currentPost && <h1>{currentPost.title}</h1>}
      <PostList posts={posts} onPostClick={onPostClick} />
    </div>
  );
};

const PostList = ({ posts, onPostClick }) => {
  return (
    <div>
      {posts.map((post) => (
        <Post post={post} onPostClick={onPostClick} />
      ))}
    </div>
  );
};

const Post = ({ post, onPostClick }) => {
  const onClick = () => {
    onPostClick(post);
  };

  return <div onClick={onClick}>{post.title}</div>;
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Improvements

Here are the four improvements that we can make to stand out. It’s important we articulate the why during the course of the interview.

1. Specify Key

By providing a key prop to our list item components, we help React identify each item when it compares the original tree with its subsequent tree. It’s important to emphasize that the key prop needs to be unique and we shouldn’t use an index as a key (changing the order on the list doesn’t change the identity of the item).

{posts.map((post) => (
  <Post key={post.id} post={post} onPostClick={onPostClick} />
))}
Enter fullscreen mode Exit fullscreen mode

2. Optimize Rendering

Every time we click on a list item, we are re-rendering PostList and every Post.

const Post = ({ post, onPostClick }) => {
  console.log("post rendered");

  const onClick = () => {
    onPostClick(post);
  };

  return <div onClick={onClick}>{post}</div>;
};
Enter fullscreen mode Exit fullscreen mode

We can optimize our PostList component by using the memo function provided by React. When we wrap our component with memo, we are telling React to not re-render this component unless the props have changed.

import { useState, useEffect, memo } from "react";

const PostList = memo(({ posts, onPostClick }) => {
  return (
    <div>
      {posts.map((post) => (
        <Post post={post} onPostClick={onPostClick} />
      ))}
    </div>
  );
});
Enter fullscreen mode Exit fullscreen mode

However, we will notice that our component continues to re-render even with memo. Our App re-renders every time currentPost state changes. Every re-render it is re-creating the onPostClick function. When a function is re-created (even if it’s the same implementation), it has a new identity. Therefore, the props technically did change, which means PostList will re-render.

const fn1 = () => {};
const fn2 = () => {};
fn1 === fn2; // => false
Enter fullscreen mode Exit fullscreen mode

We can tell React to not re-create the function by using the useCallback hook.

const onPostClick = useCallback((post) => {
  setCurrentPost(post);
}, []);
Enter fullscreen mode Exit fullscreen mode

Using useCallback might have made sense in the previous example because it is preventing us from re-rendering all of the posts again. It’s important to point out that it doesn’t always make sense to wrap a function in a useCallback.

const Post = ({ post, onPostClick }) => {
  const useCalllback(onClick = () => {
    onPostClick(post);
  }, []);

  return <div onClick={onClick}>{post.title}</div>;
};
Enter fullscreen mode Exit fullscreen mode

We can point out to the interviewer that using the useCallback in the Post component might not make sense because the component in this case is lightweight. We should only use useCallback if it makes sense (we can test by profiling). There are downsides to useCallback; it increases the complexity of the code and calling useCallback is additional code that gets run on every render.

3. Clean up when component un-mounts

Right now we are not doing any sort of clean up when the component un-mounts. For example, what if we decide to navigate away from the page before we get a response from our URL? We should cancel the request.

useEffect(() => {
  const initialize = async () => {
    const res = await fetch("https://jsonplaceholder.typicode.com/posts");
    const json = await res.json();
    setPosts(json);
  };
  initialize();
}, []);
Enter fullscreen mode Exit fullscreen mode

useEffect can be split up into two parts: code to run on mount and code to run on unmount:

useEffect(() => {
  // When component mounts what code should I run?

  return () => {
    // When component unmounts what code should I run (clean up)?
  };
}, []);
Enter fullscreen mode Exit fullscreen mode

We can cancel the request by using the AbortController and calling controller.abort() on clean up.

useEffect(() => {
  const controller = new AbortController();
  const signal = controller.signal;

  const initialize = async () => {
    try {
      const res = await fetch("https://jsonplaceholder.typicode.com/posts", { signal });
      const json = await res.json();
      setPosts(json);
    } catch (err) {
      console.log(err);
    }
  };

  initialize();

  return () => {
    controller.abort();
  };
}, []);
Enter fullscreen mode Exit fullscreen mode

4. Add accessibility

Final test that truly separates an exceptional candidate is if the candidate can talk about accessibility. Our sample application is too simple to add any tangible accessibility improvements, we should definitely talk about some things to look out for once our application grows in complexity. One test we can run is, can we use our sample application using the keyboard alone? One quick fix would be to convert items into buttons so that we can tab through them using our keyboard.

const Post = ({ post, onPostClick }) => {
  const onClick = () => {
    onPostClick(post);
  };

  return <button onClick={onClick}>{post}</button>;
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

Rendering a list in React seems like a simple interview question at first. Sometimes candidates might get frustrated why they didn’t pass the interview despite being able to implement a working solution. Next time we encounter this question, make sure to communicate to the interviewer (and implement them if given the time) the different ways we can render a list like a pro.

. . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player