Skip to content

Commit 482ca85

Browse files
feat: improve the HOC lesson
1 parent 7a04b94 commit 482ca85

File tree

8 files changed

+239
-147
lines changed

8 files changed

+239
-147
lines changed

src/course/02-lessons/01-Bronze/ConditionalRendering/lesson.mdx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,19 @@ const PokemonExplorer = () => {
8888

8989
In the first exercise we are going to look into building a Pokemon trainer status system that shows different content based on whether the trainer has earned gym badges. Go to the exercise.tsx inside the ConditionalRendering folder and start the exercise. Once completed, the Tests will show as passed in the storybook "Interactions" addon section.
9090

91+
## When to use this pattern?
92+
93+
**Use conditional rendering for:**
94+
- **Dynamic UI**: Show/hide components based on state or props
95+
- **User Permissions**: Display content based on user roles
96+
- **Loading States**: Show spinners while data loads
97+
- **Error Handling**: Display error messages when needed
98+
99+
**Avoid when:**
100+
- **Complex Logic**: Use separate components for complex conditions
101+
- **Performance**: Avoid heavy computations in render conditions
102+
- **Readability**: Don't nest too many ternary operators
103+
91104
## Feedback
92105

93106
Feedback is a gift and it helps me make this course better for you. If you have a spare 5 mins please could you fill out a feedback form for me. Thank you.

src/course/02-lessons/01-Bronze/Hooks/lesson.mdx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,19 @@ You'll create a hook that manages capture attempts, pokeball inventory, success
9999

100100
Head over to the exercise and let's get started.
101101

102+
## When to use this pattern?
103+
104+
**Use hooks for:**
105+
- **State Management**: Replace class component state with useState
106+
- **Side Effects**: Handle API calls, subscriptions with useEffect
107+
- **Logic Reuse**: Extract common logic into custom hooks
108+
- **Modern React**: Preferred approach for new components
109+
110+
**Avoid when:**
111+
- **Legacy Code**: Existing class components work fine
112+
- **Simple Components**: Pure presentational components don't need hooks
113+
- **Over-abstraction**: Don't create hooks for single-use logic
114+
102115
## Feedback
103116

104117
Feedback is a gift and it helps me make this course better for you. If you have a spare 5 mins please could you fill out a feedback form for me. Thank you.

src/course/02-lessons/01-Bronze/PropsCombination/lesson.mdx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,19 @@ You'll be refactoring a Pokemon trading card component that currently has too ma
5656

5757
Head over to the exercise file and let's begin.
5858

59+
## When to use this pattern?
60+
61+
**Use props combination for:**
62+
- **Related Data**: Group logically related props together
63+
- **Reducing Clutter**: When components have many individual props
64+
- **API Design**: Creating cleaner component interfaces
65+
- **Maintainability**: Easier to add/remove related properties
66+
67+
**Avoid when:**
68+
- **Simple Components**: Few props don't need grouping
69+
- **Unrelated Props**: Don't force unrelated props together
70+
- **Performance**: Grouping can cause unnecessary re-renders
71+
5972
## Feedback
6073

6174
Feedback is a gift and it helps me make this course better for you. If you have a spare 5 mins please could you fill out a feedback form for me. Thank you.

src/course/02-lessons/02-Silver/Compound/lesson.mdx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ The compound component pattern is useful in building complex React components su
2727

2828
A requirement has come in to reuse the Pokemon team builder in another location of our application. The current implementation of the team builder has its state management implemented only on the page that this component is used on. We need to refactor the component to use the compound design pattern so that it can be re-used on both pages. Head over to the exercise.tsx to continue.
2929

30+
## When to use this pattern?
31+
32+
**Use compound components for:**
33+
- **Complex UI**: Multi-part components like accordions, tabs, modals
34+
- **Flexible API**: When you want declarative, composable interfaces
35+
- **State Sharing**: Components that need to share state implicitly
36+
- **Reusable Libraries**: Building component libraries with flexible APIs
37+
38+
**Avoid when:**
39+
- **Simple Components**: Basic components don't need compound patterns
40+
- **Performance**: Can add complexity if not needed
41+
- **Learning Curve**: May be confusing for junior developers
42+
3043
## Feedback
3144

3245
Feedback is a gift and it helps me make this course better for you. If you have a spare 5 mins please could you fill out a feedback form for me. Thank you.

src/course/02-lessons/02-Silver/Provider/lesson.mdx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@ const Page = () => {
3434

3535
In this lesson we are going to implement the PokemonManager with the react content pattern and pull that information from a hook in our component. Head over to the exercise.tsx file to get started.
3636

37+
## When to use this pattern?
38+
39+
**Use Provider pattern for:**
40+
41+
- **Global State**: App-wide data like user authentication, themes
42+
- **Avoiding Prop Drilling**: When props pass through many components
43+
- **Shared Logic**: Common functionality across component trees
44+
- **Configuration**: App settings that many components need
45+
46+
**Avoid when:**
47+
48+
- **Local State**: Component-specific state should stay local
49+
- **Simple Apps**: Small apps don't need global state management
50+
- **Performance**: Context changes re-render all consumers
51+
3752
## Feedback
3853

3954
Feedback is a gift and it helps me make this course better for you. If you have a spare 5 mins please could you fill out a feedback form for me. Thank you.
Lines changed: 72 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,77 @@
1-
// 👨🏻‍💻 2A Import IPokemon, IPokemonManagerActions, IPokemonManageState, withPokemon from './store';
2-
3-
/**
4-
* Exercise: Implement a Higher Order Component
5-
*
6-
* What will we be doing?
7-
* We will be creating a Higher order component which will pass pokemon data
8-
* Using the same pattern that Redux used to do with their connect function
9-
*
10-
* Navigate your way to withPokemon to start.
11-
*/
12-
13-
// 👨🏻‍💻 2B - Interfaces
14-
// 👨🏻‍💻 2B.a Setup an interface called IMapStateToPropsComponentOneResponse.
15-
// This will just have pokemons: IPokemon[]; in the interface.
16-
17-
// 👨🏻‍💻 2B.b Setup an interface called IActionsComponentOneResponse.
18-
// This will just have fetchPokemons: (total: number) => Promise<void>; in the interface.
19-
20-
// 👨🏻‍💻 2B.c Setup an interface called IComponentOneProps which extends IMapStateToPropsComponentOneResponse & IActionsComponentOneResponse.
21-
// This will just have title: string; in the interface.
22-
23-
// 👨🏻‍💻 2C - Setting up the mapStateToProps & mapActionsToProps
24-
// 👨🏻‍💻 2C.a Setup a function called mapStateToProps which has a parameter state: IPokemonManagerState
25-
// it should return the IMapStateToPropsComponentOneResponse interface
26-
27-
// 👨🏻‍💻 2C.b Setup a function called mapActionsToProps which has a parameter actions: IPokemonManagerActions
28-
// it should return the IActionsComponentOneResponse interface
29-
30-
// 👨🏻‍💻 2D - Creating the component
31-
// 👨🏻‍💻 2D.a Create a Component and it should have this params { pokemons, title, fetchPokemons }: IComponentProps
32-
// 👨🏻‍💻 2D.b Make a useEffect with no dependencies and fetchPokemons - go wild with the total... why not.
33-
// 👨🏻‍💻💄 2D.c Return this markup
34-
{
35-
/* <section>
36-
<h2 className="text-2xl font-bold mb-4">{title}</h2>
37-
<div className="grid grid-cols-6 gap-6">
38-
{pokemons.map((pokemon) => (
39-
<div key={pokemon.id}>
40-
<img
41-
src={pokemon.imageUrl}
42-
alt={pokemon.name}
43-
loading="lazy"
44-
/>
45-
</div>
46-
))}
47-
</div>
48-
</section> */
1+
// No imports needed for this exercise
2+
3+
interface IPokemon {
4+
id: number;
5+
name: string;
6+
type: string;
7+
level: number;
498
}
509

51-
// 👨🏻‍💻 2E - Applying the HOC
52-
// I want you to call:
53-
// const Exercise = withPokemons<
54-
// IMapStateToPropsComponentOneResponse, // We are defining a generic here
55-
// IActionsComponentOneResponse // We are defining a generic here
56-
// >(
57-
// mapStateToProps,
58-
// mapActionsToProps
59-
// )(Component);
10+
/*
11+
* Observations
12+
* 💅 Type-based styling logic is repeated inline
13+
* Hard to reuse styling across different components
14+
* Mixing presentation logic with component logic
15+
16+
* Tasks
17+
* 1A 💻 - Create withPokemonType HOC that adds type-based styling
18+
* 1B 💻 - Apply HOC to PokemonCard component
19+
* 1C 💻 - Use enhanced component to display Pokemon with type styling
20+
* 1D 💻 - Test that different Pokemon types get different styling
21+
*/
22+
23+
// Sample Pokemon data
24+
const samplePokemon: IPokemon[] = [
25+
{ id: 1, name: 'Pikachu', type: 'Electric', level: 25 },
26+
{ id: 4, name: 'Charmander', type: 'Fire', level: 12 },
27+
{ id: 7, name: 'Squirtle', type: 'Water', level: 18 }
28+
];
29+
30+
// Basic Pokemon Card Component
31+
const PokemonCard = ({ pokemon }: { pokemon: IPokemon }) => (
32+
<div className="p-4 bg-white rounded-lg border">
33+
<h3 className="text-xl font-bold">{pokemon.name}</h3>
34+
<p className="text-gray-600">Type: {pokemon.type}</p>
35+
<p className="text-gray-600">Level: {pokemon.level}</p>
36+
</div>
37+
);
38+
39+
// Component that shows Pokemon with inline type styling (needs refactoring)
40+
const PokemonShowcase = () => {
41+
// Inline type styling (should be extracted to HOC)
42+
const getTypeStyles = (type: string) => {
43+
const styles = {
44+
Electric: 'bg-yellow-100 border-yellow-300',
45+
Fire: 'bg-red-100 border-red-300',
46+
Water: 'bg-blue-100 border-blue-300'
47+
};
48+
return (
49+
styles[type as keyof typeof styles] ||
50+
'bg-gray-100 border-gray-300'
51+
);
52+
};
53+
54+
return (
55+
<div className="space-y-6">
56+
{samplePokemon.map((pokemon) => (
57+
<div
58+
key={pokemon.id}
59+
className={`p-4 border-2 rounded-lg ${getTypeStyles(pokemon.type)}`}
60+
>
61+
<PokemonCard pokemon={pokemon} />
62+
</div>
63+
))}
64+
</div>
65+
);
66+
};
6067

6168
export const Exercise = () => {
62-
return null;
69+
return (
70+
<div className="space-y-8 max-w-2xl">
71+
<h1 className="text-3xl font-bold">
72+
Pokemon Cards (Before HOC)
73+
</h1>
74+
<PokemonShowcase />
75+
</div>
76+
);
6377
};

src/course/02-lessons/03-Gold/HigherOrderComponents/final/final.tsx

Lines changed: 59 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,72 @@
1-
import { useEffect } from 'react';
2-
import { IPokemonManagerActions, withPokemons } from './withPokemon';
3-
import {
4-
IPokemon,
5-
IPokemonManagerState
6-
} from '@shared/modules/PokemonManager/PokemonManager';
1+
import { ComponentType } from 'react';
72

8-
interface IMapStateToPropsComponentOneResponse {
9-
pokemons: IPokemon[];
3+
interface IPokemon {
4+
id: number;
5+
name: string;
6+
type: string;
7+
level: number;
108
}
119

12-
interface IActionsComponentOneResponse {
13-
fetchPokemons: (total: number) => Promise<void>;
14-
}
10+
// Sample Pokemon data
11+
const samplePokemon: IPokemon[] = [
12+
{ id: 1, name: 'Pikachu', type: 'Electric', level: 25 },
13+
{ id: 4, name: 'Charmander', type: 'Fire', level: 12 },
14+
{ id: 7, name: 'Squirtle', type: 'Water', level: 18 }
15+
];
1516

16-
interface IComponentOneProps
17-
extends IMapStateToPropsComponentOneResponse,
18-
IActionsComponentOneResponse {
19-
title: string;
20-
}
17+
// HOC: Add Pokemon type-based styling
18+
const withPokemonType = <P extends object>(
19+
Component: ComponentType<P>
20+
) => {
21+
return (props: P & { type?: string }) => {
22+
const getTypeStyles = (type?: string) => {
23+
const styles = {
24+
Electric: 'bg-yellow-100 border-yellow-300',
25+
Fire: 'bg-red-100 border-red-300',
26+
Water: 'bg-blue-100 border-blue-300'
27+
};
28+
return (
29+
styles[type as keyof typeof styles] ||
30+
'bg-gray-100 border-gray-300'
31+
);
32+
};
2133

22-
const mapStateToProps = (
23-
state: IPokemonManagerState
24-
): IMapStateToPropsComponentOneResponse => ({
25-
pokemons: state.pokemons
26-
});
34+
return (
35+
<div
36+
className={`p-4 border-2 rounded-lg ${getTypeStyles(props.type)}`}
37+
>
38+
<Component {...props} />
39+
</div>
40+
);
41+
};
42+
};
2743

28-
const mapActionsToProps = (
29-
actions: IPokemonManagerActions
30-
): IActionsComponentOneResponse => ({
31-
fetchPokemons: actions.fetchPokemons
32-
});
44+
// Basic Pokemon Card Component
45+
const PokemonCard = ({ pokemon }: { pokemon: IPokemon }) => (
46+
<div className="p-4 bg-white rounded-lg border">
47+
<h3 className="text-xl font-bold">{pokemon.name}</h3>
48+
<p className="text-gray-600">Type: {pokemon.type}</p>
49+
<p className="text-gray-600">Level: {pokemon.level}</p>
50+
</div>
51+
);
3352

34-
const Component = ({
35-
pokemons,
36-
title,
37-
fetchPokemons
38-
}: IComponentOneProps) => {
39-
useEffect(() => {
40-
fetchPokemons(12);
41-
// eslint-disable-next-line react-hooks/exhaustive-deps
42-
}, []);
53+
// Enhanced component using HOC
54+
const StyledPokemonCard = withPokemonType(PokemonCard);
4355

56+
export const Final = () => {
4457
return (
45-
<section>
46-
<h2 className="text-2xl font-bold mb-4">{title}</h2>
47-
<div className="grid grid-cols-6 gap-6">
48-
{pokemons.map((pokemon) => (
49-
<div key={pokemon.id}>
50-
<img
51-
src={pokemon.imageUrl}
52-
alt={pokemon.name}
53-
loading="lazy"
54-
/>
55-
</div>
58+
<div className="space-y-8 max-w-2xl">
59+
<h1 className="text-3xl font-bold">Pokemon Cards with HOC</h1>
60+
61+
<div className="space-y-6">
62+
{samplePokemon.map((pokemon) => (
63+
<StyledPokemonCard
64+
key={pokemon.id}
65+
pokemon={pokemon}
66+
type={pokemon.type}
67+
/>
5668
))}
5769
</div>
58-
</section>
70+
</div>
5971
);
6072
};
61-
62-
export const Final = withPokemons<
63-
IMapStateToPropsComponentOneResponse,
64-
IActionsComponentOneResponse,
65-
{ title: string }
66-
>(
67-
mapStateToProps,
68-
mapActionsToProps
69-
)(Component);

0 commit comments

Comments
 (0)