agentsclimarketplace

React architecture

Skill landim32/awesome-ai-skills/skills/react-architecture

Claude AI Skills repository

Install
npx -y skills add landim32/awesome-ai-skills --skill react-architecture

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Create the complete frontend architecture for a new entity in the React application. Generates TypeScript types, service class, context provider, custom hook, and registers the provider in main.tsx. Use this skill when the user asks to create a new entity, feature module, or domain area in the frontend.

SKILL.md

15.2 KB, as published. Nobody here has run it

This skill defines the standard approach for scaffolding a complete frontend entity architecture in this project. It creates all necessary files following the established patterns: Types, Service, Context, Hook, and Provider registration.

Prerequisites

Before creating the architecture, you MUST:

  1. Ask the user for the entity name (e.g., "Clan", "Achievement", "Shop")
  2. Ask the user for the API base path (e.g., "/api/clan", "/api/achievement")
  3. Ask the user for the entity fields/properties or check if API documentation exists in docs/
  4. Check if the entity already exists — search src/types/, src/services/, src/contexts/, and src/hooks/ before creating anything

Creation Order

Always create files in this exact order:

  1. Typessrc/types/{entity}.ts
  2. Servicesrc/services/{entity}Service.ts
  3. Contextsrc/contexts/{Entity}Context.tsx
  4. Hooksrc/hooks/use{Entity}.ts
  5. Provider registrationsrc/main.tsx

Rules

  1. Never use any type — all types must be explicitly defined.
  2. Never use alert() or window.confirm() — use toast from sonner for notifications and ConfirmModal for confirmation dialogs.
  3. Always use useCallback for all context methods to prevent unnecessary re-renders.
  4. Always include loading, error, and main data state in every context.
  5. Always use the class-based service pattern with private handleResponse method.
  6. Always use getHeaders(true) from apiHelpers for authenticated requests.
  7. Always check result.sucesso before updating state (API responses use Portuguese keys).
  8. Always export both the singleton instance and the class from services.
  9. Always export the context as default and the provider as named export.
  10. Always add JSDoc comments to service methods and type interfaces.

Step 1: Create Types

File: src/types/{entity}.ts

/** {Entity} Types — Types for the {entity description} system */

// Enums (if needed)
export enum {Entity}StatusEnum {
  Unknown = 0,
  Active = 1,
  Inactive = 2,
}

// Core Entity
/** Main {entity} information */
export interface {Entity}Info {
  {entity}Id: number;
  name: string;
  // ... other fields with JSDoc comments
}

// DTOs
/** Data required to create a new {entity} (no ID) */
export interface {Entity}InsertInfo {
  name: string;
  // ... fields required for creation
}

/** Data required to update an existing {entity} (includes ID) */
export interface {Entity}UpdateInfo {
  {entity}Id: number;
  name: string;
  // ... fields required for update
}

// API Response Types — always include sucesso, mensagem, erros (Portuguese keys)
export interface {Entity}ListResult {
  {entities}: {Entity}Info[];
  sucesso: boolean;
  mensagem: string | null;
  erros: string[] | null;
}

export interface {Entity}GetResult {
  {entity}: {Entity}Info;
  sucesso: boolean;
  mensagem: string | null;
  erros: string[] | null;
}

/** Status-only operation result (import from existing types file if already defined) */
export interface StatusResult {
  sucesso: boolean;
  mensagem: string;
  erros: string[] | null;
}

Key conventions:

  • API responses always have sucesso, mensagem, erros — Portuguese keys
  • Entity IDs use camelCase: {entity}Id (e.g., clanId)
  • Nullable fields use | null, not optional ?
  • Separate interfaces for Insert (no ID) and Update (with ID) DTOs
  • If StatusResult already exists in another types file, import it instead of redefining

Step 2: Create Service

File: src/services/{entity}Service.ts

import type {
  {Entity}ListResult, {Entity}GetResult,
  {Entity}InsertInfo, {Entity}UpdateInfo, StatusResult,
} from '../types/{entity}';
import { getHeaders } from './apiHelpers';

const API_BASE = `${import.meta.env.VITE_GOBLIN_API_URL || 'http://localhost:4041'}/api/{entity-kebab}`;

interface {Entity}ServiceConfig {
  onUnauthorized?: () => void;
}

/** {Entity} Service — Manages all API operations related to {entities} */
class {Entity}Service {
  private config: {Entity}ServiceConfig;

  constructor(config: {Entity}ServiceConfig = {}) {
    this.config = config;
  }

  private async handleResponse<T>(response: Response): Promise<T> {
    if (response.status === 401) {
      this.config.onUnauthorized?.();
      throw new Error('Unauthorized');
    }
    if (!response.ok) {
      const error = await response.text();
      throw new Error(error || 'Request failed');
    }
    return response.json();
  }

  /** List all {entities} */
  async list(): Promise<{Entity}ListResult> {
    const response = await fetch(`${API_BASE}/list`, { headers: getHeaders(true) });
    return this.handleResponse<{Entity}ListResult>(response);
  }

  /** Get a {entity} by ID */
  async getById(id: number): Promise<{Entity}GetResult> {
    const response = await fetch(`${API_BASE}/getbyid/${id}`, { headers: getHeaders(true) });
    return this.handleResponse<{Entity}GetResult>(response);
  }

  /** Create a new {entity} */
  async insert(data: {Entity}InsertInfo): Promise<{Entity}GetResult> {
    const response = await fetch(`${API_BASE}/insert`, {
      method: 'POST', headers: getHeaders(true), body: JSON.stringify(data),
    });
    return this.handleResponse<{Entity}GetResult>(response);
  }

  /** Update an existing {entity} */
  async update(data: {Entity}UpdateInfo): Promise<{Entity}GetResult> {
    const response = await fetch(`${API_BASE}/update`, {
      method: 'PUT', headers: getHeaders(true), body: JSON.stringify(data),
    });
    return this.handleResponse<{Entity}GetResult>(response);
  }

  /** Delete a {entity} by ID */
  async delete(id: number): Promise<StatusResult> {
    const response = await fetch(`${API_BASE}/delete/${id}`, {
      method: 'DELETE', headers: getHeaders(true),
    });
    return this.handleResponse<StatusResult>(response);
  }
}

export const {entity}Service = new {Entity}Service();
export default {Entity}Service;

Key conventions:

  • Class-based with private handleResponse that checks 401 and calls onUnauthorized
  • API_BASE uses VITE_GOBLIN_API_URL env var with http://localhost:4041 fallback
  • Always use getHeaders(true) for authenticated requests
  • Export both singleton instance (camelCase) and class (PascalCase default)

Step 3: Create Context

File: src/contexts/{Entity}Context.tsx

The context wraps the service and provides state management with three method categories: API Methods (direct service wrappers returning API results), State Management (handle loading/error, update local state), and optionally Utility Methods.

import { createContext, useState, useCallback, ReactNode } from 'react';
import { {entity}Service } from '../services/{entity}Service';
import type {
  {Entity}Info, {Entity}ListResult, {Entity}GetResult,
  {Entity}InsertInfo, {Entity}UpdateInfo, StatusResult,
} from '../types/{entity}';

interface {Entity}ContextType {
  // State
  {entities}: {Entity}Info[];
  selected{Entity}: {Entity}Info | null;
  loading: boolean;
  error: string | null;
  // API Methods (return API results for caller to check sucesso)
  list{Entities}: () => Promise<{Entity}ListResult>;
  get{Entity}ById: (id: number) => Promise<{Entity}GetResult>;
  insert{Entity}: (data: {Entity}InsertInfo) => Promise<{Entity}GetResult>;
  update{Entity}: (data: {Entity}UpdateInfo) => Promise<{Entity}GetResult>;
  delete{Entity}: (id: number) => Promise<StatusResult>;
  // State Management (return void, handle loading/error internally)
  load{Entities}: () => Promise<void>;
  refresh{Entities}: () => Promise<void>;
  setSelected{Entity}: (item: {Entity}Info | null) => void;
  clearError: () => void;
}

const {Entity}Context = createContext<{Entity}ContextType | undefined>(undefined);

export const {Entity}Provider = ({ children }: { children: ReactNode }) => {
  const [{entities}, set{Entities}] = useState<{Entity}Info[]>([]);
  const [selected{Entity}, setSelected{Entity}] = useState<{Entity}Info | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const handleError = (err: unknown): never => {
    const errorMsg = err instanceof Error ? err.message : 'Unknown error';
    setError(errorMsg);
    throw err;
  };

  // --- API Methods (direct service wrappers) ---

  const list{Entities} = useCallback(async (): Promise<{Entity}ListResult> => {
    try { setError(null); return await {entity}Service.list(); }
    catch (err) { return handleError(err); }
  }, []);

  const get{Entity}ById = useCallback(async (id: number): Promise<{Entity}GetResult> => {
    try { setError(null); return await {entity}Service.getById(id); }
    catch (err) { return handleError(err); }
  }, []);

  const insert{Entity} = useCallback(async (data: {Entity}InsertInfo): Promise<{Entity}GetResult> => {
    try {
      setError(null);
      const result = await {entity}Service.insert(data);
      if (result.sucesso) await load{Entities}();
      return result;
    } catch (err) { return handleError(err); }
  }, []);

  const update{Entity} = useCallback(async (data: {Entity}UpdateInfo): Promise<{Entity}GetResult> => {
    try {
      setError(null);
      const result = await {entity}Service.update(data);
      if (result.sucesso) {
        set{Entities}((prev) => prev.map((item) =>
          item.{entity}Id === data.{entity}Id ? result.{entity} : item
        ));
        if (selected{Entity}?.{entity}Id === data.{entity}Id) setSelected{Entity}(result.{entity});
      }
      return result;
    } catch (err) { return handleError(err); }
  }, [selected{Entity}]);

  const delete{Entity} = useCallback(async (id: number): Promise<StatusResult> => {
    try {
      setError(null);
      const result = await {entity}Service.delete(id);
      if (result.sucesso) {
        set{Entities}((prev) => prev.filter((item) => item.{entity}Id !== id));
        if (selected{Entity}?.{entity}Id === id) setSelected{Entity}(null);
      }
      return result;
    } catch (err) { return handleError(err); }
  }, [selected{Entity}]);

  // --- State Management ---

  const load{Entities} = useCallback(async (): Promise<void> => {
    try {
      setLoading(true);
      setError(null);
      const result = await {entity}Service.list();
      if (result.sucesso) set{Entities}(result.{entities});
      else throw new Error(result.mensagem || 'Failed to load {entities}');
    } catch (err) { handleError(err); }
    finally { setLoading(false); }
  }, []);

  const refresh{Entities} = useCallback(async () => { await load{Entities}(); }, [load{Entities}]);
  const clearError = useCallback(() => { setError(null); }, []);

  const value: {Entity}ContextType = {
    {entities}, selected{Entity}, loading, error,
    list{Entities}, get{Entity}ById, insert{Entity}, update{Entity}, delete{Entity},
    load{Entities}, refresh{Entities}, setSelected{Entity}, clearError,
  };

  return <{Entity}Context.Provider value={value}>{children}</{Entity}Context.Provider>;
};

export default {Entity}Context;

Key conventions:

  • handleError sets error state AND re-throws (returns never)
  • After insert: reload full list. After update: update item in local state. After delete: remove from local state.
  • Check result.sucesso before updating state
  • Export provider as named export, context as default export

Step 4: Create Hook

File: src/hooks/use{Entity}.ts

import { useContext } from 'react';
import {Entity}Context from '../contexts/{Entity}Context';

/** Custom hook to access the {Entity} context. Throws if used outside {Entity}Provider. */
export const use{Entity} = () => {
  const context = useContext({Entity}Context);
  if (!context) throw new Error('use{Entity} must be used within a {Entity}Provider');
  return context;
};

export default use{Entity};

Only add computed values/derived state if the user specifically requests it.

Step 5: Register Provider in main.tsx

File: src/main.tsx

// Add import at the top with other provider imports
import { {Entity}Provider } from './contexts/{Entity}Context.tsx'

// Add to the provider chain based on dependencies:
// - If it depends on AuthContext → must be inside AuthProvider
// - If it depends on GoblinContext → must be inside GoblinProvider
// - If independent → place near the end, before App

Nesting rules:

  • AuthProvider is always the outermost (all contexts depend on auth)
  • Place the new provider as close to <App /> as possible unless it has dependents
  • If other contexts will depend on this one, place it above those contexts
  • Keep related contexts grouped (economy, gameplay, admin, etc.)

Current provider order:

AuthProvider → FinanceProvider → GoblinProvider → TeamProvider →
GoboxProvider → AuctionProvider → GLogProvider → QuestProvider →
ItemProvider → ItemClaimProvider → MiningProvider → MapProvider →
TerritoryProvider → TerritoryEnemyProvider → ArenaProvider → App

Naming Convention Reference

ItemConventionExample
Types filesrc/types/{entity}.tssrc/types/clan.ts
Service filesrc/services/{entity}Service.tssrc/services/clanService.ts
Service class{Entity}ServiceClanService
Service instance{entity}ServiceclanService
Context filesrc/contexts/{Entity}Context.tsxsrc/contexts/ClanContext.tsx
Provider{Entity}ProviderClanProvider
Hook filesrc/hooks/use{Entity}.tssrc/hooks/useClan.ts
Hook functionuse{Entity}useClan
Entity ID field{entity}IdclanId
List/Get result{Entity}ListResult / {Entity}GetResultClanListResult / ClanGetResult
Insert/Update DTO{Entity}InsertInfo / {Entity}UpdateInfoClanInsertInfo / ClanUpdateInfo

Verification Checklist

  • Types file at src/types/{entity}.ts with all interfaces
  • Service file at src/services/{entity}Service.ts with class pattern
  • Context file at src/contexts/{Entity}Context.tsx with provider
  • Hook file at src/hooks/use{Entity}.ts with null-check
  • Provider imported and added to src/main.tsx in correct nesting position
  • All API response types include sucesso, mensagem, erros fields
  • All context methods use useCallback
  • handleError pattern: sets error state + re-throws
  • No any types, no alert(), no window.confirm()
  • Service uses getHeaders(true) and VITE_GOBLIN_API_URL env var

Common Gotchas

  • StatusResult may already exist: Check src/types/map.ts or other type files before redefining. Import from existing file if available.
  • Entity ID naming: Always use {entity}Id (camelCase), not {entity}_id or id.
  • API response keys are Portuguese: sucesso (not success), mensagem (not message), erros (not errors).
  • Service constructor config: Always include onUnauthorized callback in config interface.
  • Provider nesting order matters: A context cannot use hooks from providers nested inside it.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.