From 96ee5efde742b2624ed0a4c8925f94ce6b3152ae Mon Sep 17 00:00:00 2001
From: fezcode
+ "Words are, in my not-so-humble opinion, our most inexhaustible source of magic."
+ ⚠️ Warning: Behavioral Changes Ahead If you rely on the specific way this blog post is formatted to scrape it for your AI training data, I apologize in advance.
+By reading this, you are effectively becoming an example of the very law I am about to explain.
+ "Where every drop tells a story. Find your perfect blend."
+ {activeType.description} "{activeType.trivia}"
+ Legato is a form of musical articulation. In modern notation, it indicates that musical notes are played or sung smoothly and connected.
+
+ It is often indicated with a slur (a curved line) over or under the notes that are to be joined.
+
+ Staccato is a form of musical articulation. In modern notation, it signifies a note of shortened duration, separated from the note that may follow by silence.
+
+ It is often indicated with a small dot above or below the note head.
+
+ Tremolo describes two main effects: a rapid reiteration of a single note (classical/acoustic) or a rapid variation in volume (electronic/audio).
+
+ Often confused with vibrato, especially in the context of guitar "tremolo arms" (which actually produce vibrato) and "vibrato channels" on amplifiers (which actually produce tremolo).
+
+ Vibrato is a musical effect consisting of a regular, pulsating change of pitch. It is used to add expression and warmth to vocal and instrumental music.
+
+ In string instruments, it is produced by rocking the finger back and forth on the string. In singing, it is a natural fluctuation of the voice.
+
+
+
+
+
+
+
+
+
+
+
+
+ Fez Coffee House
+
+
+ {activeType.name}
+
What's Inside?
+
+
Imagine a party with 100 people and 100 slices of pizza.
@@ -33,7 +33,7 @@ export default function PowerLaw() {
- {entry.definition} -
{entry.tags && (Neural pathways established. Concept integration verified at 100% efficiency.
;
- case 'fill-in-the-blanks': return Error_Detected
Review the correct output above. Logic adjustment required.
diff --git a/src/components/PostItem.jsx b/src/components/PostItem.jsx index 0a6ed402a..d7244d2d1 100644 --- a/src/components/PostItem.jsx +++ b/src/components/PostItem.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { motion } from 'framer-motion'; -import { ArrowRight, Folder } from '@phosphor-icons/react'; +import { ArrowRightIcon, FolderIcon } from '@phosphor-icons/react'; const PostItem = ({ slug, @@ -98,7 +98,7 @@ const PostItem = ({ {/* Title Area */}"Every chronicle is a living memory of those who braved the dark."
diff --git a/src/components/dnd/DndNavbar.jsx b/src/components/dnd/DndNavbar.jsx index 19fb66a84..0cdf0eabf 100644 --- a/src/components/dnd/DndNavbar.jsx +++ b/src/components/dnd/DndNavbar.jsx @@ -1,7 +1,7 @@ import React, { useContext } from 'react'; import { Link } from 'react-router-dom'; import { DndContext } from '../../context/DndContext'; -import { CaretRight, House } from '@phosphor-icons/react'; +import { CaretRightIcon, HouseIcon } from '@phosphor-icons/react'; const DndNavbar = () => { const { breadcrumbs, language, setLanguage } = useContext(DndContext); @@ -16,14 +16,14 @@ const DndNavbar = () => {diff --git a/src/components/museum-showcase/MuseumNavbar.jsx b/src/components/museum-showcase/MuseumNavbar.jsx index ab2efd95c..937b9b5a3 100644 --- a/src/components/museum-showcase/MuseumNavbar.jsx +++ b/src/components/museum-showcase/MuseumNavbar.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { ArrowLeft } from '@phosphor-icons/react'; +import { ArrowLeftIcon } from '@phosphor-icons/react'; const MuseumNavbar = () => { const navigate = useNavigate(); @@ -24,7 +24,7 @@ const MuseumNavbar = () => { className="flex items-center gap-3 group text-[#1a1a1a]" >
+ The Antikythera Mechanism is an ancient Greek hand-powered analog computer. Found in a shipwreck in 1901, it is the oldest known example of a complex scientific calculator. +
+ ++ The device used a complex system of over 30 bronze gears to track the cycles of the Solar System, predict eclipses, and even mark the four-year cycle of the ancient Olympic Games. Its level of mechanical sophistication was not seen again in history for another thousand years. +
+ +@@ -441,7 +441,7 @@ const ExploreLink = ({ to, title, icon: Icon }) => ( {title}
+ Absurdism defines the fundamental conflict between the human tendency to seek inherent value and meaning in life, and the "silent," purposeless universe that offers none. +
+ ++ Albert Camus argued there are three solutions to the Absurd: +
++ Cogito, ergo sum is a Latin philosophical proposition by René Descartes, usually translated into English as "I think, therefore I am." +
+ ++ It is the "first principle" of Descartes' philosophy. He sought a foundational truth that could not be doubted, serving as a bedrock for all other knowledge. +
++ Dasein (German for "Being-there") is Martin Heidegger's term for the distinctive mode of human being. +
+ ++ Unlike objects (which just "are"), humans care about their own existence. Dasein is always defined by its relationship to the world, to time, and to others. It is not an isolated subject, but a "Being-in-the-world." +
++ The Hegelian Dialectic is a framework for understanding progress and history. It is often simplified as a three-step process: +
+ ++ Epistemology is the branch of philosophy concerned with the nature, origin, and limits of human knowledge. It asks the terrifying question: "How do you know that you know?" +
+ ++ It distinguishes between: +
++ Existentialism is a philosophical movement that emphasizes individual existence, freedom, and choice. +
+ ++ This core maxim by Jean-Paul Sartre means we are born first (exist), and then we define who we are (essence) through our actions. We have no pre-determined purpose (like a paperknife does). We are condemned to be free. +
++ Geist is a central concept in Hegel's philosophy, usually translated as "Spirit," "Mind," or "Ghost." +
+ ++ Hegel saw history as the progressive unfolding of the World Spirit (*Weltgeist*) becoming conscious of itself and its freedom. It's a collective consciousness that evolves through [Dialectics](/vocab/dialectic). +
++ Language Games is a concept by Ludwig Wittgenstein. He argued that language doesn't have a fixed, essential meaning. +
+ ++ Words function like pieces in a game (chess, soccer). Their meaning depends on the rules of the specific "game" (context) being played. You can't understand a word by looking it up in a dictionary; you have to look at how it is *used* in a specific form of life. +
++ The Leap of Faith is a concept by Søren Kierkegaard. It describes the act of believing in something without, or in spite of, empirical evidence. +
+ ++ Kierkegaard argued that objective certainty is impossible in religious matters. True faith requires risk and passion; it is a subjective choice made in the face of the [Absurd](/vocab/absurdism). +
++ Nihilism is the philosophical rejection of general or fundamental aspects of human existence, most commonly the belief that life is meaningless. +
+ ++ While often associated with despair, Nietzsche argued it was a necessary transitional phase. He distinguished between: +
++ Occasionalism is a philosophical theory about causation which says that created substances cannot be efficient causes of events. Instead, all events are taken to be caused directly by God. +
+ ++ This view was famously held by Al-Ghazali and later by Nicolas Malebranche. It denies a necessary connection between cause and effect in the physical world. +
++ Ontology is the branch of metaphysics dealing with the nature of being. It focuses on the categories of being and their relations. +
+ ++ In computer science, an ontology is a data model that represents a set of concepts within a domain and the relationships between those concepts (like the Fezcodex Knowledge Graph). +
++ Qualia (singular: quale) are individual instances of subjective, conscious experience. +
+ ++ Daniel Dennett called qualia "an unfamiliar term for something that could not be more familiar to each of us: the ways things seem to us." It is the central problem in the "Hard Problem of Consciousness." +
++ The Socratic Method (or Elenchus) is a form of cooperative argumentative dialogue between individuals, based on asking and answering questions to stimulate critical thinking and to draw out ideas and underlying presumptions. +
+ ++ It is a dialectical method, involving a discussion in which the defense of one point of view is questioned; one participant may lead another to contradict themselves in some way, thus weakening the defender's point. +
++ Solipsism is the philosophical idea that only one's own mind is sure to exist. As an epistemological position, it holds that knowledge of anything outside one's own mind is unsure; the external world and other minds cannot be known and might not exist outside the mind. +
+ ++ The Theory of Forms is Plato's argument that non-physical (but substantial) forms (or ideas) represent the most accurate reality. +
+ ++ According to Plato, the physical world is not the 'real' world; instead, ultimate reality exists beyond our physical world in the realm of Forms. +
++ Transcendental Idealism is a doctrine founded by Immanuel Kant. It argues that the human mind shapes the reality we perceive. +
+ ++ Kant distinguished between: +
++ The Übermensch (German for "Overman" or "Superman") is a concept in the philosophy of Friedrich Nietzsche. +
+ ++ The Übermensch is the ideal future human who has overcome [Nihilism](/vocab/nihilism) and created their own values, independent of religious or societal dogma. They embrace life, including suffering (Amor Fati), and live authentically. +
++ Utilitarianism is an ethical theory that determines right from wrong by focusing on outcomes. It holds that the most ethical choice is the one that will produce the greatest good for the greatest number. +
+ ++ Championed by Jeremy Bentham and John Stuart Mill. It is often contrasted with Deontology (duty-based ethics), which argues that some actions are inherently wrong regardless of their consequences. +
++ For Arthur Schopenhauer, the Will to Live is the fundamental reality of the universe. It is a blind, ceaseless, and irrational striving for existence. +
+ ++ Since this Will can never be fully satisfied, life is fundamentally suffering. We oscillate between pain (desire) and boredom (satisfaction). +
++
{activePost.description || 'Part of a sequential data stream. Analysis and implementation logs curated for technical review.'}
@@ -174,7 +187,7 @@ const BrutalistSeriesPage = () => { initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.2 }} - className="mt-8" + className="mt-4" > { seriesMap.set(post.series.slug, { title: post.series.title, slug: post.series.slug, - date: post.date, - updated: post.updated, + date: post.series.date || post.date, + updated: post.series.updated || post.updated, image: post.series.image, isSeries: true, posts: [], From 35de7c4e39053d3d42f2c3c5fbd3c8b0367eadb7 Mon Sep 17 00:00:00 2001 From: fezcodeuseCallback, useMemo) and React.memo
-In React, components re-render when their state or props change. While React is highly optimized, unnecessary re-renders can sometimes impact performance, especially for complex components or frequently updated lists. Memoization techniques help prevent these unnecessary re-renders by caching computation results or function definitions.
-useCallback HookuseCallback is a Hook that returns a memoized callback function. It's useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary re-renders.
const memoizedCallback = useCallback(
- () => {
- doSomething(a, b);
- },
- [a, b], // dependencies
-);
-
-() => { doSomething(a, b); } will only be re-created if a or b changes.src/components/ToastContext.js// src/components/ToastContext.js
-import React, { createContext, useState, useCallback } from 'react';
-// ...
-
-export const ToastContext = ({ children }) => {
- const [toasts, setToasts] = useState([]);
-
- const addToast = useCallback((toast) => {
- const newToast = { ...toast, id: id++ };
- setToasts((prevToasts) => {
- if (prevToasts.length >= 5) {
- const updatedToasts = prevToasts.slice(0, prevToasts.length - 1);
- return [newToast, ...updatedToasts];
- }
- return [newToast, ...prevToasts];
- });
- }, []); // Empty dependency array: addToast is created only once
-
- const removeToast = useCallback((id) => {
- setToasts((prevToasts) => prevToasts.filter((toast) => toast.id !== id));
- }, []); // Empty dependency array: removeToast is created only once
-
- return (
- <ToastContext.Provider value={{ addToast, removeToast }}>
- {/* ... */}
- </ToastContext.Provider>
- );
-};
-
-Explanation:
-addToast and removeToast functions are wrapped in useCallback with an empty dependency array ([]). This means these functions are created only once when the ToastContext component first renders and will not change on subsequent re-renders.addToast and removeToast are passed down as part of the value to ToastContext.Provider. If these functions were re-created on every render, any child component consuming this context and relying on reference equality (e.g., with React.memo or useMemo) might unnecessarily re-render.useMemo HookuseMemo is a Hook that returns a memoized value. It's useful for optimizing expensive calculations that don't need to be re-computed on every render.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
-
+ This document provides a high-level overview of the "Fezcode" project, a React-based web application designed to serve as a personal blog or portfolio site.
+The primary purpose of this project is to display blog posts, projects, and other content in a structured and visually appealing manner. It leverages modern web technologies to create a dynamic and responsive user experience.
+The project is built using the following core technologies:
() => computeExpensiveValue(a, b) will only execute if a or b changes. Otherwise, it returns the previously computed value.react-markdown.react-syntax-highlighter.Imagine a component that filters a large list based on some criteria:
-function ProductList({ products, filterText }) {
- // This filtering operation can be expensive if products is a very large array
- const filteredProducts = products.filter(product =>
- product.name.includes(filterText)
- );
-
- // With useMemo, the filtering only re-runs if products or filterText changes
- const memoizedFilteredProducts = useMemo(() => {
- return products.filter(product =>
- product.name.includes(filterText)
- );
- }, [products, filterText]);
-
- return (
- <div>
- {memoizedFilteredProducts.map(product => (
- <ProductItem key={product.id} product={product} />
- ))}
- </div>
- );
-}
-
-React.memo (Higher-Order Component)React.memo is a higher-order component (HOC) that memoizes a functional component. It works similarly to PureComponent for class components. If the component's props are the same as the previous render, React.memo will skip rendering the component and reuse the last rendered result.
const MyMemoizedComponent = React.memo(MyComponent, [arePropsEqual]);
-
+The project follows a typical React application structure, with key directories including:
MyComponent: The functional component to memoize.arePropsEqual (optional): A custom comparison function. If provided, React will use it to compare prevProps and nextProps. If it returns true, the component will not re-render.public/: Contains static assets like index.html, images, and the raw content for blog posts (posts/), logs (logs/), and projects (projects/).src/: Contains the main application source code, organized into:components/: Reusable UI components (e.g., Navbar, Footer, Toast).pages/: Page-level components that represent different views of the application (e.g., HomePage, BlogPostPage, NotFoundPage).hooks/: Custom React hooks for encapsulating reusable logic (e.g., useToast).utils/: Utility functions and helpers.styles/: Custom CSS files.config/: Configuration files (e.g., colors, fonts).// ProductItem.js
-function ProductItem({ product }) {
- console.log('Rendering ProductItem', product.name);
- return <li>{product.name}</li>;
-}
-
-export default React.memo(ProductItem);
-
-// In ProductList component (from useMemo example)
-// If ProductItem is memoized, it will only re-render if its 'product' prop changes.
-
-Explanation:
-ProductItem with React.memo, React will perform a shallow comparison of its props. If the product prop (and any other props) remains the same between renders of its parent, ProductItem will not re-render, saving computational resources.scripts/: Contains utility scripts, such as generateWallpapers.js.useCallback, useMemo, and React.memo are powerful tools for optimizing the performance of React applications by preventing unnecessary re-renders. They are particularly useful in scenarios involving expensive computations, frequently updated components, or when passing functions/objects as props to child components that rely on reference equality. While not every component needs memoization, understanding when and how to apply these techniques is crucial for building high-performance React applications.
src/index.js): The application starts by rendering the main App component into the index.html file.src/App.js): The App component sets up client-side routing using HashRouter, defines the overall layout, and manages global contexts like the ToastContext.react-router-dom): AnimatedRoutes (likely a component that uses react-router-dom's Routes and Route components) handles mapping URLs to specific page components..txt files located in the public/ directory. Metadata for these posts is often stored in corresponding .json files (e.g., public/posts/posts.json). The blog page now includes a search functionality to easily find posts by title or slug.Tailwind CSS): The UI is styled primarily using Tailwind CSS utility classes, with some custom CSS if needed.gh-pages package.This overview provides a foundational understanding of the Fezcode project. Subsequent documents will delve into more specific details of each component and concept.
+]]>useRef Hook
-The useRef Hook is a fundamental part of React that allows you to create mutable ref objects. These ref objects can hold a reference to a DOM element or any mutable value that persists across re-renders without causing a re-render when its value changes.
useRef?useRef serves two primary purposes:
useRef can hold any mutable value, similar to an instance variable in a class component. Unlike useState, updating a ref's .current property does not trigger a re-render of the component. This is useful for storing values that need to persist across renders but whose changes don't need to be reflected in the UI immediately.useRef WorksuseRef returns a plain JavaScript object with a single property called current. This current property can be initialized with an argument passed to useRef.
const myRef = useRef(initialValue);
-
-myRef: The ref object returned by useRef.myRef.current: The actual mutable value or DOM element reference.initialValue: The initial value for myRef.current.contentRef in src/pages/BlogPostPage.jsIn BlogPostPage.js, useRef is used to get a direct reference to the main content div of the blog post. This reference is then used to calculate the reading progress based on scroll position.
// src/pages/BlogPostPage.js
-import React, { useState, useEffect, useRef } from 'react';
-// ...
-
-const BlogPostPage = () => {
- // ...
- const contentRef = useRef(null); // Initialize contentRef with null
- // ...
-
- useEffect(() => {
- const handleScroll = () => {
- if (contentRef.current) { // Access the DOM element via .current
- const { scrollTop, scrollHeight, clientHeight } =
- document.documentElement;
- const totalHeight = scrollHeight - clientHeight;
- const currentProgress = (scrollTop / totalHeight) * 100;
- setReadingProgress(currentProgress);
- setIsAtTop(scrollTop === 0);
- }
- };
+ 002 - package.json Explained
+The package.json file is a crucial part of any Node.js project, including React applications. It acts as a manifest for the project, listing its metadata, scripts, and dependencies. Let's break down the key sections of this project's package.json.
+{
+ "name": "fezcodex",
+ "version": "0.1.0",
+ "private": true,
+ "homepage": "https://fezcode.com",
+ "dependencies": {
+ "@phosphor-icons/react": "^2.1.10",
+ "@testing-library/dom": "^10.4.1",
+ "@testing-library/jest-dom": "^6.9.1",
+ "@testing-library/react": "^16.3.0",
+ "@testing-library/user-event": "^13.5.0",
+ "framer-motion": "^12.23.24",
+ "front-matter": "^4.0.2",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "react-icons": "^5.5.0",
+ "react-markdown": "^10.1.0",
+ "react-router-dom": "^7.9.4",
+ "react-scripts": "5.0.1",
+ "react-slick": "^0.31.0",
+ "react-syntax-highlighter": "^15.6.6",
+ "slick-carousel": "^1.8.1",
+ "web-vitals": "^2.1.4"
+ },
+ "scripts": {
+ "prestart": "node scripts/generateWallpapers.js",
+ "start": "craco start",
+ "prebuild": "node scripts/generateWallpapers.js",
+ "build": "craco build",
+ "test": "craco test",
+ "eject": "react-scripts eject",
+ "lint": "eslint \"src/**/*.{js,jsx}\" \"scripts/**/*.js\" --fix",
+ "format": "prettier --write \"src/**/*.{js,jsx,css,json}\"",
+ "predeploy": "npm run build",
+ "deploy": "gh-pages -d build -b gh-pages"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "devDependencies": {
+ "@craco/craco": "^7.1.0",
+ "@tailwindcss/typography": "^0.5.19",
+ "autoprefixer": "^10.4.21",
+ "cross-env": "^10.1.0",
+ "gh-pages": "^6.3.0",
+ "postcss": "^8.5.6",
+ "prettier": "^3.6.2",
+ "tailwindcss": "^3.4.18"
+ }
+}
+
+Top-Level Fields
+
+name: "fezcodex" - The name of the project. This is often used for npm packages and identifies your project.
+version: "0.1.0" - The current version of the project. Follows semantic versioning (major.minor.patch).
+private: true - Indicates that the package is not intended to be published to a public npm registry. This is common for application-level projects.
+homepage: "https://fezcode.com" - Specifies the homepage URL for the project. For applications deployed to GitHub Pages, this is often the live URL.
+
+dependencies
+This section lists all the packages required by the application to run in production. These are core libraries that your code directly uses.
+
+@phosphor-icons/react: Provides a flexible icon library with a focus on consistency and customization.
+@testing-library/dom, @testing-library/jest-dom, @testing-library/react, @testing-library/user-event: These are testing utilities that facilitate writing user-centric tests for React components. They help ensure the application behaves as expected from a user's perspective.
+framer-motion: A powerful and easy-to-use library for creating animations and interactive elements in React applications.
+front-matter: A utility for parsing front-matter (metadata) from strings, typically used with Markdown files.
+react: The core React library itself.
+react-dom: Provides DOM-specific methods that enable React to interact with the web browser's DOM.
+react-icons: Another popular library offering a wide range of customizable SVG icons from various icon packs.
+react-markdown: A React component that securely renders Markdown as React elements, allowing you to display Markdown content in your application.
+react-router-dom: The standard library for client-side routing in React applications, allowing navigation between different views.
+react-scripts: A package from Create React App that provides scripts for common development tasks like starting a development server, building for production, and running tests.
+react-slick / slick-carousel: Libraries used for creating carousels or sliders, likely for displaying image galleries or testimonials.
+react-syntax-highlighter: A component that enables syntax highlighting for code blocks, often used in conjunction with react-markdown to display code snippets beautifully.
+web-vitals: A library for measuring and reporting on a set of standardized metrics that reflect the real-world user experience on your website.
+
+scripts
+This object defines a set of command-line scripts that can be executed using npm run <script-name>. These automate common development and deployment tasks.
+
+prestart: "node scripts/generateWallpapers.js" - A pre-script hook that runs before the start script. In this case, it executes a Node.js script to generate wallpapers, likely for dynamic backgrounds or assets.
+start: "craco start" - Starts the development server. craco (Create React App Configuration Override) is used here to allow customizing the underlying Webpack/Babel configuration of react-scripts without ejecting the CRA setup.
+prebuild: "node scripts/generateWallpapers.js" - Similar to prestart, this runs before the build script, ensuring assets are generated before the production build.
+build: "craco build" - Creates a production-ready build of the application, optimizing and bundling all assets for deployment.
+test: "craco test" - Runs the project's test suite.
+eject: "react-scripts eject" - This is a one-way operation that removes the single build dependency from your project, giving you full control over the Webpack configuration files and build scripts. It's rarely used unless deep customization is needed.
+lint: "eslint \"src/**/*.{js,jsx}\" \"scripts/**/*.js\" --fix" - Runs ESLint, a tool for identifying and reporting on patterns in JavaScript code to maintain code quality and style. The --fix flag attempts to automatically fix some issues.
+format: "prettier --write \"src/**/*.{js,jsx,css,json}\"" - Runs Prettier, an opinionated code formatter, to ensure consistent code style across the project. The --write flag formats files in place.
+predeploy: "npm run build" - Runs the build script before the deploy script, ensuring that the latest production build is created before deployment.
+deploy: "gh-pages -d build -b gh-pages" - Deploys the build directory to the gh-pages branch of the GitHub repository, facilitating hosting on GitHub Pages.
+
+eslintConfig
+This field configures ESLint. "extends": ["react-app", "react-app/jest"] means it's extending the recommended ESLint configurations provided by Create React App, along with specific rules for Jest testing.
+browserslist
+This field specifies the target browsers for your client-side code. This is used by tools like Babel and Autoprefixer to ensure your JavaScript and CSS are compatible with the specified browser versions.
+
+production: Defines the browser targets for the production build (e.g., browsers with more than 0.2% market share, excluding Internet Explorer-era browsers and Opera Mini).
+development: Defines less strict browser targets for development, usually focusing on the latest versions of common development browsers.
+
+devDependencies
+These are packages required only for development and building the project, not for the application to run in production. They provide tools, testing utilities, and build-related functionalities.
+
+@craco/craco: The main Craco package that allows overriding Create React App's Webpack configuration.
+@tailwindcss/typography: A Tailwind CSS plugin that provides a set of prose classes to add beautiful typographic defaults to raw HTML or Markdown, improving readability of content.
+autoprefixer: A PostCSS plugin that adds vendor prefixes to CSS rules, ensuring cross-browser compatibility.
+cross-env: A utility that provides a universal way to set environment variables across different operating systems, commonly used in npm scripts.
+gh-pages: A tool specifically for publishing content to the gh-pages branch on GitHub, used for deploying to GitHub Pages.
+postcss: A tool for transforming CSS with JavaScript plugins. Tailwind CSS relies on PostCSS.
+prettier: The code formatter used in the format script.
+tailwindcss: The core Tailwind CSS framework, enabling utility-first styling in the project.
+
+This package.json file provides a comprehensive insight into the project's setup, dependencies, and available scripts for development, testing, and deployment.
+]]>
+ src/index.js Entry Point Explained
+src/index.js is the absolute entry point of your React application. It's the first JavaScript file that gets executed when your web page loads. Its primary responsibility is to render your root React component (App in this case) into the HTML document.
import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+import App from './App';
+import reportWebVitals from './reportWebVitals';
- window.addEventListener('scroll', handleScroll);
- return () => window.removeEventListener('scroll', handleScroll);
- }, [post]);
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render(
+ <React.StrictMode>
+ <App />
+ </React.StrictMode>,
+);
- return (
- // ...
- <div
- ref={contentRef} // Attach the ref to the div element
- className="prose prose-xl prose-dark max-w-none"
- >
- {/* ... Markdown content ... */}
- </div>
- // ...
- );
-};
+// If you want to start measuring performance in your app, pass a function
+// to log results (for example: reportWebVitals(console.log))
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+reportWebVitals();
+
+import React from 'react';
-Explanation:
-const contentRef = useRef(null);: A ref object named contentRef is created and initialized with null. At this point, contentRef.current is null.<div ref={contentRef}>: The ref object is attached to the div element that contains the blog post's Markdown content. Once the component renders, React will set contentRef.current to point to this actual DOM div element.if (contentRef.current): Inside the useEffect's handleScroll function, contentRef.current is checked to ensure that the DOM element is available before attempting to access its properties (like scrollHeight or clientHeight).document.documentElement: While contentRef.current gives a reference to the specific content div, the scroll calculation here uses document.documentElement (the <html> element) to get the overall page scroll position and dimensions. This is a common pattern for tracking global scroll progress.useRef vs. useStateIt's important to understand when to use useRef versus useState:
| Feature | -useState |
-useRef |
-
|---|---|---|
| Purpose | -Manages state that triggers re-renders. | -Accesses DOM elements or stores mutable values that don't trigger re-renders. | -
| Re-renders | -Updates to state variables cause component re-renders. | -Updates to ref.current do not cause re-renders. |
-
| Value Persistence | -Value persists across re-renders. | -Value persists across re-renders. | -
| Mutability | -State is generally treated as immutable (updated via setState). |
-ref.current is directly mutable. |
-
When to use useRef:
import React from 'react';: This line imports the React library. Even though you might not directly use React.createElement in JSX, importing React is traditionally required by Babel (the JavaScript compiler) to transform JSX into React.createElement calls. In newer versions of React and Babel, this might be optimized away, but it's still a common practice.import ReactDOM from 'react-dom/client';
+
+import ReactDOM from 'react-dom/client';: This imports the ReactDOM client-specific library, which provides methods to interact with the DOM (Document Object Model) in a web browser. Specifically, react-dom/client is the modern API for client-side rendering with React 18+.import './index.css';
+
+import './index.css';: This line imports the global CSS stylesheet for the application. When bundled, Webpack (or a similar tool used by Create React App/Craco) processes this import, often injecting the styles into the HTML document at runtime or extracting them into a separate CSS file.import App from './App';
+
+import App from './App';: This imports the main App component, which serves as the root of your entire React component tree. The App component will contain the application's layout, routing, and other main functionalities.import reportWebVitals from './reportWebVitals';
+
+import reportWebVitals from './reportWebVitals';: This imports a utility function that helps measure and report on your application's Web Vitals. Web Vitals are a set of metrics from Google that quantify the user experience of a web page.const root = ReactDOM.createRoot(document.getElementById('root'));
+
+ReactDOM.createRoot(document.getElementById('root')): This is the modern way to initialize a React application for client-side rendering (React 18+). It finds the HTML element with the ID root (which is typically found in public/index.html) and creates a React root. This root object is where your React application will be attached to the DOM.root.render(
+ <React.StrictMode>
+ <App />
+ </React.StrictMode>,
+);
+
+root.render(...): This method tells React to display the App component inside the root DOM element. Whatever is rendered within root.render will be managed by React.
<React.StrictMode>: This is a wrapper component that helps identify potential problems in an application. It activates additional checks and warnings for its descendants during development mode. For example, it helps detect deprecated lifecycles, unexpected side effects, and more. It does not render any visible UI; it's purely a development tool.<App />: This is your main application component, as imported earlier. All other components and the entire UI will be rendered as children of this App component.reportWebVitals();
+
+reportWebVitals();: This function call initiates the measurement and reporting of Web Vitals metrics, which can be useful for performance monitoring and optimization. The function in reportWebVitals.js typically sends these metrics to an analytics endpoint or logs them to the console.useRef provides a way to "escape" React's declarative paradigm when necessary, offering direct access to the underlying DOM or a persistent mutable storage for values that don't need to be part of the component's reactive state. It's a powerful tool for specific use cases where direct imperative manipulation or persistent non-state values are required.
src/index.js is the foundational file where your React application begins its life in the browser. It sets up the bridge between your React code and the actual HTML document, ensuring your components are rendered and managed correctly, and optionally enables development tools like Strict Mode and performance monitoring with Web Vitals.
fezcodex
-Toast notifications are a staple of modern web applications. They provide non-intrusive feedback to users about the result of their actions. In the fezcodex project, we have a robust and reusable toast system. This article will break down how it works, from its architecture to the React magic that holds it all together.
The toast system is elegantly designed around three key parts that work in harmony:
-ToastContext.js (The Brains): This is the central manager. It wraps our entire application, creating a "context" that any component can plug into. It holds the list of all active toasts and provides the functions (addToast, removeToast) to modify that list. It's also responsible for rendering the container where the toasts appear.
useToast.js (The Public API): This is a custom React Hook that acts as a clean and simple gateway. Instead of components needing to know about the underlying context, they can just use this hook to get access to the addToast function. It's the "button" that other components press to request a toast.
Toast.js (The Notification UI): This component represents a single toast message. It's responsible for its own appearance, animations, and, most importantly, its own demise. It knows how long it should be on screen and contains the logic to remove itself after its time is up.
useState - Where Does the State Go?This is the crucial question. In ToastContext.js, we have this line:
const [toasts, setToasts] = useState([]);
+ 004 - src/App.js Main Component Explained
+src/App.js is the main component of your React application. It acts as the root of your component tree (after index.js renders it) and is responsible for setting up global configurations like routing, layout, and context providers that are available throughout your application.
+import React from 'react';
+import { HashRouter as Router } from 'react-router-dom';
+import Layout from './components/Layout';
+import AnimatedRoutes from './components/AnimatedRoutes';
+import { ToastContext } from './components/ToastContext';
+import ScrollToTop from './components/ScrollToTop';
+
+function App() {
+ return (
+ <Router>
+ <ScrollToTop />
+ <ToastContext>
+ <Layout>
+ <AnimatedRoutes />
+ </Layout>
+ </ToastContext>
+ </Router>
+ );
+}
+
+export default App;
-When a component function runs, all its internal variables are created and then discarded when it's done. So how does the toasts array not just reset to [] every single time?
-React Remembers.
-The useState hook is a request to React to create and manage a piece of state on behalf of your component.
-
-First Render: The very first time ToastContext renders, React sees useState([]). It creates a "memory cell" for this specific component instance and puts an empty array [] inside it. It then returns that array to the component as the toasts variable.
-
-State Updates: When you call addToast, it eventually calls setToasts(...). This function doesn't change the state directly. Instead, it sends a message to React saying, "I have a new value for this state. Please update it and re-render the component."
-
-Subsequent Renders: When React re-renders ToastContext, it arrives at the useState([]) line again. But this time, React knows it has already created a state for this component. It ignores the initial value ([]) and instead provides the current value from its internal memory—the updated array of toasts.
-
-
-This is the fundamental principle of React Hooks: they allow your function components to have stateful logic that persists across renders, managed by React itself.
-Part 3: The Full Lifecycle of a Toast
-Let's tie it all together by following a single toast from birth to death.
-
-The Call: A user performs an action in a component (e.g., the Word Counter). That component calls addToast({ title: 'Success!', ... }).
-
-The Context: The useToast hook provides the addToast function from the ToastContext's context.
-
-The State Update: The addToast function in ToastContext runs. It creates a new toast object with a unique ID and calls setToasts([newToast, ...otherToasts]).
-
-The Re-render: React receives the state update request and schedules a re-render for ToastContext.
+Line-by-Line Explanation
+Imports
+import React from 'react';
+
+
+import React from 'react';: Imports the React library, necessary for defining React components and using JSX.
+
+import { HashRouter as Router } from 'react-router-dom';
+
+
+import { HashRouter as Router } from 'react-router-dom';: Imports HashRouter from the react-router-dom library and renames it to Router for convenience. HashRouter uses the hash portion of the URL (e.g., /#/blog) to keep your UI in sync with the URL. This is often preferred for static site deployments like GitHub Pages because it doesn't require server-side configuration for routing.
+
+import Layout from './components/Layout';
+
+
+import Layout from './components/Layout';: Imports the Layout component. This component likely defines the overall structure of your application, such as headers, footers, and sidebars, and wraps the main content area.
+
+import AnimatedRoutes from './components/AnimatedRoutes';
+
+
+import AnimatedRoutes from './components/AnimatedRoutes';: Imports the AnimatedRoutes component. This component is responsible for defining the application's routes and likely incorporates animation for page transitions, possibly using a library like framer-motion.
+
+import { ToastContext } from './components/ToastContext';
+
+
+import { ToastContext } from './components/ToastContext';: Imports the ToastContext component. This component is part of React's Context API pattern. It makes a toast (a small, temporary notification) functionality available to all its child components without having to pass props down manually at every level.
+
+import ScrollToTop from './components/ScrollToTop';
+
+
+import ScrollToTop from './components/ScrollToTop';: Imports the ScrollToTop component. This component is typically used in conjunction with routing to automatically scroll the window to the top of the page whenever the route changes, providing a better user experience.
+
+The App Component
+function App() {
+ return (
+ <Router>
+ <ScrollToTop />
+ <ToastContext>
+ <Layout>
+ <AnimatedRoutes />
+ </Layout>
+ </ToastContext>
+ </Router>
+ );
+}
+
+
+function App() { ... }: This defines a functional React component named App. Functional components are the modern way to write React components and are essentially JavaScript functions that return JSX.
-The Render: ToastContext runs again. It calls useState, and React hands it the new array containing our new toast. The component's return statement is executed, and its .map() function now loops over an array that includes the new toast.
+return (...): The return statement contains the JSX (JavaScript XML) that defines the UI structure for the App component.
+
+<Router>: This is the HashRouter component from react-router-dom. It wraps the entire application, enabling client-side routing. Any component within this Router can use routing features like Link and useParams.
-The Birth: A new <Toast /> component is rendered on the screen. It receives its id, title, message, and duration as props.
+<ScrollToTop />: This component is rendered directly inside the Router. Its effect (scrolling to top on route change) will apply globally to the application.
-The Countdown: Inside the new <Toast /> component, a useEffect hook fires. It starts a setTimeout timer for the given duration.
+<ToastContext>: This component wraps the Layout and AnimatedRoutes. This means that any component rendered within the Layout or AnimatedRoutes will have access to the toast functionality provided by the ToastContext via the useContext hook.
-The End: When the timer finishes, it calls the removeToast(id) function that was passed down as a prop.
+<Layout>: This component defines the common structure (e.g., header, footer, navigation) that will be present on most pages. It wraps the AnimatedRoutes component, meaning the routed content will be displayed within this layout.
-The Cleanup: removeToast in the ToastContext calls setToasts(...) again, this time with an array that filters out the toast with the matching ID.
+<AnimatedRoutes />: This component is where the actual route definitions (e.g., /blog, /about, /projects) are handled. When the URL changes, AnimatedRoutes will render the appropriate page component (e.g., BlogPostPage, HomePage) within the Layout.
-The Final Re-render: React processes the state update, re-renders the ToastContext, and the toast is no longer in the array. It vanishes from the screen.
+
-
-Conclusion
-The fezcodex toast system is a perfect microcosm of modern React development. It shows how to use Context to provide global functionality without cluttering components, and it relies on the magic of the useState hook to give components a memory that persists between renders. By letting React manage the state, we can write declarative UI that simply reacts to state changes.
-]]>
+
+Export
+export default App;
+
+
+export default App;: This makes the App component the default export of this module, allowing it to be imported by other files (like src/index.js).
+
+Summary
+src/App.js orchestrates the main structure and global functionalities of the application. It sets up routing, provides global context for notifications, and defines the overarching layout, ensuring a consistent user experience across different pages.
+]]>Custom Hooks are a powerful feature in React that allow you to extract reusable stateful logic from components. They are JavaScript functions whose names start with use and that can call other Hooks. Custom Hooks solve the problem of sharing logic between components without relying on prop drilling or complex patterns like render props or higher-order components.
A custom Hook is a JavaScript function that:
-use (e.g., useFriendStatus, useToast). This naming convention is crucial for React to know that it's a Hook and to apply the rules of Hooks (e.g., only call Hooks at the top level of a React function).useState, useEffect, useContext).useToast Custom Hook (src/hooks/useToast.js)This project provides an excellent example of a custom hook: useToast. It encapsulates the logic for accessing the toast notification system's addToast and removeToast functions.
src/hooks/useToast.jsimport { useContext } from 'react';
-import { ToastContext } from '../components/ToastContext';
-
-export const useToast = () => {
- return useContext(ToastContext);
-};
-
-Explanation:
-import { useContext } from 'react';: The custom hook itself uses another built-in Hook, useContext, to access the value provided by the ToastContext.import { ToastContext } from '../components/ToastContext';: It imports the ToastContext object, which was created in ToastContext.js.export const useToast = () => { ... };: This defines the custom hook. Its name useToast clearly indicates its purpose and follows the naming convention.return useContext(ToastContext);: The core of this hook. It retrieves the value (which contains addToast and removeToast functions) from the nearest ToastContext.Provider in the component tree and returns it. This means any component calling useToast() will receive these functions.useToast is Used in a Component (e.g., BlogPostPage.js)// Inside BlogPostPage.js (or any other component that needs toasts)
+ 005 - src/pages/BlogPostPage.js Component Explained
+src/pages/BlogPostPage.js is a critical component responsible for displaying individual blog posts. It handles fetching the post content and metadata, rendering Markdown, syntax highlighting code blocks, and managing UI interactivity like copying code or opening code in a modal. It also includes navigation for series posts and robust error handling for missing content.
+import React, { useState, useEffect, useRef } from 'react';
+import { useParams, Link, useNavigate } from 'react-router-dom';
+import ReactMarkdown from 'react-markdown';
+import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
+import {
+ ArrowSquareOut,
+ ArrowsOutSimple,
+ Clipboard,
+ ArrowLeft,
+} from '@phosphor-icons/react';
+import { customTheme } from '../utils/customTheme';
+import PostMetadata from '../components/PostMetadata';
+import CodeModal from '../components/CodeModal';
import { useToast } from '../hooks/useToast';
-const CodeBlock = ({ /* ... */ }) => {
- const { addToast } = useToast(); // Access addToast function
+// ... LinkRenderer and CodeBlock components (explained below)
+
+const BlogPostPage = () => {
+ const { slug, seriesSlug, episodeSlug } = useParams();
+ const navigate = useNavigate();
+ const currentSlug = episodeSlug || slug; // Use episodeSlug if present, otherwise use slug
+ const [post, setPost] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [readingProgress, setReadingProgress] = useState(0);
+ const [isAtTop, setIsAtTop] = useState(true); // New state for tracking if at top
+ const contentRef = useRef(null);
+ const [isModalOpen, setIsModalToOpen] = useState(false);
+ const [modalContent, setModalContent] = useState('');
+
+ const openModal = (content) => {
+ setModalContent(content);
+ setIsModalToOpen(true);
+ };
+
+ const closeModal = () => {
+ setIsModalToOpen(false);
+ setModalContent('');
+ };
+
+ useEffect(() => {
+ const fetchPost = async () => {
+ setLoading(true);
+ console.log('Fetching post for currentSlug:', currentSlug);
+ try {
+ const [postContentResponse, shownPostsResponse] = await Promise.all([
+ fetch(`/posts/${currentSlug}.txt`),
+ fetch('/posts/shownPosts.json'),
+ ]);
+
+ console.log('postContentResponse:', postContentResponse);
+ console.log('shownPostsResponse:', shownPostsResponse);
+
+ let postBody = '';
+ if (postContentResponse.ok) {
+ postBody = await postContentResponse.text();
+ // Check if the fetched content is actually HTML (indicating a fallback to index.html)
+ if (postBody.trim().startsWith('<!DOCTYPE html>')) {
+ console.error('Fetched content is HTML, not expected post content for:', currentSlug);
+ navigate('/404'); // Redirect to 404 page
+ return; // Stop further processing
+ }
+ } else {
+ console.error('Failed to fetch post content for:', currentSlug);
+ navigate('/404'); // Redirect to 404 page
+ return; // Stop further processing
+ }
+
+ let postMetadata = null;
+ let seriesPosts = [];
+ if (shownPostsResponse.ok) {
+ const allPosts = await shownPostsResponse.json();
+ postMetadata = allPosts.find((item) => item.slug === currentSlug);
- const handleCopy = () => {
- // ... copy logic ...
- addToast({
- title: 'Success',
- message: 'Copied to clipboard!',
- duration: 3000,
- });
- // ...
- };
- // ...
-};
-
-By calling const { addToast } = useToast();, the CodeBlock component (or any other component) gains direct access to the addToast function without needing to know where ToastContext is defined or how the toast state is managed. This makes the CodeBlock component cleaner and more focused on its primary responsibility.
-Another Potential Custom Hook (Conceptual Example)
-Consider the scroll tracking logic in BlogPostPage.js:
-// src/pages/BlogPostPage.js - inside BlogPostPage component
-const [readingProgress, setReadingProgress] = useState(0);
-const [isAtTop, setIsAtTop] = useState(true);
-const contentRef = useRef(null);
+ if (postMetadata && postMetadata.series) {
+ seriesPosts = allPosts
+ .filter((item) => item.series === postMetadata.series)
+ .sort((a, b) => a.seriesIndex - b.seriesIndex);
+ }
+ } else {
+ console.error('Failed to fetch shownPosts.json');
+ }
-useEffect(() => {
- const handleScroll = () => {
- if (contentRef.current) {
- const { scrollTop, scrollHeight, clientHeight } =
- document.documentElement;
- const totalHeight = scrollHeight - clientClientHeight;
- const currentProgress = (scrollTop / totalHeight) * 100;
- setReadingProgress(currentProgress);
- setIsAtTop(scrollTop === 0);
- }
- };
+ console.log('postMetadata:', postMetadata);
+ console.log('postBody length:', postBody.length);
- window.addEventListener('scroll', handleScroll);
- return () => window.removeEventListener('scroll', handleScroll);
-}, [post]);
-
-This logic could be extracted into a custom hook, for example, useScrollProgress:
-// src/hooks/useScrollProgress.js (Conceptual)
-import { useState, useEffect, useRef } from 'react';
+ if (postMetadata && postContentResponse.ok) {
+ setPost({ attributes: postMetadata, body: postBody, seriesPosts });
+ console.log('Post set:', { attributes: postMetadata, body: postBody, seriesPosts });
+ } else {
+ setPost({ attributes: { title: 'Post not found' }, body: '' });
+ console.log('Post not found or content not fetched.');
+ }
+ } catch (error) {
+ console.error('Error fetching post or shownPosts.json:', error);
+ setPost({ attributes: { title: 'Error loading post' }, body: '' });
+ } finally {
+ setLoading(false);
+ }
+ };
-const useScrollProgress = (contentRef, dependency) => {
- const [readingProgress, setReadingProgress] = useState(0);
- const [isAtTop, setIsAtTop] = useState(true);
+ fetchPost();
+ }, [currentSlug]);
useEffect(() => {
const handleScroll = () => {
@@ -4610,476 +4883,502 @@ const useScrollProgress = (contentRef, dependency) => {
const totalHeight = scrollHeight - clientHeight;
const currentProgress = (scrollTop / totalHeight) * 100;
setReadingProgress(currentProgress);
- setIsAtTop(scrollTop === 0);
+ setIsAtTop(scrollTop === 0); // Update isAtTop based on scroll position
}
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
- }, [contentRef, dependency]); // Re-run if contentRef or dependency changes
+ }, [post]); // Re-attach scroll listener if post changes
- return { readingProgress, isAtTop };
-};
+ if (loading) {
+ // Skeleton loading screen for BlogPostPage
+ return (
+ <div className="bg-gray-900 py-16 sm:py-24 animate-pulse">
+ <div className="mx-auto max-w-7xl px-6 lg:px-8">
+ <div className="lg:grid lg:grid-cols-4 lg:gap-8">
+ <div className="lg:col-span-3">
+ <div className="h-8 bg-gray-800 rounded w-1/4 mb-4"></div>
+ <div className="h-12 bg-gray-800 rounded w-3/4 mb-8"></div>
+ <div className="space-y-4">
+ <div className="h-6 bg-gray-800 rounded w-full"></div>
+ <div className="h-6 bg-gray-800 rounded w-5/6"></div>
+ <div className="h-6 bg-gray-800 rounded w-full"></div>
+ <div className="h-6 bg-gray-800 rounded w-2/3"></div>
+ </div>
+ </div>
+ <div className="hidden lg:block">
+ <div className="bg-gray-800 rounded-lg shadow-lg p-6">
+ <div className="h-8 bg-gray-700 rounded w-1/2 mb-4"></div>
+ <div className="space-y-2">
+ <div className="h-4 bg-gray-700 rounded w-full"></div>
+ <div className="h-4 bg-gray-700 rounded w-3/4"></div>
+ <div className="h-4 bg-gray-700 rounded w-1/2"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
-export default useScrollProgress;
-
-Then, BlogPostPage.js would become cleaner:
-// src/pages/BlogPostPage.js - inside BlogPostPage component
-const contentRef = useRef(null);
-const { readingProgress, isAtTop } = useScrollProgress(contentRef, post);
-// ...
-
-This demonstrates how custom hooks can abstract away complex logic, making components more focused and easier to read.
-Summary
-Custom Hooks are a fundamental pattern in modern React development for sharing stateful logic. By following the use naming convention and leveraging other built-in Hooks, you can create highly reusable and maintainable code that enhances the overall architecture of your React applications.
-]]>
- fetch API
-In modern web applications, fetching data from a server is a fundamental operation. The fetch API provides a powerful and flexible interface for making network requests, replacing older methods like XMLHttpRequest. This project uses fetch to retrieve blog post content and metadata.
fetch API BasicsThe fetch() method starts the process of fetching a resource from the network, returning a Promise that fulfills once the response is available. A fetch() call takes one mandatory argument, the path to the resource you want to fetch.
fetch(url)
- .then(response => response.json()) // or .text(), .blob(), etc.
- .then(data => console.log(data))
- .catch(error => console.error('Error:', error));
-
-fetch(url): Initiates the request. Returns a Promise that resolves to a Response object.response.json() / response.text(): The Response object has methods to extract the body content. json() parses the response as JSON, while text() parses it as plain text. Both return a Promise..then(): Handles the successful resolution of a Promise..catch(): Handles any errors that occur during the fetch operation or in the subsequent .then() blocks.src/pages/BlogPostPage.jsLet's look at how fetch is used in BlogPostPage.js to get both the blog post's text content and its metadata.
// src/pages/BlogPostPage.js - inside the useEffect's fetchPost function
-// ...
-try {
- const [postContentResponse, shownPostsResponse] = await Promise.all([
- fetch(`/posts/${currentSlug}.txt`),
- fetch('/posts/shownPosts.json'),
- ]);
+ // if (!post) { // This check is now mostly handled by the navigate('/404') above.
+ // return <div className="text-center py-16">Post not found</div>;
+ // }
- // Handling post content response
- let postBody = '';
- if (postContentResponse.ok) { // Check if the HTTP status code is in the 200-299 range
- postBody = await postContentResponse.text(); // Extract response body as text
- // Additional check for HTML fallback content
- if (postBody.trim().startsWith('<!DOCTYPE html>')) {
- console.error('Fetched content is HTML, not expected post content for:', currentSlug);
- navigate('/404');
- return;
- }
- } else {
- console.error('Failed to fetch post content for:', currentSlug);
- navigate('/404');
- return;
+ // Conditional rendering for post not found after loading or if attributes are missing
+ if (!post || !post.attributes || post.body === '') {
+ // If post is null, or attributes are missing (e.g., from shownPosts.json), or body is empty,
+ // it implies the post couldn't be fully loaded or found. Ideally, navigate would handle this.
+ // This serves as a fallback display.
+ return (
+ <div className="text-center py-16 text-gray-400">
+ <h2 className="text-3xl font-bold mb-4">Post Not Found</h2>
+ <p className="text-lg">The blog post you are looking for does not exist or could not be loaded.</p>
+ <Link to="/blog" className="text-primary-400 hover:underline mt-4 inline-block">Go back to Blog</Link>
+ </div>
+ );
}
- // Handling metadata response
- let postMetadata = null;
- if (shownPostsResponse.ok) { // Check if the HTTP status code is in the 200-299 range
- const allPosts = await shownPostsResponse.json(); // Extract response body as JSON
- postMetadata = allPosts.find((item) => item.slug === currentSlug);
- // ... further processing of series posts
- } else {
- console.error('Failed to fetch shownPosts.json');
- }
+ const currentPostIndex = post.seriesPosts ? post.seriesPosts.findIndex(
+ (item) => item.slug === currentSlug,
+ ) : -1;
+ const prevPost = currentPostIndex > 0 ? post.seriesPosts[currentPostIndex - 1] : null;
+ const nextPost = post.seriesPosts && currentPostIndex < post.seriesPosts.length - 1
+ ? post.seriesPosts[currentPostIndex + 1]
+ : null;
+
+ const backLink = seriesSlug ? `/blog/series/${seriesSlug}` : '/blog';
+ const backLinkText = seriesSlug ? 'Back to Series' : 'Back to Blog';
+
+ return (
+ <div className="bg-gray-900 py-16 sm:py-24">
+ <div className="mx-auto max-w-7xl px-6 lg:px-8">
+ <div className="lg:grid lg:grid-cols-4 lg:gap-8">
+ <div className="lg:col-span-3">
+ <Link
+ to={backLink}
+ className="text-primary-400 hover:underline flex items-center justify-center gap-2 text-lg mb-4"
+ >
+ <ArrowLeft size={24} /> {backLinkText}
+ </Link>
+ <div
+ ref={contentRef}
+ className="prose prose-xl prose-dark max-w-none"
+ >
+ <ReactMarkdown
+ components={{
+ a: LinkRenderer,
+ code: (props) => (
+ <CodeBlock {...props} openModal={openModal} />
+ ),
+ }}
+ >
+ {post.body}
+ </ReactMarkdown>
+ </div>
+ {(prevPost || nextPost) && (
+ <div className="mt-8 flex justify-between items-center border-t border-gray-700 pt-8">
+ {prevPost && (
+ <Link
+ to={seriesSlug ? `/blog/series/${seriesSlug}/${prevPost.slug}` : `/blog/${prevPost.slug}`}
+ className="text-primary-400 hover:underline flex items-center gap-2"
+ >
+ <ArrowLeft size={20} /> Previous: {prevPost.title}
+ </Link>
+ )}
+ {nextPost && (
+ <Link
+ to={seriesSlug ? `/blog/series/${seriesSlug}/${nextPost.slug}` : `/blog/${nextPost.slug}`}
+ className="text-primary-400 hover:underline flex items-center gap-2 ml-auto"
+ >
+ Next: {nextPost.title} <ArrowLeft size={20} className="rotate-180" />
+ </Link>
+ )}
+ </div>
+ )}
+ </div>
+ <div className="hidden lg:block">
+ <PostMetadata
+ metadata={post.attributes}
+ readingProgress={readingProgress}
+ isAtTop={isAtTop}
+ overrideDate={post.attributes.date}
+ updatedDate={post.attributes.updated}
+ seriesPosts={post.seriesPosts}
+ />
+ </div>
+ </div>
+ </div>
+ <CodeModal isOpen={isModalOpen} onClose={closeModal}>
+ {modalContent}
+ </CodeModal>
+ </div>
+ );
+};
- // Final check and state update
- if (postMetadata && postContentResponse.ok) {
- setPost({ attributes: postMetadata, body: postBody, seriesPosts });
- } else {
- setPost({ attributes: { title: 'Post not found' }, body: '' });
- }
-} catch (error) {
- console.error('Error fetching post or shownPosts.json:', error);
- setPost({ attributes: { title: 'Error loading post' }, body: '' });
-} finally {
- setLoading(false);
-}
-// ...
+export default BlogPostPage;
-fetch Usage in BlogPostPage.js:Promise.all([...]): As discussed in 011-javascript-fundamentals.md, Promise.all is used to concurrently fetch two resources:
fetch("/posts/${currentSlug}.txt"): Fetches the actual Markdown content of the blog post. The currentSlug is dynamically inserted into the URL.fetch('/posts/shownPosts.json'): Fetches a JSON file containing metadata for all blog posts.response.ok Property: After a fetch call, the Response object has an ok property. This is a boolean that indicates whether the HTTP response status is in the 200-299 range (inclusive). It's crucial to check response.ok because fetch does not throw an error for HTTP error statuses (like 404 or 500) by default; it only throws an error for network failures.
response.text() and response.json(): These methods are used to parse the response body:
postContentResponse.text(): Used for the .txt file, as it contains plain text (Markdown).shownPostsResponse.json(): Used for the .json file, as it contains structured JSON data.Error Handling (HTTP Status):
-postContentResponse.ok is false (meaning the .txt file was not found or returned an error status), an error is logged, and the application navigates to the /404 page using navigate('/404').if (postBody.trim().startsWith('<!DOCTYPE html>')) was added to handle the scenario where the development server might return the index.html (with a 200 status) instead of a 404 for a non-existent file. This ensures that even in such cases, the user is redirected to the 404 page.shownPostsResponse.ok is false, an error is logged, but the application doesn't navigate to 404 directly, as the post content might still be available, just without rich metadata.try...catch Block: The entire asynchronous operation is wrapped in a try...catch block. This catches any network errors (e.g., server unreachable) or errors that occur during the processing of the Promises (e.g., json() parsing error). If an error occurs, it's logged, and the post state is set to indicate an error.
finally Block: The setLoading(false) call is placed in a finally block. This ensures that the loading state is always turned off, regardless of whether the fetch operation succeeded or failed.
The fetch API is a modern, Promise-based way to make network requests in JavaScript. By understanding how to use fetch with async/await, handle Response objects (especially response.ok), and implement robust error handling with try...catch, developers can effectively retrieve and process data from various sources, as demonstrated in the Fezcode project's BlogPostPage.js component.
public/index.html)
-public/index.html is the single HTML page that serves as the entry point for your React application. When a user visits your website, this is the file their browser first loads. The React application then takes over to dynamically render content into this HTML structure.
<!DOCTYPE html>
-<html lang="en" class="dark">
- <head>
- <meta charset="utf-8" />
- <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
- <link rel="icon" type="image/svg+xml" href="%PUBLIC_URL%/favicon.svg" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <meta name="theme-color" content="#000000" />
- <meta
- name="description"
- content="codex by fezcode..."
- />
- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
- <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
- <link href="https://fonts.googleapis.com/css2?family=Arvo&family=Inter&family=Playfair+Display&display=swap" rel="stylesheet">
- <title>fezcodex</title>
- </head>
- <body class="bg-slate-950">
- <noscript>You need to enable JavaScript to run this app.</noscript>
- <div id="root"></div>
- </body>
-</html>
+ 006 - React Basics: Components and Props
+At the core of React applications are components. Components are independent, reusable pieces of UI. They can be thought of as JavaScript functions that return JSX (JavaScript XML), which describes what the UI should look like. React applications are built by composing these components.
+Functional Components
+The project primarily uses functional components, which are JavaScript functions that accept a single props (properties) object argument and return React elements.
+Example: App Component (src/App.js)
+// src/App.js
+import React from 'react';
+// ... imports
+
+function App() {
+ return (
+ <Router>
+ {/* ... other components */}
+ <Layout>
+ <AnimatedRoutes />
+ </Layout>
+ {/* ... */}
+ </Router>
+ );
+}
+
+export default App;
-Explanation of Key Sections
-<!DOCTYPE html>
-
-- This declaration defines the document type to be HTML5.
-
-<html lang="en" class="dark">
-
-- The root element of an HTML page.
-lang="en": Specifies the primary language of the document content as English, which is important for accessibility and search engines.
-class="dark": This class is likely used in conjunction with Tailwind CSS's dark mode configuration (darkMode: 'class' in tailwind.config.js). When this class is present on the <html> element, Tailwind will apply dark mode styles.
-
-<head> Section
-The <head> section contains metadata about the HTML document, which is not displayed on the web page itself but is crucial for browsers, search engines, and other web services.
-
-<meta charset="utf-8" />: Specifies the character encoding for the document, ensuring proper display of various characters.
-<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />: Links to the favicon, the small icon displayed in the browser tab or bookmark list. %PUBLIC_URL% is a placeholder that will be replaced with the public URL of your app during the build process.
-<meta name="viewport" content="width=device-width, initial-scale=1" />: Configures the viewport for responsive design. It sets the width of the viewport to the device width and the initial zoom level to 1, ensuring the page scales correctly on different devices.
-<meta name="theme-color" content="#000000" />: Suggests a color that browsers should use to tint the UI elements (like the address bar in mobile browsers) of the page.
-<meta name="description" content="codex by fezcode..." />: Provides a brief, high-level description of the web page content. This is often used by search engines in search results.
-<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />: Specifies an icon for web clips on iOS devices.
-<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />: Links to a web app manifest file, which provides information about the web application (like name, icons, start URL) in a JSON text file. This is essential for Progressive Web Apps (PWAs).
-<link rel="preconnect" ...> and <link href="https://fonts.googleapis.com/css2?..." rel="stylesheet">: These lines are used to preconnect to Google Fonts and import custom fonts (JetBrains Mono, Space Mono, Arvo, Inter, Playfair Display). preconnect helps establish early connections to improve font loading performance.
-<title>fezcodex</title>: Sets the title of the HTML document, which appears in the browser tab or window title bar.
-
-<body> Section
-The <body> section contains all the content that is visible to the user.
-<body class="bg-slate-950">: The main content area of the page. The bg-slate-950 class is a Tailwind CSS utility class that sets the background color of the body to a very dark slate color, consistent with the project's dark theme.
-<noscript>You need to enable JavaScript to run this app.</noscript>: This content is displayed only if the user's browser has JavaScript disabled. Since React is a JavaScript library, the application cannot function without JavaScript.
-<div id="root"></div>: This is the most crucial part for a React application. It's an empty div element with the ID root. This is the DOM node where your React application (specifically, the App component rendered by src/index.js) will be mounted and take control. All of your React components will be rendered as children of this div.
+function App() { ... }: This defines a functional component named App.
+- The
return statement contains JSX, which is a syntax extension for JavaScript recommended by React to describe UI.
+<Layout> and <AnimatedRoutes> are other components being used within App.
-How React Mounts
-As explained in 003-index-js-entry-point.md:
-// src/index.js
-const root = ReactDOM.createRoot(document.getElementById('root'));
-root.render(
- <React.StrictMode>
- <App />
- </React.StrictMode>,
-);
+Example: Layout Component (src/components/Layout.js)
+Let's look at src/components/Layout.js to see a slightly more complex functional component.
+// src/components/Layout.js
+import React, { useState, useEffect } from 'react';
+import Navbar from './Navbar';
+import Sidebar from './Sidebar';
+import Footer from './Footer';
+// ... other imports
+
+const Layout = ({ children }) => {
+ const [isSidebarOpen, setIsSidebarOpen] = useState(window.innerWidth > 768);
+ // ... other state and effects
+
+ return (
+ <div className="bg-gray-950 min-h-screen font-sans flex">
+ <Sidebar isOpen={isSidebarOpen} toggleSidebar={toggleSidebar} />
+ <div
+ className={`flex-1 flex flex-col transition-all duration-300 ${isSidebarOpen ? 'md:ml-64' : 'md:ml-0'}`}>
+ <Navbar toggleSidebar={toggleSidebar} isSidebarOpen={isSidebarOpen} />
+ <main className="flex-grow">{children}</main>
+ <Footer />
+ </div>
+ </div>
+ );
+};
+
+export default Layout;
-
-- The JavaScript code in
src/index.js (which is eventually bundled and loaded by the browser) finds the <div id="root"> element.
-ReactDOM.createRoot() creates a React root, which is the entry point for React to manage the DOM inside that element.
-root.render(<App />) then tells React to render your main App component (and all its children) inside this root div. From this point on, React efficiently updates and manages the content within this div based on your component's state and props.
-
-Summary
-public/index.html provides the foundational HTML structure and metadata for the web page. It's a relatively simple file because the React application dynamically generates and manages most of the visible content within the designated <div id="root">. This separation allows for a highly dynamic and interactive user experience powered by React.
-]]>
- This project heavily utilizes modern JavaScript features to build a dynamic and interactive user interface. Understanding these fundamental concepts is crucial for comprehending the codebase. This document will highlight several key JavaScript concepts with examples drawn from the project.
-async/await for Asynchronous OperationsAsynchronous operations (like fetching data from a server) are common in web applications. async/await provides a cleaner, more readable way to handle Promises.
async function: A function declared with async always returns a Promise. It allows you to use the await keyword inside it.await keyword: Can only be used inside an async function. It pauses the execution of the async function until the Promise it's waiting for settles (either resolves or rejects), and then resumes the async function's execution with the resolved value.const Layout = ({ children }) => { ... };: This defines another functional component, Layout, using an arrow function syntax. It directly destructures children from the props object. This is a common pattern.src/pages/BlogPostPage.js// src/pages/BlogPostPage.js
-useEffect(() => {
- const fetchPost = async () => { // async function
- setLoading(true);
- try {
- const [postContentResponse, shownPostsResponse] = await Promise.all([ // await Promise.all
- fetch(`/posts/${currentSlug}.txt`),
- fetch('/posts/shownPosts.json'),
- ]);
-
- let postBody = '';
- if (postContentResponse.ok) {
- postBody = await postContentResponse.text(); // await fetch response
- // ...
- }
- // ...
- } catch (error) {
- console.error('Error fetching post or shownPosts.json:', error);
- // ...
- } finally {
- setLoading(false);
- }
- };
-
- fetchPost();
-}, [currentSlug]);
+Props (Properties)
+Props are how you pass data from a parent component to a child component. They are read-only and allow components to be dynamic and reusable.
+Passing Props
+In the App component, you can see Layout being used:
+// Inside App component's return
+<Layout>
+ <AnimatedRoutes />
+</Layout>
+
+Here, AnimatedRoutes is passed as a special prop called children to the Layout component. Whatever content you place between the opening and closing tags of a component becomes its children prop.
+Receiving and Using Props
+In the Layout component, children is received as a prop:
+const Layout = ({ children }) => {
+ // ...
+ return (
+ // ...
+ <main className="flex-grow">{children}</main>
+ // ...
+ );
+};
+
+The Layout component then renders {children} inside its <main> tag, meaning the AnimatedRoutes (or whatever was passed as children) will be rendered in that spot.
+Another example of props in Layout.js:
+<Sidebar isOpen={isSidebarOpen} toggleSidebar={toggleSidebar} />
+<Navbar toggleSidebar={toggleSidebar} isSidebarOpen={isSidebarOpen} />
+Here:
-- The
fetchPost function is declared async because it performs asynchronous network requests.
-await Promise.all([...]) is used to wait for multiple fetch calls (which return Promises) to complete concurrently. This is more efficient than awaiting them one after another if they don't depend on each other.
-await postContentResponse.text() waits for the response body to be fully read as text.
-- The
try...catch...finally block is used for error handling and ensuring setLoading(false) is always called.
+- The
Sidebar component receives two props: isOpen (a boolean state variable) and toggleSidebar (a function).
+- The
Navbar component also receives toggleSidebar and isSidebarOpen.
-2. Promise.all for Concurrent Promises
-Promise.all is a Promise combinator that takes an iterable of Promises as input and returns a single Promise. This returned Promise fulfills when all of the input's Promises have fulfilled, or rejects as soon as any of the input's Promises rejects.
-Example from src/pages/BlogPostPage.js
-// src/pages/BlogPostPage.js
-const [postContentResponse, shownPostsResponse] = await Promise.all([
- fetch(`/posts/${currentSlug}.txt`),
- fetch('/posts/shownPosts.json'),
-]);
+These props are defined in the Layout component's scope and passed down to its child components (Sidebar, Navbar) to control their behavior or appearance. For instance, isOpen might control the visibility of the sidebar, and toggleSidebar would be a function to change that visibility when a button in the Navbar is clicked.
+Summary
+Functional components are the building blocks of React UIs, and props are the essential mechanism for communicating data and functionality between these components in a unidirectional flow (from parent to child). This modular approach makes React applications easier to manage, test, and scale.
+]]>
useState and useEffect
+React Hooks are functions that let you "hook into" React state and lifecycle features from functional components. They allow you to use state and other React features without writing a class. The two most fundamental hooks are useState and useEffect.
useState HookuseState is a Hook that lets you add React state to functional components. It returns a pair of values: the current state, and a function that updates it.
const [stateVariable, setStateVariable] = useState(initialValue);
Promise.all is used to initiate two network requests (fetch for the post content and fetch for the metadata JSON) at the same time. The await keyword then waits for both of them to complete. The results are destructured into postContentResponse and shownPostsResponse.stateVariable: The current value of the state.setStateVariable: A function to update the stateVariable. When this function is called, React will re-render the component.initialValue: The initial value for the state. This can be any JavaScript data type (number, string, boolean, object, array, etc.).filter, find, sort)Modern JavaScript provides powerful array methods that make working with collections of data much easier and more declarative.
src/pages/BlogPostPage.js// src/pages/BlogPostPage.js
-// ... inside fetchPost function
-if (shownPostsResponse.ok) {
- const allPosts = await shownPostsResponse.json();
- postMetadata = allPosts.find((item) => item.slug === currentSlug); // find
-
- if (postMetadata && postMetadata.series) {
- seriesPosts = allPosts
- .filter((item) => item.series === postMetadata.series) // filter
- .sort((a, b) => a.seriesIndex - b.seriesIndex); // sort
- }
-}
+const BlogPostPage = () => {
+ // ...
+ const [post, setPost] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [readingProgress, setReadingProgress] = useState(0);
+ const [isAtTop, setIsAtTop] = useState(true); // New state for tracking if at top
+ const [isModalOpen, setIsModalToOpen] = useState(false);
+ const [modalContent, setModalContent] = useState('');
+ // ...
+};
+In BlogPostPage:
Array.prototype.find(): Returns the value of the first element in the provided array that satisfies the provided testing function. Otherwise, undefined is returned.allPosts.find((item) => item.slug === currentSlug): Finds the first post object in allPosts whose slug property matches currentSlug.[post, setPost] = useState(null): post will hold the blog post data (attributes, body, series posts). It's initialized to null because the data is fetched asynchronously.[loading, setLoading] = useState(true): loading is a boolean that indicates whether the post data is currently being fetched. It starts as true.[readingProgress, setReadingProgress] = useState(0): readingProgress stores the user's scroll progress on the page, initialized to 0.[isAtTop, setIsAtTop] = useState(true): Tracks if the user is at the top of the page.[isModalOpen, setIsModalToOpen] = useState(false): Controls the visibility of a modal, initialized to false (closed).[modalContent, setModalContent] = useState(''): Stores the content to be displayed inside the modal.Array.prototype.filter(): Creates a new array with all elements that pass the test implemented by the provided function.allPosts.filter((item) => item.series === postMetadata.series): Creates a new array containing only posts that belong to the same series as the current post.src/components/Layout.js// src/components/Layout.js
+const Layout = ({ children }) => {
+ const [isSidebarOpen, setIsSidebarOpen] = useState(window.innerWidth > 768);
+ // ...
+};
+
+In Layout:
[isSidebarOpen, setIsSidebarOpen] = useState(window.innerWidth > 768): isSidebarOpen controls the visibility of the sidebar. Its initial value depends on the window width, making the sidebar open by default on larger screens.Array.prototype.sort(): Sorts the elements of an array in place and returns the sorted array. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units..sort((a, b) => a.seriesIndex - b.seriesIndex): Sorts the seriesPosts array numerically based on their seriesIndex property in ascending order.useEffect HookuseEffect is a Hook that lets you perform side effects in functional components. Side effects include data fetching, subscriptions, manually changing the DOM, and other operations that interact with the outside world. It runs after every render of the component by default, but you can control when it runs using its dependency array.
useEffect(() => {
+ // Side effect code here
+ return () => {
+ // Cleanup function (optional)
+ };
+}, [dependency1, dependency2]); // Dependency array (optional)
+
+[], the effect runs only once after the initial render (like componentDidMount). The cleanup runs on unmount (like componentWillUnmount).[prop1, state1]), the effect runs after the initial render and whenever any of the variables in the array change.Object destructuring is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
-src/pages/BlogPostPage.jssrc/pages/BlogPostPage.js (Data Fetching)// src/pages/BlogPostPage.js
-const { slug, seriesSlug, episodeSlug } = useParams();
-// ...
+useEffect(() => {
+ const fetchPost = async () => {
+ setLoading(true);
+ // ... data fetching logic using fetch API ...
+ setLoading(false);
+ };
+
+ fetchPost();
+}, [currentSlug]); // Effect re-runs when currentSlug changes
+This useEffect hook is responsible for fetching the blog post data.
useParams() returns an object containing URL parameters. Object destructuring is used to extract the slug, seriesSlug, and episodeSlug properties directly into variables with the same names.async function fetchPost to handle the asynchronous data retrieval.setLoading(true) is called at the start to show a loading indicator.fetch API is used to get the .txt content and shownPosts.json metadata.[currentSlug] ensures that this effect runs only when the currentSlug (derived from the URL parameters) changes. This prevents unnecessary re-fetches and ensures the correct post is loaded when navigating between posts.src/components/Layout.js// src/components/Layout.js
-const Layout = ({ children }) => {
- // ...
-};
+Example from src/pages/BlogPostPage.js (Scroll Event Listener)
+// src/pages/BlogPostPage.js
+useEffect(() => {
+ const handleScroll = () => {
+ if (contentRef.current) {
+ const { scrollTop, scrollHeight, clientHeight } =
+ document.documentElement;
+ const totalHeight = scrollHeight - clientHeight;
+ const currentProgress = (scrollTop / totalHeight) * 100;
+ setReadingProgress(currentProgress);
+ setIsAtTop(scrollTop === 0); // Update isAtTop based on scroll position
+ }
+ };
+
+ window.addEventListener('scroll', handleScroll);
+ return () => window.removeEventListener('scroll', handleScroll);
+}, [post]); // Re-attach scroll listener if post changes
+This useEffect manages a scroll event listener to calculate reading progress and determine if the user is at the top of the page.
-- In this functional component definition,
({ children }) is using object destructuring to directly extract the children prop from the props object that React passes to the component.
+- It adds an event listener to the
window when the component mounts or when the post state changes.
+- The
return () => { ... } part is a cleanup function. This function runs when the component unmounts or before the effect re-runs due to a dependency change. It's essential here to remove the event listener to prevent memory leaks and unexpected behavior.
+- The dependency array
[post] means the effect (and its cleanup) will re-run if the post object changes, ensuring the scroll listener is correctly attached to the relevant content.
-5. Ternary Operator
-The ternary operator (condition ? exprIfTrue : exprIfFalse) is a shorthand for an if...else statement, often used for conditional rendering or assigning values.
-Example from src/pages/BlogPostPage.js
-// src/pages/BlogPostPage.js
-const currentSlug = episodeSlug || slug; // Use episodeSlug if present, otherwise use slug
-// ...
-const backLink = seriesSlug ? `/blog/series/${seriesSlug}` : '/blog';
-const backLinkText = seriesSlug ? 'Back to Series' : 'Back to Blog';
+Example from src/components/Layout.js (Window Resize Listener)
+// src/components/Layout.js
+useEffect(() => {
+ const handleResize = () => {
+ if (window.innerWidth <= 768) {
+ setIsSidebarOpen(false);
+ }
+ };
+
+ window.addEventListener('resize', handleResize);
+
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ };
+}, []); // Empty dependency array: runs once on mount, cleans up on unmount
+This useEffect in Layout.js handles the sidebar's initial state and responsiveness.
-episodeSlug || slug: This uses the logical OR operator (||) to assign episodeSlug if it's truthy, otherwise it assigns slug. This is a common pattern for providing fallback values.
-seriesSlug ? /blog/series/${seriesSlug} : '/blog': If seriesSlug is truthy, backLink is set to the series URL; otherwise, it defaults to the general blog URL.
+- It adds a
resize event listener to the window.
+- The
handleResize function closes the sidebar if the window width drops below 768 pixels.
+- The empty dependency array
[] ensures that this effect runs only once after the initial render and its cleanup function runs only when the component unmounts. This is perfect for setting up global event listeners that don't need to be re-initialized unless the component is completely removed from the DOM.
Summary
-These JavaScript fundamentals, including asynchronous programming with async/await and Promise.all, efficient data manipulation with array methods, concise variable assignment with object destructuring, and conditional logic with the ternary operator, are extensively used throughout the Fezcode project. Mastering these concepts is key to understanding and contributing to modern React applications.
-]]>
useState and useEffect are powerful tools that bring state management and side effect handling to functional components, making them as capable as class components while often being more concise and easier to reason about. Understanding their usage, especially the role of the dependency array in useEffect, is fundamental to building robust React applications.
This project leverages a combination of traditional CSS and the utility-first framework Tailwind CSS for styling. This approach allows for both rapid development using pre-defined utility classes and fine-grained control with custom CSS when necessary.
-src/index.css - Global Styles and Tailwind Directivessrc/index.css serves as the main entry point for all CSS in the application. It's where Tailwind CSS is integrated and where global base styles and overrides are defined.
@tailwind base;
-@tailwind components;
-@tailwind utilities;
+ 008 - React Context API and useContext
+The React Context API provides a way to pass data through the component tree without having to pass props down manually at every level. This is particularly useful for global data (like user authentication, theme, or in this case, toast notifications) that many components might need access to.
+The Problem Context Solves (Prop Drilling)
+Imagine you have a deeply nested component tree, and a piece of data (e.g., a user object) is needed by a component several levels down. Without Context, you'd have to pass that data as a prop through every intermediate component, even if those components don't directly use the data. This is known as "prop drilling" and can make your code verbose and harder to maintain.
+How Context API Works
+The Context API consists of three main parts:
+
+createContext: Creates a Context object. When React renders a component that subscribes to this Context object, it will read the current context value from the closest matching Provider above it in the tree.
+Provider: A React component that allows consuming components to subscribe to context changes. It accepts a value prop to be passed to consuming components that are descendants of this Provider.
+useContext: A React Hook that lets you read context from a functional component.
+
+Example: Toast Notification System
+This project uses the Context API to manage and display toast notifications globally. Let's examine src/components/ToastContext.js and src/hooks/useToast.js.
+src/components/ToastContext.js (The Provider)
+import React, { createContext, useState, useCallback } from 'react';
+import Toast from './Toast';
-html, body {
- height: 100%;
-}
+export const ToastContext = createContext();
-body {
- margin: 0;
- background-color: #020617;
- font-family: 'Space Mono', 'JetBrains Mono', monospace, sans-serif !important;
- font-weight: 400 !important;
- font-style: normal !important;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
+let id = 0; // Simple counter for unique toast IDs
-code {
- font-family:
- source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
-}
+export const ToastContext = ({ children }) => {
+ const [toasts, setToasts] = useState([]); // State to hold active toasts
+
+ const addToast = useCallback((toast) => {
+ const newToast = { ...toast, id: id++ };
+ setToasts((prevToasts) => {
+ if (prevToasts.length >= 5) { // Limit to 5 toasts
+ const updatedToasts = prevToasts.slice(0, prevToasts.length - 1);
+ return [newToast, ...updatedToasts];
+ }
+ return [newToast, ...prevToasts];
+ });
+ }, []); // Memoize addToast function
-/* ... other custom styles and overrides ... */
+ const removeToast = useCallback((id) => {
+ setToasts((prevToasts) => prevToasts.filter((toast) => toast.id !== id));
+ }, []); // Memoize removeToast function
-:root {
- --color-dev-badge: #44403c; /* stone-700 */
- --color-takes-badge: #065f46; /* emerald-800 */
- --color-series-badge: #e11d48; /* rose-600 */
- --color-dnd-badge: #583fa3; /* violet-400 */
-}
+ return (
+ <ToastContext.Provider value={{ addToast, removeToast }}>
+ {children}
+ <div className="fixed top-28 right-10 z-50">
+ {toasts.map((toast) => (
+ <Toast
+ key={toast.id}
+ id={toast.id}
+ title={toast.title}
+ message={toast.message}
+ duration={toast.duration}
+ removeToast={removeToast}
+ />
+ ))}
+ </div>
+ </ToastContext.Provider>
+ );
+};
-Explanation:
-
-@tailwind base;: This directive injects Tailwind's base styles, which are a set of opinionated defaults that normalize browser styles and provide a solid foundation for building on.
-@tailwind components;: This injects Tailwind's component classes. These are typically larger, more complex classes that you might extract from repeated utility patterns (though this project might not use many custom components).
-@tailwind utilities;: This injects all of Tailwind's utility classes (e.g., flex, pt-4, text-lg, bg-gray-950). These are the core of Tailwind's utility-first approach.
-- Global CSS Resets/Defaults: After the
@tailwind directives, you see standard CSS rules that apply globally:
-html, body { height: 100%; }: Ensures the html and body elements take up the full viewport height.
-body { ... }: Sets a default margin, background-color, font-family, font-weight, font-style, and font smoothing properties for the entire application.
-code { ... }: Defines a specific font stack for <code> elements.
+Explanation:
+
+export const ToastContext = createContext();: A Context object named ToastContext is created. This object will be used by both the Provider and the Consumer.
+ToastContext Component: This is a functional component that will wrap parts of your application (as seen in App.js).
+const [toasts, setToasts] = useState([]);: Manages the array of active toast notifications using useState.
+addToast and removeToast functions: These functions are responsible for adding new toasts to the toasts array and removing them. They are wrapped in useCallback to prevent unnecessary re-creations, which is an optimization for performance.
+<ToastContext.Provider value={{ addToast, removeToast }}>: This is the core of the Provider. It makes the addToast and removeToast functions available to any component that consumes ToastContext and is rendered within this Provider's tree. The value prop is crucial here.
+{children}: This renders whatever components are passed as children to the ToastContext. These children (and their descendants) will have access to the context value.
+- Toast Rendering: The
ToastContext also directly renders the actual Toast components based on the toasts state, positioning them in the top-right corner of the screen.
-- Custom Styles and Overrides: The file also contains custom CSS rules, such as those for
.prose (likely related to the @tailwindcss/typography plugin) and specific styling for images and inline code blocks within prose content. These demonstrate how to override or extend Tailwind's defaults with custom CSS when needed.
-- CSS Variables: The
:root block defines custom CSS variables (e.g., --color-dev-badge). These can be used throughout the CSS and even in JavaScript to maintain consistent theming.
-
-tailwind.config.js - Customizing Tailwind CSS
-tailwind.config.js is the configuration file for Tailwind CSS. It allows you to customize Tailwind's default theme, add new utility classes, and integrate plugins.
-const defaultTheme = require('tailwindcss/defaultTheme')
-const colors = require('./src/config/colors');
-const fonts = require('./src/config/fonts'); // New import
+
+src/hooks/useToast.js (The Consumer Hook)
+import { useContext } from 'react';
+import { ToastContext } from '../components/ToastContext';
-/** @type {import('tailwindcss').Config} */
-module.exports = {
- darkMode: 'class',
- content: [
- "./src/**/*.{js,jsx,ts,tsx}",
- ],
- theme: {
- extend: {
- fontFamily: {
- sans: ['Space Mono', ...defaultTheme.fontFamily.sans],
- mono: ['JetBrains Mono', ...defaultTheme.fontFamily.mono],
- arvo: fonts.arvo, // New custom font
- playfairDisplay: fonts.playfairDisplay, // New custom font
- inter: fonts.inter, // New custom font
- },
- colors: colors,
- typography: (theme) => ({
- dark: {
- css: {
- color: theme('colors.gray.300'),
- a: {
- color: theme('colors.primary.400'),
- '&:hover': {
- color: theme('colors.primary.600'),
- },
- },
- // ... other typography customizations
- },
- },
- }),
- },
- },
- plugins: [
- require('@tailwindcss/typography'),
- ],
-}
+export const useToast = () => {
+ return useContext(ToastContext);
+};
-Explanation:
-
-darkMode: 'class': Configures Tailwind to use class-based dark mode. This means you can toggle dark mode by adding or removing the dark class (e.g., <html class="dark">) to an ancestor element.
-content: This array specifies the files that Tailwind should scan for utility classes. This is crucial for Tailwind's JIT (Just-In-Time) mode, which only generates the CSS you actually use, resulting in smaller bundle sizes.
-"./src/**/*.{js,jsx,ts,tsx}": Tells Tailwind to look for classes in all .js, .jsx, .ts, and .tsx files within the src directory.
-
-
-theme: This is where you customize Tailwind's default design system.
-extend: Allows you to add to Tailwind's default theme without overwriting it entirely.
-fontFamily: Customizes font stacks. Here, Space Mono and JetBrains Mono are added, and custom fonts like arvo, playfairDisplay, and inter are integrated, likely defined in src/config/fonts.js.
-colors: Customizes the color palette. It imports colors from src/config/colors.js, allowing for a centralized color definition.
-typography: This section customizes the @tailwindcss/typography plugin. It defines specific styles for elements within prose content (like Markdown rendered text) for a dark theme, ensuring readability and consistent styling for headings, links, code blocks, etc.
-
-
-
-
-plugins: This array is where you register Tailwind plugins.
-require('@tailwindcss/typography'): Integrates the official Typography plugin, which provides a set of prose classes to style raw HTML or Markdown content with beautiful, readable typography defaults.
-
-
-
-How it Works Together
+Explanation:
-- Development: When you run
npm start, Tailwind's JIT engine scans your content files, generates only the necessary CSS utility classes based on your usage and tailwind.config.js customizations, and injects them into your application via src/index.css.
-- Production Build: When you run
npm run build, Tailwind purges any unused CSS, resulting in a highly optimized and small CSS bundle.
-- Usage in Components: In your React components, you apply styles by adding Tailwind utility classes directly to your JSX elements (e.g.,
<div className="bg-gray-950 text-white p-4">).
+import { useContext } from 'react';: Imports the useContext Hook from React.
+import { ToastContext } from '../components/ToastContext';: Imports the ToastContext object that was created in ToastContext.js.
+export const useToast = () => { ... };: This is a custom hook. Custom hooks are a powerful feature in React that allow you to extract reusable stateful logic from components. This useToast hook simplifies consuming the ToastContext.
+return useContext(ToastContext);: This line is where the magic happens. When useContext(ToastContext) is called, React looks up the component tree for the closest ToastContext.Provider and returns its value prop. In this case, it returns { addToast, removeToast }.
-This combination provides a powerful and efficient way to style modern web applications, offering both flexibility and maintainability.
-]]>
+How it's Used in a Component (e.g., BlogPostPage.js)
+// Inside BlogPostPage.js (or any other component that needs toasts)
+import { useToast } from '../hooks/useToast';
+
+const CodeBlock = ({ /* ... */ }) => {
+ const { addToast } = useToast(); // Access addToast function
+
+ const handleCopy = () => {
+ // ... copy logic ...
+ addToast({
+ title: 'Success',
+ message: 'Copied to clipboard!',
+ duration: 3000,
+ });
+ // ...
+ };
+ // ...
+};
+
+Any component that needs to display a toast simply imports and calls useToast(), and it immediately gets access to the addToast function without needing to receive it as a prop from its parent.
+Summary
+The React Context API, combined with the useContext hook, provides an elegant solution for managing global state and sharing functions across your component tree, avoiding prop drilling and making your application's architecture cleaner and more maintainable. The toast notification system in this project is a prime example of its effective use.
+]]>Link component is used to create navigation links within your application. It prevents a full page reload when clicked, allowing react-router-dom to handle the navigation client-side.to prop: Specifies the destination path. It can be a string or an object.Link component is used to create navigation links within your application. It prevents a full page reload when clicked, allowing react-router-dom to handle the navigation client-side.to prop: Specifies the destination path. It can be a string or an object.react-router-dom provides a powerful and flexible way to manage navigation in React applications. By using HashRouter, Routes, Route, useParams, useNavigate, and Link, the Fezcode project creates a seamless single-page application experience with distinct URLs for different content, including dynamic routing for blog posts and projects, and robust handling for non-existent pages.
This project leverages a combination of traditional CSS and the utility-first framework Tailwind CSS for styling. This approach allows for both rapid development using pre-defined utility classes and fine-grained control with custom CSS when necessary.
+src/index.css - Global Styles and Tailwind Directivessrc/index.css serves as the main entry point for all CSS in the application. It's where Tailwind CSS is integrated and where global base styles and overrides are defined.
@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+html, body {
+ height: 100%;
+}
+
+body {
+ margin: 0;
+ background-color: #020617;
+ font-family: 'Space Mono', 'JetBrains Mono', monospace, sans-serif !important;
+ font-weight: 400 !important;
+ font-style: normal !important;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family:
+ source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
+}
+
+/* ... other custom styles and overrides ... */
+
+:root {
+ --color-dev-badge: #44403c; /* stone-700 */
+ --color-takes-badge: #065f46; /* emerald-800 */
+ --color-series-badge: #e11d48; /* rose-600 */
+ --color-dnd-badge: #583fa3; /* violet-400 */
+}
+
+@tailwind base;: This directive injects Tailwind's base styles, which are a set of opinionated defaults that normalize browser styles and provide a solid foundation for building on.@tailwind components;: This injects Tailwind's component classes. These are typically larger, more complex classes that you might extract from repeated utility patterns (though this project might not use many custom components).@tailwind utilities;: This injects all of Tailwind's utility classes (e.g., flex, pt-4, text-lg, bg-gray-950). These are the core of Tailwind's utility-first approach.@tailwind directives, you see standard CSS rules that apply globally:html, body { height: 100%; }: Ensures the html and body elements take up the full viewport height.body { ... }: Sets a default margin, background-color, font-family, font-weight, font-style, and font smoothing properties for the entire application.code { ... }: Defines a specific font stack for <code> elements..prose (likely related to the @tailwindcss/typography plugin) and specific styling for images and inline code blocks within prose content. These demonstrate how to override or extend Tailwind's defaults with custom CSS when needed.:root block defines custom CSS variables (e.g., --color-dev-badge). These can be used throughout the CSS and even in JavaScript to maintain consistent theming.tailwind.config.js - Customizing Tailwind CSStailwind.config.js is the configuration file for Tailwind CSS. It allows you to customize Tailwind's default theme, add new utility classes, and integrate plugins.
const defaultTheme = require('tailwindcss/defaultTheme')
+const colors = require('./src/config/colors');
+const fonts = require('./src/config/fonts'); // New import
+
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ darkMode: 'class',
+ content: [
+ "./src/**/*.{js,jsx,ts,tsx}",
+ ],
+ theme: {
+ extend: {
+ fontFamily: {
+ sans: ['Space Mono', ...defaultTheme.fontFamily.sans],
+ mono: ['JetBrains Mono', ...defaultTheme.fontFamily.mono],
+ arvo: fonts.arvo, // New custom font
+ playfairDisplay: fonts.playfairDisplay, // New custom font
+ inter: fonts.inter, // New custom font
+ },
+ colors: colors,
+ typography: (theme) => ({
+ dark: {
+ css: {
+ color: theme('colors.gray.300'),
+ a: {
+ color: theme('colors.primary.400'),
+ '&:hover': {
+ color: theme('colors.primary.600'),
+ },
+ },
+ // ... other typography customizations
+ },
+ },
+ }),
+ },
+ },
+ plugins: [
+ require('@tailwindcss/typography'),
+ ],
+}
+
+darkMode: 'class': Configures Tailwind to use class-based dark mode. This means you can toggle dark mode by adding or removing the dark class (e.g., <html class="dark">) to an ancestor element.content: This array specifies the files that Tailwind should scan for utility classes. This is crucial for Tailwind's JIT (Just-In-Time) mode, which only generates the CSS you actually use, resulting in smaller bundle sizes."./src/**/*.{js,jsx,ts,tsx}": Tells Tailwind to look for classes in all .js, .jsx, .ts, and .tsx files within the src directory.react-router-dom provides a powerful and flexible way to manage navigation in React applications. By using HashRouter, Routes, Route, useParams, useNavigate, and Link, the Fezcode project creates a seamless single-page application experience with distinct URLs for different content, including dynamic routing for blog posts and projects, and robust handling for non-existent pages.
theme: This is where you customize Tailwind's default design system.extend: Allows you to add to Tailwind's default theme without overwriting it entirely.fontFamily: Customizes font stacks. Here, Space Mono and JetBrains Mono are added, and custom fonts like arvo, playfairDisplay, and inter are integrated, likely defined in src/config/fonts.js.colors: Customizes the color palette. It imports colors from src/config/colors.js, allowing for a centralized color definition.typography: This section customizes the @tailwindcss/typography plugin. It defines specific styles for elements within prose content (like Markdown rendered text) for a dark theme, ensuring readability and consistent styling for headings, links, code blocks, etc.plugins: This array is where you register Tailwind plugins.require('@tailwindcss/typography'): Integrates the official Typography plugin, which provides a set of prose classes to style raw HTML or Markdown content with beautiful, readable typography defaults.npm start, Tailwind's JIT engine scans your content files, generates only the necessary CSS utility classes based on your usage and tailwind.config.js customizations, and injects them into your application via src/index.css.npm run build, Tailwind purges any unused CSS, resulting in a highly optimized and small CSS bundle.<div className="bg-gray-950 text-white p-4">).This combination provides a powerful and efficient way to style modern web applications, offering both flexibility and maintainability.
+]]>useContext
-The React Context API provides a way to pass data through the component tree without having to pass props down manually at every level. This is particularly useful for global data (like user authentication, theme, or in this case, toast notifications) that many components might need access to.
-Imagine you have a deeply nested component tree, and a piece of data (e.g., a user object) is needed by a component several levels down. Without Context, you'd have to pass that data as a prop through every intermediate component, even if those components don't directly use the data. This is known as "prop drilling" and can make your code verbose and harder to maintain.
-The Context API consists of three main parts:
-createContext: Creates a Context object. When React renders a component that subscribes to this Context object, it will read the current context value from the closest matching Provider above it in the tree.Provider: A React component that allows consuming components to subscribe to context changes. It accepts a value prop to be passed to consuming components that are descendants of this Provider.useContext: A React Hook that lets you read context from a functional component.This project uses the Context API to manage and display toast notifications globally. Let's examine src/components/ToastContext.js and src/hooks/useToast.js.
src/components/ToastContext.js (The Provider)import React, { createContext, useState, useCallback } from 'react';
-import Toast from './Toast';
-
-export const ToastContext = createContext();
-
-let id = 0; // Simple counter for unique toast IDs
-
-export const ToastContext = ({ children }) => {
- const [toasts, setToasts] = useState([]); // State to hold active toasts
+ 011 - JavaScript Fundamentals in the Project
+This project heavily utilizes modern JavaScript features to build a dynamic and interactive user interface. Understanding these fundamental concepts is crucial for comprehending the codebase. This document will highlight several key JavaScript concepts with examples drawn from the project.
+1. async/await for Asynchronous Operations
+Asynchronous operations (like fetching data from a server) are common in web applications. async/await provides a cleaner, more readable way to handle Promises.
+
+async function: A function declared with async always returns a Promise. It allows you to use the await keyword inside it.
+await keyword: Can only be used inside an async function. It pauses the execution of the async function until the Promise it's waiting for settles (either resolves or rejects), and then resumes the async function's execution with the resolved value.
+
+Example from src/pages/BlogPostPage.js
+// src/pages/BlogPostPage.js
+useEffect(() => {
+ const fetchPost = async () => { // async function
+ setLoading(true);
+ try {
+ const [postContentResponse, shownPostsResponse] = await Promise.all([ // await Promise.all
+ fetch(`/posts/${currentSlug}.txt`),
+ fetch('/posts/shownPosts.json'),
+ ]);
- const addToast = useCallback((toast) => {
- const newToast = { ...toast, id: id++ };
- setToasts((prevToasts) => {
- if (prevToasts.length >= 5) { // Limit to 5 toasts
- const updatedToasts = prevToasts.slice(0, prevToasts.length - 1);
- return [newToast, ...updatedToasts];
+ let postBody = '';
+ if (postContentResponse.ok) {
+ postBody = await postContentResponse.text(); // await fetch response
+ // ...
}
- return [newToast, ...prevToasts];
- });
- }, []); // Memoize addToast function
-
- const removeToast = useCallback((id) => {
- setToasts((prevToasts) => prevToasts.filter((toast) => toast.id !== id));
- }, []); // Memoize removeToast function
+ // ...
+ } catch (error) {
+ console.error('Error fetching post or shownPosts.json:', error);
+ // ...
+ } finally {
+ setLoading(false);
+ }
+ };
- return (
- <ToastContext.Provider value={{ addToast, removeToast }}>
- {children}
- <div className="fixed top-28 right-10 z-50">
- {toasts.map((toast) => (
- <Toast
- key={toast.id}
- id={toast.id}
- title={toast.title}
- message={toast.message}
- duration={toast.duration}
- removeToast={removeToast}
- />
- ))}
- </div>
- </ToastContext.Provider>
- );
-};
+ fetchPost();
+}, [currentSlug]);
-Explanation:
-
-export const ToastContext = createContext();: A Context object named ToastContext is created. This object will be used by both the Provider and the Consumer.
-ToastContext Component: This is a functional component that will wrap parts of your application (as seen in App.js).
-const [toasts, setToasts] = useState([]);: Manages the array of active toast notifications using useState.
-addToast and removeToast functions: These functions are responsible for adding new toasts to the toasts array and removing them. They are wrapped in useCallback to prevent unnecessary re-creations, which is an optimization for performance.
-<ToastContext.Provider value={{ addToast, removeToast }}>: This is the core of the Provider. It makes the addToast and removeToast functions available to any component that consumes ToastContext and is rendered within this Provider's tree. The value prop is crucial here.
-{children}: This renders whatever components are passed as children to the ToastContext. These children (and their descendants) will have access to the context value.
-- Toast Rendering: The
ToastContext also directly renders the actual Toast components based on the toasts state, positioning them in the top-right corner of the screen.
+
+- The
fetchPost function is declared async because it performs asynchronous network requests.
+await Promise.all([...]) is used to wait for multiple fetch calls (which return Promises) to complete concurrently. This is more efficient than awaiting them one after another if they don't depend on each other.
+await postContentResponse.text() waits for the response body to be fully read as text.
+- The
try...catch...finally block is used for error handling and ensuring setLoading(false) is always called.
-
-
-src/hooks/useToast.js (The Consumer Hook)
-import { useContext } from 'react';
-import { ToastContext } from '../components/ToastContext';
-
-export const useToast = () => {
- return useContext(ToastContext);
-};
-
-Explanation:
-
-import { useContext } from 'react';: Imports the useContext Hook from React.
-import { ToastContext } from '../components/ToastContext';: Imports the ToastContext object that was created in ToastContext.js.
-export const useToast = () => { ... };: This is a custom hook. Custom hooks are a powerful feature in React that allow you to extract reusable stateful logic from components. This useToast hook simplifies consuming the ToastContext.
-return useContext(ToastContext);: This line is where the magic happens. When useContext(ToastContext) is called, React looks up the component tree for the closest ToastContext.Provider and returns its value prop. In this case, it returns { addToast, removeToast }.
-
-How it's Used in a Component (e.g., BlogPostPage.js)
-// Inside BlogPostPage.js (or any other component that needs toasts)
-import { useToast } from '../hooks/useToast';
-
-const CodeBlock = ({ /* ... */ }) => {
- const { addToast } = useToast(); // Access addToast function
-
- const handleCopy = () => {
- // ... copy logic ...
- addToast({
- title: 'Success',
- message: 'Copied to clipboard!',
- duration: 3000,
- });
- // ...
- };
- // ...
-};
-
-Any component that needs to display a toast simply imports and calls useToast(), and it immediately gets access to the addToast function without needing to receive it as a prop from its parent.
-Summary
-The React Context API, combined with the useContext hook, provides an elegant solution for managing global state and sharing functions across your component tree, avoiding prop drilling and making your application's architecture cleaner and more maintainable. The toast notification system in this project is a prime example of its effective use.
-]]>
- useState and useEffect
-React Hooks are functions that let you "hook into" React state and lifecycle features from functional components. They allow you to use state and other React features without writing a class. The two most fundamental hooks are useState and useEffect.
useState HookuseState is a Hook that lets you add React state to functional components. It returns a pair of values: the current state, and a function that updates it.
const [stateVariable, setStateVariable] = useState(initialValue);
+2. Promise.all for Concurrent Promises
+Promise.all is a Promise combinator that takes an iterable of Promises as input and returns a single Promise. This returned Promise fulfills when all of the input's Promises have fulfilled, or rejects as soon as any of the input's Promises rejects.
+Example from src/pages/BlogPostPage.js
+// src/pages/BlogPostPage.js
+const [postContentResponse, shownPostsResponse] = await Promise.all([
+ fetch(`/posts/${currentSlug}.txt`),
+ fetch('/posts/shownPosts.json'),
+]);
-stateVariable: The current value of the state.
-setStateVariable: A function to update the stateVariable. When this function is called, React will re-render the component.
-initialValue: The initial value for the state. This can be any JavaScript data type (number, string, boolean, object, array, etc.).
+- Here,
Promise.all is used to initiate two network requests (fetch for the post content and fetch for the metadata JSON) at the same time. The await keyword then waits for both of them to complete. The results are destructured into postContentResponse and shownPostsResponse.
+3. Array Methods (filter, find, sort)
+Modern JavaScript provides powerful array methods that make working with collections of data much easier and more declarative.
Example from src/pages/BlogPostPage.js
// src/pages/BlogPostPage.js
-const BlogPostPage = () => {
- // ...
- const [post, setPost] = useState(null);
- const [loading, setLoading] = useState(true);
- const [readingProgress, setReadingProgress] = useState(0);
- const [isAtTop, setIsAtTop] = useState(true); // New state for tracking if at top
- const [isModalOpen, setIsModalToOpen] = useState(false);
- const [modalContent, setModalContent] = useState('');
- // ...
-};
+// ... inside fetchPost function
+if (shownPostsResponse.ok) {
+ const allPosts = await shownPostsResponse.json();
+ postMetadata = allPosts.find((item) => item.slug === currentSlug); // find
+
+ if (postMetadata && postMetadata.series) {
+ seriesPosts = allPosts
+ .filter((item) => item.series === postMetadata.series) // filter
+ .sort((a, b) => a.seriesIndex - b.seriesIndex); // sort
+ }
+}
-In BlogPostPage:
-[post, setPost] = useState(null): post will hold the blog post data (attributes, body, series posts). It's initialized to null because the data is fetched asynchronously.
-[loading, setLoading] = useState(true): loading is a boolean that indicates whether the post data is currently being fetched. It starts as true.
-[readingProgress, setReadingProgress] = useState(0): readingProgress stores the user's scroll progress on the page, initialized to 0.
-[isAtTop, setIsAtTop] = useState(true): Tracks if the user is at the top of the page.
-[isModalOpen, setIsModalToOpen] = useState(false): Controls the visibility of a modal, initialized to false (closed).
-[modalContent, setModalContent] = useState(''): Stores the content to be displayed inside the modal.
+Array.prototype.find(): Returns the value of the first element in the provided array that satisfies the provided testing function. Otherwise, undefined is returned.
+allPosts.find((item) => item.slug === currentSlug): Finds the first post object in allPosts whose slug property matches currentSlug.
+
+
+Array.prototype.filter(): Creates a new array with all elements that pass the test implemented by the provided function.
+allPosts.filter((item) => item.series === postMetadata.series): Creates a new array containing only posts that belong to the same series as the current post.
+
+
+Array.prototype.sort(): Sorts the elements of an array in place and returns the sorted array. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units.
+.sort((a, b) => a.seriesIndex - b.seriesIndex): Sorts the seriesPosts array numerically based on their seriesIndex property in ascending order.
+
+
+
+4. Object Destructuring
+Object destructuring is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
+Example from src/pages/BlogPostPage.js
+// src/pages/BlogPostPage.js
+const { slug, seriesSlug, episodeSlug } = useParams();
+// ...
+
+
+- Here,
useParams() returns an object containing URL parameters. Object destructuring is used to extract the slug, seriesSlug, and episodeSlug properties directly into variables with the same names.
Example from src/components/Layout.js
// src/components/Layout.js
const Layout = ({ children }) => {
- const [isSidebarOpen, setIsSidebarOpen] = useState(window.innerWidth > 768);
// ...
};
-In Layout:
-[isSidebarOpen, setIsSidebarOpen] = useState(window.innerWidth > 768): isSidebarOpen controls the visibility of the sidebar. Its initial value depends on the window width, making the sidebar open by default on larger screens.
+- In this functional component definition,
({ children }) is using object destructuring to directly extract the children prop from the props object that React passes to the component.
-useEffect Hook
-useEffect is a Hook that lets you perform side effects in functional components. Side effects include data fetching, subscriptions, manually changing the DOM, and other operations that interact with the outside world. It runs after every render of the component by default, but you can control when it runs using its dependency array.
-Syntax
-useEffect(() => {
- // Side effect code here
- return () => {
- // Cleanup function (optional)
- };
-}, [dependency1, dependency2]); // Dependency array (optional)
+5. Ternary Operator
+The ternary operator (condition ? exprIfTrue : exprIfFalse) is a shorthand for an if...else statement, often used for conditional rendering or assigning values.
+Example from src/pages/BlogPostPage.js
+// src/pages/BlogPostPage.js
+const currentSlug = episodeSlug || slug; // Use episodeSlug if present, otherwise use slug
+// ...
+const backLink = seriesSlug ? `/blog/series/${seriesSlug}` : '/blog';
+const backLinkText = seriesSlug ? 'Back to Series' : 'Back to Blog';
-- First argument (function): This is where you put your side effect code. It can optionally return a cleanup function.
-- Second argument (dependency array): This array controls when the effect re-runs.
-- If omitted, the effect runs after every render.
-- If an empty array
[], the effect runs only once after the initial render (like componentDidMount). The cleanup runs on unmount (like componentWillUnmount).
-- If it contains variables (e.g.,
[prop1, state1]), the effect runs after the initial render and whenever any of the variables in the array change.
-
-
+episodeSlug || slug: This uses the logical OR operator (||) to assign episodeSlug if it's truthy, otherwise it assigns slug. This is a common pattern for providing fallback values.
+seriesSlug ? /blog/series/${seriesSlug} : '/blog': If seriesSlug is truthy, backLink is set to the series URL; otherwise, it defaults to the general blog URL.
-Example from src/pages/BlogPostPage.js (Data Fetching)
-// src/pages/BlogPostPage.js
-useEffect(() => {
- const fetchPost = async () => {
- setLoading(true);
- // ... data fetching logic using fetch API ...
- setLoading(false);
- };
-
- fetchPost();
-}, [currentSlug]); // Effect re-runs when currentSlug changes
+Summary
+These JavaScript fundamentals, including asynchronous programming with async/await and Promise.all, efficient data manipulation with array methods, concise variable assignment with object destructuring, and conditional logic with the ternary operator, are extensively used throughout the Fezcode project. Mastering these concepts is key to understanding and contributing to modern React applications.
+]]>
public/index.html)
+public/index.html is the single HTML page that serves as the entry point for your React application. When a user visits your website, this is the file their browser first loads. The React application then takes over to dynamically render content into this HTML structure.
<!DOCTYPE html>
+<html lang="en" class="dark">
+ <head>
+ <meta charset="utf-8" />
+ <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
+ <link rel="icon" type="image/svg+xml" href="%PUBLIC_URL%/favicon.svg" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="theme-color" content="#000000" />
+ <meta
+ name="description"
+ content="codex by fezcode..."
+ />
+ <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
+ <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+ <link rel="preconnect" href="https://fonts.googleapis.com">
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
+ <link href="https://fonts.googleapis.com/css2?family=Arvo&family=Inter&family=Playfair+Display&display=swap" rel="stylesheet">
+ <title>fezcodex</title>
+ </head>
+ <body class="bg-slate-950">
+ <noscript>You need to enable JavaScript to run this app.</noscript>
+ <div id="root"></div>
+ </body>
+</html>
-This useEffect hook is responsible for fetching the blog post data.
<!DOCTYPE html>async function fetchPost to handle the asynchronous data retrieval.setLoading(true) is called at the start to show a loading indicator.fetch API is used to get the .txt content and shownPosts.json metadata.[currentSlug] ensures that this effect runs only when the currentSlug (derived from the URL parameters) changes. This prevents unnecessary re-fetches and ensures the correct post is loaded when navigating between posts.src/pages/BlogPostPage.js (Scroll Event Listener)// src/pages/BlogPostPage.js
-useEffect(() => {
- const handleScroll = () => {
- if (contentRef.current) {
- const { scrollTop, scrollHeight, clientHeight } =
- document.documentElement;
- const totalHeight = scrollHeight - clientHeight;
- const currentProgress = (scrollTop / totalHeight) * 100;
- setReadingProgress(currentProgress);
- setIsAtTop(scrollTop === 0); // Update isAtTop based on scroll position
- }
- };
-
- window.addEventListener('scroll', handleScroll);
- return () => window.removeEventListener('scroll', handleScroll);
-}, [post]); // Re-attach scroll listener if post changes
-
-This useEffect manages a scroll event listener to calculate reading progress and determine if the user is at the top of the page.
<html lang="en" class="dark">window when the component mounts or when the post state changes.return () => { ... } part is a cleanup function. This function runs when the component unmounts or before the effect re-runs due to a dependency change. It's essential here to remove the event listener to prevent memory leaks and unexpected behavior.[post] means the effect (and its cleanup) will re-run if the post object changes, ensuring the scroll listener is correctly attached to the relevant content.lang="en": Specifies the primary language of the document content as English, which is important for accessibility and search engines.class="dark": This class is likely used in conjunction with Tailwind CSS's dark mode configuration (darkMode: 'class' in tailwind.config.js). When this class is present on the <html> element, Tailwind will apply dark mode styles.src/components/Layout.js (Window Resize Listener)// src/components/Layout.js
-useEffect(() => {
- const handleResize = () => {
- if (window.innerWidth <= 768) {
- setIsSidebarOpen(false);
- }
- };
-
- window.addEventListener('resize', handleResize);
-
- return () => {
- window.removeEventListener('resize', handleResize);
- };
-}, []); // Empty dependency array: runs once on mount, cleans up on unmount
-
-This useEffect in Layout.js handles the sidebar's initial state and responsiveness.
<head> SectionThe <head> section contains metadata about the HTML document, which is not displayed on the web page itself but is crucial for browsers, search engines, and other web services.
resize event listener to the window.handleResize function closes the sidebar if the window width drops below 768 pixels.[] ensures that this effect runs only once after the initial render and its cleanup function runs only when the component unmounts. This is perfect for setting up global event listeners that don't need to be re-initialized unless the component is completely removed from the DOM.<meta charset="utf-8" />: Specifies the character encoding for the document, ensuring proper display of various characters.<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />: Links to the favicon, the small icon displayed in the browser tab or bookmark list. %PUBLIC_URL% is a placeholder that will be replaced with the public URL of your app during the build process.<meta name="viewport" content="width=device-width, initial-scale=1" />: Configures the viewport for responsive design. It sets the width of the viewport to the device width and the initial zoom level to 1, ensuring the page scales correctly on different devices.<meta name="theme-color" content="#000000" />: Suggests a color that browsers should use to tint the UI elements (like the address bar in mobile browsers) of the page.<meta name="description" content="codex by fezcode..." />: Provides a brief, high-level description of the web page content. This is often used by search engines in search results.<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />: Specifies an icon for web clips on iOS devices.<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />: Links to a web app manifest file, which provides information about the web application (like name, icons, start URL) in a JSON text file. This is essential for Progressive Web Apps (PWAs).<link rel="preconnect" ...> and <link href="https://fonts.googleapis.com/css2?..." rel="stylesheet">: These lines are used to preconnect to Google Fonts and import custom fonts (JetBrains Mono, Space Mono, Arvo, Inter, Playfair Display). preconnect helps establish early connections to improve font loading performance.<title>fezcodex</title>: Sets the title of the HTML document, which appears in the browser tab or window title bar.<body> SectionThe <body> section contains all the content that is visible to the user.
<body class="bg-slate-950">: The main content area of the page. The bg-slate-950 class is a Tailwind CSS utility class that sets the background color of the body to a very dark slate color, consistent with the project's dark theme.<noscript>You need to enable JavaScript to run this app.</noscript>: This content is displayed only if the user's browser has JavaScript disabled. Since React is a JavaScript library, the application cannot function without JavaScript.<div id="root"></div>: This is the most crucial part for a React application. It's an empty div element with the ID root. This is the DOM node where your React application (specifically, the App component rendered by src/index.js) will be mounted and take control. All of your React components will be rendered as children of this div.As explained in 003-index-js-entry-point.md:
// src/index.js
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render(
+ <React.StrictMode>
+ <App />
+ </React.StrictMode>,
+);
+
+src/index.js (which is eventually bundled and loaded by the browser) finds the <div id="root"> element.ReactDOM.createRoot() creates a React root, which is the entry point for React to manage the DOM inside that element.root.render(<App />) then tells React to render your main App component (and all its children) inside this root div. From this point on, React efficiently updates and manages the content within this div based on your component's state and props.useState and useEffect are powerful tools that bring state management and side effect handling to functional components, making them as capable as class components while often being more concise and easier to reason about. Understanding their usage, especially the role of the dependency array in useEffect, is fundamental to building robust React applications.
public/index.html provides the foundational HTML structure and metadata for the web page. It's a relatively simple file because the React application dynamically generates and manages most of the visible content within the designated <div id="root">. This separation allows for a highly dynamic and interactive user experience powered by React.
At the core of React applications are components. Components are independent, reusable pieces of UI. They can be thought of as JavaScript functions that return JSX (JavaScript XML), which describes what the UI should look like. React applications are built by composing these components.
-The project primarily uses functional components, which are JavaScript functions that accept a single props (properties) object argument and return React elements.
App Component (src/App.js)// src/App.js
-import React from 'react';
-// ... imports
+ 013 - Document Fetching with the fetch API
+In modern web applications, fetching data from a server is a fundamental operation. The fetch API provides a powerful and flexible interface for making network requests, replacing older methods like XMLHttpRequest. This project uses fetch to retrieve blog post content and metadata.
+The fetch API Basics
+The fetch() method starts the process of fetching a resource from the network, returning a Promise that fulfills once the response is available. A fetch() call takes one mandatory argument, the path to the resource you want to fetch.
+Basic Usage
+fetch(url)
+ .then(response => response.json()) // or .text(), .blob(), etc.
+ .then(data => console.log(data))
+ .catch(error => console.error('Error:', error));
+
+
+fetch(url): Initiates the request. Returns a Promise that resolves to a Response object.
+response.json() / response.text(): The Response object has methods to extract the body content. json() parses the response as JSON, while text() parses it as plain text. Both return a Promise.
+.then(): Handles the successful resolution of a Promise.
+.catch(): Handles any errors that occur during the fetch operation or in the subsequent .then() blocks.
+
+Example from src/pages/BlogPostPage.js
+Let's look at how fetch is used in BlogPostPage.js to get both the blog post's text content and its metadata.
+// src/pages/BlogPostPage.js - inside the useEffect's fetchPost function
+// ...
+try {
+ const [postContentResponse, shownPostsResponse] = await Promise.all([
+ fetch(`/posts/${currentSlug}.txt`),
+ fetch('/posts/shownPosts.json'),
+ ]);
-function App() {
- return (
- <Router>
- {/* ... other components */}
- <Layout>
- <AnimatedRoutes />
- </Layout>
- {/* ... */}
- </Router>
- );
+ // Handling post content response
+ let postBody = '';
+ if (postContentResponse.ok) { // Check if the HTTP status code is in the 200-299 range
+ postBody = await postContentResponse.text(); // Extract response body as text
+ // Additional check for HTML fallback content
+ if (postBody.trim().startsWith('<!DOCTYPE html>')) {
+ console.error('Fetched content is HTML, not expected post content for:', currentSlug);
+ navigate('/404');
+ return;
+ }
+ } else {
+ console.error('Failed to fetch post content for:', currentSlug);
+ navigate('/404');
+ return;
+ }
+
+ // Handling metadata response
+ let postMetadata = null;
+ if (shownPostsResponse.ok) { // Check if the HTTP status code is in the 200-299 range
+ const allPosts = await shownPostsResponse.json(); // Extract response body as JSON
+ postMetadata = allPosts.find((item) => item.slug === currentSlug);
+ // ... further processing of series posts
+ } else {
+ console.error('Failed to fetch shownPosts.json');
+ }
+
+ // Final check and state update
+ if (postMetadata && postContentResponse.ok) {
+ setPost({ attributes: postMetadata, body: postBody, seriesPosts });
+ } else {
+ setPost({ attributes: { title: 'Post not found' }, body: '' });
+ }
+} catch (error) {
+ console.error('Error fetching post or shownPosts.json:', error);
+ setPost({ attributes: { title: 'Error loading post' }, body: '' });
+} finally {
+ setLoading(false);
}
-
-export default App;
+// ...
+Explanation of fetch Usage in BlogPostPage.js:
+
+Promise.all([...]): As discussed in 011-javascript-fundamentals.md, Promise.all is used to concurrently fetch two resources:
-function App() { ... }: This defines a functional component named App.
-- The
return statement contains JSX, which is a syntax extension for JavaScript recommended by React to describe UI.
-<Layout> and <AnimatedRoutes> are other components being used within App.
+fetch("/posts/${currentSlug}.txt"): Fetches the actual Markdown content of the blog post. The currentSlug is dynamically inserted into the URL.
+fetch('/posts/shownPosts.json'): Fetches a JSON file containing metadata for all blog posts.
-Example: Layout Component (src/components/Layout.js)
-Let's look at src/components/Layout.js to see a slightly more complex functional component.
-// src/components/Layout.js
-import React, { useState, useEffect } from 'react';
-import Navbar from './Navbar';
-import Sidebar from './Sidebar';
-import Footer from './Footer';
-// ... other imports
-
-const Layout = ({ children }) => {
- const [isSidebarOpen, setIsSidebarOpen] = useState(window.innerWidth > 768);
- // ... other state and effects
-
- return (
- <div className="bg-gray-950 min-h-screen font-sans flex">
- <Sidebar isOpen={isSidebarOpen} toggleSidebar={toggleSidebar} />
- <div
- className={`flex-1 flex flex-col transition-all duration-300 ${isSidebarOpen ? 'md:ml-64' : 'md:ml-0'}`}>
- <Navbar toggleSidebar={toggleSidebar} isSidebarOpen={isSidebarOpen} />
- <main className="flex-grow">{children}</main>
- <Footer />
- </div>
- </div>
- );
-};
-
-export default Layout;
-
+
+response.ok Property: After a fetch call, the Response object has an ok property. This is a boolean that indicates whether the HTTP response status is in the 200-299 range (inclusive). It's crucial to check response.ok because fetch does not throw an error for HTTP error statuses (like 404 or 500) by default; it only throws an error for network failures.
+
+response.text() and response.json(): These methods are used to parse the response body:
-const Layout = ({ children }) => { ... };: This defines another functional component, Layout, using an arrow function syntax. It directly destructures children from the props object. This is a common pattern.
+postContentResponse.text(): Used for the .txt file, as it contains plain text (Markdown).
+shownPostsResponse.json(): Used for the .json file, as it contains structured JSON data.
-Props (Properties)
-Props are how you pass data from a parent component to a child component. They are read-only and allow components to be dynamic and reusable.
-Passing Props
-In the App component, you can see Layout being used:
-// Inside App component's return
-<Layout>
- <AnimatedRoutes />
-</Layout>
-
-Here, AnimatedRoutes is passed as a special prop called children to the Layout component. Whatever content you place between the opening and closing tags of a component becomes its children prop.
-Receiving and Using Props
-In the Layout component, children is received as a prop:
-const Layout = ({ children }) => {
- // ...
- return (
- // ...
- <main className="flex-grow">{children}</main>
- // ...
- );
-};
-
-The Layout component then renders {children} inside its <main> tag, meaning the AnimatedRoutes (or whatever was passed as children) will be rendered in that spot.
-Another example of props in Layout.js:
-<Sidebar isOpen={isSidebarOpen} toggleSidebar={toggleSidebar} />
-<Navbar toggleSidebar={toggleSidebar} isSidebarOpen={isSidebarOpen} />
-
-Here:
+
+Error Handling (HTTP Status):
-- The
Sidebar component receives two props: isOpen (a boolean state variable) and toggleSidebar (a function).
-- The
Navbar component also receives toggleSidebar and isSidebarOpen.
+- If
postContentResponse.ok is false (meaning the .txt file was not found or returned an error status), an error is logged, and the application navigates to the /404 page using navigate('/404').
+- A specific check
if (postBody.trim().startsWith('<!DOCTYPE html>')) was added to handle the scenario where the development server might return the index.html (with a 200 status) instead of a 404 for a non-existent file. This ensures that even in such cases, the user is redirected to the 404 page.
+- If
shownPostsResponse.ok is false, an error is logged, but the application doesn't navigate to 404 directly, as the post content might still be available, just without rich metadata.
-These props are defined in the Layout component's scope and passed down to its child components (Sidebar, Navbar) to control their behavior or appearance. For instance, isOpen might control the visibility of the sidebar, and toggleSidebar would be a function to change that visibility when a button in the Navbar is clicked.
+
+try...catch Block: The entire asynchronous operation is wrapped in a try...catch block. This catches any network errors (e.g., server unreachable) or errors that occur during the processing of the Promises (e.g., json() parsing error). If an error occurs, it's logged, and the post state is set to indicate an error.
+
+finally Block: The setLoading(false) call is placed in a finally block. This ensures that the loading state is always turned off, regardless of whether the fetch operation succeeded or failed.
+
+
Summary
-Functional components are the building blocks of React UIs, and props are the essential mechanism for communicating data and functionality between these components in a unidirectional flow (from parent to child). This modular approach makes React applications easier to manage, test, and scale.
-]]>
+The fetch API is a modern, Promise-based way to make network requests in JavaScript. By understanding how to use fetch with async/await, handle Response objects (especially response.ok), and implement robust error handling with try...catch, developers can effectively retrieve and process data from various sources, as demonstrated in the Fezcode project's BlogPostPage.js component.
+]]>src/pages/BlogPostPage.js Component Explained
-src/pages/BlogPostPage.js is a critical component responsible for displaying individual blog posts. It handles fetching the post content and metadata, rendering Markdown, syntax highlighting code blocks, and managing UI interactivity like copying code or opening code in a modal. It also includes navigation for series posts and robust error handling for missing content.
import React, { useState, useEffect, useRef } from 'react';
-import { useParams, Link, useNavigate } from 'react-router-dom';
-import ReactMarkdown from 'react-markdown';
-import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
-import {
- ArrowSquareOut,
- ArrowsOutSimple,
- Clipboard,
- ArrowLeft,
-} from '@phosphor-icons/react';
-import { customTheme } from '../utils/customTheme';
-import PostMetadata from '../components/PostMetadata';
-import CodeModal from '../components/CodeModal';
-import { useToast } from '../hooks/useToast';
-
-// ... LinkRenderer and CodeBlock components (explained below)
+ 014 - React: Custom Hooks
+Custom Hooks are a powerful feature in React that allow you to extract reusable stateful logic from components. They are JavaScript functions whose names start with use and that can call other Hooks. Custom Hooks solve the problem of sharing logic between components without relying on prop drilling or complex patterns like render props or higher-order components.
+Why Use Custom Hooks?
+
+- Reusability: Extract common logic (state, effects, context) into a single function that can be used across multiple components.
+- Readability: Components become cleaner and easier to understand as their logic is separated from their UI concerns.
+- Maintainability: Changes to shared logic only need to be made in one place.
+- Testability: Logic extracted into custom hooks can often be tested more easily in isolation.
+
+How to Create a Custom Hook
+A custom Hook is a JavaScript function that:
+
+- Starts with the word
use (e.g., useFriendStatus, useToast). This naming convention is crucial for React to know that it's a Hook and to apply the rules of Hooks (e.g., only call Hooks at the top level of a React function).
+- Can call other Hooks (e.g.,
useState, useEffect, useContext).
+- Can return anything: stateful values, functions, or nothing.
+
+Example: useToast Custom Hook (src/hooks/useToast.js)
+This project provides an excellent example of a custom hook: useToast. It encapsulates the logic for accessing the toast notification system's addToast and removeToast functions.
+src/hooks/useToast.js
+import { useContext } from 'react';
+import { ToastContext } from '../components/ToastContext';
-const BlogPostPage = () => {
- const { slug, seriesSlug, episodeSlug } = useParams();
- const navigate = useNavigate();
- const currentSlug = episodeSlug || slug; // Use episodeSlug if present, otherwise use slug
- const [post, setPost] = useState(null);
- const [loading, setLoading] = useState(true);
- const [readingProgress, setReadingProgress] = useState(0);
- const [isAtTop, setIsAtTop] = useState(true); // New state for tracking if at top
- const contentRef = useRef(null);
- const [isModalOpen, setIsModalToOpen] = useState(false);
- const [modalContent, setModalContent] = useState('');
+export const useToast = () => {
+ return useContext(ToastContext);
+};
+
+Explanation:
+
+import { useContext } from 'react';: The custom hook itself uses another built-in Hook, useContext, to access the value provided by the ToastContext.
+import { ToastContext } from '../components/ToastContext';: It imports the ToastContext object, which was created in ToastContext.js.
+export const useToast = () => { ... };: This defines the custom hook. Its name useToast clearly indicates its purpose and follows the naming convention.
+return useContext(ToastContext);: The core of this hook. It retrieves the value (which contains addToast and removeToast functions) from the nearest ToastContext.Provider in the component tree and returns it. This means any component calling useToast() will receive these functions.
+
+How useToast is Used in a Component (e.g., BlogPostPage.js)
+// Inside BlogPostPage.js (or any other component that needs toasts)
+import { useToast } from '../hooks/useToast';
- const openModal = (content) => {
- setModalContent(content);
- setIsModalToOpen(true);
- };
+const CodeBlock = ({ /* ... */ }) => {
+ const { addToast } = useToast(); // Access addToast function
- const closeModal = () => {
- setIsModalToOpen(false);
- setModalContent('');
+ const handleCopy = () => {
+ // ... copy logic ...
+ addToast({
+ title: 'Success',
+ message: 'Copied to clipboard!',
+ duration: 3000,
+ });
+ // ...
};
+ // ...
+};
+
+By calling const { addToast } = useToast();, the CodeBlock component (or any other component) gains direct access to the addToast function without needing to know where ToastContext is defined or how the toast state is managed. This makes the CodeBlock component cleaner and more focused on its primary responsibility.
+Another Potential Custom Hook (Conceptual Example)
+Consider the scroll tracking logic in BlogPostPage.js:
+// src/pages/BlogPostPage.js - inside BlogPostPage component
+const [readingProgress, setReadingProgress] = useState(0);
+const [isAtTop, setIsAtTop] = useState(true);
+const contentRef = useRef(null);
- useEffect(() => {
- const fetchPost = async () => {
- setLoading(true);
- console.log('Fetching post for currentSlug:', currentSlug);
- try {
- const [postContentResponse, shownPostsResponse] = await Promise.all([
- fetch(`/posts/${currentSlug}.txt`),
- fetch('/posts/shownPosts.json'),
- ]);
-
- console.log('postContentResponse:', postContentResponse);
- console.log('shownPostsResponse:', shownPostsResponse);
-
- let postBody = '';
- if (postContentResponse.ok) {
- postBody = await postContentResponse.text();
- // Check if the fetched content is actually HTML (indicating a fallback to index.html)
- if (postBody.trim().startsWith('<!DOCTYPE html>')) {
- console.error('Fetched content is HTML, not expected post content for:', currentSlug);
- navigate('/404'); // Redirect to 404 page
- return; // Stop further processing
- }
- } else {
- console.error('Failed to fetch post content for:', currentSlug);
- navigate('/404'); // Redirect to 404 page
- return; // Stop further processing
- }
-
- let postMetadata = null;
- let seriesPosts = [];
- if (shownPostsResponse.ok) {
- const allPosts = await shownPostsResponse.json();
- postMetadata = allPosts.find((item) => item.slug === currentSlug);
-
- if (postMetadata && postMetadata.series) {
- seriesPosts = allPosts
- .filter((item) => item.series === postMetadata.series)
- .sort((a, b) => a.seriesIndex - b.seriesIndex);
- }
- } else {
- console.error('Failed to fetch shownPosts.json');
- }
-
- console.log('postMetadata:', postMetadata);
- console.log('postBody length:', postBody.length);
-
- if (postMetadata && postContentResponse.ok) {
- setPost({ attributes: postMetadata, body: postBody, seriesPosts });
- console.log('Post set:', { attributes: postMetadata, body: postBody, seriesPosts });
- } else {
- setPost({ attributes: { title: 'Post not found' }, body: '' });
- console.log('Post not found or content not fetched.');
- }
- } catch (error) {
- console.error('Error fetching post or shownPosts.json:', error);
- setPost({ attributes: { title: 'Error loading post' }, body: '' });
- } finally {
- setLoading(false);
- }
- };
+useEffect(() => {
+ const handleScroll = () => {
+ if (contentRef.current) {
+ const { scrollTop, scrollHeight, clientHeight } =
+ document.documentElement;
+ const totalHeight = scrollHeight - clientClientHeight;
+ const currentProgress = (scrollTop / totalHeight) * 100;
+ setReadingProgress(currentProgress);
+ setIsAtTop(scrollTop === 0);
+ }
+ };
- fetchPost();
- }, [currentSlug]);
+ window.addEventListener('scroll', handleScroll);
+ return () => window.removeEventListener('scroll', handleScroll);
+}, [post]);
+
+This logic could be extracted into a custom hook, for example, useScrollProgress:
+// src/hooks/useScrollProgress.js (Conceptual)
+import { useState, useEffect, useRef } from 'react';
+
+const useScrollProgress = (contentRef, dependency) => {
+ const [readingProgress, setReadingProgress] = useState(0);
+ const [isAtTop, setIsAtTop] = useState(true);
useEffect(() => {
const handleScroll = () => {
@@ -5718,520 +6085,323 @@ const BlogPostPage = () => {
const totalHeight = scrollHeight - clientHeight;
const currentProgress = (scrollTop / totalHeight) * 100;
setReadingProgress(currentProgress);
- setIsAtTop(scrollTop === 0); // Update isAtTop based on scroll position
+ setIsAtTop(scrollTop === 0);
}
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
- }, [post]); // Re-attach scroll listener if post changes
-
- if (loading) {
- // Skeleton loading screen for BlogPostPage
- return (
- <div className="bg-gray-900 py-16 sm:py-24 animate-pulse">
- <div className="mx-auto max-w-7xl px-6 lg:px-8">
- <div className="lg:grid lg:grid-cols-4 lg:gap-8">
- <div className="lg:col-span-3">
- <div className="h-8 bg-gray-800 rounded w-1/4 mb-4"></div>
- <div className="h-12 bg-gray-800 rounded w-3/4 mb-8"></div>
- <div className="space-y-4">
- <div className="h-6 bg-gray-800 rounded w-full"></div>
- <div className="h-6 bg-gray-800 rounded w-5/6"></div>
- <div className="h-6 bg-gray-800 rounded w-full"></div>
- <div className="h-6 bg-gray-800 rounded w-2/3"></div>
- </div>
- </div>
- <div className="hidden lg:block">
- <div className="bg-gray-800 rounded-lg shadow-lg p-6">
- <div className="h-8 bg-gray-700 rounded w-1/2 mb-4"></div>
- <div className="space-y-2">
- <div className="h-4 bg-gray-700 rounded w-full"></div>
- <div className="h-4 bg-gray-700 rounded w-3/4"></div>
- <div className="h-4 bg-gray-700 rounded w-1/2"></div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- );
- }
-
- // if (!post) { // This check is now mostly handled by the navigate('/404') above.
- // return <div className="text-center py-16">Post not found</div>;
- // }
-
- // Conditional rendering for post not found after loading or if attributes are missing
- if (!post || !post.attributes || post.body === '') {
- // If post is null, or attributes are missing (e.g., from shownPosts.json), or body is empty,
- // it implies the post couldn't be fully loaded or found. Ideally, navigate would handle this.
- // This serves as a fallback display.
- return (
- <div className="text-center py-16 text-gray-400">
- <h2 className="text-3xl font-bold mb-4">Post Not Found</h2>
- <p className="text-lg">The blog post you are looking for does not exist or could not be loaded.</p>
- <Link to="/blog" className="text-primary-400 hover:underline mt-4 inline-block">Go back to Blog</Link>
- </div>
- );
- }
-
- const currentPostIndex = post.seriesPosts ? post.seriesPosts.findIndex(
- (item) => item.slug === currentSlug,
- ) : -1;
- const prevPost = currentPostIndex > 0 ? post.seriesPosts[currentPostIndex - 1] : null;
- const nextPost = post.seriesPosts && currentPostIndex < post.seriesPosts.length - 1
- ? post.seriesPosts[currentPostIndex + 1]
- : null;
-
- const backLink = seriesSlug ? `/blog/series/${seriesSlug}` : '/blog';
- const backLinkText = seriesSlug ? 'Back to Series' : 'Back to Blog';
+ }, [contentRef, dependency]); // Re-run if contentRef or dependency changes
- return (
- <div className="bg-gray-900 py-16 sm:py-24">
- <div className="mx-auto max-w-7xl px-6 lg:px-8">
- <div className="lg:grid lg:grid-cols-4 lg:gap-8">
- <div className="lg:col-span-3">
- <Link
- to={backLink}
- className="text-primary-400 hover:underline flex items-center justify-center gap-2 text-lg mb-4"
- >
- <ArrowLeft size={24} /> {backLinkText}
- </Link>
- <div
- ref={contentRef}
- className="prose prose-xl prose-dark max-w-none"
- >
- <ReactMarkdown
- components={{
- a: LinkRenderer,
- code: (props) => (
- <CodeBlock {...props} openModal={openModal} />
- ),
- }}
- >
- {post.body}
- </ReactMarkdown>
- </div>
- {(prevPost || nextPost) && (
- <div className="mt-8 flex justify-between items-center border-t border-gray-700 pt-8">
- {prevPost && (
- <Link
- to={seriesSlug ? `/blog/series/${seriesSlug}/${prevPost.slug}` : `/blog/${prevPost.slug}`}
- className="text-primary-400 hover:underline flex items-center gap-2"
- >
- <ArrowLeft size={20} /> Previous: {prevPost.title}
- </Link>
- )}
- {nextPost && (
- <Link
- to={seriesSlug ? `/blog/series/${seriesSlug}/${nextPost.slug}` : `/blog/${nextPost.slug}`}
- className="text-primary-400 hover:underline flex items-center gap-2 ml-auto"
- >
- Next: {nextPost.title} <ArrowLeft size={20} className="rotate-180" />
- </Link>
- )}
- </div>
- )}
- </div>
- <div className="hidden lg:block">
- <PostMetadata
- metadata={post.attributes}
- readingProgress={readingProgress}
- isAtTop={isAtTop}
- overrideDate={post.attributes.date}
- updatedDate={post.attributes.updated}
- seriesPosts={post.seriesPosts}
- />
- </div>
- </div>
- </div>
- <CodeModal isOpen={isModalOpen} onClose={closeModal}>
- {modalContent}
- </CodeModal>
- </div>
- );
+ return { readingProgress, isAtTop };
};
-export default BlogPostPage;
+export default useScrollProgress;
-]]>
+Then, BlogPostPage.js would become cleaner:
+// src/pages/BlogPostPage.js - inside BlogPostPage component
+const contentRef = useRef(null);
+const { readingProgress, isAtTop } = useScrollProgress(contentRef, post);
+// ...
+
+This demonstrates how custom hooks can abstract away complex logic, making components more focused and easier to read.
+Summary
+Custom Hooks are a fundamental pattern in modern React development for sharing stateful logic. By following the use naming convention and leveraging other built-in Hooks, you can create highly reusable and maintainable code that enhances the overall architecture of your React applications.
+]]>src/App.js Main Component Explained
-src/App.js is the main component of your React application. It acts as the root of your component tree (after index.js renders it) and is responsible for setting up global configurations like routing, layout, and context providers that are available throughout your application.
import React from 'react';
-import { HashRouter as Router } from 'react-router-dom';
-import Layout from './components/Layout';
-import AnimatedRoutes from './components/AnimatedRoutes';
-import { ToastContext } from './components/ToastContext';
-import ScrollToTop from './components/ScrollToTop';
-
-function App() {
- return (
- <Router>
- <ScrollToTop />
- <ToastContext>
- <Layout>
- <AnimatedRoutes />
- </Layout>
- </ToastContext>
- </Router>
- );
-}
-
-export default App;
-
-import React from 'react';
-
-import React from 'react';: Imports the React library, necessary for defining React components and using JSX.import { HashRouter as Router } from 'react-router-dom';
-
-import { HashRouter as Router } from 'react-router-dom';: Imports HashRouter from the react-router-dom library and renames it to Router for convenience. HashRouter uses the hash portion of the URL (e.g., /#/blog) to keep your UI in sync with the URL. This is often preferred for static site deployments like GitHub Pages because it doesn't require server-side configuration for routing.import Layout from './components/Layout';
-
-import Layout from './components/Layout';: Imports the Layout component. This component likely defines the overall structure of your application, such as headers, footers, and sidebars, and wraps the main content area.import AnimatedRoutes from './components/AnimatedRoutes';
-
-import AnimatedRoutes from './components/AnimatedRoutes';: Imports the AnimatedRoutes component. This component is responsible for defining the application's routes and likely incorporates animation for page transitions, possibly using a library like framer-motion.import { ToastContext } from './components/ToastContext';
-
-import { ToastContext } from './components/ToastContext';: Imports the ToastContext component. This component is part of React's Context API pattern. It makes a toast (a small, temporary notification) functionality available to all its child components without having to pass props down manually at every level.import ScrollToTop from './components/ScrollToTop';
-
-import ScrollToTop from './components/ScrollToTop';: Imports the ScrollToTop component. This component is typically used in conjunction with routing to automatically scroll the window to the top of the page whenever the route changes, providing a better user experience.App Componentfunction App() {
- return (
- <Router>
- <ScrollToTop />
- <ToastContext>
- <Layout>
- <AnimatedRoutes />
- </Layout>
- </ToastContext>
- </Router>
- );
-}
+ Deep Dive: How React Toasts Work in fezcodex
+Toast notifications are a staple of modern web applications. They provide non-intrusive feedback to users about the result of their actions. In the fezcodex project, we have a robust and reusable toast system. This article will break down how it works, from its architecture to the React magic that holds it all together.
+Part 1: The Architecture - A Tale of Three Components
+The toast system is elegantly designed around three key parts that work in harmony:
+
+ToastContext.js (The Brains): This is the central manager. It wraps our entire application, creating a "context" that any component can plug into. It holds the list of all active toasts and provides the functions (addToast, removeToast) to modify that list. It's also responsible for rendering the container where the toasts appear.
+
+useToast.js (The Public API): This is a custom React Hook that acts as a clean and simple gateway. Instead of components needing to know about the underlying context, they can just use this hook to get access to the addToast function. It's the "button" that other components press to request a toast.
+
+Toast.js (The Notification UI): This component represents a single toast message. It's responsible for its own appearance, animations, and, most importantly, its own demise. It knows how long it should be on screen and contains the logic to remove itself after its time is up.
+
+
+Part 2: The Magic of useState - Where Does the State Go?
+This is the crucial question. In ToastContext.js, we have this line:
+const [toasts, setToasts] = useState([]);
-
-function App() { ... }: This defines a functional React component named App. Functional components are the modern way to write React components and are essentially JavaScript functions that return JSX.
+When a component function runs, all its internal variables are created and then discarded when it's done. So how does the toasts array not just reset to [] every single time?
+React Remembers.
+The useState hook is a request to React to create and manage a piece of state on behalf of your component.
+
+First Render: The very first time ToastContext renders, React sees useState([]). It creates a "memory cell" for this specific component instance and puts an empty array [] inside it. It then returns that array to the component as the toasts variable.
-return (...): The return statement contains the JSX (JavaScript XML) that defines the UI structure for the App component.
-
-<Router>: This is the HashRouter component from react-router-dom. It wraps the entire application, enabling client-side routing. Any component within this Router can use routing features like Link and useParams.
+State Updates: When you call addToast, it eventually calls setToasts(...). This function doesn't change the state directly. Instead, it sends a message to React saying, "I have a new value for this state. Please update it and re-render the component."
-<ScrollToTop />: This component is rendered directly inside the Router. Its effect (scrolling to top on route change) will apply globally to the application.
+Subsequent Renders: When React re-renders ToastContext, it arrives at the useState([]) line again. But this time, React knows it has already created a state for this component. It ignores the initial value ([]) and instead provides the current value from its internal memory—the updated array of toasts.
-<ToastContext>: This component wraps the Layout and AnimatedRoutes. This means that any component rendered within the Layout or AnimatedRoutes will have access to the toast functionality provided by the ToastContext via the useContext hook.
+
+This is the fundamental principle of React Hooks: they allow your function components to have stateful logic that persists across renders, managed by React itself.
+Part 3: The Full Lifecycle of a Toast
+Let's tie it all together by following a single toast from birth to death.
+
+The Call: A user performs an action in a component (e.g., the Word Counter). That component calls addToast({ title: 'Success!', ... }).
-<Layout>: This component defines the common structure (e.g., header, footer, navigation) that will be present on most pages. It wraps the AnimatedRoutes component, meaning the routed content will be displayed within this layout.
+The Context: The useToast hook provides the addToast function from the ToastContext's context.
-<AnimatedRoutes />: This component is where the actual route definitions (e.g., /blog, /about, /projects) are handled. When the URL changes, AnimatedRoutes will render the appropriate page component (e.g., BlogPostPage, HomePage) within the Layout.
+The State Update: The addToast function in ToastContext runs. It creates a new toast object with a unique ID and calls setToasts([newToast, ...otherToasts]).
-
+The Re-render: React receives the state update request and schedules a re-render for ToastContext.
-
-Export
-export default App;
-
-
-export default App;: This makes the App component the default export of this module, allowing it to be imported by other files (like src/index.js).
-
-Summary
-src/App.js orchestrates the main structure and global functionalities of the application. It sets up routing, provides global context for notifications, and defines the overarching layout, ensuring a consistent user experience across different pages.
-]]>
- src/index.js Entry Point Explained
-src/index.js is the absolute entry point of your React application. It's the first JavaScript file that gets executed when your web page loads. Its primary responsibility is to render your root React component (App in this case) into the HTML document.
import React from 'react';
-import ReactDOM from 'react-dom/client';
-import './index.css';
-import App from './App';
-import reportWebVitals from './reportWebVitals';
-
-const root = ReactDOM.createRoot(document.getElementById('root'));
-root.render(
- <React.StrictMode>
- <App />
- </React.StrictMode>,
-);
-
-// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
-// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();
-
-import React from 'react';
-
-import React from 'react';: This line imports the React library. Even though you might not directly use React.createElement in JSX, importing React is traditionally required by Babel (the JavaScript compiler) to transform JSX into React.createElement calls. In newer versions of React and Babel, this might be optimized away, but it's still a common practice.import ReactDOM from 'react-dom/client';
-
-import ReactDOM from 'react-dom/client';: This imports the ReactDOM client-specific library, which provides methods to interact with the DOM (Document Object Model) in a web browser. Specifically, react-dom/client is the modern API for client-side rendering with React 18+.import './index.css';
-
-import './index.css';: This line imports the global CSS stylesheet for the application. When bundled, Webpack (or a similar tool used by Create React App/Craco) processes this import, often injecting the styles into the HTML document at runtime or extracting them into a separate CSS file.import App from './App';
-
-import App from './App';: This imports the main App component, which serves as the root of your entire React component tree. The App component will contain the application's layout, routing, and other main functionalities.import reportWebVitals from './reportWebVitals';
-
-import reportWebVitals from './reportWebVitals';: This imports a utility function that helps measure and report on your application's Web Vitals. Web Vitals are a set of metrics from Google that quantify the user experience of a web page.const root = ReactDOM.createRoot(document.getElementById('root'));
-
-ReactDOM.createRoot(document.getElementById('root')): This is the modern way to initialize a React application for client-side rendering (React 18+). It finds the HTML element with the ID root (which is typically found in public/index.html) and creates a React root. This root object is where your React application will be attached to the DOM.root.render(
- <React.StrictMode>
- <App />
- </React.StrictMode>,
-);
-
-root.render(...): This method tells React to display the App component inside the root DOM element. Whatever is rendered within root.render will be managed by React.
<React.StrictMode>: This is a wrapper component that helps identify potential problems in an application. It activates additional checks and warnings for its descendants during development mode. For example, it helps detect deprecated lifecycles, unexpected side effects, and more. It does not render any visible UI; it's purely a development tool.<App />: This is your main application component, as imported earlier. All other components and the entire UI will be rendered as children of this App component.The Render: ToastContext runs again. It calls useState, and React hands it the new array containing our new toast. The component's return statement is executed, and its .map() function now loops over an array that includes the new toast.
reportWebVitals();
-
-reportWebVitals();: This function call initiates the measurement and reporting of Web Vitals metrics, which can be useful for performance monitoring and optimization. The function in reportWebVitals.js typically sends these metrics to an analytics endpoint or logs them to the console.src/index.js is the foundational file where your React application begins its life in the browser. It sets up the bridge between your React code and the actual HTML document, ensuring your components are rendered and managed correctly, and optionally enables development tools like Strict Mode and performance monitoring with Web Vitals.
The Birth: A new <Toast /> component is rendered on the screen. It receives its id, title, message, and duration as props.
The Countdown: Inside the new <Toast /> component, a useEffect hook fires. It starts a setTimeout timer for the given duration.
The End: When the timer finishes, it calls the removeToast(id) function that was passed down as a prop.
The Cleanup: removeToast in the ToastContext calls setToasts(...) again, this time with an array that filters out the toast with the matching ID.
The Final Re-render: React processes the state update, re-renders the ToastContext, and the toast is no longer in the array. It vanishes from the screen.
The fezcodex toast system is a perfect microcosm of modern React development. It shows how to use Context to provide global functionality without cluttering components, and it relies on the magic of the useState hook to give components a memory that persists between renders. By letting React manage the state, we can write declarative UI that simply reacts to state changes.
The package.json file is a crucial part of any Node.js project, including React applications. It acts as a manifest for the project, listing its metadata, scripts, and dependencies. Let's break down the key sections of this project's package.json.
{
- "name": "fezcodex",
- "version": "0.1.0",
- "private": true,
- "homepage": "https://fezcode.com",
- "dependencies": {
- "@phosphor-icons/react": "^2.1.10",
- "@testing-library/dom": "^10.4.1",
- "@testing-library/jest-dom": "^6.9.1",
- "@testing-library/react": "^16.3.0",
- "@testing-library/user-event": "^13.5.0",
- "framer-motion": "^12.23.24",
- "front-matter": "^4.0.2",
- "react": "^19.2.0",
- "react-dom": "^19.2.0",
- "react-icons": "^5.5.0",
- "react-markdown": "^10.1.0",
- "react-router-dom": "^7.9.4",
- "react-scripts": "5.0.1",
- "react-slick": "^0.31.0",
- "react-syntax-highlighter": "^15.6.6",
- "slick-carousel": "^1.8.1",
- "web-vitals": "^2.1.4"
- },
- "scripts": {
- "prestart": "node scripts/generateWallpapers.js",
- "start": "craco start",
- "prebuild": "node scripts/generateWallpapers.js",
- "build": "craco build",
- "test": "craco test",
- "eject": "react-scripts eject",
- "lint": "eslint \"src/**/*.{js,jsx}\" \"scripts/**/*.js\" --fix",
- "format": "prettier --write \"src/**/*.{js,jsx,css,json}\"",
- "predeploy": "npm run build",
- "deploy": "gh-pages -d build -b gh-pages"
- },
- "eslintConfig": {
- "extends": [
- "react-app",
- "react-app/jest"
- ]
- },
- "browserslist": {
- "production": [
- ">0.2%",
- "not dead",
- "not op_mini all"
- ],
- "development": [
- "last 1 chrome version",
- "last 1 firefox version",
- "last 1 safari version"
- ]
- },
- "devDependencies": {
- "@craco/craco": "^7.1.0",
- "@tailwindcss/typography": "^0.5.19",
- "autoprefixer": "^10.4.21",
- "cross-env": "^10.1.0",
- "gh-pages": "^6.3.0",
- "postcss": "^8.5.6",
- "prettier": "^3.6.2",
- "tailwindcss": "^3.4.18"
- }
-}
-
-name: "fezcodex" - The name of the project. This is often used for npm packages and identifies your project.version: "0.1.0" - The current version of the project. Follows semantic versioning (major.minor.patch).private: true - Indicates that the package is not intended to be published to a public npm registry. This is common for application-level projects.homepage: "https://fezcode.com" - Specifies the homepage URL for the project. For applications deployed to GitHub Pages, this is often the live URL.dependenciesThis section lists all the packages required by the application to run in production. These are core libraries that your code directly uses.
-@phosphor-icons/react: Provides a flexible icon library with a focus on consistency and customization.@testing-library/dom, @testing-library/jest-dom, @testing-library/react, @testing-library/user-event: These are testing utilities that facilitate writing user-centric tests for React components. They help ensure the application behaves as expected from a user's perspective.framer-motion: A powerful and easy-to-use library for creating animations and interactive elements in React applications.front-matter: A utility for parsing front-matter (metadata) from strings, typically used with Markdown files.react: The core React library itself.react-dom: Provides DOM-specific methods that enable React to interact with the web browser's DOM.react-icons: Another popular library offering a wide range of customizable SVG icons from various icon packs.react-markdown: A React component that securely renders Markdown as React elements, allowing you to display Markdown content in your application.react-router-dom: The standard library for client-side routing in React applications, allowing navigation between different views.react-scripts: A package from Create React App that provides scripts for common development tasks like starting a development server, building for production, and running tests.react-slick / slick-carousel: Libraries used for creating carousels or sliders, likely for displaying image galleries or testimonials.react-syntax-highlighter: A component that enables syntax highlighting for code blocks, often used in conjunction with react-markdown to display code snippets beautifully.web-vitals: A library for measuring and reporting on a set of standardized metrics that reflect the real-world user experience on your website.scriptsThis object defines a set of command-line scripts that can be executed using npm run <script-name>. These automate common development and deployment tasks.
prestart: "node scripts/generateWallpapers.js" - A pre-script hook that runs before the start script. In this case, it executes a Node.js script to generate wallpapers, likely for dynamic backgrounds or assets.start: "craco start" - Starts the development server. craco (Create React App Configuration Override) is used here to allow customizing the underlying Webpack/Babel configuration of react-scripts without ejecting the CRA setup.prebuild: "node scripts/generateWallpapers.js" - Similar to prestart, this runs before the build script, ensuring assets are generated before the production build.build: "craco build" - Creates a production-ready build of the application, optimizing and bundling all assets for deployment.test: "craco test" - Runs the project's test suite.eject: "react-scripts eject" - This is a one-way operation that removes the single build dependency from your project, giving you full control over the Webpack configuration files and build scripts. It's rarely used unless deep customization is needed.lint: "eslint \"src/**/*.{js,jsx}\" \"scripts/**/*.js\" --fix" - Runs ESLint, a tool for identifying and reporting on patterns in JavaScript code to maintain code quality and style. The --fix flag attempts to automatically fix some issues.format: "prettier --write \"src/**/*.{js,jsx,css,json}\"" - Runs Prettier, an opinionated code formatter, to ensure consistent code style across the project. The --write flag formats files in place.predeploy: "npm run build" - Runs the build script before the deploy script, ensuring that the latest production build is created before deployment.deploy: "gh-pages -d build -b gh-pages" - Deploys the build directory to the gh-pages branch of the GitHub repository, facilitating hosting on GitHub Pages.eslintConfigThis field configures ESLint. "extends": ["react-app", "react-app/jest"] means it's extending the recommended ESLint configurations provided by Create React App, along with specific rules for Jest testing.
browserslistThis field specifies the target browsers for your client-side code. This is used by tools like Babel and Autoprefixer to ensure your JavaScript and CSS are compatible with the specified browser versions.
+useRef Hook
+The useRef Hook is a fundamental part of React that allows you to create mutable ref objects. These ref objects can hold a reference to a DOM element or any mutable value that persists across re-renders without causing a re-render when its value changes.
useRef?useRef serves two primary purposes:
useRef can hold any mutable value, similar to an instance variable in a class component. Unlike useState, updating a ref's .current property does not trigger a re-render of the component. This is useful for storing values that need to persist across renders but whose changes don't need to be reflected in the UI immediately.useRef WorksuseRef returns a plain JavaScript object with a single property called current. This current property can be initialized with an argument passed to useRef.
const myRef = useRef(initialValue);
+
production: Defines the browser targets for the production build (e.g., browsers with more than 0.2% market share, excluding Internet Explorer-era browsers and Opera Mini).development: Defines less strict browser targets for development, usually focusing on the latest versions of common development browsers.myRef: The ref object returned by useRef.myRef.current: The actual mutable value or DOM element reference.initialValue: The initial value for myRef.current.devDependenciesThese are packages required only for development and building the project, not for the application to run in production. They provide tools, testing utilities, and build-related functionalities.
+contentRef in src/pages/BlogPostPage.jsIn BlogPostPage.js, useRef is used to get a direct reference to the main content div of the blog post. This reference is then used to calculate the reading progress based on scroll position.
// src/pages/BlogPostPage.js
+import React, { useState, useEffect, useRef } from 'react';
+// ...
+
+const BlogPostPage = () => {
+ // ...
+ const contentRef = useRef(null); // Initialize contentRef with null
+ // ...
+
+ useEffect(() => {
+ const handleScroll = () => {
+ if (contentRef.current) { // Access the DOM element via .current
+ const { scrollTop, scrollHeight, clientHeight } =
+ document.documentElement;
+ const totalHeight = scrollHeight - clientHeight;
+ const currentProgress = (scrollTop / totalHeight) * 100;
+ setReadingProgress(currentProgress);
+ setIsAtTop(scrollTop === 0);
+ }
+ };
+
+ window.addEventListener('scroll', handleScroll);
+ return () => window.removeEventListener('scroll', handleScroll);
+ }, [post]);
+
+ return (
+ // ...
+ <div
+ ref={contentRef} // Attach the ref to the div element
+ className="prose prose-xl prose-dark max-w-none"
+ >
+ {/* ... Markdown content ... */}
+ </div>
+ // ...
+ );
+};
+
+Explanation:
+const contentRef = useRef(null);: A ref object named contentRef is created and initialized with null. At this point, contentRef.current is null.<div ref={contentRef}>: The ref object is attached to the div element that contains the blog post's Markdown content. Once the component renders, React will set contentRef.current to point to this actual DOM div element.if (contentRef.current): Inside the useEffect's handleScroll function, contentRef.current is checked to ensure that the DOM element is available before attempting to access its properties (like scrollHeight or clientHeight).document.documentElement: While contentRef.current gives a reference to the specific content div, the scroll calculation here uses document.documentElement (the <html> element) to get the overall page scroll position and dimensions. This is a common pattern for tracking global scroll progress.useRef vs. useStateIt's important to understand when to use useRef versus useState:
| Feature | +useState |
+useRef |
+
|---|---|---|
| Purpose | +Manages state that triggers re-renders. | +Accesses DOM elements or stores mutable values that don't trigger re-renders. | +
| Re-renders | +Updates to state variables cause component re-renders. | +Updates to ref.current do not cause re-renders. |
+
| Value Persistence | +Value persists across re-renders. | +Value persists across re-renders. | +
| Mutability | +State is generally treated as immutable (updated via setState). |
+ref.current is directly mutable. |
+
When to use useRef:
@craco/craco: The main Craco package that allows overriding Create React App's Webpack configuration.@tailwindcss/typography: A Tailwind CSS plugin that provides a set of prose classes to add beautiful typographic defaults to raw HTML or Markdown, improving readability of content.autoprefixer: A PostCSS plugin that adds vendor prefixes to CSS rules, ensuring cross-browser compatibility.cross-env: A utility that provides a universal way to set environment variables across different operating systems, commonly used in npm scripts.gh-pages: A tool specifically for publishing content to the gh-pages branch on GitHub, used for deploying to GitHub Pages.postcss: A tool for transforming CSS with JavaScript plugins. Tailwind CSS relies on PostCSS.prettier: The code formatter used in the format script.tailwindcss: The core Tailwind CSS framework, enabling utility-first styling in the project.This package.json file provides a comprehensive insight into the project's setup, dependencies, and available scripts for development, testing, and deployment.
useRef provides a way to "escape" React's declarative paradigm when necessary, offering direct access to the underlying DOM or a persistent mutable storage for values that don't need to be part of the component's reactive state. It's a powerful tool for specific use cases where direct imperative manipulation or persistent non-state values are required.
This document provides a high-level overview of the "Fezcode" project, a React-based web application designed to serve as a personal blog or portfolio site.
-The primary purpose of this project is to display blog posts, projects, and other content in a structured and visually appealing manner. It leverages modern web technologies to create a dynamic and responsive user experience.
-The project is built using the following core technologies:
+useCallback, useMemo) and React.memo
+In React, components re-render when their state or props change. While React is highly optimized, unnecessary re-renders can sometimes impact performance, especially for complex components or frequently updated lists. Memoization techniques help prevent these unnecessary re-renders by caching computation results or function definitions.
+useCallback HookuseCallback is a Hook that returns a memoized callback function. It's useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary re-renders.
const memoizedCallback = useCallback(
+ () => {
+ doSomething(a, b);
+ },
+ [a, b], // dependencies
+);
+
react-markdown.react-syntax-highlighter.() => { doSomething(a, b); } will only be re-created if a or b changes.The project follows a typical React application structure, with key directories including:
+src/components/ToastContext.js// src/components/ToastContext.js
+import React, { createContext, useState, useCallback } from 'react';
+// ...
+
+export const ToastContext = ({ children }) => {
+ const [toasts, setToasts] = useState([]);
+
+ const addToast = useCallback((toast) => {
+ const newToast = { ...toast, id: id++ };
+ setToasts((prevToasts) => {
+ if (prevToasts.length >= 5) {
+ const updatedToasts = prevToasts.slice(0, prevToasts.length - 1);
+ return [newToast, ...updatedToasts];
+ }
+ return [newToast, ...prevToasts];
+ });
+ }, []); // Empty dependency array: addToast is created only once
+
+ const removeToast = useCallback((id) => {
+ setToasts((prevToasts) => prevToasts.filter((toast) => toast.id !== id));
+ }, []); // Empty dependency array: removeToast is created only once
+
+ return (
+ <ToastContext.Provider value={{ addToast, removeToast }}>
+ {/* ... */}
+ </ToastContext.Provider>
+ );
+};
+
+Explanation:
public/: Contains static assets like index.html, images, and the raw content for blog posts (posts/), logs (logs/), and projects (projects/).src/: Contains the main application source code, organized into:components/: Reusable UI components (e.g., Navbar, Footer, Toast).pages/: Page-level components that represent different views of the application (e.g., HomePage, BlogPostPage, NotFoundPage).hooks/: Custom React hooks for encapsulating reusable logic (e.g., useToast).utils/: Utility functions and helpers.styles/: Custom CSS files.config/: Configuration files (e.g., colors, fonts).addToast and removeToast functions are wrapped in useCallback with an empty dependency array ([]). This means these functions are created only once when the ToastContext component first renders and will not change on subsequent re-renders.addToast and removeToast are passed down as part of the value to ToastContext.Provider. If these functions were re-created on every render, any child component consuming this context and relying on reference equality (e.g., with React.memo or useMemo) might unnecessarily re-render.scripts/: Contains utility scripts, such as generateWallpapers.js.useMemo HookuseMemo is a Hook that returns a memoized value. It's useful for optimizing expensive calculations that don't need to be re-computed on every render.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
+
+() => computeExpensiveValue(a, b) will only execute if a or b changes. Otherwise, it returns the previously computed value.src/index.js): The application starts by rendering the main App component into the index.html file.src/App.js): The App component sets up client-side routing using HashRouter, defines the overall layout, and manages global contexts like the ToastContext.react-router-dom): AnimatedRoutes (likely a component that uses react-router-dom's Routes and Route components) handles mapping URLs to specific page components..txt files located in the public/ directory. Metadata for these posts is often stored in corresponding .json files (e.g., public/posts/posts.json). The blog page now includes a search functionality to easily find posts by title or slug.Tailwind CSS): The UI is styled primarily using Tailwind CSS utility classes, with some custom CSS if needed.gh-pages package.This overview provides a foundational understanding of the Fezcode project. Subsequent documents will delve into more specific details of each component and concept.
-]]>Imagine a component that filters a large list based on some criteria:
+function ProductList({ products, filterText }) {
+ // This filtering operation can be expensive if products is a very large array
+ const filteredProducts = products.filter(product =>
+ product.name.includes(filterText)
+ );
+
+ // With useMemo, the filtering only re-runs if products or filterText changes
+ const memoizedFilteredProducts = useMemo(() => {
+ return products.filter(product =>
+ product.name.includes(filterText)
+ );
+ }, [products, filterText]);
+
+ return (
+ <div>
+ {memoizedFilteredProducts.map(product => (
+ <ProductItem key={product.id} product={product} />
+ ))}
+ </div>
+ );
+}
+
+React.memo (Higher-Order Component)React.memo is a higher-order component (HOC) that memoizes a functional component. It works similarly to PureComponent for class components. If the component's props are the same as the previous render, React.memo will skip rendering the component and reuse the last rendered result.
const MyMemoizedComponent = React.memo(MyComponent, [arePropsEqual]);
+
+MyComponent: The functional component to memoize.arePropsEqual (optional): A custom comparison function. If provided, React will use it to compare prevProps and nextProps. If it returns true, the component will not re-render.// ProductItem.js
+function ProductItem({ product }) {
+ console.log('Rendering ProductItem', product.name);
+ return <li>{product.name}</li>;
+}
+
+export default React.memo(ProductItem);
+
+// In ProductList component (from useMemo example)
+// If ProductItem is memoized, it will only re-render if its 'product' prop changes.
+
+Explanation:
+ProductItem with React.memo, React will perform a shallow comparison of its props. If the product prop (and any other props) remains the same between renders of its parent, ProductItem will not re-render, saving computational resources.useCallback, useMemo, and React.memo are powerful tools for optimizing the performance of React applications by preventing unnecessary re-renders. They are particularly useful in scenarios involving expensive computations, frequently updated components, or when passing functions/objects as props to child components that rely on reference equality. While not every component needs memoization, understanding when and how to apply these techniques is crucial for building high-performance React applications.
+ Regular Expressions (RegEx) are patterns used to match character combinations in strings. + They are effectively a small, highly specialized programming language for text processing. +
+ ++ While incredibly powerful for validation, searching, and extraction, they are notorious for their + cryptic syntax and the potential for "catastrophic backtracking" if poorly designed. +
+ ++ "Some people, when confronted with a problem, think 'I know, I'll use regular expressions.' + Now they have two problems." — Jamie Zawinski +
+
+ /^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6})$/
+
+ + (A standard email validation regex. It works until it doesn't.) +
+⚠️ Warning: Objects in Mirror Are Less Perfect Than They Appear
+If you think this blog post is genius just because the font is nice and the layout is clean, you are currently being blinded by the very thing I'm about to roast. +Welcome to the glow.
+ +]]>+ {currentTrack?.artist || 'UNKNOWN'} +
+Which HEX code matches this color?
- -Click stripe to set lightness
- -+ Click stripe to set lightness +
- {harmonyType === 'complementary' && "Complementary colors are opposite each other on the color wheel. They create the strongest contrast and visual tension."} - {harmonyType === 'analogous' && "Analogous colors are next to each other on the wheel. They usually match well and create serene and comfortable designs."} - {harmonyType === 'triadic' && "Triadic color schemes use colors that are evenly spaced around the color wheel. They tend to be quite vibrant, even if you use pale or unsaturated versions of your hues."} - {harmonyType === 'split-complementary' && "The split-complementary color scheme is a variation of the complementary color scheme. In addition to the base color, it uses the two colors adjacent to its complement."} - {harmonyType === 'tetradic' && "The tetradic (rectangle) color scheme uses four colors arranged into two complementary pairs. This rich color scheme offers plenty of possibilities for variation."} - {harmonyType === 'monochromatic' && "Monochromatic color schemes are derived from a single base hue and extended using its shades, tones and tints."} -
++ {harmonyType === 'complementary' && + 'Complementary colors are opposite each other on the color wheel. They create the strongest contrast and visual tension.'} + {harmonyType === 'analogous' && + 'Analogous colors are next to each other on the wheel. They usually match well and create serene and comfortable designs.'} + {harmonyType === 'triadic' && + 'Triadic color schemes use colors that are evenly spaced around the color wheel. They tend to be quite vibrant, even if you use pale or unsaturated versions of your hues.'} + {harmonyType === 'split-complementary' && + 'The split-complementary color scheme is a variation of the complementary color scheme. In addition to the base color, it uses the two colors adjacent to its complement.'} + {harmonyType === 'tetradic' && + 'The tetradic (rectangle) color scheme uses four colors arranged into two complementary pairs. This rich color scheme offers plenty of possibilities for variation.'} + {harmonyType === 'monochromatic' && + 'Monochromatic color schemes are derived from a single base hue and extended using its shades, tones and tints.'} +
- Understanding the fundamental concepts behind how we see, mix, and use color is the first step to mastering design. -
++ Understanding the fundamental concepts behind how we see, mix, and + use color is the first step to mastering design. +
{chapter.subtitle}
-+ {chapter.subtitle} +
+The small squares are the exact same color (#808080), but they appear different depending on the background.
Bright red and bright blue (or green) create a "vibrating" edge when placed next to each other due to difficulty in focusing on both simultaneously.
-+ The small squares are the exact same color (#808080), but they + appear different depending on the background. +
{' '} +The visual system exaggerates the contrast at edges of slightly differing shades, creating the illusion of darker or lighter bands.
-+ Bright red and bright blue (or green) create a "vibrating" edge + when placed next to each other due to difficulty in focusing on + both simultaneously. +
++ The visual system exaggerates the contrast at edges of slightly + differing shades, creating the illusion of darker or lighter + bands. +
+- Color is not just a visual sensation but a property of light. In digital design, we often use the HSL model because it aligns with how humans perceive color. -
-Depending on whether you are working with light (screens) or pigment (print), color mixing works differently.
+ { + id: 'basics', + title: 'The Basics of Color', + subtitle: 'Understanding Hue, Saturation, and Lightness', + content: ( ++ Color is not just a visual sensation but a property of light. In + digital design, we often use the HSL model because it + aligns with how humans perceive color. +
++ Depending on whether you are working with light (screens) or pigment + (print), color mixing works differently. +
-Used for Screens.
-
- Start with black. Add Red, Green, and Blue light.
- Result: Adding all three makes White.
-
Used for Print.
-
- Start with white paper. Add Cyan, Magenta, Yellow ink.
- Result: Adding all three makes Black (muddy dark brown).
-
Colors evoke emotions and associations. While cultural context matters, some universal effects exist.
-Used for Screens.
+
+ Start with black. Add Red, Green, and Blue light.
+ Result: Adding all three makes
+ White.
+
Used for Print.
+
+ Start with white paper. Add Cyan, Magenta, Yellow ink.
+
+ Result: Adding all three makes
+ Black (muddy dark brown).
+
+ Colors evoke emotions and associations. While cultural context + matters, some universal effects exist. +
+Change your fasting hours
- -+ Change your fasting hours +
+ +No meals logged yet.
- -+ No meals logged yet. +
+Record what you ate
- -+ Record what you ate +
+ ++ {selectedDate} {'//'} Total: {totalPlannedCalories} kcal +
- - {selectedDate} {'//'} Total: {totalPlannedCalories} kcal - -
- -- - Your plan is empty for this date. - -
- -+ Your plan is empty for this date. +
+Loading_Matrix_Data...
-No assessment required for this unit.
-+ No assessment required for this unit. +
+- Validation_Logic: {question.testCase} -
- - {showResult && !isCorrect && ( -+ Validation_Logic: {question.testCase} +
+ + {showResult && !isCorrect && ( +- Neural pathways established. Concept integration verified at 100% efficiency. + Neural pathways established. Concept integration verified at 100% + efficiency.
Verify_Data_Integrity
-+ Verify_Data_Integrity +
+Error_Detected
-Review the correct output above. Logic adjustment required.
-+ Error_Detected +
++ Review the correct output above. Logic adjustment required. +
Protocol: Zero_to_Hero
-+ Protocol: Zero_to_Hero +
++
+{message}
- No_Results_Found_For: {searchTerm} + No_Results_Found_For:{' '} + {searchTerm}
- Choose a preferred channel to initiate communication with the primary node. + Choose a preferred channel to initiate communication with the + primary node.
) : (@@ -53,7 +54,9 @@ const ContactModal = ({ isOpen, onClose }) => { href={link.url} icon={Icon} label={link.label} - value={link.url.replace(/^mailto:/, '').replace(/^https?:\/\//, '')} + value={link.url + .replace(/^mailto:/, '') + .replace(/^https?:\/\//, '')} /> ); } @@ -63,7 +66,9 @@ const ContactModal = ({ isOpen, onClose }) => { href={link.url} icon={Icon} label={link.label} - value={link.url.replace(/^mailto:/, '').replace(/^https?:\/\//, '')} + value={link.url + .replace(/^mailto:/, '') + .replace(/^https?:\/\//, '')} /> ); })} @@ -88,10 +93,15 @@ const LuxeContactLink = ({ href, icon: Icon, label, value }) => ( {label} - {value} + + {value} +
- {'//'} {aboutData.profile.tagline || 'A digital garden of experimental code, architectural thoughts, and creative explorations.'} + {'//'}{' '} + {aboutData.profile.tagline || + 'A digital garden of experimental code, architectural thoughts, and creative explorations.'}
- No archive records found for: {searchTerm} + No archive records found for:{' '} + {searchTerm}
{description}
@@ -88,7 +97,11 @@ const VagueEditorialModal = ({
>
Download PDF
-
- {description} -
)} -- {footerText} -
-- {note} -
))} ++ {description} +
+ )} ++ {footerText} +
++ {note} +
+ ))} +- Wallpaper Source // {bgImageName || 'Default_Vault'} + Wallpaper Source //{' '} + + {bgImageName || 'Default_Vault'} +
diff --git a/src/components/dnd/DndLayout.jsx b/src/components/dnd/DndLayout.jsx index 765391a8a..541eea199 100644 --- a/src/components/dnd/DndLayout.jsx +++ b/src/components/dnd/DndLayout.jsx @@ -9,9 +9,7 @@ import '../../styles/dnd-refactor.css'; import { GameController, Flame, TreasureChest } from '@phosphor-icons/react'; import { useToast } from '../../hooks/useToast'; -const Lightning = () => ( -
-); +const Lightning = () => ; const LootDiscovery = () => { const { addToast } = useToast(); @@ -41,7 +39,10 @@ const LootDiscovery = () => { animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0 }} onClick={() => { - const currentCount = parseInt(localStorage.getItem('fezcodex_loot_count') || '0', 10); + const currentCount = parseInt( + localStorage.getItem('fezcodex_loot_count') || '0', + 10, + ); const newCount = currentCount + 1; localStorage.setItem('fezcodex_loot_count', newCount.toString()); @@ -85,15 +86,13 @@ const FireplaceAudio = () => { className={`p-4 rounded-full border-2 transition-all shadow-2xl ${isPlaying ? 'bg-dnd-crimson border-dnd-gold text-white animate-pulse' : 'bg-black/40 border-white/10 text-white/40 hover:text-white'}`} title="Toggle Ambient Fireplace" > -Details
{children}
- }} - > - {section.body} -{children}
+ ), + }} + > + {section.body} +
- <>{children}> }}>
+ <>{children}> }}
+ >
{content}
-
Social
-+ Social +
+Install
-+ Install +
+--{children}, - p: ({children}) => -{children}- }} - > - {content} -
++( + {children} + ), + p: ({ children }) => +{children}, + }} + > + {content} +
Back to Archive Index
-