Skip to content

Commit e410482

Browse files
committed
chore: check route validity and added content
1 parent baf9c80 commit e410482

13 files changed

+289
-4
lines changed

src/App.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { useState } from 'react';
21
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
32
import { useLocalStorage } from '@uidotdev/usehooks';
43
import CategoryList from './components/CategoryList';
54
import TopicList from './components/TopicList';
65
import MarkdownViewer from './components/MarkdownViewer';
76
import Navbar from './components/Navbar';
7+
import Error from './components/Error';
88

99
const App = () => {
1010
const [isDark, setIsDark] = useLocalStorage('isDark', false);
@@ -41,6 +41,14 @@ const App = () => {
4141
path='/:category/:topic'
4242
element={<MarkdownViewer />}
4343
/>
44+
<Route
45+
path='/error'
46+
element={<Error />}
47+
/>
48+
<Route
49+
path='*'
50+
element={<Error />}
51+
/>
4452
</Routes>
4553
</Router>
4654
</div>

src/components/Error.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Link } from 'react-router-dom';
2+
3+
const Error = () => {
4+
return (
5+
<div className='flex items-center justify-center h-[90vh] text-white'>
6+
The requested page doesn't exist. Try searching for topics or navigate
7+
back to{' '}
8+
<Link
9+
className='ml-1 underline text-blue-400'
10+
to='/'
11+
>
12+
home
13+
</Link>
14+
</div>
15+
);
16+
};
17+
18+
export default Error;

src/components/MarkdownViewer.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,32 @@ import { useState, useEffect } from 'react';
22
import ReactMarkdown from 'react-markdown';
33
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
44
import { dracula } from 'react-syntax-highlighter/dist/esm/styles/prism';
5-
import { useParams } from 'react-router-dom';
5+
import { useNavigate, useParams } from 'react-router-dom';
66
import remarkGfm from 'remark-gfm';
77
import CustomLink from './Link';
88
import GitHubButton from './ui/github-button';
99
import Breadcrumb from './ui/breadcrumb';
1010
import BackToTop from './ui/back-to-top';
11+
import checkRoute from '../utils/checkRoute';
1112

1213
const MarkdownViewer = () => {
1314
const { category, topic } = useParams();
1415
const [markdown, setMarkdown] = useState('');
1516
const [isLoading, setIsLoading] = useState(true);
1617
const [isVisible, setIsVisible] = useState(false);
1718

19+
const navigate = useNavigate();
20+
21+
checkRoute();
22+
1823
useEffect(() => {
1924
const fetchMarkdown = async () => {
2025
try {
2126
const module = await import(`../data/${category}/${topic}.md`);
2227
setMarkdown(module.default);
2328
} catch (error) {
2429
console.error('Error fetching markdown:', error);
30+
navigate('/error');
2531
}
2632
};
2733

src/components/TopicList.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { Link, useParams } from 'react-router-dom';
22
import { topics } from '../utils/lists';
33
import Breadcrumb from './ui/breadcrumb';
4+
import checkRoute from '../utils/checkRoute';
45

56
const TopicList = () => {
67
const { category } = useParams<{ category: string }>();
8+
9+
checkRoute();
10+
711
const topicList =
812
topics[category?.replaceAll('-', '_') as keyof typeof topics] || [];
913

@@ -14,7 +18,7 @@ const TopicList = () => {
1418
<Breadcrumb category={category} />
1519
</div>
1620

17-
<h1 className='my-6 z-50'>{category?.replaceAll('-', ' ')}</h1>
21+
<h1 className='my-10 z-50'>{category?.replaceAll('-', ' ')}</h1>
1822
<ul className='grid md:grid-cols-3 gap-6 list-none'>
1923
{topicList.map((topic) => (
2024
<li

src/data/channels/channels-in-go.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Channels
2+
3+
Channels are a typed, thread-safe queue. Channels allow different goroutines to communicate with each other.
4+
5+
## Create a channel
6+
7+
Like maps and slices, channels must be created *before* use. They also use the same `make` keyword:
8+
9+
```go
10+
ch := make(chan int)
11+
```
12+
13+
## Send data to a channel
14+
15+
```go
16+
ch <- 69
17+
```
18+
19+
The `<-` operator is called the *channel operator*. Data flows in the direction of the arrow. This operation will *block* until another goroutine is ready to receive the value.
20+
21+
## Receive data from a channel
22+
23+
```go
24+
v := <-ch
25+
```
26+
27+
This reads and removes a value from the channel and saves it into the variable `v`. This operation will *block* until there is a value in the channel to be read.
28+
29+
## Blocking and deadlocks
30+
31+
A [deadlock](https://yourbasic.org/golang/detect-deadlock/#:~:text=yourbasic.org%2Fgolang,look%20at%20this%20simple%20example.) is when a group of goroutines are all blocking so none of them can continue. This is a common bug that you need to watch out for in concurrent programming.
32+
33+
> Empty structs are often used as `tokens` in Go programs. In this context, a token is a [unary](https://en.wikipedia.org/wiki/Unary_operation) value. In other words, we don't care *what* is passed through the channel. We care *when* and *if* it is passed.
34+
35+
We can block and wait until *something* is sent on a channel using the following syntax
36+
37+
```go
38+
<-ch
39+
```
40+
41+
This will block until it pops a single item off the channel, then continue, discarding the item.

src/data/channels/channels-review.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Channels Review
2+
3+
Here are a few extra things you should understand about channels from [Dave Cheney's awesome article](https://dave.cheney.net/2014/03/19/channel-axioms).
4+
5+
### A send to a nil channel blocks forever
6+
7+
```go
8+
var c chan string // c is nil
9+
c <- "let's get started" // blocks
10+
```
11+
12+
### A receive from a nil channel blocks forever
13+
14+
```go
15+
var c chan string // c is nil
16+
fmt.Println(<-c) // blocks
17+
```
18+
19+
### A send to a closed channel panics
20+
21+
```go
22+
var c = make(chan int, 100)
23+
close(c)
24+
c <- 1 // panic: send on closed channel
25+
```
26+
27+
### A receive from a closed channel returns the zero value immediately
28+
29+
```go
30+
var c = make(chan int, 100)
31+
close(c)
32+
fmt.Println(<-c) // 0
33+
```

src/data/channels/closing-channels.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Closing channels in Go
2+
3+
Channels can be explicitly closed by a *sender*:
4+
5+
```go
6+
ch := make(chan int)
7+
8+
// do some stuff with the channel
9+
10+
close(ch)
11+
```
12+
13+
## Checking if a channel is closed
14+
15+
Similar to the `ok` value when accessing data in a `map`, receivers can check the `ok` value when receiving from a channel to test if a channel was closed.
16+
17+
```go
18+
v, ok := <-ch
19+
```
20+
21+
ok is `false` if the channel is empty and closed.
22+
23+
## Don't send on a closed channel
24+
25+
Sending on a closed channel will cause a panic. A panic on the main goroutine will cause the entire program to crash, and a panic in any other goroutine will cause *that goroutine* to crash.
26+
27+
Closing isn't necessary. There's nothing wrong with leaving channels open, they'll still be garbage collected if they're unused. You should close channels to indicate explicitly to a receiver that nothing else is going to come across.
28+
29+
## Buffered Channels
30+
31+
Channels can *optionally* be buffered.
32+
33+
### Creating a channel with a buffer
34+
35+
You can provide a buffer length as the second argument to `make()` to create a buffered channel:
36+
37+
```go
38+
ch := make(chan int, 100)
39+
```
40+
41+
Sending on a buffered channel only blocks when the buffer is *full*.
42+
43+
Receiving blocks only when the buffer is *empty*.

src/data/channels/concurrency.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Concurrency
2+
3+
## What is concurrency?
4+
5+
Concurrency is the ability to perform multiple tasks at the same time. Typically, our code is executed one line at a time, one after the other. This is called *sequential execution* or *synchronous execution*.
6+
7+
![concurrency](https://i.imgur.com/1pQKFgw.png)
8+
9+
If the computer we're running our code on has multiple cores, we can even execute multiple tasks at *exactly* the same time. If we're running on a single core, a single code executes code at *almost* the same time by switching between tasks very quickly. Either way, the code we write looks the same in Go and takes advantage of whatever resources are available.
10+
11+
## How does concurrency work in Go?
12+
13+
Go was designed to be concurrent, which is a trait *fairly* unique to Go. It excels at performing many tasks simultaneously safely using a simple syntax.
14+
15+
There isn't a popular programming language in existence where spawning concurrent execution is quite as elegant, at least in my opinion.
16+
17+
Concurrency is as simple as using the `go` keyword when calling a function:
18+
19+
```go
20+
go doSomething()
21+
```
22+
23+
In the example above, `doSomething()` will be executed concurrently with the rest of the code in the function. The `go` keyword is used to spawn a new *[goroutine](https://gobyexample.com/goroutines)*.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Select Default Case
2+
3+
The `default` case in a `select` statement executes *immediately* if no other channel has a value ready. A `default` case stops the `select` statement from blocking.
4+
5+
```go
6+
select {
7+
case v := <-ch:
8+
// use v
9+
default:
10+
// receiving from ch would block
11+
// so do something else
12+
}
13+
```
14+
15+
## Tickers
16+
17+
- [time.Tick()](https://golang.org/pkg/time/#Tick) - is a standard library function that returns a channel that sends a value on a given interval.
18+
- [time.After()](https://golang.org/pkg/time/#After) - sends a value once after the duration has passed.
19+
- [time.Sleep()](https://golang.org/pkg/time/#Sleep) - blocks the current goroutine for the specified amount of time.
20+
21+
## Read-only Channels
22+
23+
A channel can be marked as read-only by casting it from a `chan` to a `<-chan` type. For example:
24+
25+
```go
26+
func main(){
27+
ch := make(chan int)
28+
readCh(ch)
29+
}
30+
31+
func readCh(ch <-chan int) {
32+
// ch can only be read from
33+
// in this function
34+
}
35+
```
36+
37+
## Write-only Channels
38+
39+
The same goes for write-only channels, but the arrow's position moves.
40+
41+
```go
42+
func writeCh(ch chan<- int) {
43+
// ch can only be written to
44+
// in this function
45+
}
46+
```

src/data/channels/select-statement.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Select
2+
3+
Sometimes we have a single goroutine listening to multiple channels and want to process data in the order it comes through each channel.
4+
5+
A `select` statement is used to listen to multiple channels at the same time. It is similar to a `switch` statement but for channels.
6+
7+
```go
8+
select {
9+
case i, ok := <- chInts:
10+
fmt.Println(i)
11+
case s, ok := <- chStrings:
12+
fmt.Println(s)
13+
}
14+
```
15+
16+
The first channel with a value ready to be received will fire and its body will execute. If multiple channels are ready at the same time one is chosen randomly. The `ok` variable in the example above refers to whether or not the channel has been closed by the sender yet.
17+
18+
## Range
19+
20+
Similar to slices and maps, channels can be ranged over.
21+
22+
```go
23+
for item := range ch {
24+
// item is the next value received from the channel
25+
}
26+
```
27+
28+
This example will receive values over the channel (blocking at each iteration if nothing new is there) and will exit only when the channel is closed.

src/utils/checkRoute.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useEffect } from 'react';
2+
import { categories } from './lists';
3+
import { useNavigate, useParams } from 'react-router-dom';
4+
5+
function checkRoute() {
6+
const { category } = useParams();
7+
const navigate = useNavigate();
8+
9+
useEffect(() => {
10+
if (category) {
11+
const isCategoryExist = categories.some(
12+
(prevCategory) =>
13+
prevCategory.link.slice(1).toLowerCase() === category?.toLowerCase()
14+
);
15+
16+
if (!isCategoryExist) {
17+
console.log('Category does not exist:', category);
18+
navigate('/error');
19+
}
20+
21+
return;
22+
}
23+
}, [category, navigate]);
24+
}
25+
26+
export default checkRoute;

src/utils/lists.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,4 +172,13 @@ export const topics = {
172172
'pointer-receivers',
173173
'wrapping-up',
174174
],
175+
channels: [
176+
'concurrency',
177+
'channels-in-go',
178+
'closing-channels',
179+
'select-statement',
180+
'select-default-case',
181+
'channels-review',
182+
'wrapping-up',
183+
],
175184
};

tsconfig.app.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
55
"target": "ES2020",
66
"useDefineForClassFields": true,
7-
"lib": ["ES2020", "DOM", "DOM.Iterable"],
7+
"lib": ["ES2021", "DOM", "DOM.Iterable"],
88
"module": "ESNext",
99
"skipLibCheck": true,
1010

0 commit comments

Comments
 (0)