Skip to content

Project Structure with React Signify

Overview

A well-organized project structure with React Signify helps:

  • Easy maintenance: Code is logically organized, easy to find and fix
  • Scalable: Easy to expand as the project grows
  • Team-friendly: New members can easily understand and contribute
  • Performance: Optimized re-rendering and memory usage
  • Reliability: Prevents module loading issues and circular dependencies

⚠️ Common Anti-Pattern to Avoid

DON'T declare Signify instances in the same file as your components:

tsx
// ❌ BAD: Don't do this in component files
import { signify } from "react-signify";

// This can cause module loading issues!
export const sSearchName = signify("");
export const sSearchEmail = signify("");

export default function FormSearch() {
  const searchName = sSearchName.use();
  // ...
}

Why this is problematic:

  • Module loading race conditions: Other components importing these signify instances might load before this component's module, causing undefined errors
  • Circular dependencies: Components importing signify from other component files can create dependency cycles
  • Code organization: Mixing state management with UI components violates separation of concerns

DO create signify instances in dedicated store files:

tsx
// ✅ GOOD: Create separate store files
// store/search.ts
import { signify } from "react-signify";

export const sSearchName = signify("");
export const sSearchEmail = signify("");
tsx
// components/FormSearch.tsx
import { sSearchName, sSearchEmail } from "../store/search";

export default function FormSearch() {
  const searchName = sSearchName.use();
  const searchEmail = sSearchEmail.use();
  // ...
}

📁 Basic Structure

src/
├── components/          # Shared components
│   ├── ui/             # Basic UI components
│   ├── layout/         # Layout components
│   └── features/       # Feature-specific components
├── hooks/              # Custom hooks
├── pages/              # Page components
├── store/              # Signify stores
│   ├── index.ts        # Export all stores
│   ├── app.ts          # App-level state
│   ├── users.ts        # User data management
│   ├── search.ts       # Search filters
│   └── features/       # Feature stores
├── types/              # TypeScript types
├── utils/              # Utility functions
└── App.tsx

📁 Advanced Structure (Large projects)

src/
├── components/
│   ├── ui/
│   │   ├── Button/
│   │   ├── Modal/
│   │   └── index.ts
│   └── features/
│       ├── user/
│       │   ├── UserList/
│       │   ├── UserCard/
│       │   └── index.ts
│       └── search/
│           ├── FormSearch/
│           ├── SearchFilters/
│           └── index.ts
├── features/           # Feature modules
│   ├── user/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── store/
│   │   ├── types/
│   │   └── index.ts
│   └── search/
│       ├── components/
│       ├── hooks/
│       ├── store/
│       ├── types/
│       └── index.ts
├── shared/             # Shared across features
│   ├── store/
│   ├── hooks/
│   ├── utils/
│   └── types/
└── App.tsx

Store Organization

Application Level - App-wide

tsx
// store/app.ts
import { signify } from "react-signify";

// Theme and app states
export const sTheme = signify<"light" | "dark">("light");
export const sLoading = signify(false);
export const sTitle = signify("My App");

Simple Store Example

tsx
// data/users.ts
export interface User {
  id: number;
  name: string;
  email: string;
}

export const mockUsers: User[] = [
  { id: 1, name: "John Smith", email: "john.smith@example.com" },
  { id: 2, name: "Alice Johnson", email: "alice.johnson@example.com" },
  { id: 3, name: "Bob Wilson", email: "bob.wilson@example.com" },
  { id: 4, name: "Emma Davis", email: "emma.davis@example.com" },
];
tsx
// store/index.ts
import { signify } from "react-signify";

// Search filters in a JSON object
interface SearchFilters {
  name: string;
  email: string;
}

export const sSearchFilters = signify<SearchFilters>({
  name: "",
  email: "",
});

Simple FormSearch Component

tsx
// components/FormSearch.tsx
import React from "react";
import { sSearchFilters } from "../store";

export function FormSearch() {
  // Get the entire object to display in form
  const searchFilters = sSearchFilters.use();

  return (
    <div>
      <h3>Search Users</h3>

      <input
        type="text"
        placeholder="Enter name..."
        value={searchFilters.name}
        onChange={(e) =>
          sSearchFilters.set((prev) => {
            prev.value.name = e.target.value;
          })
        }
      />

      <input
        type="text"
        placeholder="Enter email..."
        value={searchFilters.email}
        onChange={(e) =>
          sSearchFilters.set((prev) => {
            prev.value.email = e.target.value;
          })
        }
      />

      <button
        onClick={() => {
          sSearchFilters.set({ name: "", email: "" });
        }}
      >
        Clear
      </button>
    </div>
  );
}

Simple UserList Component

tsx
// components/UserList.tsx
import React from "react";
import { mockUsers } from "../data/users";
import { sSearchFilters } from "../store";

export function UserList() {
  // Optimized way: Use callback to get only the needed property
  const searchName = sSearchFilters.use((filters) => filters.name);
  const searchEmail = sSearchFilters.use((filters) => filters.email);

  // Filter users directly in component
  const filteredUsers = mockUsers.filter((user) => {
    const nameMatch =
      !searchName || user.name.toLowerCase().includes(searchName.toLowerCase());
    const emailMatch =
      !searchEmail ||
      user.email.toLowerCase().includes(searchEmail.toLowerCase());
    return nameMatch && emailMatch;
  });

  return (
    <div>
      <h3>User List ({filteredUsers.length})</h3>

      {/* Display current search filters */}
      {(searchName || searchEmail) && (
        <p>
          Searching:
          {searchName && ` Name="${searchName}"`}
          {searchEmail && ` Email="${searchEmail}"`}
        </p>
      )}

      {filteredUsers.map((user) => (
        <div
          key={user.id}
          style={{ border: "1px solid #ccc", margin: "10px", padding: "10px" }}
        >
          <h4>{user.name}</h4>
          <p>{user.email}</p>
        </div>
      ))}

      {filteredUsers.length === 0 && (searchName || searchEmail) && (
        <p>No users found.</p>
      )}
    </div>
  );
}

Simple Usage Example

tsx
// App.tsx
import React from "react";
import { FormSearch } from "./components/FormSearch";
import { UserList } from "./components/UserList";

function App() {
  return (
    <div>
      <h1>User Management</h1>
      <FormSearch />
      <UserList />
    </div>
  );
}

export default App;

Advanced Patterns

After understanding the basic usage with .set() and .use(), you can learn more about:

  • Slice: Automatic data computation
  • Actions: Group related operations
  • HardWrap: Performance optimization
  • Cache: Data storage
  • Sync: Synchronization between tabs

Key Points

✅ Just remember 2 methods:

  1. .set() - To assign/update data:

    tsx
    // Update one property in object
    sSearchFilters.set((prev) => {
      prev.value.name = "John";
    });
    
    // Or set the entire object
    sSearchFilters.set({ name: "John", email: "test@email.com" });
  2. .use() - To get data in component:

    tsx
    // Method 1: Get the entire object
    const filters = sSearchFilters.use();
    
    // Method 2: Use callback (performance optimized)
    const name = sSearchFilters.use((f) => f.name); // Only re-render when name changes
    const email = sSearchFilters.use((f) => f.email); // Only re-render when email changes

✅ Basic pattern:

  • Data: Create mockUsers (static data)
  • Store: Create signify JSON object for search filters
  • FormSearch: Use .set() to update object properties
  • UserList: Import mockUsers + use .use(callback) to get specific properties

Next, learn about Using with TypeScript to leverage type safety.


💡 Tip: Always ask yourself "Am I creating signify in the right place?" before writing code.

Released under the MIT License.