Skip to content

Using Method .use()

Overview

Method .use() is the primary way to subscribe to Signify in React components. When the value changes, the component will automatically re-render with the new value. The method uses deep comparison to optimize re-renders, ensuring components only update when the actual data changes.

tsx
const value = signify.use(pickFunction?);

How It Works

The .use() method provides:

  1. Automatic Re-renders: Your component updates when the state changes
  2. Smart Subscriptions: Automatically connects and disconnects from state updates
  3. Efficient Updates: Only re-renders when the value actually changes
  4. Clean Memory Usage: No memory leaks - everything is cleaned up automatically
  5. Optimized Performance: When using picker functions, only watches specific parts of your state

Basic Usage

tsx
import { signify } from "react-signify";

const sCount = signify(0);

function Counter() {
  const count = sCount.use(); // Subscribe and get value

  // ✅ Component will re-render when sCount changes
  // ✅ When component unmounts:
  //     - Subscription automatically cleaned up
  //     - No memory leaks
  //     - Signify value still exists for other components

  return <div>Count: {count}</div>;
}

Picker Function

1. Transform Value

tsx
const sUser = signify({
  firstName: "John",
  lastName: "Doe",
  profile: { age: 25, city: "Hanoi" },
});

function UserComponent() {
  // ✅ Get entire object
  const user = sUser.use();

  // ✅ Only get firstName
  const firstName = sUser.use((user) => user.firstName);

  // ✅ Computed value
  const fullName = sUser.use((user) => `${user.firstName} ${user.lastName}`);

  // ✅ Nested property
  const age = sUser.use((user) => user.profile.age);

  return (
    <div>
      <h1>{fullName}</h1>
      <p>Age: {age}</p>
      <p>City: {user.profile.city}</p>
    </div>
  );
}

2. Performance Optimization

tsx
const sLargeObject = signify({
  data: {
    /* large object */
  },
  metadata: {
    /* another large object */
  },
  settings: { theme: "dark", language: "vi" },
});

function SettingsComponent() {
  // ✅ Only subscribe to settings
  const settings = sLargeObject.use((obj) => obj.settings);

  // Component only re-renders when settings change
  // DOES NOT re-render when data or metadata changes

  return (
    <div>
      <p>Theme: {settings.theme}</p>
      <p>Language: {settings.language}</p>
    </div>
  );
}

3. Conditional Returns

tsx
const sApiResponse = signify({ data: null, loading: true, error: null });

function DataComponent() {
  // ✅ Transform API state
  const status = sApiResponse.use((response) => {
    if (response.loading) return "loading";
    if (response.error) return "error";
    if (response.data) return "success";
    return "idle";
  });

  const data = sApiResponse.use((response) => response.data);
  const error = sApiResponse.use((response) => response.error);

  switch (status) {
    case "loading":
      return <div>Loading...</div>;
    case "error":
      return <div>Error: {error}</div>;
    case "success":
      return <div>Data: {JSON.stringify(data)}</div>;
    default:
      return <div>No data</div>;
  }
}

Comparison with Alternatives

vs .value Property

tsx
const sCount = signify(0);

function Component() {
  // ❌ Using .value - NO re-render
  const count1 = sCount.value; // Static value, no subscription

  // ✅ Using .use() - Automatic re-render
  const count2 = sCount.use(); // Subscribe to changes

  return (
    <div>
      <p>Static: {count1}</p> {/* Doesn't update when signify changes */}
      <p>Reactive: {count2}</p> {/* Automatically updates */}
    </div>
  );
}

vs React useState

tsx
import { useState } from "react";

// ❌ React useState - Local component state only
function CounterWithUseState() {
  const [count, setCount] = useState(0);

  // State is isolated to this component
  // Other components cannot access or modify this state

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

// ✅ Signify .use() - Global reactive state
const sCount = signify(0);

function CounterWithSignify() {
  const count = sCount.use();

  // State is shared across all components using sCount
  // Any component can modify: sCount.set(newValue)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => sCount.set(count + 1)}>+</button>
    </div>
  );
}

vs .Wrap Component

tsx
const sUser = signify({ name: "John", age: 25 });

// ✅ Using .use() - Simple, direct
function UserProfile1() {
  const user = sUser.use();
  return (
    <div>
      <h1>{user.name}</h1>
      <p>Age: {user.age}</p>
    </div>
  );
}

// ✅ Using .Wrap - Function as children
function UserProfile2() {
  return (
    <sUser.Wrap>
      {(user) => (
        <div>
          <h1>{user.name}</h1>
          <p>Age: {user.age}</p>
        </div>
      )}
    </sUser.Wrap>
  );
}

Advanced Patterns

1. Multiple Subscriptions

tsx
const sUser = signify({ name: "John", profile: { age: 25 } });

function UserDashboard() {
  // ✅ Multiple subscriptions in one component
  const userName = sUser.use((user) => user.name);
  const userAge = sUser.use((user) => user.profile.age);
  const isAdult = sUser.use((user) => user.profile.age >= 18);

  return (
    <div>
      <h1>Welcome, {userName}!</h1>
      <p>Age: {userAge}</p>
      {isAdult && <p>You can vote!</p>}
    </div>
  );
}

2. Derived State Pattern

tsx
const sItems = signify([]);

function ItemsList() {
  // ✅ Derived states with .use()
  const items = sItems.use();
  const itemCount = sItems.use((items) => items.length);
  const hasItems = sItems.use((items) => items.length > 0);
  const totalValue = sItems.use((items) =>
    items.reduce((sum, item) => sum + item.price, 0)
  );

  return (
    <div>
      <h2>Items ({itemCount})</h2>
      {hasItems ? (
        <div>
          <ul>
            {items.map((item) => (
              <li key={item.id}>
                {item.name} - ${item.price}
              </li>
            ))}
          </ul>
          <p>Total: ${totalValue}</p>
        </div>
      ) : (
        <p>No items found</p>
      )}
    </div>
  );
}

Performance Considerations

1. Deep Comparison Behavior

The .use() method uses deep comparison to determine if a re-render is needed:

tsx
const sData = signify({ count: 0, user: { name: "John" } });

function ExampleComponent() {
  // ✅ Only re-renders when count actually changes
  const count = sData.use((data) => data.count);

  // ✅ Only re-renders when user object content changes
  const user = sData.use((data) => data.user);

  // ✅ Even if the user object reference changes but content is same,
  // no re-render occurs due to deep comparison

  return (
    <div>
      {count} - {user.name}
    </div>
  );
}

2. Avoiding Unnecessary Re-renders

tsx
const sGlobalState = signify({
  user: { name: "John" },
  cart: [],
  theme: "light",
  notifications: [],
});

// ❌ Entire component re-renders when any part changes
function BadComponent() {
  const state = sGlobalState.use();

  return (
    <div>
      <header>User: {state.user.name}</header>
      <main>Cart items: {state.cart.length}</main>
    </div>
  );
}

// ✅ Only re-renders when specific data changes
function GoodComponent() {
  const userName = sGlobalState.use((state) => state.user.name);
  const cartCount = sGlobalState.use((state) => state.cart.length);

  return (
    <div>
      <header>User: {userName}</header>
      {/* Only re-renders when user.name changes */}
      <main>Cart items: {cartCount}</main>{" "}
      {/* Only re-renders when cart.length changes */}
    </div>
  );
}

Common Patterns

1. Form Handling

tsx
const sForm = signify({
  email: "",
  password: "",
  errors: {},
});

function LoginForm() {
  const email = sForm.use((form) => form.email);
  const password = sForm.use((form) => form.password);
  const errors = sForm.use((form) => form.errors);

  const updateEmail = (value) => {
    sForm.set((prev) => {
      prev.value.email = value;
      // Clear error when user types
      delete prev.value.errors.email;
    });
  };

  return (
    <form>
      <input
        type="email"
        value={email}
        onChange={(e) => updateEmail(e.target.value)}
      />
      {errors.email && <span>{errors.email}</span>}

      <input
        type="password"
        value={password}
        onChange={(e) =>
          sForm.set((prev) => {
            prev.value.password = e.target.value;
          })
        }
      />
    </form>
  );
}

2. List Management

tsx
const sTodos = signify([]);

function TodoList() {
  const todos = sTodos.use();
  const completedCount = sTodos.use(
    (todos) => todos.filter((t) => t.completed).length
  );
  const activeCount = sTodos.use(
    (todos) => todos.filter((t) => !t.completed).length
  );

  return (
    <div>
      <h2>Todos</h2>
      <p>
        Active: {activeCount}, Completed: {completedCount}
      </p>

      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <span
              style={{
                textDecoration: todo.completed ? "line-through" : "none",
              }}
            >
              {todo.text}
            </span>
          </li>
        ))}
      </ul>
    </div>
  );
}

3. Async Data Loading

tsx
import { useEffect } from "react";

const sAsyncData = signify({
  data: null,
  loading: false,
  error: null,
});

function DataDisplay() {
  const data = sAsyncData.use((state) => state.data);
  const loading = sAsyncData.use((state) => state.loading);
  const error = sAsyncData.use((state) => state.error);

  useEffect(() => {
    loadData();
  }, []);

  const loadData = async () => {
    sAsyncData.set((prev) => {
      prev.value.loading = true;
      prev.value.error = null;
    });

    try {
      const result = await fetchData();
      sAsyncData.set((prev) => {
        prev.value.data = result;
        prev.value.loading = false;
      });
    } catch (err) {
      sAsyncData.set((prev) => {
        prev.value.error = err.message;
        prev.value.loading = false;
      });
    }
  };

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!data) return <div>No data</div>;

  return <div>Data: {JSON.stringify(data)}</div>;
}

Best Practices

✅ Do's

tsx
// ✅ Use picker function for specific data
const userName = sUser.use((user) => user.name);

// ✅ Multiple specific subscriptions instead of one large subscription
const email = sUser.use((user) => user.email);
const age = sUser.use((user) => user.profile.age);

// ✅ Transform data directly in picker
const fullName = sUser.use((user) => `${user.firstName} ${user.lastName}`);

// ✅ Conditional logic in picker
const canVote = sUser.use((user) => user.age >= 18);

❌ Don'ts

tsx
// ❌ Don't use .value in component
const count = sCount.value; // No re-render!

// ❌ Don't subscribe to entire large object if you only need a part
const largeState = sLargeState.use(); // Inefficient

// ❌ Don't perform expensive operations in picker without caching
const expensiveResult = sData.use((data) => {
  return heavyComputation(data); // Re-computes every render
});

// ❌ Don't mutate returned value
const user = sUser.use();
user.name = "Modified"; // Dangerous!

TypeScript Support

The .use() method provides full TypeScript support with generic type inference:

tsx
interface User {
  id: number;
  name: string;
  profile: {
    age: number;
    avatar?: string;
  };
}

const sUser = signify<User | null>(null);

function TypedComponent() {
  // ✅ Full type inference
  const user = sUser.use(); // Type: User | null
  const userName = sUser.use((u) => u?.name); // Type: string | undefined
  const userAge = sUser.use((u) => u?.profile.age); // Type: number | undefined

  // ✅ Type-safe picker functions with explicit return type
  const isAdult = sUser.use((u): boolean => {
    return u ? u.profile.age >= 18 : false;
  });

  // ✅ Complex transformations with inferred types
  const userInfo = sUser.use((u) =>
    u
      ? {
          displayName: u.name.toUpperCase(),
          isAdult: u.profile.age >= 18,
          hasAvatar: !!u.profile.avatar,
        }
      : null
  ); // Type: { displayName: string; isAdult: boolean; hasAvatar: boolean; } | null

  return (
    <div>
      {user && (
        <div>
          <h1>{userName}</h1>
          <p>Age: {userAge}</p>
          {isAdult && <p>Can vote!</p>}
          {userInfo && (
            <p>
              Display: {userInfo.displayName} | Avatar:
              {userInfo.hasAvatar ? "Yes" : "No"}
            </p>
          )}
        </div>
      )}
    </div>
  );
}

Debugging

1. Development Monitoring

tsx
import { useEffect } from "react";

function DebuggingComponent() {
  const data = sData.use();

  // ✅ Log changes during development
  useEffect(() => {
    if (process.env.NODE_ENV === "development") {
      console.log("Data changed:", data);
    }
  }, [data]);

  return <div>{JSON.stringify(data)}</div>;
}

2. Conditional Debugging

tsx
import { useEffect } from "react";

function ConditionalDebugging() {
  const count = sCount.use();

  // ✅ Debug specific conditions
  useEffect(() => {
    if (count > 100) {
      console.warn("Count is getting high:", count);
    }
  }, [count]);

  return <div>Count: {count}</div>;
}

Summary

Method .use() is the backbone of React Signify:

  1. Global State Access - Connect your component to shared state across your app
  2. Smart Re-rendering - Only updates when your data actually changes
  3. Automatic Cleanup - No memory leaks - everything is cleaned up automatically
  4. Selective Data - Pick only the data you need for better performance
  5. Full TypeScript Support - Complete type inference and type safety

Key Benefits:

  • Seamless integration with React components
  • Automatic lifecycle management
  • Efficient updates that prevent unnecessary re-renders
  • Flexible data transformation and selection

When to use .use():

  • Need reactive data in component (vs local useState)
  • Want automatic re-render on state changes
  • Need to transform/pick specific data from larger state
  • Building reactive UI that shares state between components

When NOT to use .use():

  • Only need to read value once without reactivity (use .value)
  • Want to listen to changes without triggering re-render (use .watch())
  • Prefer function-as-children pattern (use .Wrap/.HardWrap)

💡 Tip: Start with basic .use(), then optimize with picker functions for performance. Remember that deep comparison means you can safely transform objects without worrying about reference equality.

Released under the MIT License.