I'm an aspiring junior dev. This is modified version of the code I wrote for a technical interview I did today (Didn't pass. Sad.). The problem was to fetch two json files, and display their data on a table. No JQuery, only a simple un-editable html file.
I'm looking for:
- Ways I could have made the code more readable, improved variable names, commenting.
- Security problems with injecting data into the DOM
- Is the pattern of trying to normalize the data correct?
- Is it good to curry functions like this?
- Should I have memoized the user data in a store?
- (Only if it's allowed on CR) Tips for an interview question like this.
const apiAddress = 'https://api.myjson.com/bins/'
const tableSchema = {short_name: 'User', logins: 'Logins', attempts: 'Attempts'}
// JSON data stored at
// https://api.myjson.com/bins/14pl91
// https://api.myjson.com/bins/xkdzp
/**
* Fetch should be polyfilled if supporting older browsers
*
* (String) => Object
* @param {String} path The localpath and filename of the json file
* @returns {Object}
*/
const fetchData = (path) => {
return fetch(apiAddress + path)
.then(res => {
return res.json()
}).catch(console.error)
}
/**
* Use this: https://github.com/paularmstrong/normalizr instead
*
* (String) => (Array) => Object
* @param {string} key the key used to tell if different objects are representing the same thing
* @param {[Object<{key: String}>]} objectArray an array of objects, each object must contain the key as a property
* @returns {Object<{[key: String]: Object}>} Returns an object that contains the input objects mapped by the key
*/
const normalizer = (key) => (objectArray) => {
return objectArray.reduce((acc, cur) => {
if (cur.hasOwnProperty(key)) {
return Object.assign(acc, {[cur[key]]: {...cur}})
}
}, {})
}
/**
* ([Object]) => Object
* @param {[Object<{[key: String]: Object}>]} objectArray An array of keyed (normalized) objects that will be merged by said key
* @returns {Object<{[key: String]: Object}>} The merged object
*/
const mergeObjects = (objectArray) => {
return objectArray.reduce((acc, cur) => {
Object.keys(cur).map((key) => {
acc[key] = Object.assign(cur[key], acc[key])
})
return acc
}, {})
}
/**
* Should place these table functions within a class, prototypes, component
*
* (schema: Object<ITableSchema>) => Table
* @param {Object<ITableSchema>} schema The schema the table will conform to
* @returns {Table} A new table instance
*/
const createTable = (schema) => {
const table = document.createElement('table')
const headerRow = table.createTHead().insertRow()
// Add the titles in each column
obj2Arr(schema).forEach((title) => {
const th = document.createElement('th')
th.innerText = title
headerRow.appendChild(th)
})
return table
}
/**
* (table: Table, fields: [String]) => (entry: Object<any>) => void
* @param {Table} table Adds rows to a table reference
* @param {[String]} fields Fields is an ordered array of keys to get from the entry data
* @param {Object<any>} entry Data that is to be inserted into the row cells
*/
const createRowInTable = (table, fields) => (entry) => {
const row = table.insertRow()
fields.forEach((field) => {
const cell = row.insertCell()
cell.innerText = entry[field]
})
}
/**
* Utils
* Makes everything below a bit cleaner and easier to follow
* Would like to place these in separate files or use lodash
*
* @param {Object} object A mapped object
* @returns {Array} An array without mappings
*/
const obj2Arr = (object) => Object.keys(object).map((key) => object[key])
/**
* Filters objects that don't contain all keys
*
* @param {[String]} keys Keys to check exist in the object
* @param {Object} object Object that will be checked contains keys
* @returns {Boolean}
*/
const objFilter = (keys, object) =>
keys.reduce((acc, cur) => {
return acc ? object.hasOwnProperty(cur) : false
}, true)
/**
* init() main() <- for people that cmd+f
*/
Promise.all([
fetchData('14pl91'),
fetchData('xkdzp')
]).then(res => {
// Manually strip the response, ugly but can't think of a better way
const strippedResponse = res.map((ele) => {
if (ele.hasOwnProperty('users')) return ele.users
if (ele.hasOwnProperty('user_stats')) return ele.user_stats
})
// Process the response
const normalizedUsers = strippedResponse.map(normalizer('id'))
const users = mergeObjects(normalizedUsers)
// Filters users without the requisite fields
const columnFilter = objFilter.bind({}, Object.keys(tableSchema)) // To show PA instead of currying
const filteredUsers = obj2Arr(users).filter(columnFilter)
// Build the table
const table = createTable(tableSchema)
// Put the filtered users inside the table
const createUserRow = createRowInTable(table, Object.keys(tableSchema))
filteredUsers.forEach(createUserRow)
// Find the table's injection point
const container = document.getElementsByClassName('json-table')[0]
.lastElementChild
.lastElementChild
// Place table inside DOM
container.appendChild(table)
})
<!DOCTYPE html>
<html>
<head>
<script src="script.js"></script>
</head>
<body>
<div class="json-table">
<h3>JSON Table</h3>
<div class="module-border">
<div class="module-content">
</div>
</div>
</div>
</body>
</html>
Note: I've removed all references to the company and mutated the data while keeping the structure and problem identical.
EDIT
Question Specification- Fetch the contents of the JSON users.json and stats.json
- The purpose of the table is to show user stats. If a user does not have stats, don't show them.
- Parse the JSON files and display their data using a table as shown below, using the
short_name
,logins
, andattempts
fields.
.map((key) => object[key])
can be written.map(key => object[key])
which some people find more readable. \$\endgroup\$obj2Arr
\$\endgroup\$