Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
aecc8b5
setup part 3
AlemTuzlak Sep 25, 2025
e903432
Merge branch 'main' into part_3
AlemTuzlak Sep 25, 2025
93ef3dc
setup part 3
AlemTuzlak Sep 25, 2025
bebe50c
improve exercise outcome
Sep 26, 2025
8f617e3
filtering & sorting
AlemTuzlak Oct 2, 2025
e872b49
change back to rust-free client
AlemTuzlak Oct 2, 2025
8c30880
additional improvements
AlemTuzlak Oct 3, 2025
c1f5d85
additional improvements
AlemTuzlak Oct 3, 2025
614705a
infinite loading
AlemTuzlak Oct 4, 2025
77d03d8
make search and categories work
AlemTuzlak Oct 4, 2025
049ec4c
add first problem
AlemTuzlak Oct 4, 2025
3345637
add 01 solution
AlemTuzlak Oct 4, 2025
2d89971
exercise 1 problem instructions!
AlemTuzlak Oct 4, 2025
085319c
exercise 1 solution
AlemTuzlak Oct 4, 2025
ef46c32
create problem 2
AlemTuzlak Oct 27, 2025
39c2473
search with url
AlemTuzlak Oct 27, 2025
88e216f
add instructions for exercise 2
AlemTuzlak Oct 27, 2025
7a9a56a
fix exercise 2 diffs
AlemTuzlak Oct 27, 2025
2d48a07
fix up diffs
AlemTuzlak Oct 27, 2025
8bf0a6b
add exercise 3 solution
AlemTuzlak Oct 27, 2025
1be071b
extract filtering
AlemTuzlak Oct 27, 2025
b60a9b5
wrap up exercise 3
AlemTuzlak Oct 27, 2025
127376b
added problem 4
AlemTuzlak Oct 27, 2025
0a44887
add in exercise 4
AlemTuzlak Oct 27, 2025
c953421
exercise 4 done
AlemTuzlak Oct 27, 2025
17af983
add problem for exercise 5
AlemTuzlak Oct 29, 2025
2737f67
add problem formulation
AlemTuzlak Oct 29, 2025
393d38e
remove ex 5
AlemTuzlak Oct 29, 2025
53d3e02
fix
AlemTuzlak Oct 29, 2025
67fb062
Merge branch 'main' into part_3
AlemTuzlak Oct 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .codex/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
full-auto = true
bypass-approvals = true
bypass-sandbox = true
trusted-workspace = true
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ data.db
# file as well, but since this is for a workshop
# we're going to keep them around.
# .env
test-results
test-results
**/generated/**
data.db*
.env
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { products } from 'data/products'
import { ArrowRight, Star } from 'lucide-react'
import { Link } from 'react-router'
import { products } from '../../../data/products'

export const FeaturedProductsSection = () => {
const featuredProducts = products.slice(0, 4)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Outlet } from 'react-router'
import { Footer } from '~/components/footer'
import { Header } from '~/components/header'
import { Footer } from '#app/components/footer'
import { Header } from '#app/components/header'

export default function LayoutPage() {
return (
Expand Down
16 changes: 3 additions & 13 deletions exercises/02.metadata/01.problem.styling/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,13 @@
"**/.client/**/*",
".react-router/types/**/*"
],
"extends": ["@epic-web/config/typescript"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["node", "vite/client"],
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"rootDirs": [".", "./.react-router/types"],
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
"#app/*": ["./app/*"]
},
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true
"noUncheckedIndexedAccess": false
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { products } from 'data/products'
import { ArrowRight, Star } from 'lucide-react'
import { Link } from 'react-router'
import { products } from '../../../data/products'

export const FeaturedProductsSection = () => {
const featuredProducts = products.slice(0, 4)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Outlet } from 'react-router'
import { Footer } from '~/components/footer'
import { Header } from '~/components/header'
import { Footer } from '#app/components/footer'
import { Header } from '#app/components/header'

export default function LayoutPage() {
return (
Expand Down
16 changes: 3 additions & 13 deletions exercises/02.metadata/01.solution.styling/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,13 @@
"**/.client/**/*",
".react-router/types/**/*"
],
"extends": ["@epic-web/config/typescript"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["node", "vite/client"],
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"rootDirs": [".", "./.react-router/types"],
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
"#app/*": ["./app/*"]
},
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true
"noUncheckedIndexedAccess": false
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { products } from 'data/products'
import { ArrowRight, Star } from 'lucide-react'
import { Link } from 'react-router'
import { products } from '../../../data/products'

export const FeaturedProductsSection = () => {
const featuredProducts = products.slice(0, 4)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Outlet } from 'react-router'
import { Footer } from '~/components/footer'
import { Header } from '~/components/header'
import { Footer } from '#app/components/footer'
import { Header } from '#app/components/header'

export default function LayoutPage() {
return (
Expand Down
16 changes: 3 additions & 13 deletions exercises/02.metadata/02.problem.titles-react/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,13 @@
"**/.client/**/*",
".react-router/types/**/*"
],
"extends": ["@epic-web/config/typescript"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["node", "vite/client"],
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"rootDirs": [".", "./.react-router/types"],
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
"#app/*": ["./app/*"]
},
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true
"noUncheckedIndexedAccess": false
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { products } from 'data/products'
import { ArrowRight, Star } from 'lucide-react'
import { Link } from 'react-router'
import { products } from '../../../data/products'

export const FeaturedProductsSection = () => {
const featuredProducts = products.slice(0, 4)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Outlet } from 'react-router'
import { Footer } from '~/components/footer'
import { Header } from '~/components/header'
import { Footer } from '#app/components/footer'
import { Header } from '#app/components/header'

export default function LayoutPage() {
return (
Expand Down
16 changes: 3 additions & 13 deletions exercises/02.metadata/02.solution.titles-react/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,13 @@
"**/.client/**/*",
".react-router/types/**/*"
],
"extends": ["@epic-web/config/typescript"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["node", "vite/client"],
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"rootDirs": [".", "./.react-router/types"],
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
"#app/*": ["./app/*"]
},
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true
"noUncheckedIndexedAccess": false
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { products } from 'data/products'
import { ArrowRight, Star } from 'lucide-react'
import { Link } from 'react-router'
import { products } from '../../../data/products'

export const FeaturedProductsSection = () => {
const featuredProducts = products.slice(0, 4)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Outlet } from 'react-router'
import { Footer } from '~/components/footer'
import { Header } from '~/components/header'
import { Footer } from '#app/components/footer'
import { Header } from '#app/components/header'

export default function LayoutPage() {
return (
Expand Down
16 changes: 3 additions & 13 deletions exercises/02.metadata/03.problem.titles-with-meta/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,13 @@
"**/.client/**/*",
".react-router/types/**/*"
],
"extends": ["@epic-web/config/typescript"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["node", "vite/client"],
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"rootDirs": [".", "./.react-router/types"],
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
"#app/*": ["./app/*"]
},
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true
"noUncheckedIndexedAccess": false
}
}
5 changes: 5 additions & 0 deletions exercises/02.metadata/03.solution.titles-with-meta/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,9 @@ which is a nice safety net to have.
Now that you fully understand the `meta` export and how to use it effectively, let's
move onto the next module when you're ready!

🧝‍♀️ I'm going to help you out for the next exercise, from what I understand it's all about
data fetching and loaders, so I'm going to set up a database for you to use in the next exercise
and also add some utilities to help you out with fetching data from the database! I hope
this helps you out!

You're doing great! 🚀
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { products } from 'data/products'
import { ArrowRight, Star } from 'lucide-react'
import { Link } from 'react-router'
import { products } from '../../../data/products'

export const FeaturedProductsSection = () => {
const featuredProducts = products.slice(0, 4)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Outlet } from 'react-router'
import { Footer } from '~/components/footer'
import { Header } from '~/components/header'
import { Footer } from '#app/components/footer'
import { Header } from '#app/components/header'

export default function LayoutPage() {
return (
Expand Down
16 changes: 3 additions & 13 deletions exercises/02.metadata/03.solution.titles-with-meta/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,13 @@
"**/.client/**/*",
".react-router/types/**/*"
],
"extends": ["@epic-web/config/typescript"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["node", "vite/client"],
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"rootDirs": [".", "./.react-router/types"],
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
"#app/*": ["./app/*"]
},
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true
"noUncheckedIndexedAccess": false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DATABASE_URL="file:./prisma/data.db"
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Data fetching with loaders

Amazing work so far! Now is the time for us to dive deep into data fetching using
loaders in React Router. Loaders are a powerful feature that allows us to fetch
data before rendering a route, ensuring that our components have the data they need
right from the start.

## Loaders Overview

Loaders are functions that run on the server (or during the build process in SPA mode)
before a route is rendered. (Keep this in mind if you want navigations to be instant)
They can fetch data from APIs, databases, or any other source, and the data they return
is made available to the route's component via the `useLoaderData` hook or the
`loaderData` prop on the route component. First is used in components far from the route
definition, second is used in the route definition itself, as a rule-of-thumb, prefer the
second one.

Loaders return serialized data using `turbo-stream` which means you can return complex
data structures, including nested objects and arrays, without worrying about serialization
issues. (Think of promises and how you could optimize your loaders by returning slow fetches)

Loaders can also redirect the user to different routes based on certain conditions,
such as authentication status or data availability. This allows us to handle routing
logic in a centralized manner, improving the overall structure of our application.

If a loader throws an error, React Router will catch it and render the nearest
error boundary, otherwise if a response is thrown it will either redirect if it's a
redirect response (status 302) or also go to the nearest ErrorBoundary.

Because they run on a server you can cache their results using HTTP caching headers,
which can significantly improve the performance of your application by reducing
the number of requests made to your backend services, or improving the response of
the loaders themselves, this is useful for data that doesn't change often.

## Exercise Goals


In this exercise, we will set up loaders for our routes to fetch product data from
a database.

🧝‍♀️ Has already set up a database for us!

The point of this exercise is to get comfortable with loaders, understand how they work,
and how we can optimize them to perform faster when it comes to fetching the data itself.

We have 3 different routes that will require data fetching, and each aims to teach you
a different concept of data fetching with loaders:

1. **Basic Data Fetching**: In the first route, we will implement a simple loader that fetches
all products from the database and displays them on the page. This will help you understand
the basics of setting up a loader and using the fetched data in your components.
2. **Using URL Parameters**: The second route will introduce URL parameters. We will create a loader
that fetches a product based on the ID provided in the URL. This will teach you how to read
URL parameters in loaders and use them to fetch specific data.
3. **Optimizing Data Fetching**: In the final route, we will focus on optimizing our data fetching.
We will use a simple trick of parallelizing multiple data fetches to improve performance. This will help you
understand how to make your loaders more efficient.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@import 'tailwindcss';

@theme {
--font-sans:
'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
}

html,
body {
@apply bg-white dark:bg-gray-950;

@media (prefers-color-scheme: dark) {
color-scheme: dark;
}
}
Loading
Loading