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();
// ...
}
Recommended Structure
📁 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:
.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" });
.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.