Preact Composition API
Tags: preact, vue, composition, hooks
Published: 2019-09-10 20:07:49

---
title: Preact Composition API
published: true
description: Implement the new Vue Composition API in Preact
tags: preact, vue, composition, hooks
---

My name is Porfírio and I work at [Agroop](https://agroop.net) for 3 years, building an App using React.
I'm always curious with new technologies and ways to do things, so I started testing React Hooks since the first day it was announced and started using them in production right after the first release.

So when I heard all the fuzz around an RFC in Vuejs because of a new API I started looking at it and try to understand what it was.
After the initial fuzz, they get to set a quite interesting [API](https://vue-composition-api-rfc.netlify.com)

At the time I was also reading [Preact](https://github.com/preactjs/preact) source, the advantage of having a small library, is that it is possible to read the code and understand most of it.
Infact Preact source for [Hooks](https://github.com/preactjs/preact/blob/master/hooks/src/index.js), had helped me understand how Hooks work, in a way I was not able to do when I _tried_ to read React source. And I found out this interesting API in Preact that let you *hook* into the rendering process and thats what they use to introduce Hooks into Preact as a separate bundle without increasing Preact size

Has I enjoyed so much the new Vue API and I was messing with Preact I decided to create a proof of concept of implementing the new Vue Composition API on Preact.
You can find it here: https://github.com/porfirioribeiro/preact/blob/composition-api/composition/src/index.js

Meanwhile I created a WIP PR on preact repo: https://github.com/preactjs/preact/pull/1923

Of course that there are diferences from the Vue api, as both libraries handle things differently.


## Comparing with Vue Composition API

https://vue-composition-api-rfc.netlify.com

Preact Composition API is heavily inspired by Vue, so it's API tries to mimic Vue API but it's not exactly the same, due to some limitations or by design.

#### [createComponent](https://vue-composition-api-rfc.netlify.com/api.html#createcomponent) / [setup](https://vue-composition-api-rfc.netlify.com/api.html#setup)

Vue uses `createComponent` accepts an object that includes `setup`, that is Vue way to define components, with objects. In fact,`createComponent` does nothing, mostly helps with typing.
In Preact `createComponent` accepts a function that returns a function component. It does not do much in Preact either, it just marks that function as a Composition function so Preact can handle it diferently.

```tsx
export const MyComp = createComponent(() => {
	// composition code
	//
	// return function component
	return ({ message }) => <div>{message}</div>;
});
```

#### [reactive](https://vue-composition-api-rfc.netlify.com/api.html#reactive)

`reactive` wraps an object in a [proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) so that every time the object is changed the component is updated, working as a state holder.

```tsx
export const MyComp = createComponent(() => {
	const obj = reactive({ count: 0 });

	function increase() {
		obj.count++;
	}

	return ({ message }) => (
		<div onClick={increase}>
			{message} {obj.count}
		</div>
	);
});

```

#### [ref](https://vue-composition-api-rfc.netlify.com/api.html#ref)

`ref` is also a state holder, mostly it wraps one value, we need this as in JavaScript natives are passed by value, not reference.
When `theRef.value` is changed, the component is updated.
The implementation of `ref` is more simple than `reactive` as it uses an object with getters/setters.

```tsx
export const MyComp = createComponent(() => {
	const count = ref(0);

	function increase() {
		count.value++;
	}

	return ({ message }) => (
		<div onClick={increase}>
			{message} {count.value}
		</div>
	);
});
```

#### [isRef](https://vue-composition-api-rfc.netlify.com/api.html#isref)

`isRef` returns if a object is a `ref`
`unwrapRef` try to unwrap the ref

```tsx
const value = isRef(foo) ? foo.value : foo; //same as
const value = unwrapRef(foo);
```


#### [toRefs](https://vue-composition-api-rfc.netlify.com/api.html#torefs)

`toRefs` is not implemented yet as the design of the API in Preact is different of the Vue one, didn't found a good use for it, yet.


#### [computed](https://vue-composition-api-rfc.netlify.com/api.html#computed)

`computed` is not implemented as is, it's mostly joined with `watch` as the Preact life cycle works a little different from Vue

#### [watch](https://vue-composition-api-rfc.netlify.com/api.html#watch)

`watch` in Preact is a bit different from `watch` in Vue, due to the differences from Preact and Vue, and also some API design to support other Preact features like Context
Because of that nature, we have 2 functions alike: `watch` and `effect`
`watch` runs before render and can return a `ref` with the result of it's execution
`effect` is run after update, as a side effect

```tsx
//un-exhausted example of what watch can do!
const countGetter = props => props.countProp;

export const MyComp = createComponent(() => {
	const countRef = ref(0);
	const reactiveObj = reactive({ count: 0 });

	const memoizedComputedValue = watch(
		[countRef, reactiveObj, countGetter],
		// this will be computed when any of those 3 dependencies are updated
		// works as computing and memoization
		([count, obj, countFromProps]) => count * obj * countFromProps
	);

	effect(
		memoizedComputedValue,
		value => (document.title = `computed [${value}]`)
	);

	function increase() {
		countRef.value++;
	}

	return ({ message }) => (
		<div onClick={increase}>
			{message} {memoizedComputedValue.value}
		</div>
	);
});
```


#### [lifecycle-hooks](https://vue-composition-api-rfc.netlify.com/api.html#lifecycle-hooks)

Only some lifecycle hooks are implemented, some not implemented yet, others will not be implemented as it does not make sense or can't be implemented in Preact

* `onMounted` Callback to call after the component mounts on DOM
* `onUnmounted` Callback to call right before the component is removed from DOM
* `effect` cannot be considered a lifecycle, but can be used to achieve the same as `onUpdated` in Vue, tracking the needed dependencies.

#### [provide-inject](https://vue-composition-api-rfc.netlify.com/api.html#provide-inject)

`provide` and `inject` is not implemented as Preact already have a Context API, probably it can be implemented later.

We can achieve `inject` like feature by passing a Context as src on `watch` or `effect`, making the component subscribe to the closest Provider of that Context

```tsx
export const MyComp = createComponent(() => {
	const userCtx = watch(UserContext);

	return ({ message }) => (
		<div>
			{message} {userCtx.value.name}
		</div>
	);
});
```

## Comparing with (P)React Hooks

https://reactjs.org/docs/hooks-reference.html

At the first look we might find React hooks and Preact Composition API(PCApi)  alike, but there is a HUGE difference between them.

The function passed to `createComponent` when we call the composition functions is only executed once during the component lifecycle, the returned function component is executed at each update.
And in React the hooks are always called and (most of it) redefined in each render, [Vue has a good explanation of the differences](https://vue-composition-api-rfc.netlify.com/#comparison-with-react-hooks)

This has to bring a mind shift, in hooks you can deal with simple variables but have to deal with code re-declaration and memoizing of values and callbacks to avoid children re-renders.

#### [useState](https://reactjs.org/docs/hooks-reference.html#usestate)

`useState` is used in React as a state holder, in PCApi `ref` or `reactive` can be used, depending on the need of holding a single value or multiple value object

```tsx
// (P)React hooks
const Counter = ({ initialCount }) => {
	// redeclared and rerun on each render
	const [count, setCount] = useState(initialCount);
	const reset = () => setCount(initialCount);
	const increment = () => setCount(prevCount => prevCount + 1);
	const decrement = () => setCount(prevCount => prevCount - 1);
	return (
		<>
			Count: {count}
			<button onClick={reset}>Reset to {initialCount}</button>
			<button onClick={increment}>+</button>
			<button onClick={decrement}>-</button>
		</>
	);
};
// Preact Composition
const Counter = createComponent(props => {
	// run once
	const countRef = ref(props.initialCount);
	const reset = () => (countRef.value = props.initialCount);
	const increment = () => (countRef.value += 1);
	const decrement = () => (countRef.value -= 1);
	return ({ initialCount }) => (// run on each render
		<>
			Count: {countRef.value}
			<button onClick={reset}>Reset to {initialCount}</button>
			<button onClick={increment}>+</button>
			<button onClick={decrement}>-</button>
		</>
	);
});
```

Both implementations have mostly the same size and code looks alike, the difference is mostly that the composition functions only run once and the callbacks are not redeclared in each render.
It might not matters much, but having to swap event handlers in each render is not optimal and one of the reasons why React implemented [SyntheticEvents](https://reactjs.org/docs/events.html).


#### [useEffect](https://reactjs.org/docs/hooks-reference.html#useeffect)

`useEffect` is a all in one effect handler, you can use it for mount (`onMounted`)/unmount(`onUnmounted`) lifecycles or for update based on dependencies.

```tsx
// (P)React
const Comp = props => {
	useEffect(() => {
		// subscribe
		const subscription = props.source.subscribe();
		return () => {
			// Clean up the subscription
			subscription.unsubscribe();
		};
	}, []);
	return <div>irrelevant</div>;
};
// Preact Composition
const Comp = createComponent(props => {
	let subscription;
	onMounted(() => {
		// subscribe
		subscription = props.source.subscribe();
	});
	onUnmounted(() => {
		// Clean up the subscription
		subscription.unsubscribe();
	});
	return () => <div>irrelevant</div>;
});
```

Again, code for both approaches are mostly alike, `useEffect` will check dependencies and find the empty array making the effect never change and bail out the new function.


Now if you need to subscribe based on some dependency (eg. a prop) its a little bit difference.

```tsx
// (P)React
const Comp = props => {
	useEffect(() => {
		const subscription = props.source.subscribe(props.id);
		return () => subscription.unsubscribe();
	}, [props.id, props.source]);
	return <div>irrelevant</div>;
};
// Preact Composition
const Comp = createComponent(props => {
	effect(
		props => [props.id, props.source],
		([id, source], _oldArgs, onCleanup) => {
			const subscription = source.subscribe(id);
			onCleanup(() => subscription.unsubscribe());
		}
	);
	return () => <div>irrelevant</div>;
});
```

`effect` gives you 3 things, newArgs, oldArgs (in case of update), onCleanup that is a special function that you can call and pass a cleanup function. It does not use the return callback aproach because effect callback may be async!


#### [useContext](https://reactjs.org/docs/hooks-reference.html#usecontext)

`useContext` let you subscribe and get the value of a context in a parent component, in Composition API you can use the context as a source of a watch or effect function.

```tsx
// (P)React
const Comp = props => {
	const ctxValue = useContext(MyContext);
	return <div>{ctxValue}</div>;
};
// Preact Composition
const Comp = createComponent(props => {
	const ctx = watch(MyContext);
	return () => <div>{ctx.value}</div>;
});
```

`watch` gives you some advantages and let you connect many sources together!


#### [useReducer](https://reactjs.org/docs/hooks-reference.html#usereducer)

There is no `useReducer` alternative yet, but it could be easly implemented

#### [useCallback](https://reactjs.org/docs/hooks-reference.html#usecallback)

In most scenarios, a `useCallback` like function is not necessary, as you can define your callbacks at setup time only once and the reference will never change, thats one of the great features of this API.
Normaly your callacks are called sync, so you can access your state and props references with the right values, but sometimes you may be passing a function to a component that will be called at a different time and you want that to be called with the current value.

```tsx
// (P)React
const Comp = props => {
	const handlePostSubmit = useCallback(
		() => console.log('This will be called with actual id', props.id),
		[props.id]
	);
	return <Form onPostSubmit={handlePostSubmit}>irrelevant</Form>;
};
// Preact Composition
const Comp = createComponent(props => {
	const handlePostSubmit = watch(
		props => props.id,
		id => console.log('This will be called with actual id', id)
	);
	return () => <Form onPostSubmit={handlePostSubmit.value}>irrelevant</Form>;
});
```

#### [useMemo](https://reactjs.org/docs/hooks-reference.html#usememo)

`useMemo` allows you to memoize values and avoid recalculating big values unless its need

```tsx
// (P)React
const Comp = props => {
	const [filter, setFilter] = useState('ALL');
	const filteredItems = useMemo(() => filterItems(props.items, filter), [
		props.items,
		filter
	]);
	return <ItemList items={filteredItems} />;
};
// Preact Composition
const Comp = createComponent(() => {
	const filterRef = ref('ALL');
	const filteredItems = watch(
		[props => props.items, filterRef],
		([items, filter]) => filterItems(items, filter)
	);
	return () => <ItemList items={filteredItems.value} />;
});
```


#### [useRef](https://reactjs.org/docs/hooks-reference.html#useref)

`useRef` is used mainly for 2 things, handle DOM references and save components values between renders

As we have the setup function, all var's declared there can be used between renders, so no `useRef` needed.
For DOM values you can use callbacks and local var's or `React.createRef`


#### [useImperativeHandle](https://reactjs.org/docs/hooks-reference.html#useimperativehandle)
Haven't found a need for it yet, but I beleve it can be implemented

#### [useLayoutEffect](https://reactjs.org/docs/hooks-reference.html#uselayouteffect)
At the moment there is no direct replacement for this.

#### [useDebugValue](https://reactjs.org/docs/hooks-reference.html#usedebugvalue)
Haven't found a need for it yet, but I beleve it can be implemented


### Conclusion

The point here is not to say that this API is better, its different, both have pitfals as Evan You as pointed on Twitter: https://twitter.com/youyuxi/status/1169325119984082945

![Compare](https://thepracticaldev.s3.amazonaws.com/i/rhw687zrw2aqcgfeszfx.jpeg)