Turbocharge Your React + TypeScript Apps!

Turbocharge Your React + TypeScript Apps!

Ready for an exciting adventure in the land of React and TypeScript? Picture this: You are a star mechanic, and your task is to supercharge a fancy race car (our web app). Let's dive right in!

1. Optimize Large Components

Large components can be slow. Let's break them down!

Imagine we have a list of video game characters. Each character has a unique ability that we want to display.

type Character = {
  id: number;
  name: string;
  ability: string;
};

const characters: Character[] = [
  //... a large list of characters
];

const CharacterList = () => {
  return (
    <div>
      {characters.map((character) => (
        <div key={character.id}>
          <h2>{character.name}</h2>
          <p>{character.ability}</p>
        </div>
      ))}
    </div>
  );
};

This component could be more efficient if we break it down:

const CharacterCard = ({ character }: { character: Character }) => (
  <div key={character.id}>
    <h2>{character.name}</h2>
    <p>{character.ability}</p>
  </div>
);

const CharacterList = () => {
  return (
    <div>
      {characters.map((character) => (
        <CharacterCard character={character} />
      ))}
    </div>
  );
};

2. Use React.memo

We can avoid unnecessary re-renders using React.memo. Let's apply this to our CharacterCard:

const CharacterCard = React.memo(({ character }: { character: Character }) => (
  <div key={character.id}>
    <h2>{character.name}</h2>
    <p>{character.ability}</p>
  </div>
));

Now, CharacterCard will only re-render if any property in character prop changes, not just if the object reference changes. To learn more, read about shallow equality and React.memo.

3. Use useCallback and useMemo

Imagine we have an action to select a character, which filters the character list:

const CharacterList = () => {
  const [selectedId, setSelectedId] = React.useState<number | null>(null);

  const handleSelect = (id: number) => {
    setSelectedId(id);
  };

  return (
    <div>
      {characters.map((character) => (
        <CharacterCard 
          character={character}
          onSelect={handleSelect} 
        />
      ))}
    </div>
  );
};

Every render creates a new handleSelect function. We can optimize this with useCallback:

const handleSelect = React.useCallback((id: number) => {
  setSelectedId(id);
}, []);

Now, what if we only want to display selected characters? We could filter them every render, but useMemo makes this more efficient:

const selectedCharacters = React.useMemo(
  () => characters.filter((character) => character.id === selectedId),
  [characters, selectedId]
);

return (
  <div>
    {selectedCharacters.map((character) => (
      <CharacterCard 
        character={character}
        onSelect={handleSelect} 
      />
    ))}
  </div>
);

Now selectedCharacters will only update if the characters or selectedId changes, instead of on every re-render.

4. Use Selectors for State Management

Selectors efficiently grab state parts. Using the Redux toolkit as an example:

const getSelectedCharacters = (state: RootState) =>
  state.characters.filter((character) => character.id === state.selectedId);

// Then in your component
const selectedCharacters = useSelector(getSelectedCharacters);

Now you can reuse this data in multiple components and keep the state logic in one place. There's so much we can do with the Redux toolkit.

5. Lazy Load Components

React allows you to only load components when they're needed using React.lazy:

const CharacterCard = React.lazy(() => import('./CharacterCard'));

// Don't forget to wrap your component with `React.Suspense` in your parent component
return (
  <React.Suspense fallback={<div>Loading...</div>}>
    <CharacterCard character={character} />
  </React.Suspense>
);

And there you have it, friends! With these tips, your React + TypeScript application will zoom faster than ever. Keep practising, keep learning, and most importantly, have fun! See you at the finish line!