createComponent
Creates a new Refract component. Components are the building blocks of your application—they're pure functions that take props and return JSX.
Reference
import { createComponent } from 'refract';
const MyComponent = createComponent(({ lens, ...props }) => {
// Component logic here
return <div>Hello, world!</div>;
});
Parameters
- componentFunction: A function that receives props and returns JSX. The first parameter is always the
lensobject, followed by any other props you define.
Returns
A Refract component that can be used in JSX or rendered with createApp().
Usage
Creating a basic component
The simplest component just returns some JSX:
const Greeting = createComponent(({ lens, name }) => {
return <h1>Hello, {name}!</h1>;
});
// Use it like this:
<Greeting name="Sarah" />
Components with state
Use lens.useRefraction() to add reactive state to your component:
const Counter = createComponent(({ lens, initialCount = 0 }) => {
const count = lens.useRefraction(initialCount);
const increment = () => count.set(count.value + 1);
const decrement = () => count.set(count.value - 1);
return (
<div>
<p>Count: {count.value}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
});
Components with side effects
Use lens.useEffect() to handle side effects like data fetching:
const UserProfile = createComponent(({ lens, userId }) => {
const user = lens.useRefraction(null);
const loading = lens.useRefraction(true);
lens.useEffect(() => {
const fetchUser = async () => {
loading.set(true);
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
user.set(userData);
} catch (error) {
console.error('Failed to fetch user:', error);
} finally {
loading.set(false);
}
};
fetchUser();
}, [userId]);
if (loading.value) {
return <div>Loading...</div>;
}
if (!user.value) {
return <div>User not found</div>;
}
return (
<div>
<h2>{user.value.name}</h2>
<p>{user.value.email}</p>
</div>
);
});
Using optics for reusable logic
Optics let you share logic between components:
// Define an optic
function useToggle(initial = false) {
const value = lens.useRefraction(initial);
const toggle = () => value.set(!value.value);
const setTrue = () => value.set(true);
const setFalse = () => value.set(false);
return { value: value.value, toggle, setTrue, setFalse };
}
// Use it in a component
const DropdownMenu = createComponent(({ lens, children, trigger }) => {
const { value: isOpen, toggle, setFalse } = useToggle();
return (
<div>
<button onClick={toggle}>{trigger}</button>
{isOpen && (
<div className="dropdown" onClick={setFalse}>
{children}
</div>
)}
</div>
);
});
The lens object
Every component receives a lens object as its first prop. The lens provides access to:
- State management:
useRefraction(),useGlobalRefraction() - Side effects:
useEffect(),useFlash() - Performance:
useMemo(),useCallback() - References:
useRef() - Optics: Custom reusable logic
Think of the lens as your component's toolkit—everything you need to build reactive UIs.
const ExampleComponent = createComponent(({ lens, title }) => {
// State
const count = lens.useRefraction(0);
// Side effects
lens.useEffect(() => {
document.title = `${title} - Count: ${count.value}`;
}, [title, count.value]);
// Memoization
const expensiveValue = lens.useMemo(() => {
return count.value * 1000;
}, [count.value]);
// Refs
const buttonRef = lens.useRef();
return (
<div>
<h1>{title}</h1>
<p>Count: {count.value}</p>
<p>Expensive value: {expensiveValue}</p>
<button
ref={buttonRef}
onClick={() => count.set(count.value + 1)}
>
Increment
</button>
</div>
);
});
TypeScript
When using TypeScript, define your props interface and use the ComponentFunction type:
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>
);
};
Best practices
Keep components small and focused
Each component should have a single responsibility:
// Good: Focused component
const TodoItem = createComponent(({ lens, todo, onToggle, onDelete }) => {
return (
<div className={`todo-item ${todo.completed ? 'completed' : ''}`}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</div>
);
});
// Better than one large TodoApp component doing everything
Use descriptive names
Component names should clearly describe what they do:
// Good
const UserAvatarWithStatus = createComponent(({ lens, user }) => {
// ...
});
// Less clear
const UserThing = createComponent(({ lens, user }) => {
// ...
});
Extract reusable logic to optics
If you find yourself repeating logic across components, create an optic:
// Reusable optic
function useApi(endpoint) {
const data = lens.useRefraction(null);
const loading = lens.useRefraction(false);
const error = lens.useRefraction(null);
const fetch = async () => {
loading.set(true);
error.set(null);
try {
const response = await window.fetch(endpoint);
const result = await response.json();
data.set(result);
} catch (err) {
error.set(err.message);
} finally {
loading.set(false);
}
};
return { data: data.value, loading: loading.value, error: error.value, fetch };
}
// Use in multiple components
const UserList = createComponent(({ lens }) => {
const { data: users, loading, error, fetch } = useApi('/api/users');
lens.useEffect(() => {
fetch();
}, []);
// Render logic...
});
That's everything you need to know about creating components in Refract. Next, learn about useRefraction to manage state in your components.