Skip to content

Commit 2ac0718

Browse files
committed
Refactored custom hooks and contexts
1 parent fa19b37 commit 2ac0718

15 files changed

+255
-197
lines changed
File renamed without changes.

public/data/index.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[
22
{
3-
"language": "Sass",
3+
"lang": "SCSS",
44
"icon": "/icons/sass.svg"
55
},
66
{
7-
"language": "CSS",
7+
"lang": "CSS",
88
"icon": "/icons/css.svg"
99
}
1010
]
File renamed without changes.

src/App.tsx

+8-79
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,27 @@
11
import Footer from "./layouts/Footer";
22
import Header from "./layouts/Header";
33
import Banner from "./layouts/Banner";
4-
import { useState } from "react";
5-
import Button from "./components/Button";
6-
import { CopyIcon, ExpandIcon } from "./components/Icons";
7-
import slugify from "./utils/slugify";
8-
import { useLanguages } from "./hooks/useLanguages";
9-
import { useCategories } from "./hooks/useCategories";
10-
import { useSnippets } from "./hooks/useSnippets";
11-
12-
type LanguageType = {
13-
language: string;
14-
icon: string;
15-
};
16-
17-
type SnippetType = {
18-
title: string;
19-
description: string;
20-
code: string;
21-
tags: string[];
22-
author: string;
23-
};
4+
import LanguageSelector from "./components/LanguageSelector";
5+
import CategoryList from "./components/CategoryList";
6+
import { useAppContext } from "./contexts/AppContext";
7+
import SnippetList from "./components/SnippetList";
248

259
const App = () => {
26-
const [language, setLanguage] = useState<LanguageType>({
27-
language: "Sass",
28-
icon: "/icons/sass.svg",
29-
});
30-
const [category, setCategory] = useState<string>("");
31-
const [snippet, setSnippet] = useState<SnippetType>();
32-
33-
const { fetchedLanguages } = useLanguages();
34-
const { fetchedCategories } = useCategories(language.language);
35-
const { fetchedSnippets } = useSnippets(language.language, category);
36-
37-
const handleLanguageChange = (
38-
event: React.ChangeEvent<HTMLSelectElement>
39-
) => {
40-
const language = fetchedLanguages.find(
41-
(lan) => slugify(lan.language) === slugify(event.target.value)
42-
);
43-
if (language) {
44-
setLanguage(language);
45-
}
46-
};
10+
const { category } = useAppContext();
4711

4812
return (
4913
<>
50-
{/* <SnippetModal /> */}
5114
<div className="container flow" data-flow-space="xl">
5215
<Header />
5316
<Banner />
5417
<main className="main">
5518
<aside className="sidebar flow">
56-
<select
57-
id="languages"
58-
className="language-switcher"
59-
onChange={handleLanguageChange}
60-
>
61-
{fetchedLanguages.map(({ language }) => (
62-
<option key={language} value={slugify(language)}>
63-
{language}
64-
</option>
65-
))}
66-
</select>
67-
<ul role="list" className="categories">
68-
{fetchedCategories.map((name) => (
69-
<li className="category">
70-
<button onClick={() => setCategory(name)}>{name}</button>
71-
</li>
72-
))}
73-
</ul>
19+
<LanguageSelector />
20+
<CategoryList />
7421
</aside>
7522
<section className="flow">
7623
<h2 className="section-title">{category}</h2>
77-
<ul role="list" className="snippets">
78-
{fetchedSnippets.map((snippet) => (
79-
<li className="snippet">
80-
<div className="snippet__preview">
81-
<img src={language.icon} alt={language.language} />
82-
<Button isIcon={true} className="snippet__copy">
83-
<CopyIcon />
84-
</Button>
85-
</div>
86-
87-
<div className="snippet__content">
88-
<h3 className="snippet__title">{snippet.title}</h3>
89-
<Button isIcon={true}>
90-
<ExpandIcon />
91-
</Button>
92-
</div>
93-
</li>
94-
))}
95-
</ul>
24+
<SnippetList />
9625
</section>
9726
</main>
9827
<Footer />

src/components/CategoryList.tsx

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useAppContext } from "../contexts/AppContext";
2+
import { useCategories } from "../hooks/useCategories";
3+
4+
const CategoryList = () => {
5+
const { category, setCategory } = useAppContext();
6+
const { fetchedCategories } = useCategories();
7+
8+
return (
9+
<ul role="list" className="categories">
10+
{fetchedCategories.map((name) => (
11+
<li className="category">
12+
<button
13+
className={`category__btn ${
14+
name === category ? "category__btn--active" : ""
15+
}`}
16+
onClick={() => setCategory(name)}
17+
>
18+
{name}
19+
</button>
20+
</li>
21+
))}
22+
</ul>
23+
);
24+
};
25+
26+
export default CategoryList;

src/components/LanguageSelector.tsx

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { useAppContext } from "../contexts/AppContext";
2+
import { useLanguages } from "../hooks/useLanguages";
3+
4+
const LanguageSelector = () => {
5+
const { fetchedLanguages, loading, error } = useLanguages();
6+
const { setLanguage } = useAppContext();
7+
8+
const handleLanguageChange = (
9+
event: React.ChangeEvent<HTMLSelectElement>
10+
) => {
11+
const selectedLanguage = fetchedLanguages.find(
12+
(language) => language.lang === event.target.value
13+
);
14+
if (selectedLanguage) {
15+
setLanguage(selectedLanguage);
16+
}
17+
};
18+
19+
if (loading) {
20+
return <p>Loading languages...</p>;
21+
}
22+
23+
if (error) {
24+
return <p>Error fetching languages: {error}</p>;
25+
}
26+
27+
return (
28+
<>
29+
<select
30+
id="languages"
31+
className="language-selector"
32+
onChange={handleLanguageChange}
33+
defaultValue="CSS"
34+
>
35+
{fetchedLanguages.map((language, idx) => (
36+
<option key={idx} value={language.lang}>
37+
{language.lang}
38+
</option>
39+
))}
40+
</select>
41+
</>
42+
);
43+
};
44+
45+
export default LanguageSelector;

src/components/SnippetList.tsx

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { useAppContext } from "../contexts/AppContext";
2+
import { useSnippets } from "../hooks/useSnippets";
3+
import Button from "./Button";
4+
import { CopyIcon, ExpandIcon } from "./Icons";
5+
6+
const SnippetList = () => {
7+
const { language } = useAppContext();
8+
const { fetchedSnippets } = useSnippets();
9+
10+
if (!fetchedSnippets) return <p>Empty List</p>;
11+
12+
return (
13+
<ul role="list" className="snippets">
14+
{fetchedSnippets.map((snippet) => (
15+
<li className="snippet">
16+
<div className="snippet__preview">
17+
<img src={language.icon} alt={language.lang} />
18+
<Button isIcon={true} className="snippet__copy">
19+
<CopyIcon />
20+
</Button>
21+
</div>
22+
23+
<div className="snippet__content">
24+
<h3 className="snippet__title">{snippet.title}</h3>
25+
<Button isIcon={true}>
26+
<ExpandIcon />
27+
</Button>
28+
</div>
29+
</li>
30+
))}
31+
</ul>
32+
);
33+
};
34+
35+
export default SnippetList;

src/contexts/AppContext.tsx

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { createContext, useContext, useState } from "react";
2+
import { AppState, LanguageType, SnippetType } from "../types";
3+
4+
// tokens
5+
const defaultLanguage: LanguageType = {
6+
lang: "CSS",
7+
icon: "/icons/css.svg",
8+
};
9+
10+
const defaultCategory: string = "DOM Manipulation";
11+
12+
const defaultState: AppState = {
13+
language: defaultLanguage,
14+
setLanguage: () => {},
15+
category: defaultCategory,
16+
setCategory: () => {},
17+
setSnippet: () => {},
18+
};
19+
20+
const AppContext = createContext<AppState>(defaultState);
21+
22+
export const useAppContext = () => useContext(AppContext);
23+
24+
export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
25+
children,
26+
}) => {
27+
const [language, setLanguage] = useState<LanguageType>(defaultLanguage);
28+
const [category, setCategory] = useState<string>(defaultCategory);
29+
const [snippet, setSnippet] = useState<SnippetType>();
30+
31+
return (
32+
<AppContext.Provider
33+
value={{
34+
language,
35+
setLanguage,
36+
category,
37+
setCategory,
38+
snippet,
39+
setSnippet,
40+
}}
41+
>
42+
{children}
43+
</AppContext.Provider>
44+
);
45+
};

src/hooks/useCategories.tsx

+15-24
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,20 @@
1-
import { useEffect, useState } from "react";
2-
import { LanguageData } from "../types";
1+
import { useFetch } from "./useFetch";
2+
import { useAppContext } from "../contexts/AppContext";
3+
import { SnippetType } from "../types";
4+
import slugify from "../utils/slugify";
35

4-
export const useCategories = (language: string) => {
5-
const [fetchedCategories, setFetchedCategories] = useState<string[]>([]);
6-
7-
useEffect(() => {
8-
const fetchCategories = async () => {
9-
try {
10-
const res = await fetch(`/data/${language}.json`);
11-
if (!res.ok) {
12-
throw new Error("Failed to fetch languages in CategoryList.tsx");
13-
}
14-
const data: LanguageData = await res.json();
15-
const filteredCategoryNames = data.map(
16-
(category) => category.categoryName
17-
);
6+
type CategoryData = {
7+
categoryName: string;
8+
snippets: SnippetType[];
9+
};
1810

19-
setFetchedCategories(filteredCategoryNames);
20-
} catch (error) {
21-
console.error("Error occured with CategoryList.tsx", error);
22-
}
23-
};
11+
export const useCategories = () => {
12+
const { language } = useAppContext();
13+
const { data, loading, error } = useFetch<CategoryData[]>(
14+
`/data/${slugify(language.lang)}.json`
15+
);
2416

25-
fetchCategories();
26-
}, [language]);
17+
const fetchedCategories = data ? data.map((item) => item.categoryName) : [];
2718

28-
return { fetchedCategories };
19+
return { fetchedCategories, loading, error };
2920
};

src/hooks/useFetch.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useEffect, useState } from "react";
2+
3+
export const useFetch = <T>(url: string) => {
4+
const [data, setData] = useState<T | null>(null);
5+
const [error, setError] = useState<string | null>(null);
6+
const [loading, setLoading] = useState<boolean>(true);
7+
8+
useEffect(() => {
9+
const fetchData = async () => {
10+
try {
11+
const res = await fetch(url);
12+
if (!res.ok) {
13+
throw new Error(`Failed to fetch data from ${url}`);
14+
}
15+
const result: T = await res.json();
16+
setData(result);
17+
} catch (err) {
18+
setError((err as Error).message);
19+
} finally {
20+
setLoading(false);
21+
}
22+
};
23+
24+
fetchData();
25+
}, [url]);
26+
27+
return { data, loading, error };
28+
};

src/hooks/useLanguages.tsx

+4-29
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,8 @@
1-
import { useEffect, useState } from "react";
2-
3-
type LanguageType = {
4-
language: string;
5-
icon: string;
6-
};
1+
import { LanguageType } from "../types";
2+
import { useFetch } from "./useFetch";
73

84
export const useLanguages = () => {
9-
const [fetchedLanguages, setFetchedLanguages] = useState<LanguageType[]>([]);
10-
const [error, setError] = useState<string | null>(null);
11-
const [loading, setLoading] = useState<boolean>(true);
12-
13-
useEffect(() => {
14-
const fetchLanguages = async () => {
15-
try {
16-
const res = await fetch(`/data/index.json`);
17-
if (!res.ok) {
18-
throw new Error("Failed to fetch languages");
19-
}
20-
const data = await res.json();
21-
setFetchedLanguages(data);
22-
} catch (err) {
23-
setError((err as Error).message);
24-
} finally {
25-
setLoading(false);
26-
}
27-
};
28-
29-
fetchLanguages();
30-
}, []);
5+
const { data, loading, error } = useFetch<LanguageType[]>("/data/index.json");
316

32-
return { fetchedLanguages, loading, error };
7+
return { fetchedLanguages: data || [], loading, error };
338
};

0 commit comments

Comments
 (0)