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
@@ -0,0 +1,37 @@
import React from 'react';

// 🧑🏻‍💻 1.A Setup the following types
// Props with fallback & children values both are React.ReactNode | React.ReactNode[]
// State with hasError: boolean
type Props = {
fallback: React.ReactNode;
children: React.ReactNode;
};

type State = {
hasError: boolean;
};

// 🧑🏻‍💻 1.B Create a class component called ErrorBoundary which extends the React.Component interface. The params the interface will take are <Props, State>
// Ew why? It's because functional components do not have all the life cycle methods you need where as a class does.
// https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary

export class ErrorBoundary extends React.Component<Props, State> {
state: State = { hasError: false };

static getDerivedStateFromError() {
return { hasError: true };
}

componentDidCatch(error: unknown, info: unknown) {
console.error('Error caught by boundary:', error, info);
}

render() {
if (this.state.hasError) {
return this.props.fallback;
}

return this.props.children;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { PokemonBackground } from '@shared/components/PokemonBackground/PokemonBackground';

export const Fallback = () => {
return (
<PokemonBackground bodyClassName="max-w-[768px] mx-auto text-center flex flex-col justify-center h-screen">
<div>
<h1>
<img
src="/pokemon-logo.png"
alt="Pokemon Battle Picker"
className="inline-block w-96"
/>
<span className="text-[76px] font-bold mb-4 block text-yellow-400">
Opps, look like this one got away from us!
</span>
</h1>
<p className="text-lg font-bold mb-4 block text-yellow-400 mb-8">
We will make sure to try and catch it next time.
</p>
<a
href="/?path=/story/lessons-🥇-gold-error-boundaries-03-final--default"
className="my-12 rounded-lg py-6 px-16 text-white text-2xl font-bold bg-blue-900 hover:bg-blue-950 focus-within:bg-blue-950 no-underline"
>
Please try again
</a>
</div>
</PokemonBackground>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Meta, StoryObj } from '@storybook/react';

import { Final } from './exercise';

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

export default meta;
type Story = StoryObj<typeof Final>;

/*
* See https://storybook.js.org/docs/writing-stories/play-function#working-with-the-canvas
* to learn more about using the canvasElement to query the DOM
*/
export const Default: Story = {
play: async () => {},
args: {}
};
83 changes: 83 additions & 0 deletions src/course/02- lessons/03-Gold/ErrorBoundary/exercise/exercise.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { PokemonBackground } from '@shared/components/PokemonBackground/PokemonBackground';
import { Skeleton } from '@shared/components/Skeleton/Skeleton.component';
import {
TPokemonCardsApiResponse,
usePokedex
} from '@shared/hooks/usePokedex';

/**
* Exercise: Add an error boundary
*
* 🤔 Observations of this file
* So it's clear that line 92 is the error. A developer has added a ts-comment to ignore the problem but what we want to do first is make an error boundary so we know that we have caught the error incase this happens again.
*
* You may notice there already is a components/Feedback.tsx made and that was made purely on the basis that it's not the focus of the course to style things. We will be using that later.
*
* We need to tackle this two stages:
*
* Stage one - Create the error boundary in components/ErrorBoundary.tsx
* Stage two - Apply the ErrorBoundary.
*
*/

export const Final = () => {
const { data, isLoading, isError } = usePokedex<
TPokemonCardsApiResponse[]
>({
path: 'cards',
queryParams: 'pageSize=24&q=types:fire&supertype:pokemon'
});

if (isError) {
return (
<PokemonBackground>
<strong className="font-bold">Holy smokes!</strong> <br />
<span className="block sm:inline">
It looks like Team Rocket has captured the fire pokemon!
</span>
</PokemonBackground>
);
}

if (isLoading) {
return (
<PokemonBackground>
<Skeleton height="h-12" width="w-96" />
<div className="grid grid-cols-6 gap-6">
{[...new Array(12)].map((_, index) => (
<Skeleton key={index} height="h-[207px]" />
))}
</div>
</PokemonBackground>
);
}

return (
<PokemonBackground>
<h2 className="text-2xl font-bold mb-4 text-yellow-400">
Fire Pokemon
</h2>
<div className="grid grid-cols-6 gap-6">
{data &&
data.length > 0 &&
data.map((pokemon) => (
<div key={pokemon.id}>
<img
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
src={pokemon.imagessss.small}
alt={pokemon.name}
loading="lazy"
/>
</div>
))}
</div>
</PokemonBackground>
);
};

// export const Final = () => (
// <ErrorBoundary fallback={<Fallback />}>
// <Screen />
// </ErrorBoundary>
// );
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';

type Props = {
fallback: React.ReactNode;
children: React.ReactNode;
};

type State = {
hasError: boolean;
};

export class ErrorBoundary extends React.Component<Props, State> {
state: State = { hasError: false };

static getDerivedStateFromError() {
return { hasError: true };
}

componentDidCatch(error: unknown, info: unknown) {
console.error('Error caught by boundary:', error, info);
}

render() {
if (this.state.hasError) {
return this.props.fallback;
}

return this.props.children;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { PokemonBackground } from '@shared/components/PokemonBackground/PokemonBackground';

export const Fallback = () => {
return (
<PokemonBackground bodyClassName="max-w-[768px] mx-auto text-center flex flex-col justify-center h-screen">
<div>
<h1>
<img
src="/pokemon-logo.png"
alt="Pokemon Battle Picker"
className="inline-block w-96"
/>
<span className="text-[76px] font-bold mb-4 block text-yellow-400">
Opps, look like this one got away from us!
</span>
</h1>
<p className="text-lg font-bold mb-4 block text-yellow-400 mb-8">
We will make sure to try and catch it next time.
</p>
<a
href="/?path=/story/lessons-🥇-gold-error-boundaries-03-final--default"
className="my-12 rounded-lg py-6 px-16 text-white text-2xl font-bold bg-blue-900 hover:bg-blue-950 focus-within:bg-blue-950 no-underline"
>
Please try again
</a>
</div>
</PokemonBackground>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Meta, StoryObj } from '@storybook/react';

import { Final } from './final';

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

export default meta;
type Story = StoryObj<typeof Final>;

/*
* See https://storybook.js.org/docs/writing-stories/play-function#working-with-the-canvas
* to learn more about using the canvasElement to query the DOM
*/
export const Default: Story = {
play: async () => {},
args: {}
};
70 changes: 70 additions & 0 deletions src/course/02- lessons/03-Gold/ErrorBoundary/final/final.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Skeleton } from '@shared/components/Skeleton/Skeleton.component';
import {
TPokemonCardsApiResponse,
usePokedex
} from '@shared/hooks/usePokedex';
import { ErrorBoundary } from './components/ErrorBoundary';
import { Fallback } from './components/Fallback';
import { PokemonBackground } from '@shared/components/PokemonBackground/PokemonBackground';

const Screen = () => {
const { data, isLoading, isError } = usePokedex<
TPokemonCardsApiResponse[]
>({
path: 'cards',
queryParams: 'pageSize=24&q=types:fire&supertype:pokemon'
});

if (isError) {
return (
<PokemonBackground>
<strong className="font-bold">Holy smokes!</strong> <br />
<span className="block sm:inline">
It looks like Team Rocket has captured the fire pokemon!
</span>
</PokemonBackground>
);
}

if (isLoading) {
return (
<PokemonBackground>
<Skeleton height="h-12" width="w-96" />
<div className="grid grid-cols-6 gap-6">
{[...new Array(12)].map((_, index) => (
<Skeleton key={index} height="h-[207px]" />
))}
</div>
</PokemonBackground>
);
}

return (
<PokemonBackground>
<h2 className="text-2xl font-bold mb-4 text-yellow-400">
Fire Pokemon
</h2>
<div className="grid grid-cols-6 gap-6">
{data &&
data.length > 0 &&
data.map((pokemon) => (
<div key={pokemon.id}>
<img
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
src={pokemon.imagessss.small}
alt={pokemon.name}
loading="lazy"
/>
</div>
))}
</div>
</PokemonBackground>
);
};

export const Final = () => (
<ErrorBoundary fallback={<Fallback />}>
<Screen />
</ErrorBoundary>
);
Loading
Loading