Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { Final } from './exercise';

const meta: Meta<typeof Final> = {
title: 'Lessons/🥇 Gold/Error Boundaries/02-Exercise',
title: 'Lessons/🥇 Gold/🛡️ Error Boundaries Pattern/02-Exercise',
component: Final
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { Final } from './final';

const meta: Meta<typeof Final> = {
title: 'Lessons/🥇 Gold/Error Boundaries/03-Final',
title: 'Lessons/🥇 Gold/🛡️ Error Boundaries Pattern/03-Final',
component: Final
};

Expand Down
4 changes: 2 additions & 2 deletions src/course/02- lessons/03-Gold/ErrorBoundary/lesson.mdx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Meta } from '@storybook/blocks';

<Meta title="Lessons/🥇 Gold/Error Boundaries/01-Lesson" />
<Meta title="Lessons/🥇 Gold/🛡️ Error Boundaries Pattern/01-Lesson" />

# Error Boundaries Pattern
# 🛡️ Error Boundaries Pattern

React makes it easy to build interactive UIs, but what happens when something goes wrong during rendering? Without safeguards, an error in one part of the UI can crash the entire application.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ import type { Meta, StoryObj } from '@storybook/react';

import { userEvent, within, expect } from '@storybook/test';

import { ComponentOne } from './exercise';
import { PokemonTrainerStatus } from './exercise';

const meta: Meta<typeof ComponentOne> = {
const meta: Meta<typeof PokemonTrainerStatus> = {
title:
'Lessons/🥉 Bronze/Conditional Rendering Pattern/02-Exercise',
component: ComponentOne
'Lessons/🥉 Bronze/🔀 Conditional Rendering Pattern/02-Exercise',
component: PokemonTrainerStatus
};

export default meta;
type Story = StoryObj<typeof ComponentOne>;
type Story = StoryObj<typeof PokemonTrainerStatus>;

const username = 'John Doe';
const trainerName = 'Ash';

/*
* See https://storybook.js.org/docs/writing-stories/play-function#working-with-the-canvas
Expand All @@ -24,28 +24,28 @@ export const Default: Story = {
const canvas = within(canvasElement);

await userEvent.click(
canvas.getByRole('button', { name: 'Login' })
canvas.getByRole('button', { name: '🎯 Challenge Gym Leader' })
);

await expect(
canvas.getByText(`Welcome ${username}`)
canvas.getByText(`Welcome Gym Leader ${trainerName}! 🏆`)
).toBeInTheDocument();
await expect(
canvas.queryByRole('button', { name: 'Login' })
canvas.queryByRole('button', { name: '🎯 Challenge Gym Leader' })
).toBeNull();

await userEvent.click(
canvas.getByRole('button', { name: 'Logout' })
canvas.getByRole('button', { name: '🔄 Reset Journey' })
);

await expect(
canvas.queryByText(`Welcome ${username}`)
canvas.queryByText(`Welcome Gym Leader ${trainerName}! 🏆`)
).toBeNull();
await expect(
canvas.queryByRole('button', { name: 'Logout' })
canvas.queryByRole('button', { name: '🔄 Reset Journey' })
).toBeNull();
},
args: {
username
trainerName
}
};
};
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { Button } from '@shared/components/Button/Button.component';

interface IComponentProps {
username: string;
interface ITrainerProps {
trainerName: string;
}

// 1g - 💣 The ignore lint rules can be removed now
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const ComponentOne = (props: IComponentProps) => {
// 1a - 👨🏻💻 add a useState that has false as default. Name the variable [isAuthenticated, setIsAuthenticated]
export const PokemonTrainerStatus = (props: ITrainerProps) => {
// 1a - 👨🏻💻 add a useState that has false as default. Name the variable [hasGymBadges, setHasGymBadges]

// 1b - 👨🏻💻 create me a onLogin function which setIsAuthenticated to be true
// 1b - 👨🏻💻 create me a onEarnBadge function which setHasGymBadges to be true

// 1c - 👨🏻💻 create me a onLogout function which setIsAuthenticated to be false
// 1c - 👨🏻💻 create me a onLoseBadges function which setHasGymBadges to be false

// 1d - 👨🏻💻 if authenticated, return a button called "Logout" with the onClick of onLogout
// 1e - 👨🏻💻 if authenticated, return some text called "Welcome {props.username}"
// 1d - 👨🏻💻 if hasGymBadges, return a button called "Reset Journey" with the onClick of onLoseBadges
// 1e - 👨🏻💻 if hasGymBadges, return some text called "Welcome Gym Leader {props.trainerName}! 🏆"

// 1f - 👨🏻💻 add onClick function onLogin to the button
return <Button>Login</Button>;
};
// 1f - 👨🏻💻 add onClick function onEarnBadge to the button
return <Button>🎯 Challenge Gym Leader</Button>;
};
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react';

import { userEvent, within, expect } from '@storybook/test';
import { ComponentOne } from './final';

const meta: Meta<typeof ComponentOne> = {
title: 'Lessons/🥉 Bronze/Conditional Rendering Pattern/03-Final',
component: ComponentOne
import { PokemonTrainerStatus } from './final';

const meta: Meta<typeof PokemonTrainerStatus> = {
title:
'Lessons/🥉 Bronze/🔀 Conditional Rendering Pattern/03-Final',
component: PokemonTrainerStatus
};

export default meta;
type Story = StoryObj<typeof ComponentOne>;
type Story = StoryObj<typeof PokemonTrainerStatus>;

const username = 'John Doe';
const trainerName = 'Ash';

/*
* See https://storybook.js.org/docs/writing-stories/play-function#working-with-the-canvas
Expand All @@ -22,28 +24,22 @@ export const Default: Story = {
const canvas = within(canvasElement);

await userEvent.click(
canvas.getByRole('button', { name: 'Login' })
canvas.getByRole('button', { name: '🎯 Challenge Gym Leader' })
);

await expect(
canvas.getByText(`Welcome ${username}`)
canvas.getByText(`Welcome Gym Leader ${trainerName}! 🏆`)
).toBeInTheDocument();
await expect(
canvas.queryByRole('button', { name: 'Login' })
).toBeNull();

await userEvent.click(
canvas.getByRole('button', { name: 'Logout' })
canvas.getByRole('button', { name: '🔄 Reset Journey' })
);

await expect(
canvas.queryByText(`Welcome ${username}`)
).toBeNull();
await expect(
canvas.queryByRole('button', { name: 'Logout' })
).toBeNull();
canvas.getByRole('button', { name: '🎯 Challenge Gym Leader' })
).toBeInTheDocument();
},
args: {
username
trainerName
}
};
};
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
import { useState } from 'react';
import { Button } from '@shared/components/Button/Button.component';

interface IComponentProps {
username: string;
interface ITrainerProps {
trainerName: string;
}

export const ComponentOne = (props: IComponentProps) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
export const PokemonTrainerStatus = (props: ITrainerProps) => {
const [hasGymBadges, setHasGymBadges] = useState(false);

const onLogin = () => {
setIsAuthenticated(true);
const onEarnBadge = () => {
setHasGymBadges(true);
};

const onLogout = () => {
setIsAuthenticated(false);
const onLoseBadges = () => {
setHasGymBadges(false);
};

return (
<header>
{/* Other components */}
{!isAuthenticated && <Button onClick={onLogin}>Login</Button>}
{isAuthenticated && (
<>
<Button onClick={onLogout}>Logout</Button>
<h1>Welcome {props.username}</h1>
</>
<div className="p-6 bg-blue-50 rounded-lg border-2 border-blue-200">
{!hasGymBadges && (
<div className="text-center">
<h2 className="text-xl mb-4">🎒 Pokemon Trainer</h2>
<p className="mb-4">Ready to challenge the Gym Leader?</p>
<Button onClick={onEarnBadge}>🎯 Challenge Gym Leader</Button>
</div>
)}
</header>
{hasGymBadges && (
<div className="text-center">
<h1 className="text-2xl font-bold text-yellow-600 mb-2">
Welcome Gym Leader {props.trainerName}! 🏆
</h1>
<p className="mb-4">🥇 You've earned your gym badges!</p>
<Button onClick={onLoseBadges}>🔄 Reset Journey</Button>
</div>
)}
</div>
);
};
};
62 changes: 31 additions & 31 deletions src/course/02-lessons/01-Bronze/ConditionalRendering/lesson.mdx
Original file line number Diff line number Diff line change
@@ -1,82 +1,82 @@
import { Meta } from '@storybook/blocks';

<Meta title="Lessons/🥉 Bronze/Conditional Rendering Pattern/01-Lesson" />
<Meta title="Lessons/🥉 Bronze/🔀 Conditional Rendering Pattern/01-Lesson" />

# Conditional Rendering Pattern
# 🔀 Conditional Rendering Pattern

The conditional rendering pattern is a way to dynamically change UI based what values are set at that time. The most common way that this is done is by using an if statement. For example:
The conditional rendering pattern is a way to dynamically change UI based on what values are set at that time. The most common way that this is done is by using an if statement. For example:

```jsx
const Component = (props) => {
if (props.isAuthenticated) {
return <h1>Welcome {props.username}!</h1>;
const TrainerStatus = (props) => {
if (props.hasGymBadges) {
return <h1>Welcome Gym Leader {props.trainerName}!</h1>;
} else {
return <h1>Not logged in.</h1>;
return <h1>Welcome Pokemon Trainer!</h1>;
}
};
```

There are many syntactical ways you can do the same as above such as the ternary:

```jsx
const Component = (props) => {
return props.isAuthenticated ? (
<h1>Welcome {props.username}!</h1>
const TrainerStatus = (props) => {
return props.hasGymBadges ? (
<h1>Welcome Gym Leader {props.trainerName}!</h1>
) : (
<h1>Not logged in.</h1>
<h1>Welcome Pokemon Trainer!</h1>
);
};
```

Or you can use the AND syntax:

```jsx
const Component = (props) => {
return props.isAuthenticated && <h1>Welcome {props.username}!</h1>;
const TrainerStatus = (props) => {
return props.hasGymBadges && <h1>Welcome Gym Leader {props.trainerName}!</h1>;
};
```

If I had a lot of complexity in this component that still would be getting executed but the component will just return nothing if it's not authenticated. The best way to return something like this is to do the conditional render outside of the component, for example:
If I had a lot of complexity in this component that still would be getting executed but the component will just return nothing if the trainer doesn't have badges. The best way to return something like this is to do the conditional render outside of the component, for example:

```jsx
const ComponentOne = (props) => {
return <h1>Welcome {props.username}</h1>;
const GymLeaderWelcome = (props) => {
return <h1>Welcome Gym Leader {props.trainerName}</h1>;
};

const ComponentTwo = (props) => {
const TrainerDashboard = (props) => {
return (
<header>
{/* Other components */}
{props.isAuthenticated && <ComponentOne username="JohnDoe" />}
{props.hasGymBadges && <GymLeaderWelcome trainerName="Ash" />}
</header>
);
};
```

### Event driven rendering

There may be times when you need to conditionally render a component based on an event that has been changed. This example conditionally renders a box when you click the button.
There may be times when you need to conditionally render a component based on an event that has been changed. This example conditionally renders a wild Pokemon encounter when you click the explore button.

```jsx
const Component = () => {
const [displayBox, setDisplayBox] = useState(false);
const PokemonExplorer = () => {
const [wildPokemonFound, setWildPokemonFound] = useState(false);

const showBox = () => {
setDisplayBox(true);
const startExploring = () => {
setWildPokemonFound(true);
};

const hideBox = () => {
setDisplayBox(false);
const stopExploring = () => {
setWildPokemonFound(false);
};

return (
<>
<button type="button" onClick={displayBox ? hideBox : showBox}>
Toggle Box
<button type="button" onClick={wildPokemonFound ? stopExploring : startExploring}>
{wildPokemonFound ? 'Stop Exploring' : 'Explore Tall Grass'}
</button>
{displayBox && (
<div>
<p>Box</p>
{wildPokemonFound && (
<div className="wild-encounter">
<p>🌿 A wild Pikachu appeared!</p>
</div>
)}
</>
Expand All @@ -86,7 +86,7 @@ const Component = () => {

## Exercise

In the first exercise we are going to look into building a login and logout toggle which will render a username when they have logged in. 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.
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.

## Feedback

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { Exercise } from './exercise';

const meta: Meta<typeof Exercise> = {
title: 'Lessons/🥉 Bronze/Hooks Pattern/02-Exercise',
title: 'Lessons/🥉 Bronze/🎣 Hooks Pattern/02-Exercise',
component: Exercise
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { Final } from './final';

const meta: Meta<typeof Final> = {
title: 'Lessons/🥉 Bronze/Hooks Pattern/03-Final',
title: 'Lessons/🥉 Bronze/🎣 Hooks Pattern/03-Final',
component: Final
};

Expand Down
Loading
Loading