Skip to content

Commit 1968558

Browse files
feat: error boundary issue (#42)
1 parent e106742 commit 1968558

File tree

11 files changed

+460
-1
lines changed

11 files changed

+460
-1
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React from 'react';
2+
3+
// 🧑🏻‍💻 1.A Setup the following types
4+
// Props with fallback & children values both are React.ReactNode | React.ReactNode[]
5+
// State with hasError: boolean
6+
type Props = {
7+
fallback: React.ReactNode;
8+
children: React.ReactNode;
9+
};
10+
11+
type State = {
12+
hasError: boolean;
13+
};
14+
15+
// 🧑🏻‍💻 1.B Create a class component called ErrorBoundary which extends the React.Component interface. The params the interface will take are <Props, State>
16+
// Ew why? It's because functional components do not have all the life cycle methods you need where as a class does.
17+
// https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary
18+
19+
export class ErrorBoundary extends React.Component<Props, State> {
20+
state: State = { hasError: false };
21+
22+
static getDerivedStateFromError() {
23+
return { hasError: true };
24+
}
25+
26+
componentDidCatch(error: unknown, info: unknown) {
27+
console.error('Error caught by boundary:', error, info);
28+
}
29+
30+
render() {
31+
if (this.state.hasError) {
32+
return this.props.fallback;
33+
}
34+
35+
return this.props.children;
36+
}
37+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { PokemonBackground } from '@shared/components/PokemonBackground/PokemonBackground';
2+
3+
export const Fallback = () => {
4+
return (
5+
<PokemonBackground bodyClassName="max-w-[768px] mx-auto text-center flex flex-col justify-center h-screen">
6+
<div>
7+
<h1>
8+
<img
9+
src="/pokemon-logo.png"
10+
alt="Pokemon Battle Picker"
11+
className="inline-block w-96"
12+
/>
13+
<span className="text-[76px] font-bold mb-4 block text-yellow-400">
14+
Opps, look like this one got away from us!
15+
</span>
16+
</h1>
17+
<p className="text-lg font-bold mb-4 block text-yellow-400 mb-8">
18+
We will make sure to try and catch it next time.
19+
</p>
20+
<a
21+
href="/?path=/story/lessons-🥇-gold-error-boundaries-03-final--default"
22+
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"
23+
>
24+
Please try again
25+
</a>
26+
</div>
27+
</PokemonBackground>
28+
);
29+
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
3+
import { Final } from './exercise';
4+
5+
const meta: Meta<typeof Final> = {
6+
title: 'Lessons/🥇 Gold/Error Boundaries/02-Exercise',
7+
component: Final
8+
};
9+
10+
export default meta;
11+
type Story = StoryObj<typeof Final>;
12+
13+
/*
14+
* See https://storybook.js.org/docs/writing-stories/play-function#working-with-the-canvas
15+
* to learn more about using the canvasElement to query the DOM
16+
*/
17+
export const Default: Story = {
18+
play: async () => {},
19+
args: {}
20+
};
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { PokemonBackground } from '@shared/components/PokemonBackground/PokemonBackground';
2+
import { Skeleton } from '@shared/components/Skeleton/Skeleton.component';
3+
import {
4+
TPokemonCardsApiResponse,
5+
usePokedex
6+
} from '@shared/hooks/usePokedex';
7+
8+
/**
9+
* Exercise: Add an error boundary
10+
*
11+
* 🤔 Observations of this file
12+
* 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.
13+
*
14+
* 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.
15+
*
16+
* We need to tackle this two stages:
17+
*
18+
* Stage one - Create the error boundary in components/ErrorBoundary.tsx
19+
* Stage two - Apply the ErrorBoundary.
20+
*
21+
*/
22+
23+
export const Final = () => {
24+
const { data, isLoading, isError } = usePokedex<
25+
TPokemonCardsApiResponse[]
26+
>({
27+
path: 'cards',
28+
queryParams: 'pageSize=24&q=types:fire&supertype:pokemon'
29+
});
30+
31+
if (isError) {
32+
return (
33+
<PokemonBackground>
34+
<strong className="font-bold">Holy smokes!</strong> <br />
35+
<span className="block sm:inline">
36+
It looks like Team Rocket has captured the fire pokemon!
37+
</span>
38+
</PokemonBackground>
39+
);
40+
}
41+
42+
if (isLoading) {
43+
return (
44+
<PokemonBackground>
45+
<Skeleton height="h-12" width="w-96" />
46+
<div className="grid grid-cols-6 gap-6">
47+
{[...new Array(12)].map((_, index) => (
48+
<Skeleton key={index} height="h-[207px]" />
49+
))}
50+
</div>
51+
</PokemonBackground>
52+
);
53+
}
54+
55+
return (
56+
<PokemonBackground>
57+
<h2 className="text-2xl font-bold mb-4 text-yellow-400">
58+
Fire Pokemon
59+
</h2>
60+
<div className="grid grid-cols-6 gap-6">
61+
{data &&
62+
data.length > 0 &&
63+
data.map((pokemon) => (
64+
<div key={pokemon.id}>
65+
<img
66+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
67+
// @ts-expect-error
68+
src={pokemon.imagessss.small}
69+
alt={pokemon.name}
70+
loading="lazy"
71+
/>
72+
</div>
73+
))}
74+
</div>
75+
</PokemonBackground>
76+
);
77+
};
78+
79+
// export const Final = () => (
80+
// <ErrorBoundary fallback={<Fallback />}>
81+
// <Screen />
82+
// </ErrorBoundary>
83+
// );
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React from 'react';
2+
3+
type Props = {
4+
fallback: React.ReactNode;
5+
children: React.ReactNode;
6+
};
7+
8+
type State = {
9+
hasError: boolean;
10+
};
11+
12+
export class ErrorBoundary extends React.Component<Props, State> {
13+
state: State = { hasError: false };
14+
15+
static getDerivedStateFromError() {
16+
return { hasError: true };
17+
}
18+
19+
componentDidCatch(error: unknown, info: unknown) {
20+
console.error('Error caught by boundary:', error, info);
21+
}
22+
23+
render() {
24+
if (this.state.hasError) {
25+
return this.props.fallback;
26+
}
27+
28+
return this.props.children;
29+
}
30+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { PokemonBackground } from '@shared/components/PokemonBackground/PokemonBackground';
2+
3+
export const Fallback = () => {
4+
return (
5+
<PokemonBackground bodyClassName="max-w-[768px] mx-auto text-center flex flex-col justify-center h-screen">
6+
<div>
7+
<h1>
8+
<img
9+
src="/pokemon-logo.png"
10+
alt="Pokemon Battle Picker"
11+
className="inline-block w-96"
12+
/>
13+
<span className="text-[76px] font-bold mb-4 block text-yellow-400">
14+
Opps, look like this one got away from us!
15+
</span>
16+
</h1>
17+
<p className="text-lg font-bold mb-4 block text-yellow-400 mb-8">
18+
We will make sure to try and catch it next time.
19+
</p>
20+
<a
21+
href="/?path=/story/lessons-🥇-gold-error-boundaries-03-final--default"
22+
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"
23+
>
24+
Please try again
25+
</a>
26+
</div>
27+
</PokemonBackground>
28+
);
29+
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
3+
import { Final } from './final';
4+
5+
const meta: Meta<typeof Final> = {
6+
title: 'Lessons/🥇 Gold/Error Boundaries/03-Final',
7+
component: Final
8+
};
9+
10+
export default meta;
11+
type Story = StoryObj<typeof Final>;
12+
13+
/*
14+
* See https://storybook.js.org/docs/writing-stories/play-function#working-with-the-canvas
15+
* to learn more about using the canvasElement to query the DOM
16+
*/
17+
export const Default: Story = {
18+
play: async () => {},
19+
args: {}
20+
};
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Skeleton } from '@shared/components/Skeleton/Skeleton.component';
2+
import {
3+
TPokemonCardsApiResponse,
4+
usePokedex
5+
} from '@shared/hooks/usePokedex';
6+
import { ErrorBoundary } from './components/ErrorBoundary';
7+
import { Fallback } from './components/Fallback';
8+
import { PokemonBackground } from '@shared/components/PokemonBackground/PokemonBackground';
9+
10+
const Screen = () => {
11+
const { data, isLoading, isError } = usePokedex<
12+
TPokemonCardsApiResponse[]
13+
>({
14+
path: 'cards',
15+
queryParams: 'pageSize=24&q=types:fire&supertype:pokemon'
16+
});
17+
18+
if (isError) {
19+
return (
20+
<PokemonBackground>
21+
<strong className="font-bold">Holy smokes!</strong> <br />
22+
<span className="block sm:inline">
23+
It looks like Team Rocket has captured the fire pokemon!
24+
</span>
25+
</PokemonBackground>
26+
);
27+
}
28+
29+
if (isLoading) {
30+
return (
31+
<PokemonBackground>
32+
<Skeleton height="h-12" width="w-96" />
33+
<div className="grid grid-cols-6 gap-6">
34+
{[...new Array(12)].map((_, index) => (
35+
<Skeleton key={index} height="h-[207px]" />
36+
))}
37+
</div>
38+
</PokemonBackground>
39+
);
40+
}
41+
42+
return (
43+
<PokemonBackground>
44+
<h2 className="text-2xl font-bold mb-4 text-yellow-400">
45+
Fire Pokemon
46+
</h2>
47+
<div className="grid grid-cols-6 gap-6">
48+
{data &&
49+
data.length > 0 &&
50+
data.map((pokemon) => (
51+
<div key={pokemon.id}>
52+
<img
53+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
54+
// @ts-expect-error
55+
src={pokemon.imagessss.small}
56+
alt={pokemon.name}
57+
loading="lazy"
58+
/>
59+
</div>
60+
))}
61+
</div>
62+
</PokemonBackground>
63+
);
64+
};
65+
66+
export const Final = () => (
67+
<ErrorBoundary fallback={<Fallback />}>
68+
<Screen />
69+
</ErrorBoundary>
70+
);

0 commit comments

Comments
 (0)