Refract Documentation

website·Indexed 30 days ago

https://judy-refract.netlify.app/

A framework for building reactive UIs featuring a state management system based on refractions, optical composition for reusable logic patterns, and a lightweight compiler for performance optimization. Includes guides on components, lenses, side effects, and TypeScript integration.

Tokens
25.1K
Snippets
134
Records
141
Agent score
74%

What's inside Refract

  1. Overview of Refract custom optics

    Custom optics are functions that encapsulate reactive state, side effects, and computed values into reusable units. They are similar to React hooks but offer enhanced capabilities including dependency-aware caching (automatic memoization of expensive computations), animation-aware transitions, automatic resource cleanup, and seamless composability.
  2. Overview of Refract framework

    Refract is a JavaScript framework designed for building reactive user interfaces. Unlike frameworks that rely on virtual DOM diffing, Refract uses fine-grained reactivity to update only the specific parts of the UI that change. It emphasizes 'optical composition,' allowing developers to bundle state, effects, and behaviors into reusable optics.
  3. Manage reactive state with Refractions

    Refractions are reactive state containers in Refract. When the value inside a refraction changes, any component using that refraction automatically re-renders.

    Key operations:

    • .value: Access the current value.
    • .set(newValue): Update the value and trigger re-renders.
    import { useRefraction } from 'refract';
    
    // Create a refraction with an initial value
    const count = useRefraction(0);
    
    // Read the current value
    console.log(count.value); // 0
    
    // Update the value
    count.set(5);
  4. Understand and use Refractions for reactive state

    A refraction is a reactive state container in Refract that holds a value and automatically notifies any component using that value when it changes, eliminating the need for manual re-render triggers.
    import { useRefraction } from "refract";
    
    const count = useRefraction(0);
    console.log(count.value); // 0
    
    count.set(5);
    console.log(count.value); // 5
  5. Understand and use Lenses in Refract components

    Lenses are the primary mechanism in Refract for providing controlled access to state, side effects, and lifecycle events. Every Refract component receives a lens parameter. Instead of importing hooks globally, all reactive operations are accessed through this lens object, which allows the framework to better track, optimize, and debug component behavior.
    const MyComponent = createComponent(({ lens, someProp }) => {
      // Access state through the lens
      const count = lens.useRefraction(0);
      
      // Access side effects through the lens
      lens.useEffect(() => {
        console.log('Component mounted');
      }, []);
      
      return <div>Count: {count.value}</div>;
    });
  6. Trigger one-time actions with lens.useFlash

    Use lens.useFlash() when a side effect needs to occur exactly once immediately after the component renders. This is ideal for tasks like triggering entrance animations or focusing an input field. Unlike useEffect, it is not designed for ongoing operations or responding to state changes over time.
    const AnimatedModal = createComponent(({ lens, isVisible }) => {
      const modalRef = lens.useRefraction(null);
      
      lens.useFlash(() => {
        if (isVisible && modalRef.value) {
          // Trigger entrance animation
          modalRef.value.classList.add('animate-in');
          
          // Focus the first input
          const firstInput = modalRef.value.querySelector('input');
          if (firstInput) firstInput.focus();
        }
      });
      
      if (!isVisible) return null;
      
      return (
        <div 
          ref={(el) => modalRef.set(el)}
          className="modal"
        >
          <input placeholder="Enter your name" />
          <button>Submit</button>
        </div>
      );
    });
  7. Implement side effects in custom optics

    Custom optics can handle API calls, subscriptions, and other side effects by combining createOptic with useEffect. This allows for managing loading and error states reactively.
    import { createOptic, useRefraction, useEffect } from "refract";
    
    const useApiData = createOptic((url) => {
      const data = useRefraction(null);
      const loading = useRefraction(false);
      const error = useRefraction(null);
      
      const fetchData = async () => {
        loading.set(true);
        error.set(null);
        
        try {
          const response = await fetch(url);
          const result = await response.json();
          data.set(result);
        } catch (err) {
          error.set(err.message);
        } finally {
          loading.set(false);
        }
      };
      
      useEffect(() => {
        fetchData();
      }, [url]);
      
      return {
        data: data.value,
        loading: loading.value,
        error: error.value,
        refetch: fetchData
      };
    });
  8. Create the Refract application entry point and HTML

    To initialize a Refract app, create a main JavaScript entry point that mounts the root component to a DOM element, and an HTML file containing that root element.
    // src/main.js
    import { createApp } from 'refract';
    import App from './components/App';
    
    createApp(App).mount('#root');
    <!-- src/index.html -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>My Refract App</title>
    </head>
    <body>
      <div id="root"></div>
      <script src="./main.js"></script>
    </body>
    </html>
  9. Handle asynchronous side effects with lens.useEffect

    Use lens.useEffect to handle side effects such as fetching data. This allows you to manage loading and error states reactively based on dependency changes (e.g., a userId prop).
    const UserProfile = createComponent(({ lens, userId }) => {
      const user = lens.useRefraction(null);
      const loading = lens.useRefraction(true);
      const error = lens.useRefraction(null);
    
      lens.useEffect(async () => {
        try {
          loading.set(true);
          const userData = await fetchUser(userId);
          user.set(userData);
        } catch (err) {
          error.set(err.message);
        } finally {
          loading.set(false);
        }
      }, [userId]);
    
      if (loading.value) return <div>Loading...</div>;
      if (error.value) return <div>Error: {error.value}</div>;
      if (!user.value) return <div>User not found</div>;
    
      return (
        <div>
          <h2>{user.value.name}</h2>
          <p>{user.value.email}</p>
        </div>
      );
    });
  10. Use TypeScript with Refract components

    When using TypeScript, define an interface for your component props and apply the ComponentFunction type to the component definition to ensure type safety for both the lens object and custom props.
    import { ComponentFunction } from 'refract';
    
    interface CounterProps {
      initialCount?: number;
      step?: number;
      onCountChange?: (count: number) => void;
    }
    
    const Counter: ComponentFunction<CounterProps> = ({ 
      lens, 
      initialCount = 0, 
      step = 1, 
      onCountChange 
    }) => {
      const count = lens.useRefraction(initialCount);
      
      const increment = () => {
        const newCount = count.value + step;
        count.set(newCount);
        onCountChange?.(newCount);
      };
      
      return <button onClick={increment}>Count: {count.value}</button>;
    };
  11. Update objects and arrays in refractions

    Refractions trigger re-renders based on reference changes. When working with objects or arrays, you must replace the entire value with a new reference (immutability) rather than mutating the existing object/array.
    // Objects: Use spread operator to create a new object
    const user = lens.useRefraction({ name: 'Alice', email: 'alice@example.com' });
    user.set({ ...user.value, email: 'new@example.com' });
    
    // Arrays: Use spread or filter/map to create a new array
    const todos = lens.useRefraction([]);
    
    // Adding item
    todos.set([...todos.value, { id: 1, text: 'New Todo' }]);
    
    // Removing item
    todos.set(todos.value.filter(todo => todo.id !== 1));
  12. Monitor component render times in production

    Use a PerformanceObserver to track measure entries starting with refract-component to monitor the render duration of components in a production environment.
    // analytics/performance.js
    export function trackPerformance() {
      const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          if (entry.name.startsWith('refract-component')) {
            analytics.track('component_render_time', {
              component: entry.name,
              duration: entry.duration
            });
          }
        }
      });
      
      observer.observe({ entryTypes: ['measure'] });
    }