DEV Community

loading...

The JavaScript you need to know for React — Part 4

frontendexp
Mobile and Web development
Originally published at javascript.plainenglish.io on ・10 min read

How do we update data values if they are supposed to be immutable? Well, you produce transformed copies using functions.
These functions make our code less imperative and thus reduce complexity.
There are two core functions to mention: JavaScript: Array.map and Array.reduce.
Consider this array of high schools:
const schools = [“Yorktown”,“Washington & Lee”,“Wakefield”]
Use of Array.join function:
console.log( schools.join(“, “) )// “Yorktown, Washington & Lee, Wakefield”
join is a built-in JavaScript array method that we can use to extract a delimited string from our array.
If we wanted to create a function that creates a new array of the schools that begin with the letter “W”, we could use the Array.filter method:
const wSchools = schools.filter(school => school[0] === “W”)console.log( wSchools )// [“Washington & Lee”, “Wakefield”]
Array.filter is a built-in JavaScript function that produces a new array from a source array.
What is a predicate?
A predicate is a function that always returns a Boolean value: true or false.
Array.filter invokes this predicate once for every item in the array.
That item is passed to the predicate as an argument and the return value is used to decide if that item shall be added to the new array.
In this case, Array.filter is checking every school to see if its name begins with a “W”.
When it is time to remove an item from an array we should use Array.filter over
Array.pop or Array.splice because Array.filter is immutable.
In this next sample, the cutSchool function returns new arrays that filter out specific school names:
const cutSchool = (cut, list) =>list.filter(school => school !== cut)console.log(cutSchool(“Washington & Lee”, schools).join(“ * “))// “Yorktown * Wakefield”console.log(schools.join(“\n”))// Yorktown// Washington & Lee// Wakefield
In this case, the cutSchool function is used to return a new array that does not contain “Washington & Lee”.
Then the join function is used with this new array to create a star-delimited string out of the remaining two school names. cutSchool is a pure function.
It takes a list of schools and the name of the school that should be removed and returns a new array without that specific school.
Another array function that is essential to functional programming is Array.map.
Instead of a predicate, the Array.map method takes a function as its argument.
This function will be invoked once for every item in the array, and whatever it returns will be added to the new array:
const highSchools = schools.map(school => ${school} High School)console.log(highSchools.join(“\n”))// Yorktown High School// Washington & Lee High School// Wakefield High Schoolconsole.log(schools.join(“\n”))// Yorktown// Washington & Lee// Wakefield
In this case, the map function was used to append “High School” to each school name.
The schools array is still intact.
In the last example, we produced an array of strings from an array of strings.
Here is an example of the map function returning an object for every school:
const highSchools = schools.map(school => ({ name: school }))console.log( highSchools )// [// { name: “Yorktown” },// { name: “Washington & Lee” },// { name: “Wakefield” }// ]
An array containing objects was produced from an array that contains strings.
If you need to create a pure function that changes one object in an array of objects, map can be used for this, too.
In the following example, we will change the school with the name of “Stratford” to “HB Woodlawn” without mutating the schools array:
let schools = [{ name: “Yorktown”},{ name: “Stratford” },{ name: “Washington & Lee”},{ name: “Wakefield”}]let updatedSchools = editName(“Stratford”, “HB Woodlawn”, schools)console.log( updatedSchools[1] ) // { name: “HB Woodlawn” }console.log( schools[1] ) // { name: “Stratford” },
The updatedSchools variable calls the edit Name function and we send it the school we want to update, the new school, and the schools array.
This changes the new array but makes no edits to the original:
const editName = (oldName, name, arr) =>arr.map(item => {if (item.name === oldName) {return {…item,name}} else {return item}})
Within editName, the map function is used to create a new array of objects based upon the original array. The editName function can be written entirely in one line.
Here’s an example of the same function using a shorthand if/else statement:
const editName = (oldName, name, arr) =>arr.map(item => (item.name === oldName) ?({…item,name}) :item)
If you need to transform an array into an object, you can use Array.map in conjunction with Object.keys.
Object.keys is a method that can be used to return an array of keys from an object.
Let’s say we needed to transform schools object into an array of schools:
const schools = {“Yorktown”: 10,“Washington & Lee”: 2,“Wakefield”: 5}const schoolArray = Object.keys(schools).map(key =>({name: key,wins: schools[key]}))console.log(schoolArray)// [// {// name: “Yorktown”,// wins: 10// },// {// name: “Washington & Lee”,// wins: 2// },// {// name: “Wakefield”,// wins: 5} ]
In this example, Object.keys returns an array of school names, and we can use map on that array to produce a new array of the same length.
The name of the new object will be set using the key, and wins is set equal to the value.
The final tool that we need in our functional arsenal is the ability to transform arrays into primitives and other objects.
The reduce and reduceRight functions can be used to transform an array into any value, including a number, string, boolean, object, or even a function.
Let’s say we needed to find the maximum number in an array of numbers. We need to transform an array into a number; therefore, we can use reduce:
const ages = [21,18,42,40,64,63,34];const maxAge = ages.reduce((max, age) => {console.log(${age} > ${max} = ${age > max});if (age > max) {return age} else {return max}}, 0)console.log(‘maxAge’, maxAge);// 21 > 0 = true// 18 > 21 = false// 42 > 21 = true// 40 > 42 = false// 64 > 42 = true// 63 > 64 = false// 34 > 64 = false// maxAge 64
The ages array has been reduced into a single value: the maximum age, 64. reduce
takes two arguments: a callback function and an original value.
In this case, the original value is 0, which sets the initial maximum value to 0. The callback is invoked once for every item in the array.
The first time this callback is invoked, age is equal to 21, the first value in the array, and max is equal to 0, the initial value.
The callback returns the greater of the two numbers, 21, and that becomes the max value during the next iteration.
Each iteration compares each age against the max value and returns the greater of the two.
Finally, the last number in the array is compared and returned from the previous callback.
Higher-Order Functions
Higher-order functions are also essential to functional programming.
They can take functions in as arguments, or return functions, or both.
The first category of higher-order functions are functions that expect other functions as arguments.
Array.map, Array.filter, and Array.reduce all take functions as arguments.
They are higher-order functions.
In the following example, we create an invokeIf callback function that will test a condition and invoke on callback function when it is true and another callback function when that condition is false:
const invokeIf = (condition, fnTrue, fnFalse) =>(condition) ? fnTrue() : fnFalse()const showWelcome = () =>console.log(“Welcome!!!”)const showUnauthorized = () =>console.log(“Unauthorized!!!”)invokeIf(true, showWelcome, showUnauthorized) // “Welcome”invokeIf(false, showWelcome, showUnauthorized) // “Unauthorized”
invokeIf expects two functions: one for true, and one for false.
This is demonstrated by sending both showWelcome and showUnauthorized to invokeIf.
When the condition is true, showWelcome is invoked. When it is false, showUnauthorized is invoked.
Higher-order functions that return other functions can help us handle the complexities associated with asynchronicity in JavaScript.
They can help us create functions that can be used or reused at our convenience.
Currying is a functional technique that involves the use of higher-order functions.
The following is an example of currying.
The userLogs function hangs on to some information (the username) and returns a function that can be used and reused when the rest of the information (the message) is made available.
Recursion
What is recursion?
Recursion is a technique that involves creating functions that recall themselves.
Often when faced with a challenge that involves a loop, a recursive function can be used instead.
Consider the task of counting down from 10. We could create a for loop to solve this problem, or we could alternatively use a recursive function.
In this example, countdown is the recursive function:
const countdown = (value, fn) => {fn(value)return (value > 0) ? countdown(value-1, fn) : value}
countdown expects a number and a function as arguments.
In this example, it is invoked with a value of 10 and a callback function.
When countdown is invoked, the callback is invoked, which logs the current value. Next, countdown checks the value to see if it is greater than 0. If it is, countdown recalls itself with a decremented value.
Eventually, the value will be 0, and countdown will return that value all the way back up the call stack.
Browser Call Stack Limitations
Recursion should be used over loops wherever possible, but not all
JavaScript engines are optimized for a large amount of recursion. Too much recursion can cause JavaScript errors. These errors can be avoided by implementing advanced techniques to clear the call stack and flatten out recursive calls. Future JavaScript engines plan to eliminate call stack limitations entirely.
Recursion is another functional technique that works well with asynchronous processes.
Functions can recall themselves when they are ready.
The countdown function can be modified to count down with a delay.
This modified version of the countdown function can be used to create a countdown clock:
const countdown = (value, fn, delay=1000) => {fn(value)return (value > 0) ?setTimeout(() => countdown(value-1, fn, delay), delay) :value}const log = value => console.log(value)countdown(10, log);
In this example, we create a 10-second countdown by initially invoking countdown
once with the number 10 in a function that logs the countdown.
Instead of recalling itself right away, the countdown function waits one second before recalling itself, thus creating a clock.
You can use recursion to iterate through subfolders until a folder that contains only files is identified.
You can also use recursion to iterate through the HTML DOM until you find an element that does not contain any children.
In the next example, we will use recursion to iterate deeply into an object to retrieve a nested value:
var dan = {type: “person”,data: {gender: “male”,info: {id: 22,fullname: {first: “Dan”,last: “Deacon”}}}}deepPick(“type”, dan); // “person”deepPick(“data.info.fullname.first”, dan); // “Dan”
deepPick can be used to access Dan’s type, stored immediately in the first object, or to dig down into nested objects to locate Dan’s first name.
Sending a string that uses dot notation, we can specify where to locate values that are nested deep within an object:
const deepPick = (fields, object={}) => {const [first, …remaining] = fields.split(“.”)return (remaining.length) ?deepPick(remaining.join(“.”), object[first]) :object[first]}
The deepPick function is either going to return a value or recall itself, until it eventually returns a value.
First, this function splits the dot-notated fields string into an array and uses array destructuring to separate the first value from the remaining values.
If there are remaining values, deepPick recalls itself with slightly different data, allowing it to dig one level deeper.
This function continues to call itself until the fields string no longer contains dots, meaning that there are no more remaining fields. In this sample, you can see how the values for first, remaining, and object[first] change as deepPick iterates through:
deepPick(“data.info.fullname.first”, dan); // “Deacon”// First Iteration// first = “data”// remaining.join(“.”) = “info.fullname.first”// object[first] = { gender: “male”, {info} }// Second Iteration// first = “info”// remaining.join(“.”) = “fullname.first”// object[first] = {id: 22, {fullname}}// Third Iteration// first = “fullname”// remaining.join(“.” = “first”// object[first] = {first: “Dan”, last: “Deacon” }// Finally…// first = “first”// remaining.length = 0// object[first] = “Deacon”
Composition
Functional programs break up their logic into small pure functions that are focused on specific tasks.
Eventually, you will need to put these smaller functions together.
Specifically, you may need to combine them, call them in series or parallel, or compose them into larger functions until you eventually have an application.
When it comes to composition, there are a number of different implementations, patterns, and techniques.
One that you may be familiar with is chaining.
In JavaScript, functions can be chained together using dot notation to act on the return value of then previous function.
Strings have a replace method.
The replace method returns a template string which also will have a replace method.
Therefore, we can chain together replace methods with dot notation to transform a string.
const template = “hh:mm:ss tt”const clockTime = template.replace(“hh”, “03”).replace(“mm”, “33”).replace(“ss”, “33”).replace(“tt”, “PM”)console.log(clockTime)// “03:33:33 PM”
In this example, the template is a string. By chaining replace methods to the end of the template string, we can replace hours, minutes, seconds, and time of day in the string with new values. The template itself remain intact and can be reused to create more clock time displays.
Chaining is one composition technique, but there are others. The goal of composition is to “generate a higher order function by combining simpler functions.”
The both function is one function that pipes a value through two separate functions.
The output of civilian hours becomes the input for appendAMPM, and we can change a date using both of these functions combined into one.
const both = date => appendAMPM(civilianHours(date))
However, this syntax is hard to comprehend and therefore tough to maintain or scale.
What happens when we need to send a value through 20 different functions?
Another implementation of compose is found in Redux
A more elegant approach is to create a higher order function we can use to compose functions into larger functions.
const both = compose(civilianHours,appendAMPM)both(new Date())
This approach looks much better. It is easy to scale because we can add more functions at any point. This approach also makes it easy to change the order of the composed functions.
The compose function is a higher order function. It takes functions as arguments and returns a single value.
const compose = (…fns) =>(arg) =>fns.reduce((composed, f) => f(composed)arg)
compose takes in functions as arguments and returns a single function. In this implementation, the spread operator is used to turn those function arguments into an array called fns.
A function is then returned that expects one argument, arg. When this function is invoked, the fns array is piped starting with the argument we want to send through the function. The argument becomes the initial value for composed and then each iteration of the reduced callback returns. Notice that the callback takes two arguments: composed and a function f. Each function is invoked with compose which is the result of the previous function’s output. Eventually, the last function will be invoked and the last result returned.
This is a simple example of a compose function designed to illustrate composition techniques. This function becomes more complex when it is time to handle more than one argument or deal with arguments that are not functions.
This is something to look out for if you use a compose function from a different library.
Now that we’ve been introduced to the core concepts of functional programming, let’s put those concepts to work for us and build a small JavaScript application.
Since JavaScript will let you slip away from the functional paradigm, and you do not have to follow the rules, you will need to stay focused.
Summary:

  1. Keep data immutable.
  2. Keep functions pure, accept at least one argument, return data or another function.
  3. Use recursion over looping (wherever possible). And this is the end of JavaScript for React series! Let me know if I missed something! You can find the rest of the series here in my dev.to profile!

Discussion (0)