Deep dive into ripples
Intro
A ripple represents an object, similar to an Atom in Recoil. A ripple is declared into the Lake using the createRipples
function.
It takes a name and an object that represent the Ripple and its initial state.
When you use a Ripple from a component, you receive a copy of the Ripple state and a set function to update the state. The Ripple object can be locally modified by the component, but the changes will not be reflected in the Lake.
The Ripple source is immutable, but the changes are propagated to the Lake via the updater, then modifications are propagated to all the components that use the Ripple.
Let's imagine the following Ripple definition:
- my-lake.ts
... = createRipple({ // Lake
counter: { // Counter ripple
count: 0,
};
});
We have a small Ripple that contains a single property, count
, initialized to 0.
Inside the component
We can use this Ripple in a component:
- MyComponent
import { rippleHooks } from './my-lake';
const { useCounter } = rippleHooks;
const MyComponent = () => {
const [counter, /*...updaterHook...*/] = useCounter();
return (
<div>
<p>Counter: {counter.count}</p>
</div>
);
};
The useCounter
hook returns a tuple with the Ripple state and the updater function.
Remind that the ripple source is immutable, so we can't do counter.count++
directly and have the modification applied.
But we can do counter.count++
and then call the updater function to propagate the changes to the Lake.
The source value remains unchanged, but the Lake will be updated with the new value.
We will study the updater in the next chapter.
Destructuring the ripple
As a Ripple is linked to the lake internally, you can destructure a ripple and still be rerendered when the ripple changes.
- MyComponent (destructuring)
import { rippleHooks } from './my-lake';
const { useCounter } = rippleHooks;
const MyComponent = () => {
const [counter, setCounter] = useCounter();
const { count } = counter;
return (
<div>
<p>Counter: {count}</p>
<button onClick={() => {
counter.count++;
setCounter();
}}>Increment</button>
</div>
);
};
Obviously, in this example, it takes more line of code to destructure the ripple than to use it directly, but the purpose is just to explain what you can do. Local modifications can be cancelled or applied to the Lake using the updater function.
Behind the scene
Behind the scene, we keep tracks of the local changes by using a Proxy object. So the ripple is not a copy of the state object. It works the same, but a proxy has some small differences still, like using an array with the Array.isArray function will not works as expected.
When you use a ripple, the Lake will create a proxy object that will be used to keep track of the changes.
This way, a ripple even with complex structure with lots of nested objects will not be a performance issue. As we keep track of changes, only the modified properties will be updated in one shot, or cancelled as per demand.
The real purpose is to limit to the maximum the need for the developer to implement a business logic inside the component. Use the component for rendering only.