DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Stand out in a React interview by rendering a list like a pro
Andrew Lee
Andrew Lee

Posted on

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

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.

Top comments (14)

Collapse
 
danwalsh profile image
Dan Walsh • Edited on

Nice article!

One suggestion for 1. Specify key: Sometimes your data structure doesn’t contain a usable value necessary for the key. In those cases we can use the second .map() parameter (index) to generate our keys on the fly like so:

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

Edit: You should only use the array index for your keys if your array is static and there is no requirement for filtering nor sorting. Examples could be your primary navigation menu, or a list of tags associated with an article, or even a list of posts.

Source: developer.mozilla.org/en-US/docs/W...

Collapse
 
zablon18 profile image
Fernando Zablah

The index of an element should not be used as a key, as the order could change.
Here is some more info on the subject:
React official docs
robinpokorny.com/blog/index-as-a-k...

Collapse
 
danwalsh profile image
Dan Walsh • Edited on

Absolutely agreeβ€”if you are intending to manipulate or update the array (by adding, removing or re-ordering the items) then a generated key (or if available, an array item prop) is definitely the way to go.

But... if the array is static and never to be filtered nor sorted, then using the array index is perfectly fineβ€”"Horses for courses" πŸ‡

There was no mention in the OP that array manipulation was an expectation, which was the basis for my suggestion. I'll edit my original comment for clarification.

Thread Thread
 
zablon18 profile image
Fernando Zablah

Never thought of it that way, excellent reasoning!

Collapse
 
jamada52620882 profile image
jamada

It's only partially true

Don't use the ID as the key if the order might change, but there are a bunch of cases where the order of the elements won't change, in which case you can use the ID as the key (unless you have no other options)

The documentation says it's not recommended, but it's not forbidden either to use ID as key, In documentation you can even read that

When you don’t have stable IDs for rendered items, you may use the item index as a key as a last resort

Collapse
 
hijazi313 profile image
Muhammad Hamza Hijazi

Never use useMemo or any memorization at first place. useMemo() basically compares new changes with old changes, and each comparison takes time than normal program flow. Use it only when it's necessary

Collapse
 
andyrewlee profile image
Andrew Lee

Yes, we should only memoize when it's necessary. In this article we memoized the component that was re-rendering 100 posts every time the the parent state changed but we chose not to memoize inside of the item component. Adding memoization adds code complexity and additional code execution...sometimes these costs don't outweigh the benefits. There might be ways to avoid re-rendering by restructuring the app as well: Before You memo().

Collapse
 
leob profile image
leob

I love that article - "Before You memo()" !

That gave me way more insight than the ubiquitous "just use memo or callback" 'hack' which gets thrown around everywhere you look. Sprinkling 'use memo' all over your code just doesn't feel right.

But I stand by my opinion that this sort of "low level" mechanical thing should be taken care of by the framework rather than the programmer (hence why I'm still more impressed by Vue than by React).

Collapse
 
leob profile image
leob

I don't know, this is what still bugs me about React, compared to Vue - Vue is more like - it lets you code the logic, and tries to optimize the reactivity and the rendering itself, while with React the burden is more on the programmer (as your "useMemo" and "useCallback" examples show).

Vue just does more for you, and on top of that it's even smaller, and more performant ... but yeah React has the jobs and the popularity :-P

Collapse
 
igstefano profile image
Igor Stefano

Considering point 4 is about accessibility, I'm surprised you chose to use buttons inside a div instead of an ul with lis, coupled with tabIndexes or something in that spirit. It would've been way more semantic, considering we are rendering a list.

Collapse
 
rajajaganathan profile image
Raja Jaganathan

Some time Post onClick is costlier if no of post grows. So use event delegation technique so that both onClick and useCallback can be removed.

Collapse
 
kuicpet profile image
Kingsley Umujeyan

Nice tips

Collapse
 
enigma6174 profile image
Anish

This is a great article! I am a junior React developer and I am preparing for interviews and this will be of great help. Thank you!

Collapse
 
whytrno profile image
Wahyu Triono

Hallo can we like each other's posts?

Classic DEV post:

CLI tools you won't be able to live without πŸ”§

CLI tools