Published
- 11 min read
Job Interview - React Coding Challenge
I successfully completed this job interview coding challenge. It’s going to be interesting to look at the questions and solutions. I will go a little deeper and explain why the questions were asked and how the coding challenge can be improved.
For my full solution visit this repository Note: you will have to add a Github API Token key in the .env
-file to receive data from the server.
Procedure
It was a ‘good faith’ challenge. A base skeleton was provided by email and after 2 hours you handed in your solution. If there was anything unclear there was the option to ask additional questions by email.
Questions
The challenge focused on 3 different aspects:
- Creating a component in React
- Using your created component with GraphQL and the Github API
- General JavaScript programming skills
This structure is quite good and covers most of the common tasks you will face when working with React.
Strategy for approaching the challenge
As with any test - try to figure out what the simplest task is and if tasks are dependent on each other. In this case, it is the JavaScript question is independent and the quickest to implement. After that, you must do the React component as it is going to be used by the GraphQL question.
Question 1: The JavaScript Question
Implement a function that takes an arbitrary nested JS Object and do the following transformations:
- add +1 to each Number within in Object (eg: x: 9 => x: 10)
- add ‘AE’ to each String within in Object (eg: y: ‘abc’ => y: ‘abc AE’)
- The object should keep its structure!
- Log the result to the browser console
See a rough example structure below:
// initial object
{
a: 123,
b: 'abc'
c: [1, 2, 3]
}
// resulting object
{
a: 124,
b: 'abc AE'
c: [2, 3, 4]
}
Comments to the Questions
Overall the question is quite good, it allows the candidate to demonstrate general programming skills, how to write functions, basic data types, conditionals, loops, and recursion.
The question is going to be answered in TypeScript, so you also have to demonstrate your approach to TypeScript typings
Approach to the Solution
The best way to work through this problem is to write for each transformation rule its own function.
- add +1 to each Number within in Object (eg: x: 9 => x: 10)
export const transformNumber = (num: number): number => {
return num + 1
}
- add ‘AE’ to each String within in Object (eg: y: ‘abc’ => y: ‘abc AE’)
Now the string transformation question is interesting - you could just solve it using the ECMAScript 5 approach:
export const transformString = (str: string): string => {
return str + ' AE'
}
Using string functions:
export const transformString = (str: string): string => {
return str.concat(' AE')
}
Or using ECMAScript 6 string-templates:
export const transformString = (str: string): string => {
return `${str} AE`
}
Personally, I would prefer the template string solution as it demonstrates the knowledge of the most recent ECMAScript functions it also has the added benefit that it ensures that the output no matter the input - the output will always be a string.
- Transforming an Array
At this point, we can introduce a general function transformValue
that will add support for transforming all values in an array. This will get done by recursively calling the function on each Array element.
While not explicitly asked in the description, it is hinted at in the example object.
export const transformValue = (val: any): any => {
if (Array.isArray(val)) {
return val.map(transformValue)
}
if (typeof val === "string") {
return transformString(val)
}
if (typeof val === "number") {
return transformNumber(val)
}
return val
}
Now we have to transform an arbitrary object.
const transformObject = (obj: any) => {
const transformedObject: { [x: string]: any } = {}
Object.entries(obj).forEach(([key, val]) => {
transformedObject[key] = transformValue(val)
})
return transformedObject
}
Note: I am using Object.entries
and am creating a new object to ensure that the object structure stays the same.
Lastly, we have to transform the object recursively, as any nested object should be transformed the same way.
Here we simply have to adjust the transformValue function to be able to process objects.
export const transformValue = (val: any): any => {
if (Array.isArray(val)) {
return val.map(transformValue)
}
if (typeof val === "object") {
return transformObject(val)
}
if (typeof val === "string") {
return transformString(val)
}
if (typeof val === "number") {
return transformNumber(val)
}
return val
}
Here you now demonstrate your knowledge of JavaScript types, as arrays have the type object
in javascript
typeof [] === "object //true
The Array.isArray
check must happen before the typeof
check.
Improvement of the question - Use Tests
The last requirement “Log the result to the browser console” - It is a bad habit of JavaScript developers to just slap in a “console.log” statement - instead of setting a breakpoint in the DevTools to debug.
Overall “console.log” should be used sparingly and for its purpose - to log something. In this case, it is being used as a “test”.
Note: console.assert
https://developer.mozilla.org/en-US/docs/Web/API/console/assert would also be an alternative to use, if you do not want to avoid adding a testing tool, however, if it is successful, there is no output on the console, thus not fulfilling the requirement of the original description of the task.
I would add to the code skeleton a failing test:
it("can transform an object", () => {
const input = {
a: 123,
b: 'abc',
c: [1, 2, 3]
}
const output =
{
a: 124,
b: 'abc AE',
c: [2, 3, 4]
}
expect(transformObject(input)).toBe(output)
}
Then reformulate the question that all provided tests should be passing.
Question 2: React Component
Implement a Master Detail component that renders a list of items on the left and a detail view on the right. When the user clicks on a list item the render function of the detail view should be called with the corresponding payload. The component should provide the following API:
<MasterDetail>
<MasterDetail.Item payload={{content: "Hello Peers"}}>Intro</MasterDetail.Item>
<MasterDetail.Item payload={{content: "Welcome to Cool Company"}}>Welcome</MasterDetail.Item>
<MasterDetail.Detail>
{(payload) => payload.content)}
</MasterDetail.Detail>
</MasterDetail>
Solution
For my solution I simply ignored the provided API and provided a simpler one
<MasterDetail
payload={[
{ title: "Intro", content: "Hello Peers" },
{ title: "Welcome", content: "Welcome to Cool Company" },
]}
Additionally I just added styled-components
to have a quick way of styling my solution.
This is generally speaking a risky move in a Job Interview. You should try to implement the API even if you have objections, you can express them later in the interview. Just adjusting the API may get you directly rejected.
Only do it if you really know what you are doing. You may have to answer follow-up questions. You will have to provide compelling reasons why you changed the API.
My personal preference is I would rather be rejected for doing the right thing. As I anyway would not have fitted into that company culture. So the rejection is good. As long as I solved the problem - I am happy. I see it as that it demonstrates initiative and that you think along. Not every company needs/wants those skills.
Improvement of the question - Better API / Looser Definition of the Question
Why did I not just implement the provided API?
The obvious first point is to get this Syntax: <MasterDetail.Item>
- you need to use class-based components in React. With React functional components you would be then suddenly in the situation where you are adjusting the prototype of the function and that is something you probably will want to avoid.
Additionally the API opens a lot of unanswered questions:
- What happens if you change the order of Items and Detail?
- What happens if you forget Detail?
- What happens if you have no Items?
- What happens with other children?
- Why are you exposing Detail and Item in the first place? What benefit are you trying to provide to the next developer?
I would adjust the API, or just not define an API and see what the candidate comes up with.
Question 3: GraphQL
Query a list of all public repos of “facebook” via the GitHub API and display the result with your created component.
Solution
You need to first of course read the Github API and find the right resource that will list the repositories.
GitHub provides a tool to quickly experiment with Queries: Explorer.
From GraphQL you need to know a couple of basic concepts:
After reading the documentation and figuring out that you will have to query search to get the result.
You will end up with a Query that looks something like this:
query {
search(query: "org.facebook", type: REPOSITORY, first: 100) {
nodes {
... on Repository {
title: name
content: description
}
}
}
}
Improvements of the question
This question is great, as you have to demonstrate that you can read the documentation and at the same time find the solution in a reasonable time.
However the provided code skeleton provides an old version of the Apollo Client. Thus you are debugging, why the code provided by the documentation does not work until you realize that you have to switch to the legacy documentation.
Improving the Coding Challenge Code Skeleton
The provided package.json
file has a couple of oddities and red flags.
Dependencies
These are the original Dependencies
"dependencies": {
"apollo-boost": "^0.4.3",
"graphql": "^14.4.2",
"react": "^16.12.0",
"react-apollo": "^3.1.4",
"react-apollo-hooks": "^0.5.0",
"react-dom": "^16.13.1",
"react-simple-code-editor": "^0.11.0"
},
"devDependencies": {
"@types/react": "16.8.23",
"@types/react-dom": "^16.9.6",
"prettier": "^2.0.4",
"react-scripts": "3.3.0-next.62",
"typescript": "^3.9.0-dev.20200407",
"watch": "^1.0.2",
"webpack": "4.41.2"
},
The first thing that sticks out is the “Preview Versions”:
{
"react-scripts": "3.3.0-next.62",
"typescript": "^3.9.0-dev.20200407"
}
Preview Versions
You would try to avoid “Beta” or “Preview” Versions in your production code. In a project, I was working on we encountered build errors when transitioning from TypeScript 3.9.0 to 4.0.0 on Linux. The issues then got resolved with TypeScript 4.0.1.
I would use proven stable versions, because you want to make the candidate less nervous. You do not know how stable these dependencies are. If the candidate has to debug why he cannot run your code on his own machine, that will just create unneeded tension.
Deprecated and Old Packages
Deprecated and outdated packages need to be updated to their current version. While it can be interesting to see that a candidate can deal with the situation of looking for an older documentation - That is just a frustrating tasks, and creates unneeded friction.
You are telling the candidate that you are not keeping up to date and doing basic maintenance work and the candidate will ask himself if working with older tech is the default at this company.
Unneeded Packages
When looking into the codebase there are a couple of packages that are not used:
{
"react-simple-code-editor": "^0.11.0",
"prettier": "^2.0.4",
"watch": "^1.0.2",
"webpack": "4.41.2"
}
(Note: I know the VSCode plugin for prettier just looks if prettier is installed in the project or not and then executes. However I added it in the list, because the .prettierrc.json
file is missing)
NPM Scripts
Here are the provided scripts:
{
"scripts": {
"start": "react-scripts start"
}
}
Lets compare it to the default scripts when a project is created using create-react-app
.
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",
},
You can remove the “build” and “eject” script. They are not needed for this exercise.
However removing “test”-script is problematic. That is a deliberate choice, either by “I am so good I don’t write tests” and signals that this company does not value tests.
Red Flags
- An outdated style of creating React Components combined an API that may cause unintended side effects. -> Unclear what the quality of the production codebase is going to be.
- Deal Breaker: Not using any tests, deliberately removing the test script execution script. -> Gives a strong impression, that tests are misunderstood and underutilized at the company.
package.json
, outdated packages, experimental packages -> may be an indication of a missing dependency monitoring, update strategy.
Conclusion
A coding challenge is a demonstration of the skill of the candidate and a demonstration of the values and quality of the company. The company must ensure that the coding challenge reflects their values - It is the first and probably only impression you will get into the codebase of the company. As a candidate, you must then think about if you really want to work with this codebase and the people that wrote this code. Do you share the same values?
This is one of the most interesting and well-balanced challenges I have encountered in a while. I think with a couple of minor tweaks, it is an awesome challenge.