createComponent
createComponent is how you build the building blocks of your Refract application. It takes a function and turns it into a reactive component that automatically updates when your data changes.
Think of it as the foundation of your UI - every interactive element, every piece of content that needs to respond to user actions or data changes starts with createComponent.
Why createComponent?
In traditional React, you write components that manage their own state and manually handle updates. With Refract, createComponent creates components that are inherently reactive - they automatically stay in sync with your data without any extra work.
The key difference is the lens parameter - this is your gateway to Refract's reactive features. Through the lens, you can create reactive state, handle side effects, and build reusable logic patterns.
createComponent
Since v1.0.0Creates a new Refract component. Components are pure functions that receive props and a lens object for accessing reactive features.
Signature
const Component = createComponent(({ lens, ...props }) => JSX)
Parameters
componentFunction(props: { lens: Lens, [key: string]: any }) => JSX.ElementrequiredFunction that receives props and lens, returns JSX
Returns
A Refract component that can be used in JSX
Example
import { createComponent } from "refract";
const MyComponent = createComponent(({ lens, title, onClick }) => {
const count = lens.useRefraction(0);
return (
<div>
<h2>{title}</h2>
<p>Count: {count.value}</p>
<button onClick={() => count.set(count.value + 1)}>
Increment
</button>
<button onClick={onClick}>
External Action
</button>
</div>
);
});
// Usage
<MyComponent title="My Counter" onClick={() => alert('Clicked!')} />
Comparison with Traditional Approaches
Let's see how createComponent compares to traditional React component patterns:
- React Component
- Refract Component
- Live Result
import React, { useState } from 'react';
const Counter = ({ title, onReset }) => {
const [count, setCount] = useState(0);
const [message, setMessage] = useState('');
// Manual state synchronization
const increment = () => {
setCount(prev => prev + 1);
};
const reset = () => {
setCount(0);
onReset?.();
};
return (
<div>
<h2>{title}</h2>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={reset}>Reset</button>
</div>e
);
};
export default Counter;
Issues with React components:
- Manual state synchronization with useEffect
- Multiple useState calls for related data
- Must remember to export components
- Verbose for simple reactive behavior
import { createComponent } from 'refract';
const Counter = createComponent(({ lens, title, onReset }) => {
const count = lens.useRefraction(0);
// Automatically reactive - no manual effects needed
const increment = () => {
count.set(count.value + 1);
};
const reset = () => {
count.set(0);
onReset?.();
};
return (
<div>
<h2>{title}</h2>
<p>Count: {count.value}</p>
<button onClick={increment}>Increment</button>
<button onClick={reset}>Reset</button>
</div>
);
});
// No export needed - createComponent handles it
Benefits of Refract components:
- Automatic reactivity - computed values update automatically
- Single reactive state container
- No need for manual exports
- Less boilerplate for reactive behavior
- Built-in lens system for advanced patterns
My Counter
Current count is 0
Basic Usage
Simple Component
Creating a basic Refract component:
import { createComponent } from "refract";
const Greeting = createComponent(({ lens, name }) => {
return (
<div>
<h1>Hello, {name}!</h1>
<p>Welcome to Refract</p>
</div>
);
});
// Usage
const App = createComponent(({ lens }) => {
return (
<div>
<Greeting name="World" />
<Greeting name="Refract" />
</div>
);
});
Component with State
Stateful Component
Using the lens object to manage component state:
const Counter = createComponent(({ lens, initialValue = 0 }) => {
const count = lens.useRefraction(initialValue);
const message = lens.useRefraction("Click the button!");
const handleIncrement = () => {
count.set(count.value + 1);
message.set(`Clicked ${count.value + 1} times!`);
};
return (
<div className="counter">
<h2>{message.value}</h2>
<div className="count-display">
Count: {count.value}
</div>
<button onClick={handleIncrement}>
Increment
</button>
<button onClick={() => {
count.set(initialValue);
message.set("Reset to initial value!");
}}>
Reset
</button>
</div>
);
});
Component with Effects
Component with Side Effects
Using lens methods for side effects and lifecycle management:
const UserProfile = createComponent(({ lens, userId }) => {
const user = lens.useRefraction(null);
const loading = lens.useRefraction(true);
const error = lens.useRefraction(null);
// Fetch user data when userId changes
lens.useEffect(() => {
loading.set(true);
error.set(null);
fetch(`/api/users/${userId}`)
.then(response => {
if (!response.ok) throw new Error('Failed to fetch user');
return response.json();
})
.then(userData => {
user.set(userData);
loading.set(false);
})
.catch(err => {
error.set(err.message);
loading.set(false);
});
}, [userId]);
// Update document title when user data loads
lens.useEffect(() => {
if (user.value) {
document.title = `${user.value.name} - Profile`;
}
return () => {
document.title = "My App"; // Cleanup
};
}, [user.value?.name]);
if (loading.value) return <div>Loading user...</div>;
if (error.value) return <div>Error: {error.value}</div>;
if (!user.value) return <div>User not found</div>;
return (
<div className="user-profile">
<img src={user.value.avatar} alt={user.value.name} />
<h1>{user.value.name}</h1>
<p>{user.value.email}</p>
<p>Joined: {new Date(user.value.createdAt).toLocaleDateString()}</p>
</div>
);
});
Component Composition
Composing Components
Building complex UIs by composing simple components:
const Button = createComponent(({ lens, children, variant = "primary", onClick }) => {
const isPressed = lens.useRefraction(false);
return (
<button
className={`btn btn--${variant} ${isPressed.value ? 'btn--pressed' : ''}`}
onMouseDown={() => isPressed.set(true)}
onMouseUp={() => isPressed.set(false)}
onMouseLeave={() => isPressed.set(false)}
onClick={onClick}
>
{children}
</button>
);
});
const Modal = createComponent(({ lens, children, title, onClose }) => {
const isVisible = lens.useRefraction(true);
// Handle escape key
lens.useEffect(() => {
const handleEscape = (e) => {
if (e.key === 'Escape') {
onClose();
}
};
document.addEventListener('keydown', handleEscape);
return () => document.removeEventListener('keydown', handleEscape);
}, []);
if (!isVisible.value) return null;
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<div className="modal-header">
<h2>{title}</h2>
<Button variant="secondary" onClick={onClose}>
×
</Button>
</div>
<div className="modal-body">
{children}
</div>
</div>
</div>
);
});
// Usage
const App = createComponent(({ lens }) => {
const showModal = lens.useRefraction(false);
return (
<div>
<Button onClick={() => showModal.set(true)}>
Open Modal
</Button>
{showModal.value && (
<Modal
title="Example Modal"
onClose={() => showModal.set(false)}
>
<p>This is modal content!</p>
<Button onClick={() => showModal.set(false)}>
Close
</Button>
</Modal>
)}
</div>
);
});