natemoo-re / microsite

Do more with less JavaScript. Microsite is a smarter, performance-obsessed static site generator powered by Preact and Snowpack.
https://npm.im/microsite
MIT License
878 stars 16 forks source link

Make withHydrate a no-op when nested rather than an error #148

Open ratorx opened 3 years ago

ratorx commented 3 years ago

Currently withHydrate detects when it is used on the child element of a hydrated parent and errors. Instead of this, I think it might be better to do nothing (just return the component, don't create the hydration marker). This doesn't change the API, but removes a restriction.

Doing this makes withHydrate more composable. Right now, when creating a child component, you need to worry about whether it's should be the root a hydration tree or not. Ideally, you wouldn't have to do this - you could write components and tag them with withHydrate if they should be hydrated and have the framework automatically determine good hydration roots. I think just ignoring the nested withHydrate achieves this. Children are hydrated as expected (because their parent has a withHydrate) and parents don't have to worry about whether their children are hydrated or not.

Example code (which is not as easily possible today)

components/button.tsx:

const SimpleButton: FunctionalComponent<{classNames: string[]}> = (props) => <button class={props.classNames} onClick={() => alert("Test")}/>Test</button>;
export default withHydrate(SimpleButton);

components/multiple.tsx:

const MultipleButtons = (_: {}) => {
    const [s, setS] = useState(false);
    return <><button onClick={() => setS((s) => !s)}>Change colour</button><SimpleButton classNames={s ? ["red"] : ["blue"]} /></>
}

export default withHydrate(MultipleButtons)

pages/index.tsx:

const Index = (_: {}) => <main><SimpleButton classNames={[]}/><MultipleButtons /></main>

export default definePage(Index);

In this case, SimpleButton is both a root of a hydrated tree (in one context) and a child of a hydrated parent (in a different one). To make this work now, you'd have to export SimpleButton as both a hydrated export and a regular export. If withHydrate ignored children, then only 1 export is enough to make this work.