Pumpkiinbell Blog and Documentation

website·Indexed 31 days ago

https://www.pumpkiinbell.com/

A technical resource covering frontend development, featuring guides on React performance optimization, reusable components, and state management using Jotai and React Hook Form. It includes deep dives into TypeScript exhaustiveness checking, JavaScript module systems (ES6, CommonJS, AMD), and schema validation using Zod and Superstruct.

Tokens
11.3K
Snippets
45
Records
63
Agent score
57%

What's inside Pumpkiinbell

  1. Compare ES6 Modules with CommonJS and AMD

    ES6 introduced a standardized module system to replace fragmented specifications like CommonJS, AMD, and the cumbersome UMD (Universal Module Definition). ES6 modules combine the strengths of previous systems:

    • Circular references (similar to CommonJS).
    • Asynchronous module imports (similar to AMD).
    • Static module structure.
    • Simpler syntax compared to UMD.
  2. Improve code readability by avoiding unnecessary destructuring

    To reduce the cognitive load (Short Term Memory usage) for reviewers, avoid destructuring objects when the resulting variable name doesn't provide enough context about its origin. Using dot notation allows a developer to immediately identify where a value comes from without having to track the destructuring assignment at the top of the function.
    // Avoid this: requires remembering that 'type' came from 'payload'
    const { payload: { type, name } } = useFormContext();
    type === 'a' ? doA() : doSomething();
    
    // Prefer this: origin is explicit
    const { payload } = useFormContext();
    payload.type === 'a' ? doA() : doSomething();
  3. Handle circular references in ES6 modules

    ES6 modules support circular references, similar to CommonJS. A circular reference occurs when Module A references Module B, and Module B references Module A (or through a longer chain like A → B → C → A). While generally discouraged, this is necessary for certain structures like DOM parent-child relationships or during large-scale application refactoring. ES6 modules handle these scenarios without causing system errors.
  4. Organize hooks by scope and domain

    Distinguish between global/common hooks and domain-specific hooks through folder structure. This provides immediate context to developers about where a hook is intended to be used and how the project is structured.
    ㄴ common
      ㄴ hooks
        ㄴ useModal.ts          // Global/Reusable hook
    ㄴ page
      ㄴ product
        ㄴ components
          ㄴ ProductList.tsx
        ㄴ hooks
          ㄴ useProductList.ts  // Domain-specific hook
  5. Manage complex state logic with useReducer

    Use useReducer when state logic becomes complex or when multiple state variables depend on each other. It allows you to decouple the state transition logic (the reducer) from the component or hook, making the code more readable, maintainable, and easier to test. This is particularly useful when adding new state variables would exponentially increase the number of action handlers required in a useState pattern.
  6. Understand asynchronous loading in ES6 modules

    ES6 modules support Asynchronous Loading, a feature similar to AMD. Unlike CommonJS, which blocks execution until all imported modules are loaded (problematic in browser network environments), asynchronous loading allows modules to execute in the order they are loaded, improving usability and performance in web environments.
  7. Export values from a JavaScript module

    Use the export statement to make functions, objects, or primitives available to other modules. Modules using export always operate in strict mode. There are two primary styles of exports:

    1. Named Exports: Allows exporting 0 to N targets. You can export variables (var, let, const), functions, and classes. These can be declared inline or listed in an export block at the top level.
    2. Default Exports: Allows exporting exactly one target per module. If multiple default exports are declared, the last one overrides previous ones.

    Aliasing: Use the as keyword to export a target under a different name. You can also use as default to designate a named export as the default export.

    // Named Exports
    export { myFunction, myVariable };
    export let myVariable = Math.sqrt(2);
    export function myFunction() { /* ... */ }
    
    // Default Export
    export default function () { /* ... */ }
    
    // Aliasing
    export { myFunction as function1, myVariable as variable };
    export { myFunction as default }; // Equivalent to export default myFunction;
  8. Achieve Render-as-you-fetch with React Query

    React Query's default suspense mode follows the 'fetch-on-render' pattern. To implement a 'render-as-you-fetch' strategy, you must trigger the fetch before the component mounts. Recommended approaches include:

    • Prefetching data within routing callbacks.
    • Triggering queries via UI events before the target component is mounted.
    • Starting the fetch before importing or mounting parent components.
  9. Prevent runtime errors using TypeScript exhaustiveness checks

    Performing an exhaustiveness check in TypeScript allows developers to ensure that all possible cases in a union type or enum are handled, thereby preventing unexpected runtime errors when new types are added to the union.
  10. Isolate field-level re-renders using a FieldProvider

    Create a FieldProvider that consumes a global form state context and provides only a specific field's value via a new context. This ensures that components subscribing to a specific field only re-render when that specific field's value changes, rather than on any form-wide state update.
    export function FieldProvider({ name, children }: { name: keyof FormState; children: React.ReactNode }) {
      const formState = useFormStateContext();
      // Only provide the value of the specific field requested by 'name'
      return <FieldValueContext.Provider value={formState[name]}>{children}</FieldValueContext.Provider>;
    }
  11. Parse and validate data with Zod

    Use z.string().parse() or z.object().parse() to validate that a value matches a specific schema. If the validation fails, Zod throws an error. This is useful for ensuring API return values or payloads match expected types, effectively acting as a type guard that strips unexpected fields when using z.object.
    import { z } from 'zod';
    
    // Simple string validation
    const name: unknown = 'John Doe';
    const validatedName = z.string().parse(name); // validatedName is typed as string
    
    // Object validation and filtering
    const Something = z.object({
      name: z.string(),
      content: z.string(),
    });
    
    function fetchSomething(payload: any) {
      // .parse() removes keys not defined in the schema (e.g., 'id')
      const newPayload = Something.parse(payload);
      // do something...
    }
  12. Optimize form performance using Jotai atoms and Context API

    To eliminate re-renders of the parent <Form> component and achieve fine-grained updates, store Jotai atoms within a Context. Since atoms do not trigger re-renders upon update, only the specific components calling useAtom for a particular field will re-render. Use useAtomCallback to handle form submission without causing the form container to subscribe to every single state change.
    import { atom, useAtom } from 'jotai';
    import { useAtomCallback } from 'jotai/utils';
    import React, { createContext, useContext, useMemo } from 'react';
    
    type FormState = { email: PrimitiveAtom<string>; password: PrimitiveAtom<string>; };
    const FormStateContext = createContext<FormState | null>(null);
    
    export function FormProvider({ children }: { children: React.ReactNode }) {
      // Create atoms once
      const formState = useMemo(() => ({ email: atom(''), password: atom('') }), []);
    
      // Derived atom to get all values for submission
      const payloadAtom = useMemo(() => atom((get) => {
        return Object.fromEntries(Object.entries(formState).map(([k, v]) => [k, get(v)]));
      }), [formState]);
    
      // handleSubmit does not trigger re-renders of FormProvider
      const handleSubmit = useAtomCallback((get, _, event) => {
        event.preventDefault();
        console.debug(get(payloadAtom));
      }, [payloadAtom]);
    
      return (
        <FormStateContext.Provider value={formState}>
          <Form onSubmit={handleSubmit}>{children}</Form>
        </FormStateContext.Provider>
      );
    }
    
    export function useFormFieldState(name: keyof FormState) {
      const context = useContext(FormStateContext);
      if (!context) throw new Error('Missing FormStateContext.Provider');
      return useAtom(context[name]); // Only this component re-renders when this atom changes
    }