React: Essential Concepts and Best Practices
Tags: react, tutorial, webdev, javascript
Published: 2024-06-08 15:06:45

## Introduction
A overview to refresh your knowledge of React's core functionality.

Let's take a look at a React app and explore ways to improve the code.

## Initial code

```javascript
export default function App() {
  const cars = [
    {
      name: "Lightning McQueen ⚡️",
      image:
        "https://i.pinimg.com/1200x/c4/50/07/c45007f8ec1d37926d87befde23ec323.jpg",
    },
    // ...
  ];

  return (
    <div className="App">
      <Container cars={cars} />
    </div>
  );
}

function Container({ cars }) {
  let title = "Participants";
  const [isTitleUppercase, setTitleUppercase] = useState(false);

  const generateExtraParams = () => {
    return {
      position: 1,
      selfConfidence: Infinity,
    };
  };

  const handleClick = () => {
    setTitleUppercase((prev) => !prev);
  };

  return (
    <>
      <header>
        <h1>{isTitleUppercase ? title.toUpperCase() : title}</h1>{" "}
        <button onClick={handleClick}>Case toggle 🖲️</button>
      </header>
      <main>
        {cars.map((car, index) => (
          <CarCard
            key={index}
            car={car}
            // extraParams={generateExtraParams()}
          />
        ))}
      </main>
    </>
  );
}

function CarCard({ car, extraParams }) {
  console.log(`🏎️ Ka-Chow! ⚡️⚡️⚡️`);

  return (
    <>
      <p>{car.name}</p>
      <div id="iw">
        <img src={car.image} width={100} />
      </div>

      {extraParams &&
        Object.entries(extraParams).map(([key, value]) => (
          <p key={Math.random()}>
            <i>{key}:</i> {value}
          </p>
        ))}
    </>
  );
}
```

## 1. Prevent Unnecessary Re-renders

Now, when we click the 'Case toggle' button, we see that `CarCard` is re-rendering. Why does this happen and what can we do about it?

This happens because the button handler exists in the parent component, which triggers a re-render of that component and all its children. To fix this, we can use `React.memo` to wrap our `CarCard` component. `React.memo` is a higher-order component that will only re-render the wrapped component if its props change. This way, `CarCard` won't re-render unless its specific props change.

Here's how we can implement it:

```javascript
const CarCard = React.memo(function ({ car, extraParams }) {
  console.log(`🏎️ Ka-Chow! ⚡️⚡️⚡️`);

  return (
    <>
      <p>{car.name}</p>
      <div id="iw">
        <img src={car.image} width={100} />
      </div>

      {extraParams &&
        Object.entries(extraParams).map(([key, value]) => (
          <p key={Math.random()}>
            <i>{key}:</i> {value}
          </p>
        ))}
    </>
  );
});

```

## 2. Memoize Function Calls

When you uncomment the line `extraParams={generateExtraParams()}` and click the `'Case toggle' button`, you'll notice that CarCard re-renders. This happens because generateExtraParams creates a new object on every render, and React treats it as new data.

To prevent this, we use `React.useMemo` to memoize the result of `generateExtraParams`. This ensures that the function is only called once and the same object is used on subsequent renders, preventing unnecessary re-renders.

```javascript
const generateExtraParams = () => {
  return {
    position: 1,
    selfConfidence: Infinity,
  };
};

const extraParams = React.useMemo(() => generateExtraParams(), []);

```

## 3. Create a Custom Hook

Can we create a custom hook to manage the toggle uppercase functionality, since we might need this in multiple places in the future? Absolutely. Let's implement it:

```javascript
const useToggle = () => {
  const [isTitleUppercase, setTitleUppercase] = useState(false);

  const toggle = () => {
    setTitleUppercase((prev) => !prev);
  };

  return [isTitleUppercase, toggle];
};

// Inside the Container
const [isTitleUppercase, toggle] = useToggle();

// And use it like this:
<header>
  <h1>{isTitleUppercase ? title.toUpperCase() : title}</h1>{" "}
  <button onClick={toggle}>Case toggle 🖲️</button>
</header>
```
We moved the state and function to a custom hook, which we can reuse in different places as needed.

## 4. Use Context for Deep Prop Drilling

Now, if we need to pass a prop deeply through the component tree, can we do it better than just prop drilling? Yes, we can use context for this.

React Context is a way to manage state globally. It allows you to create a context object, which can then be provided to the component tree. Any component in the tree can consume the context value without needing to pass props through every level.

Here's how we can implement it:

1. Create Context:
We create a context object using `React.createContext()` and provide a default value. In this case, we'll create a context for our cars.

2. Provide Context:
We use a context provider to wrap our component tree. This makes the context value available to all components within the provider.

3. Consume Context:
Any component that needs the context value can use the useContext hook to access it.

We also create a custom hook `useCarsContext` to make it easier to consume the context. This hook encapsulates the useContext call, so we don't need to repeat it in every component that needs the context value.

Here's how the updated code looks:
```javascript
import React, { useMemo, useState, useContext, createContext } from "react";

const CarsContext = createContext([]);
const useCarsContext = () => useContext(CarsContext);

export default function App() {
  const cars = [
    {
      name: "Lightning McQueen ⚡️",
      image:
        "https://i.pinimg.com/1200x/c4/50/07/c45007f8ec1d37926d87befde23ec323.jpg",
    },
    // ...
  ];

  return (
    <CarsContext.Provider value={cars}>
      <div className="App">
        <Container />
      </div>
    </CarsContext.Provider>
  );
}

const useToggle = () => {
  const [isTitleUppercase, setTitleUppercase] = useState(false);

  const toggle = () => {
    setTitleUppercase((prev) => !prev);
  };

  return [isTitleUppercase, toggle];
};

function Container() {
  let title = "Participants";
  const [isTitleUppercase, toggle] = useToggle();

  const generateExtraParams = () => {
    return {
      position: 1,
      selfConfidence: Infinity,
    };
  };

  const extraParams = useMemo(() => generateExtraParams(), []);
  const cars = useCarsContext();

  return (
    <>
      <header>
        <h1>{isTitleUppercase ? title.toUpperCase() : title}</h1>{" "}
        <button onClick={toggle}>Case toggle 🖲️</button>
      </header>
      <main>
        {cars.map((car, index) => (
          <CarCard key={index} car={car} extraParams={extraParams} />
        ))}
      </main>
    </>
  );
}

const CarCard = React.memo(function ({ car, extraParams }) {
  console.log(`🏎️ Ka-Chow! ⚡️⚡️⚡️`);

  return (
    <>
      <p>{car.name}</p>
      <div id="iw">
        <img src={car.image} width={100} />
      </div>

      {extraParams &&
        Object.entries(extraParams).map(([key, value]) => (
          <p key={Math.random()}>
            <i>{key}:</i> {value}
          </p>
        ))}
    </>
  );
});
```

## 5. Avoid Using Index and Math.random() for Keys
One last improvement: using index and Math.random() for keys in lists is not a good practice. Instead, use a unique identifier related to the item. This ensures React can track which items are added, moved, or removed correctly.

## Conclusion

I hope this refreshed your memory about core React functionality. By exploring these basic examples and improvements, we've seen how to make our React apps more efficient and easier to maintain.

*Thanks for hanging out!* Hit subscribe for more! 👋