Simplify API with React Hooks/Optional Implicit Behaviour
Should be relatively painless (for consumers) as everything is already a render function. Defining what animations you want to happen could be tricky though. And the collector behaviour we have now probably would need to be re-written. Hmm.
Ignoring implementation details the API could be something like this?
import { useBaba, revealMove, circleExpand, wait } from 'yubaba';
const Component = () => {
// Each animation is now a function, call it with or without args.
const { ref, style, className } = useBaba('my-baba', [ // lmao this almost sounds like yubaba. i like it.
revealMove({ duration: 400 }),
wait(),
circleExpand({ background: '#ccc' }),
], {}); // Third arg has optional props such as "in" prop in current Baba component.
// alternatively we could have a indefinite args where the last one is dynamically the optional props. would have some perf overhead.
useBaba('my-baba', revealMove(), wait(), circleExpand(), {});
return <div ref={ref} style={style} className={className} />;
};
If we compare the above to todays world:
import Baba, { Wait, RevealMove, Expand } from 'yubaba';
<Baba name="my-baba">
<RevealMove duration={400}>
<Wait>
<CircleExpand background="#ccc">
{({ ref, style, className }) => <div ref={ref} style={style} className={className} />}
</CircleExpand>
</Wait>
</RevealMove>
</Baba>
Much simpler end result with the react elements, however the initial setup seems to be.. a more verbose API.
We'd also want a better API internally for animations.
Today it looks like:
class Animation extends React.Component {
beforeAnimate = (data, onFinish, setChildProps) => {
};
animate = (data, onFinish, setChildProps) => {
};
render() {
const { children } = this.props;
return (
<Collector
data={{
action: 'animation',
payload: {
beforeAnimate: this.beforeAnimate,
animate: this.animate,
},
}}
>
{children}
</Collector>
);
}
}
If we were to re-imagine using hooks, perhaps it wouldn't even need to be a component or hook - just a function that returns an animation descriptor. Making the entire collector implementation redundant...
const animation = () => ({
action: 'animation',
beforeAnimate: (data, onFinish, setChildProps) => {},
animate: (data, onFinish, setChildProps) => {},
});
Simpler yes - but it also puts in the restriction of not allowing any instance state. Maybe that's actually a good thing though.
Including the baba manager component as well (thoughts: is baba manager considered a parent here of the baba?)
const manager = useBabaManager(/* optional name if multiple child babas */ 'my-baba');
const baba = useBaba('my-baba', revealMove(), wait(), circleExpand());
<div {...manager}>
<div {...baba} />
</div>
Next thought:
How could we compose animations with hooks?
Currently it's render props everywhere. I think the ideal state (considering we still need to use React Context to communicate between some components) is going to be a mix of hooks and components.
import { useMotion, useMove, useCircleExpand } from '@element-motion/core';
() => {
// Creates a motion component dynamically.
// Use a component so we can pass around context as needed.
const Motion = useMotion([
// Motion hooks so we can memoize over the react lifecycle
useMove(),
useCircleExpand(),
]);
return (
<Motion>
/* Implicitly pass down className/styles */
<div>hello world</div>
</Motion>
);
return (
<Motion>
/* Explicitly pass down className/styles */
{motion => <div {...motion}>hello world</div>}
</Motion>
);
}