4 JavaScript Concepts You Need To Know Before You Can Master React

4 JavaScript Concepts You Need To Know Before You Can Master React

·

8 min read

If you’re new to React, you’ve probably heard people tell you that you should learn JavaScript first.

While that’s good advice, it can be overwhelming.

JavaScript isn’t something you’ll be able to master in a few hours. Trying to learn everything about JavaScript before you move to React isn’t worth the time. Instead, focusing on important JavaScript concepts that are often used in React will get you much further.

You’ll waste less time with Learner's Fatigue and start building applications faster.

In this post, we’ll take a look at 4 JavaScript concepts that you’ll see in most — if not all — React code.

1. Closures

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).

The quote above is from MDN. If you want an in-depth explanation of what a closure is and how it works, take a look at MDN’s page on closures.

Instead of re-hashing the MDN page, I’m going to show you what a closure is through code:

function createACounterClosure() {
    let count = 0;

    function increaseCount() {
        count = count + 1;
    }

    function getCount() {
        return count;
    }

    return { increaseCount, getCount };
}

const counter = createACounterClosure();

counter.getCount(); // 1
counter.increaseCount();
counter.getCount(); // 2
counter.count; // undefined

The combination of the increaseCount function and the count variable is a closure.

There's no way to access count directly. However, you can use either function that closes over the count variable to interact with it. Even though createACountClosure has finished running, the count variable is still accessible.

Here’s an example of how React uses closures:

function MyComponent() {
    const [count, setCount] = React.useState(0);
    React.useEffect(function effectClosure() {
        console.log(count);
        return function cleanupClosure() {
            console.log(count);
        }
    }, [count]);
}

Effects are used all the time in React.

The function you pass to and return from useEffect both close over the count variable. React calls the cleanupClosure function sometime in the future when count changes.

The key is that the function still has access to the count variable even after effectClosure returns.

2. Array Functions

Almost all React applications use arrays to render lists of components.

Learning basic array functions will help you write better React code. It lets you build components in a declarative style which is easier to maintain and understand.

The most common array functions I see in React code are the following:

  • map
  • filter
  • includes
  • find

map

The map function lets you execute a function for each item in an array. A new array is returned where each value is the result of executing the mapper on the original item.

const array = [1, 2, 3];
const mappedArray = array.map(function double(item) {
    return item * 2;
});
mappedArray; // [2, 4, 6]

filter

The filter function also lets you execute a function for each item in an array. A new array is returned where an item is excluded if the function returns false.

const array = [1, '2', 3];
const filteredArray = array.filter(function isNumber(item) {
    return typeof item === 'number';
});
filteredArray; // [1, 3];

includes

The includes function lets you check if an item is in an array.

const array = [1, 2, 3];
array.includes(2); // true
array.includes(4); // false

find

The find function executes a function for each item in an array and returns the first element where the function evaluates to true.

const array = [1, 2, 3, 4];
const num = array.find(function biggerThan2(item) {
    return item > 2;
});
num; // 3

Here’s an example that uses the filter and map functions in React:

const names = ['Jack', 'Jill', 'James', 'Bill'];
function MyComponent() {
    const jNames = names.filter(function isJName(name) {
        return name.startsWith('J');
    });

    const nameItems = jNames.map(function buildNameItem(name) {
        return <span key={name}>Name: {name}</span>;
    });

    return <div>{nameItems}</div>;
}

3. Destructuring

Destructuring lets you unpack array values or object properties into variables.

// Normal:
const array = [1, 2];
const person = { name: 'Jack', age: 100 };
array[0]; // 1
array[1]; // 2
person.name; // 'Jack'
person.age; // 100

// Destructure:
const [a, b] = [1, 2];
const { name, age } = { name: 'Jack', age: 100 };
a; // 1
b; // 2
name; // 'Jack'
age; // 100

When you destructure an array, you can name the variables anything you want. However, if you destructure an object then the variable names have to match the property you’re trying to destructure.

This is a common pattern in React.

The useState hook is a great example. It returns an array with the state and a function to update the state. It’s common to destructure those properties with descriptive names.

function MyComponent() {
    const [count, setCount] = React.useState(0);
    count; // The initial value 0
    setCount; // A function to update the state value
}

4. Object References

Object references are often misunderstood.

If you’re not careful it can lead to mutation bugs.

Mutation bugs are common when more than one variable references the same object in memory. Changing the object from one variable can lead to unexpected effects for the second variable. While mutation isn’t inherently bad, it can lead to bugs that are hard to solve.

Understanding the basics of mutation will help you write better React code.

In JavaScript, when you assign an object to a variable, it points to the object's reference in memory. When you compare two objects, you’re really checking if the variables point to the same place in memory. This is why you see things like the following:

const objOne = {};
const objTwo = {};
const objThree = objOne;

objOne === objTwo; // false (refers to different locations in memory)
objOne === objThree; // true (refers to the same location in memory)

Object mutation is reflected in every variable that references it

const objOne = {};
const objTwo = objOne;

objOne.age = 100;
objTwo.age; // 100

Both objects refer to the same location in memory. Updating one variable is reflected in the other variable.

Arrays and Functions

In JavaScript, everything that isn’t a primitive value is an object.

This means that variables containing arrays and functions also behave the same way.

const fnOne = () => true;
const fnTwo = () => true;
fnOne === fnTwo; // false

const arrOne = [];
const arrTwo = [];
arrOne === arrTwo; // false

Object references in React state

React can bail out of a render caused by a state update if the updated state is the same as the old state.

function MyComponent() {
    const [count, setCount] = React.useState(0);

    React.useEffect(() => {
        // 0 === 0
        // React will bail out of the render
        setCount(0);
    }, []);
}

React improves performance by skipping updates that would result in the same output.

But what happens when we store an object in state?

function MyComponent() {
    const [person, setPerson] = React.useState({ name: 'Sai', age: 100 });

    React.useEffect(() => {
        const newPerson = person;
        newPerson.age = 200;

        // newPerson === person (same reference)
        // React will bail out of the render
        setPerson(newPerson);
    }, []);
}

Obviously, this isn’t what we want.

So how do we get around it?

function MyComponent() {
    const [person, setPerson] = React.useState({ name: 'Sai', age: 100 });

    React.useEffect(() => {
        // Create a new object
        const newPerson = { name: person.name, age: 200 };

        // newPerson !== person (different reference)
        // React won't bail out of the render
        setPerson(newPerson);
    }, []);
}

Object references in React dependency arrays

Another common source of confusion is React dependency arrays.

React uses dependency arrays for useEffect, useCallback and useMemo. It does a shallow comparison (===) between renders to check if it needs to do work. This leads to a similar problem as the useState use case.

function MyComponent() {
    const doSomething = () => {
        // ...
    };

    const value = React.useMemo(() => doSomething(), [doSomething]);
    const callback = React.useCallback(doSomething, [doSomething]);
    React.useEffect(() => {
        doSomething();
    }, [doSomething]);
}

The doSomething function is used in all of the dependency arrays above.

The problem is that doSomething is recreated every render cycle, so its reference is different. When React compares doSomething between render cycles, each version will point to a different location in memory. This means that each hook will execute every time the component renders which is probably not what we want.

Here are a couple of things you can do instead:

const doSomethingExternal = () => {};

function MyComponent() {
    const doSomething = React.useCallback(() => {
        // ...
    }, []);

    const value = React.useMemo(() => {
        if (/* true */) return doSomething();
        return doSomethingExternal();
    }, [doSomething]);

    React.useEffect(() => {
        doSomething();
        doSomethingExternal();
    }, [doSomething]);
}

One solution is to define a function outside of the component. This guarantees that the reference is the same between renders because the function is only defined once. You can also define a function with the useCallback hook. This also guarantees referential stability between renders which makes it safe to use in dependency arrays.

Conclusion

You need to understand the basics of JavaScript before you can use React effectively.

However, there's no need to try to master everything. It's better to focus on the important parts and try to apply what you've learned directly in React. It adds context to how concepts are used in real applications. This leads to a deeper understanding.

In the end, you're probably trying to use JavaScript to write React code.

So do that.