Skip to content

Commit

Permalink
Refactored custom hooks and contexts
Browse files Browse the repository at this point in the history
  • Loading branch information
dostonnabotov committed Dec 5, 2024
1 parent fa19b37 commit 2ac0718
Show file tree
Hide file tree
Showing 15 changed files with 255 additions and 197 deletions.
File renamed without changes.
4 changes: 2 additions & 2 deletions public/data/index.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[
{
"language": "Sass",
"lang": "SCSS",
"icon": "/icons/sass.svg"
},
{
"language": "CSS",
"lang": "CSS",
"icon": "/icons/css.svg"
}
]
File renamed without changes.
87 changes: 8 additions & 79 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,98 +1,27 @@
import Footer from "./layouts/Footer";
import Header from "./layouts/Header";
import Banner from "./layouts/Banner";
import { useState } from "react";
import Button from "./components/Button";
import { CopyIcon, ExpandIcon } from "./components/Icons";
import slugify from "./utils/slugify";
import { useLanguages } from "./hooks/useLanguages";
import { useCategories } from "./hooks/useCategories";
import { useSnippets } from "./hooks/useSnippets";

type LanguageType = {
language: string;
icon: string;
};

type SnippetType = {
title: string;
description: string;
code: string;
tags: string[];
author: string;
};
import LanguageSelector from "./components/LanguageSelector";
import CategoryList from "./components/CategoryList";
import { useAppContext } from "./contexts/AppContext";
import SnippetList from "./components/SnippetList";

const App = () => {
const [language, setLanguage] = useState<LanguageType>({
language: "Sass",
icon: "/icons/sass.svg",
});
const [category, setCategory] = useState<string>("");
const [snippet, setSnippet] = useState<SnippetType>();

const { fetchedLanguages } = useLanguages();
const { fetchedCategories } = useCategories(language.language);
const { fetchedSnippets } = useSnippets(language.language, category);

const handleLanguageChange = (
event: React.ChangeEvent<HTMLSelectElement>
) => {
const language = fetchedLanguages.find(
(lan) => slugify(lan.language) === slugify(event.target.value)
);
if (language) {
setLanguage(language);
}
};
const { category } = useAppContext();

return (
<>
{/* <SnippetModal /> */}
<div className="container flow" data-flow-space="xl">
<Header />
<Banner />
<main className="main">
<aside className="sidebar flow">
<select
id="languages"
className="language-switcher"
onChange={handleLanguageChange}
>
{fetchedLanguages.map(({ language }) => (
<option key={language} value={slugify(language)}>
{language}
</option>
))}
</select>
<ul role="list" className="categories">
{fetchedCategories.map((name) => (
<li className="category">
<button onClick={() => setCategory(name)}>{name}</button>
</li>
))}
</ul>
<LanguageSelector />
<CategoryList />
</aside>
<section className="flow">
<h2 className="section-title">{category}</h2>
<ul role="list" className="snippets">
{fetchedSnippets.map((snippet) => (
<li className="snippet">
<div className="snippet__preview">
<img src={language.icon} alt={language.language} />
<Button isIcon={true} className="snippet__copy">
<CopyIcon />
</Button>
</div>

<div className="snippet__content">
<h3 className="snippet__title">{snippet.title}</h3>
<Button isIcon={true}>
<ExpandIcon />
</Button>
</div>
</li>
))}
</ul>
<SnippetList />
</section>
</main>
<Footer />
Expand Down
26 changes: 26 additions & 0 deletions src/components/CategoryList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useAppContext } from "../contexts/AppContext";
import { useCategories } from "../hooks/useCategories";

const CategoryList = () => {
const { category, setCategory } = useAppContext();
const { fetchedCategories } = useCategories();

return (
<ul role="list" className="categories">
{fetchedCategories.map((name) => (
<li className="category">
<button
className={`category__btn ${
name === category ? "category__btn--active" : ""
}`}
onClick={() => setCategory(name)}
>
{name}
</button>
</li>
))}
</ul>
);
};

export default CategoryList;
45 changes: 45 additions & 0 deletions src/components/LanguageSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useAppContext } from "../contexts/AppContext";
import { useLanguages } from "../hooks/useLanguages";

const LanguageSelector = () => {
const { fetchedLanguages, loading, error } = useLanguages();
const { setLanguage } = useAppContext();

const handleLanguageChange = (
event: React.ChangeEvent<HTMLSelectElement>
) => {
const selectedLanguage = fetchedLanguages.find(
(language) => language.lang === event.target.value
);
if (selectedLanguage) {
setLanguage(selectedLanguage);
}
};

if (loading) {
return <p>Loading languages...</p>;
}

if (error) {
return <p>Error fetching languages: {error}</p>;
}

return (
<>
<select
id="languages"
className="language-selector"
onChange={handleLanguageChange}
defaultValue="CSS"
>
{fetchedLanguages.map((language, idx) => (
<option key={idx} value={language.lang}>
{language.lang}
</option>
))}
</select>
</>
);
};

export default LanguageSelector;
35 changes: 35 additions & 0 deletions src/components/SnippetList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useAppContext } from "../contexts/AppContext";
import { useSnippets } from "../hooks/useSnippets";
import Button from "./Button";
import { CopyIcon, ExpandIcon } from "./Icons";

const SnippetList = () => {
const { language } = useAppContext();
const { fetchedSnippets } = useSnippets();

if (!fetchedSnippets) return <p>Empty List</p>;

return (
<ul role="list" className="snippets">
{fetchedSnippets.map((snippet) => (
<li className="snippet">
<div className="snippet__preview">
<img src={language.icon} alt={language.lang} />
<Button isIcon={true} className="snippet__copy">
<CopyIcon />
</Button>
</div>

<div className="snippet__content">
<h3 className="snippet__title">{snippet.title}</h3>
<Button isIcon={true}>
<ExpandIcon />
</Button>
</div>
</li>
))}
</ul>
);
};

export default SnippetList;
45 changes: 45 additions & 0 deletions src/contexts/AppContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { createContext, useContext, useState } from "react";
import { AppState, LanguageType, SnippetType } from "../types";

// tokens
const defaultLanguage: LanguageType = {
lang: "CSS",
icon: "/icons/css.svg",
};

const defaultCategory: string = "DOM Manipulation";

const defaultState: AppState = {
language: defaultLanguage,
setLanguage: () => {},
category: defaultCategory,
setCategory: () => {},
setSnippet: () => {},
};

const AppContext = createContext<AppState>(defaultState);

export const useAppContext = () => useContext(AppContext);

export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [language, setLanguage] = useState<LanguageType>(defaultLanguage);
const [category, setCategory] = useState<string>(defaultCategory);
const [snippet, setSnippet] = useState<SnippetType>();

return (
<AppContext.Provider
value={{
language,
setLanguage,
category,
setCategory,
snippet,
setSnippet,
}}
>
{children}
</AppContext.Provider>
);
};
39 changes: 15 additions & 24 deletions src/hooks/useCategories.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
import { useEffect, useState } from "react";
import { LanguageData } from "../types";
import { useFetch } from "./useFetch";
import { useAppContext } from "../contexts/AppContext";
import { SnippetType } from "../types";
import slugify from "../utils/slugify";

export const useCategories = (language: string) => {
const [fetchedCategories, setFetchedCategories] = useState<string[]>([]);

useEffect(() => {
const fetchCategories = async () => {
try {
const res = await fetch(`/data/${language}.json`);
if (!res.ok) {
throw new Error("Failed to fetch languages in CategoryList.tsx");
}
const data: LanguageData = await res.json();
const filteredCategoryNames = data.map(
(category) => category.categoryName
);
type CategoryData = {
categoryName: string;
snippets: SnippetType[];
};

setFetchedCategories(filteredCategoryNames);
} catch (error) {
console.error("Error occured with CategoryList.tsx", error);
}
};
export const useCategories = () => {
const { language } = useAppContext();
const { data, loading, error } = useFetch<CategoryData[]>(
`/data/${slugify(language.lang)}.json`
);

fetchCategories();
}, [language]);
const fetchedCategories = data ? data.map((item) => item.categoryName) : [];

return { fetchedCategories };
return { fetchedCategories, loading, error };
};
28 changes: 28 additions & 0 deletions src/hooks/useFetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useEffect, useState } from "react";

export const useFetch = <T>(url: string) => {
const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(true);

useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Failed to fetch data from ${url}`);
}
const result: T = await res.json();
setData(result);
} catch (err) {
setError((err as Error).message);
} finally {
setLoading(false);
}
};

fetchData();
}, [url]);

return { data, loading, error };
};
33 changes: 4 additions & 29 deletions src/hooks/useLanguages.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,8 @@
import { useEffect, useState } from "react";

type LanguageType = {
language: string;
icon: string;
};
import { LanguageType } from "../types";
import { useFetch } from "./useFetch";

export const useLanguages = () => {
const [fetchedLanguages, setFetchedLanguages] = useState<LanguageType[]>([]);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(true);

useEffect(() => {
const fetchLanguages = async () => {
try {
const res = await fetch(`/data/index.json`);
if (!res.ok) {
throw new Error("Failed to fetch languages");
}
const data = await res.json();
setFetchedLanguages(data);
} catch (err) {
setError((err as Error).message);
} finally {
setLoading(false);
}
};

fetchLanguages();
}, []);
const { data, loading, error } = useFetch<LanguageType[]>("/data/index.json");

return { fetchedLanguages, loading, error };
return { fetchedLanguages: data || [], loading, error };
};
Loading

0 comments on commit 2ac0718

Please sign in to comment.