05 Jun 2022 - Ronald
An actual React.js interview coding challenge with solution.
Some time ago I was in a tech interview for a front-end position and I was requested to code a React.js app from stratch. The instructions were as follows:
https://jsonplaceholder.typicode.com/postshttps://jsonplaceholder.typicode.com/commentsThat’s all, and I was given 30 minutes to complete it!
To be honest I didn’t complete the challenge during the interview, but I did complete it afterwards!
What I did was to start a new project with Create React App. Run the following commands in your command line (You’ll need Node.js installed on your machine):
npx create-react-app my-app
cd my-app
npm startNow let’s begin coding! :)
The first instruction is to fetch posts data from an API and to show the list of titles on screen.
Open src/App.js (this is the only file we’ll be working on), delete all imports and the contents of the return statement, so you’ll end up with:
function App() {
return (
);
}
export default App;Now import React, useState and useEffect (We’ll be working with a function component, that’s why we can use hooks):
import React, { useState, useEffect } from "react";
function App() {
return (
);
}
export default App;Let’s create a state variable to store our data fetched from the API:
import React, { useState, useEffect } from "react";
function App() {
const [posts, setPosts] = useState([]);
return (
);
}
export default App;And let’s bring that data and save it in our variable:
import React, { useState, useEffect } from "react";
function App() {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((res) => res.json())
.then((data) => setPosts(data));
}, []);
return (
);
}
export default App;As you can see we are passing a second argument to the useEffect hook, an empty array in this case, meaning we are not observing any state variable or props, we just want to run this code after the first render (after component mount.)
Excellent! Now we have our data, but we still need to render it. To do it, add the code in the return statement below.
import React, { useState, useEffect } from "react";
function App() {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((res) => res.json())
.then((data) => setPosts(data));
}, []);
return (
<div>
<h1>List of posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<span
style={{ color: "blue", cursor: "pointer" }}
>
{post.title}
</span>
</li>
))}
</ul>
</div>
);
}
export default App;This should show the list of post titles. The key props is necessary in this case. It’s used internally by React. If you don’t include it, a warning will be issued to the console (in DevTools if you are using Chrome or Firefox.)
The second thing to note is that I’ve styled span elements instead of creating a tags. This is because React is a framework for building single page applications (SPA) and an a tag essentially creates a link to another page, so you better avoid it. If you want to create routes for your app, with links pointing to those routes you can use React Router.
And that’s it. Let’s move on to the next part, which is to show the post details.
First we’ll need a flag to know if the app shows the list of posts or the details for a post.
const [showList, setShowList] = useState(true);And two more state variables for the post we want to show and its associated comments.
const [post, setPost] = useState({});
const [comments, setComments] = useState([]);Then add the onClick props to each title.
onClick={() => onClickHandler(post.id)}And declare the onClickHandler method.
const onClickHandler = (id) => {
let arr = posts.filter((post) => id === post.id);
setPost(arr[0]);
setShowList(false);
};Now, each time the post variable is updated we want to fetch its comments. To acomplish this we use another useEffect.
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/comments")
.then((res) => res.json())
.then((data) => {
let filteredComments = data.filter((comment) => post.id === comment.postId);
setComments(filteredComments);
});
}, [post]);Finally, in the post details view we’ll want a link to come back to the list of post titles. To do this we create a click handler that changes the value of the flag showList.
const backToList = () => {
setShowList(true);
};The code so far will look like this, including the JSX (in the return statement) necessary to show either the list of post titles or the post details with its comments, based on the value of the flag showList.
import React, { useState, useEffect } from "react";
function App() {
const [posts, setPosts] = useState([]);
const [post, setPost] = useState({});
const [comments, setComments] = useState([]);
const [showList, setShowList] = useState(true);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((res) => res.json())
.then((data) => setPosts(data));
}, []);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/comments")
.then((res) => res.json())
.then((data) => {
let filteredComments = data.filter((comment) => post.id === comment.postId);
setComments(filteredComments);
});
}, [post]);
const onClickHandler = (id) => {
let arr = posts.filter((post) => id === post.id);
setPost(arr[0]);
setShowList(false);
};
const backToList = () => {
setShowList(true);
};
return (
<div>
{showList ? (
<>
<h1>List of posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<span
style={{ color: "blue", cursor: "pointer" }}
onClick={() => onClickHandler(post.id)}
>
{post.title}
</span>
</li>
))}
</ul>
</>
) : (
<>
<p style={{ color: "blue", cursor: "pointer" }} onClick={backToList}>
Back to list
</p>
<h1>{post.title}</h1>
<p>{post.body}</p>
<h2>Comments</h2>
{comments.map((comment) => (
<div key={comment.id}>
<p>
<strong>{comment.name}</strong>
</p>
<p>{comment.body}</p>
</div>
))}
</>
)}
</div>
);
}
export default App;We are almost ready, remember those extra points?
Let’s create two more state variables, one for storing the search term, initially set as an empty string, and another one for the search results.
const [searchTerm, setSearchTerm] = useState("");
const [searchResults, setSearchResults] = useState([]);And an event handler searchHandler to take care of both variables. This function will run each time the user releases a key from the keyboard while typing in the search box.
const searchHandler = (e) => {
let searchString = e.target.value;
let results = posts.filter((post) => post.title.includes(searchString));
setSearchResults(results);
setSearchTerm(searchString);
};Now we are able to use the searchTerm value as a condition in the return statement, to show either the whole list of post titles, or the filtered list stored in searchResults.
The final code for this React coding challenge is below.
import React, { useState, useEffect } from "react";
function App() {
const [posts, setPosts] = useState([]);
const [post, setPost] = useState({});
const [comments, setComments] = useState([]);
const [showList, setShowList] = useState(true);
const [searchTerm, setSearchTerm] = useState("");
const [searchResults, setSearchResults] = useState([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((res) => res.json())
.then((data) => setPosts(data));
}, []);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/comments")
.then((res) => res.json())
.then((data) => {
let filteredComments = data.filter((comment) => post.id === comment.postId);
setComments(filteredComments);
});
}, [post]);
const onClickHandler = (id) => {
let arr = posts.filter((post) => id === post.id);
setPost(arr[0]);
setShowList(false);
};
const backToList = () => {
setShowList(true);
};
const searchHandler = (e) => {
let searchString = e.target.value;
let results = posts.filter((post) => post.title.includes(searchString));
setSearchResults(results);
setSearchTerm(searchString);
};
return (
<div>
{showList ? (
<>
<h1>List of posts</h1>
<input
type="text"
id="posts-search"
name="q"
placeholder="Search"
onKeyUp={searchHandler}
></input>
<ul>
{searchTerm === ""
? posts.map((post) => (
<li key={post.id}>
<span
style={{ color: "blue", cursor: "pointer" }}
onClick={() => onClickHandler(post.id)}
>
{post.title}
</span>
</li>
))
: searchResults.map((post) => (
<li key={post.id}>
<span
style={{ color: "blue", cursor: "pointer" }}
onClick={() => onClickHandler(post.id)}
>
{post.title}
</span>
</li>
))}
</ul>
</>
) : (
<>
<p style={{ color: "blue", cursor: "pointer" }} onClick={backToList}>
Back to list
</p>
<h1>{post.title}</h1>
<p>{post.body}</p>
<h2>Comments</h2>
{comments.map((comment) => (
<div key={comment.id}>
<p>
<strong>{comment.name}</strong>
</p>
<p>{comment.body}</p>
</div>
))}
</>
)}
</div>
);
}
export default App;Notice how I used an input of type “text” for the search box. First I attempted an input of type “search” but in some browsers it shows a “x” icon that can be clicked on to remove the search term if desired. Because this does not trigger a onKeyUp event, the list won’t update on clicking the “x” icon.
That’s all! I hope you enjoyed it and learned something new!
The whole project can be found at Github.
If you are looking for a remote job don’t forget to check out RemoteGecko.com :) It has plenty of job opportunities from start ups and established companies.