Evan's React Interview Cheat Sheet
In this article I'm going to list some of the things that are useful in React coding interviews, but are easily forgotten or overlooked. Most of the techniques here are useful for solving problems that often arise in coding interviews.
Table of contents
- Imitating componentDidMount using hooks
- Using the previous state in the useState hook
- Using useRef to refer to an element
- Custom usePrevious hook to simplify a common useRef use case
- Vanilla js debounce method
- Use useContext to avoid prop-drilling
Imitating componentDidMount using React hooks
The useEffect hook is one of the more popular additions to the React hooks API. The problem with it is that it replaces a handful of critical lifecycle methods that were much easier to understand.
It's much easier to understand the meaning of "componentDidMount" than "useEffect({}, [])", especially when useEffect replaces componentDidMount, componentDidUpdate, and componentWillUnmount.
The most common use of the useEffect hook in interviews is to replace componentDidMount. The componentDidMount method is often used for loading whatever data is needed by the component. In fact, the reason it's called useEffect is because you are using a side effect.
The default behavior of useEffect is to run after ever render, but it can also be run conditionally using a second argument that triggers the effect when it changes.
In this example, we use the useEffect hook to load data from the omdb api.
// fetch movies. Notice the use of async
const fetchMovies = (newSearchTerm = searchTerm) => {
// See http://www.omdbapi.com/
fetch(`http://www.omdbapi.com/?apikey=${apikey}&s=${newSearchTerm}&page=${pageNumber}`).then(async (response) => {
const responseJson = await response.json();
// if this is a new search term, then replace the movies
if(newSearchTerm != previousSearchTerm) {
setMovies(responseJson.Search);
} else {
// if the search term is the same, then append the new page to the end of movies
setMovies([...movies, ...responseJson.Search]);
}
}).catch((error) => {
console.error(error);
});
};
// imitate componentDidMount
useEffect(fetchMovies, []);
Note that this syntax for using asynchronous code in useEffect is the suggested way to do so.
Using the previous state in the useState hook
The useState hook is probably the easiest and most natural hook, but it does obscure one common use case. For instance, do you know how to use the previous state in the useState hook?
It turns out that you can pass a function into the useState set method and that function can take the previous state as an argument.
Here's a simple counter example.
const [count, setCount] = useState({});
setCount(prevState => {
return prevState + 1;
});
Using useRef to refer to an element
The useRef hook is used to store any mutable value across calls to the component render method. The most common use is to use it to access an element in the DOM.
Initialize the reference using the useRef hook.
// use useRef hook to keep track of a specific element
const movieContainerRef = useRef();
Then attach it to an element in the render return.
<div className={MovieListStyles.movieContainer} ref={movieContainerRef}>
{movies && movies.length > 0 &&
movies.map(MovieItem)
}
</div>
Then you can use the .current property to access the current DOM element for that div, and attach listeners or do anything else you need to do with a div.
// set up a scroll handler
useEffect(() => {
const handleScroll = debounce(() => {
const scrollTop = movieContainerRef.current.scrollTop;
const scrollHeight = movieContainerRef.current.scrollHeight;
// do something with the scrolling properties here...
}, 150);
// add the handler to the movie container
movieContainerRef.current.addEventListener("scroll", handleScroll, { passive: true });
// remove the handler from the movie container
return () => movieContainerRef.current.removeEventListener("scroll", handleScroll);
}, []);
Custom usePrevious hook to simplify a common useRef use case
The useRef hook can be used to store any mutable value. So it's a great choice when you want to look at the previous value in a state variable. Unfortunately, the logic to do so is somewhat tortuous and can get repetitive. I prefer to use a custom usePrevious hook from usehooks.com.
import {useEffect, useRef} from 'react';
// See https://usehooks.com/usePrevious/
function usePrevious(value) {
// The ref object is a generic container whose current property is mutable ...
// ... and can hold any value, similar to an instance property on a class
const ref = useRef();
// Store current value in ref
useEffect(() => {
ref.current = value;
}, [value]); // Only re-run if value changes
// Return previous value (happens before update in useEffect above)
return ref.current;
}
export default usePrevious;
Using it is as simple as one extra line when setting up a functional component.
// use the useState hook to store the search term
const [searchTerm, setSearchTerm] = useState('orange');
// use custom usePrevious hook
const previousSearchTerm = usePrevious(searchTerm);
Vanilla js debounce method
Okay, this next one has nothing to do with React, except for the fact that it's a commonly needed helper method. Yes, I'm talking about "debounce". If you want to reduce the jittery quality of a user interface, but you still want to respond to actions by the user, then it's important to throttle the rate of events your code responds to. Debounce is the name of a method for doing this from the lodash library.
The debounce method waits a preset interval until after the last call to debounce to call a callback method. Effectively, it waits until it stops receiving events to call the callback. This is commonly needed when responding to scroll or mouse events.
The problem is that you don't want to install lodash in a coding interview just to use one method. So here's a vanilla javascript debounce method from Josh Comeau.
//
const debounce = (callback, wait) => {
let timeoutId = null;
return (...args) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => {
callback.apply(null, args);
}, wait);
};
}
export default debounce;
Here's an example of how to use it to update a movie list when a new search term is entered.
// handle text input
const handleSearchChange = debounce((event) => {
setSearchTerm(event.target.value);
fetchMovies(event.target.value);
}, 150);
return (
<div className={MovieListStyles.movieList}>
<h2>Movie List</h2>
<div className={MovieListStyles.searchTermContainer}>
<label>
Search:
<input type="text" defaultValue={searchTerm} onChange={handleSearchChange} />
</label>
</div>
<div
className={MovieListStyles.movieContainer}
ref={movieContainerRef}
>
{movies && movies.length > 0 &&
movies.map(MovieItem)
}
</div>
</div>
);
Use useContext to avoid prop-drilling
The last thing an interviewer wants to see during a React coding interview is prop drilling. Prop drilling occurs when you need to pass a piece of data from one parent component, through several intervening components, to a child component. Prop drilling results in a bunch of repeated code where we are piping a variable through many unrelated components.
To avoid prop drilling, you should use the useContext hook.
The useContext hook is a React implementation of the provider pattern. The provider pattern is a way of providing system wide access to some resource.
It takes three code changes to implement the useContext hook. You've got to call createContext in the parent component that will maintain the data. Then you've got to wrap your app with a special tag.
export const DataContext = React.createContext()
function App() {
const data = { ... }
return (
<div>
<DataContext.Provider value={data}>
<SideBar />
<Content />
</DataContext.Provider>
</div>
)
}
Then you've got to import the context in the child component, and call useContext to get the current value.
import DataContext from '../app.js';
...
const { data } = React.useContext(DataContext);
What has made you stumble in React interviews?
What are some other common ways to make mistakes in React coding interviews? Send me your biggest React coding headaches on Twitter @EvanXMerz.