JavaScript, The React Parts
A JavaScript Primer for Learning React
See Our Public Workshops:
React became really popular around the same time that ES2015 (ES6) came into existence (those are just the technical version names for JavaScript). For this reason, some beginners learning React are also trying to tackle more modern JavaScript syntax at the same time. If you're new to both, it can be confusing as to "what is JavaScript and what is React". This document should serve as a primer to help you get up-to-speed on JavaScript syntax that we feel matters the most for learning React.
Contents
- Semicolons
- Variable Scope:
var
,let
, andconst
- Template Literals
- Expressions vs Statements and Declarations
- Functions
- ES2015 Syntax Sugar
- Arrays
- Short Circuiting with
&&
- Optional Chaining with
?.
Semicolons
Perhaps you've heard or seen that semicolons aren't exactly required in JavaScript. There has been a ton of debate on whether or not devs should use them anyways, but the main points usually boil down to:
- Point: You should use them because there are some edge cases where not having them can be a problem
- Counterpoint: True, but if we use Babel to "transpile" our code, Babel is going to take the code we wrote without semicolons and it's going to add them back in for us anyways, so why does it matter?
- Counterpoint: Yes, but... and it goes on and on
These days, I think most JS developers don't use semicolons but whether you decide to use them is up to you. One piece of tooling that seems to normalize the conversation a bit is prettier.io, a formatting tool which rewrites the code as you type, or as you save, or as you push -- whichever you prefer. With tools like prettier, many of the "what is your preference" conversations are going away because tooling helps to normalize the code.
Variable Scope: var
, let
, and const
JavaScript has always had var
, which creates function-scope (or global scope). This can be a little confusing sometimes and is not often what we need.
"Block Scope" can be easier to understand and manage which is why JavaScript got let
, and const
in ES2015. Here's a quick rundown of how all three work:
// `var` is not block scope, it has global-scope in this// case. Here, `name` always refers to the same thing// because of that global scope.var name = 'Michael'if (true) {var name = 'Bruce'name // 'Bruce'}name // 'Bruce'// `let` is block scope. This means if we declare name with// `let` in the block of the if-statement, that `name` will// be "Bruce" inside that block, and the outer `name` will// still be "Michael"let name = 'Michael'if (true) {let name = 'Bruce'name // 'Bruce'}name // 'Michael'// `const` is also block scope like letconst name = 'Michael'if (true) {const name = 'Bruce'name // 'Bruce'}name // 'Michael'// The difference is that `let` can be reassignedlet isOpen = trueisOpen = falseisOpen // false// `const` cannot be reassignedconst isOpen = trueisOpen = false // throws error// `const` in JavaScript does not mean it's a super// global constant for the whole application like how// other languages might have. In JS, it just means// it's block scope and cannot be re-assigned for that// block.// Although const cannot be reassigned, if the value// is an array or an object, it's inner parts can be// changed, as long as the array or object itself isn't// reassignedconst list = []// The `list` will always be this array, we can't change// that, but we can modify the parts:list.push('Michael')// But this is not allowed, we cannot change (reassign)// list to be something other than the array it started// off to belist = 'turn list into a string'
We find block scope to make more sense to people and is generally more useful. Personally, I never use var
anymore simply because it doesn't do anything I need. 99% of the time I use const
since I don't need re-assignment, and if I do re-assignment, I use let
. Others like to use let more often than const. Usually it doesn't really matter.
Template Literal
Strings in JavaScript can be made with single or double quotes. But when you make strings this way, you can't have multiline unless you manually add new lines. Template literals (sometimes referred to as Template strings) allow us to do multiline in a much cleaner way. Template literals use the back-tick instead of a single or double quote.
// Manual hard returns with \\n isn't funconst str = 'multiline\\nwith\\nmanual\\nhard returns'// This is much nicer.const str = `multilinewithoutthemess`
Another benefit is string interpolation (making strings from variables)
const something = 'ugly stuff'const str = 'instead of ' + something + ' like this'const something = 'lovely stuff'const str = `you can do ${something} like this`
In the first example, the str
is being built with a variable. Notice we have to use +
concatenation to do so. In the second example, the str
is a Template literal which can use ${}
to interpolate variables into the string.
When strings are made, the end result is no different if we use back-ticks, single quotes, or double quotes. So the fact that something
was made with single-quotes doesn't mean anything when it comes to using it as an interpolated variable into the template literal.
JavaScript would even coerce numbers if needed:
const version = 16const str = `At the time of this writing, React is on version ${version}`
Expressions vs Statements and Declarations
Most code in JavaScript is said to be either an Expression (Operator) or Declaration/Statement. It's not so important to memorize every last detail about these, but it is important to know some things about expressions for React since only expressions are allowed in JSX and not statements or declarations.
The brief definition is: Expressions resolve to a single value.
// If we're thinking in terms of statements, we might// write code like this, with an If-Statement:let result = nullif (someCondition) {result = 'Michael'} else {result = 'Bruce'}// Here's how we might the same logic using a// ternary operator, which is a type of expression// because the line of code resolves to a single// value for resultconst result = someCondition ? 'Michael' : 'Bruce'
In this example, we have four separate expressions:
const name = 'michael jackson'const parts = name.split(' ') // parts: ['michael', 'jackson']let first = parts[0] // first: 'michael'first = first.toUpperCase() // first: 'MICHAEL'
Even though these are all expressions, JavaScript lets us combine and chain expressions together. In effect, all the expressions above can be rewritten into one expression:
const name = 'michael jackson'const first = name.split(' ')[0].toUpperCase()// We could have even done this:const first = 'michael jackson'.split(' ')[0].toUpperCase()
Chaining expressions looks funny at first if you're coming from a language that doesn't do this sort of thing, but if you read it left to right, each part is resolving to a value and then making itself available to the next part. When we do name.split(' ')
, this resolves to an array, which means the next part can pick off the 0 index with [0]
. That resolves to a string value of 'michael'
which can then have a string method added to it like .toUpperCase()
. Whatever the far right side of the expression resolves to gets returned to the left-hand side of the equal sign, in our case a variable called first
.
Functions
Functions in JavaScript can be created in several ways, each with different tradeoffs. Here are three ways to be aware of:
// Function Declarationfunction getName() {return 'Michael'}// Function Expressionconst getName = function () {return 'Michael'}// Arrow Function (Which is also an expression)const getName = () => {return 'Michael'}
Based on the previous section on Declarations and Expressions, it's probably more clear as to why the first two get their names. The Function Expression is an "expression" because the function is being assigned to a value. Technically arrow functions are also expressions but conversationally we usually just refer to them as "arrow functions" and not "arrow function expressions".
The tradeoffs between function declarations and expressions is that declarations can be "hoisted" and expressions cannot. However, many times hoisting doesn't matter so most developers choose one or the other simply based on personal syntax preference.
Arrow Functions are Special
Arrow functions are function expressions with a slightly different syntax. In the example above, you can see that the arrow function looks just like function expression example but without the word function and then with a =>
fat arrow between the parens and the opening curly-brace.
You may have heard that functions create their own scope in JavaScript. This means JavaScript functions create their own context for this
which can be problematic if we want a function but without having its own context for this
. One of the characteristics of an arrow function is that they don't create context so this
inside the arrow function is the same as the this
on the outside.
Arrow functions can also be really compact. Look at these two examples that do the exact same thing:
const getName = () => {return 'Michael'}// Same as above but more compactconst getName = () => 'Michael'
When arrow functions omit their curly-braces, it means we want the thing on the right-hand side of the fat arrow to be the return (without saying return
). This is called an implicit return.
There are some more subtle details to know about arrow functions like how to return an object literal and how to omit the parenthesis for a single parameter.
ES2015+ Syntax Sugar
ES2015-ES2018 has brought a lot of new syntax to JavaScript that lets us do things we could always do before, but now with nicer syntax. Here are some notable examples:
Shortand for Object Methods
You can drop off the :
and the word function
for methods when defining them:
const obj = {insteadOfThis: function () {// do stuff},youCanDoThis() {// do stuff},}
Note that the above is not an arrow function, just a shorter syntax for object methods.
Object Destructuring
Object destructuring is a way to take an object and to pull out its internal properties into variables outside of the object:
const obj = { x: 1, y: 2 }// instead of:const x = obj.xconst y = obj.y// We can "destructure" the values into ordinary// variables:const { x, y } = objx // 1y // 2// you can use this all over the place, like function// parameters. Notice how we're passing just one thing// (an object) into the add function. If the function// is expecting an argument, it can destructure the// values right in the parameter list.function add({ x, y }) {return x + y}add({ x: 3, y: 4 }) // 7
It can be a little confusing at first because now curly-braces are used to make objects and to destructure them depending on context. So how can you tell?
// If the curlies are on the right-hand sign of the// expression (equal sign) then we're making an objectconst obj = { x: 1, y: 2 }// If they're on the left-hand side (or the receiving// side as with parameters), then it's destructuring:const { x } = objx // 1
Array Destructuring
Array destructuring works almost the same as Object Destructuring but with square-brackets instead of curly-braces:
const arr = ['michael', 'jackson']const [first, last] = arrfirst // michaellast // jackson
The other difference between them is that objects have property names so those have to be used in the destructuring part. Since array values are numerically ordered and without names, the order that we destructure is tied to what value we get -- in other words, first
is the first variable in the destructure so it gets the first value of the array.
Property Shorthand
Property Shorthand lets you type less if a property name matches the variable name in an object:
// Instead of having to type name twice like thisconst name = 'Michael'const person = { name: name }// If the property and the variable are the same you can just// type it like this and omit the colon and the double wordconst person = { name }
...Spread Syntax
When creating objects or arrays, there is a new way to create properties from the properties of an existing object or array. This is much easier shown in code than explained:
// Let's say you have this arrayconst person = ['Michael', 'Jackson']// If you were to add the above array to a new one like this:const profile = [person, 'developer']// The end result would be an array in an array like this:profile // [['Michael', 'Jackson'], 'developer']profile[0] // this is an arrayprofile[1] // this is the string 'developer'// However, if we had made profile like this with ...const profile = [...person, 'developer']// Then the end result would be this:profile // ['Michael', 'Jackson', 'developer']// The same concept works with objectsconst person = { first: 'Michael', last: 'Jackson' }const profile = { ...person, occupation: 'developer' }profile // { first: 'Michael', last: 'Jackson', occupation: 'developer' }
...Rest Syntax
This might look similar to "spread" but the difference is that ...
rest is not used to build objects or arrays, it's used to break then down into pieces. Here's an example of rest while destructuring:
const profile = { first: 'Michael', last: 'Jackson', occupation: 'developer' }const { occupation, ...rest } = profileoccupation // developerrest // { first: 'Michael', last: 'Jackson' }
Remember, destructuring is a way to break an object or an array apart into pieces. The above code makes an ordinary string variable called occupation
through destructuring. The three dots ...
followed by a variable name means we want all the rest of the properties into this rest
object. Note that ...
can be used while destructuring arrays as well. Also, the variable name doesn't have to be "rest". We could have done ...whatever
.
The next form of rest comes in the form of function parameters:
function myFunction(first, last, ...rest) {return rest}console.log(myFunction('Michael', 'Jackson', 'Developer', 'California'))// output: ['Developer', 'California']
The function parameters is suggesting that it wants a first and last name as its first two arguments, but anything you pass in after that will all be added to rest
as an array.
ES Modules
Organizing and breaking your app into different re-usable files is key for a React application. Each JavaScript file is called a "module". In order to let modules work together, they need to be able to import and export code between them. While ES Modules aren't natively supported in browsers (yet), we use Webpack (or Rollup) and Babel to re-write our code that has modules into something the browser does understand.
In NodeJS, the "pattern" developed for this is "CommonJS" or (cjs). Here's what it looks like:
const SomeModule = require('some-module')SomeModule.someMethod()// more code here...module.exports = SomethingToExport
"ES Modules" is an alternative pattern that is mostly compatible with CommonJS but has a different syntax:
import SomeModule from 'some-module'SomeModule.someMethod()// more code here...export default SomethingToExport
Or we can do a destructuring-like syntax on the import:
import { someMethod } from 'some-module'someMethod()// more code here...export default SomethingToExport
Arrays
Here are some common array methods and functions to be familiar with:
Array.isArray()
// Check to see if a value is an arrayconst myArray = ['hello']console.log(Array.isArray(myArray)) // true
.map()
Map takes an array, iterates over it with a function and whatever the function returns will be the replacement value for the item we're currently on:
const myArray = [1, 2, 3, 4]const result = myArray.map(function (item) {return item + 5})console.log(result) // [6, 7, 8, 9]// The above could have also been written like this with// an arrow function:const result = myArray.map((item) => item + 5)
.reduce()
Reduce is similar to .map
in that it iterates over an array but the end result is just one value instead of replacing all the values in the array:
// Let's add up all the values to get one value of 10const myArray = [1, 2, 3, 4]const total = myArray.reduce(function (tally, current) {return tally + current}, 0)console.log(total) // 10
The callback function will give us two important arguments. The first is a running tally of what we've made so far. The second is the current item we're iterating over (in our case the numbers). So, you can see that we're just taking what we have so far and adding each number to it. The only problem is we need tally to start off as 0
otherwise the first iteration won't know how to add things. That's where the second argument for reduce()
comes in -- the first being the function and the second being a starting value for the "accumulator" which we're calling tally
The above could have also been written as an arrow function:
const total = myArray.reduce((tally, current) => tally + current, 0)
.filter
Filter gives us a new array with the same values, but only if the iterator function returns true
:
const myArray = [1, 2, 3, 4]const result = myArray.filter(function (item) {const isBiggerThanTwo = item > 2return isBiggerThanTwo})console.log(result) // [3, 4]// An an arrow functionconst result = myArray.filter((item) => item > 2)console.log(result) // [3, 4]
The first example clearly shows that we need to return a boolean based on if the input number is bigger than two. This can be simplified into an arrow function with an implicit return.
.find
Find is similar to Filter but instead of returning an array, only the first item to get true returned from the iterator function is returned from Find:
const people = [{ id: 3, name: 'Michael'}, {id: 5 name: 'Bruce' }]const person = people.find(item => item.id === 3)console.log(person) // { id: 3, name: 'Michael'}
Short-Circuiting with &&
You already know how &&
works in If-Statements, but perhaps you didn't know they are used to do what is called "short circuiting". Here's how it works:
function one() {console.log('one was called')return false}function two() {console.log('two was called')return false}if (one() && two()) {console.log('Here we go!')}// The only output of this code is "one was called" because of// short circuiting
The only output from this code is "one was called". The output for "Here we go!" is not going to happen because the two function calls return false
. But why is the function two()
not called at all? We know it wasn't called because we never get "two was called". The reason is that most programming languages short-circuit, which means when the thing before &&
is false, then there's no point in checking the rest of the expression because one thing being false means the end result has to be false. Maybe you know most of that but never thought of it that way.
Here's another way to take advantage of short-circuiting:
// This will cause an error if `users` is not an arrayfunction findById(users, id) {return users.find((item) => item.id === id)}// Now we are returning the person if `users` is an array// If `users` is not an array then false will be returned// because a falsy short circuit returns the left part// when falsy:function findById(users, id) {return Array.isArray(users) && users.find((item) => item.id === id)}
Optional Chaining with ?.
This one is used in similar cases to the &&
short-circuiting operator. It's actually the normal .
accessor operator with an additional feature. Let's say you wanted to access users.length
but users
is either an array or might be null
or undefined
. If you tried to do users.length
, you might get:
Uncaught TypeError: Cannot read property 'length' of null
So developers will do users && users.length
to ensure it's not falsy (null
or undefined
). Of course, this doesn't ensure the value is an array to get the length, but we'll address that coming up.
Instead of doing the more verbose users && users.length
, you could users?.length
which does this:
- Evaluate
users
to see if it's truthy. If it isn't, returnundefined
from the expression without doing.length
to it. - If it is truthy, then continue with the rest of the
.length
expression.
Therefore, it will return undefined
or the length depending on whether user is truthy. So you can see that it's very similar to &&
short-circuiting except that ?.
will return undefined
if the variable is "falsy" - not the actual "falsy" value of the variable like &&
would.
In a previous short-circuit example, we checked to see if users
was an array before trying to do .find
on it. This will be typical in JavaScript because there would be no other way of knowing that you do in-fact have an array. But in TypeScript, this check would be redundant since with types we know that users
is an array already:
function findById(users: User[] | null, id: number): User | undefined {return users?.find((item) => item.id === id)}
For this reason, I find myself using optional-chaining more often in TypeScript since I know the types and therefore don't need the additional check that would embrace &&
.
Summary
The React community has really embraced modern JavaScript so learning React will most likely force you to learn the latest modern syntax after you read enough articles and documentation. I hope you enjoyed this read! If you're looking for more material on JavaScript, Mozilla Developer Network is a great place to start.