TLDR;
Here is a snack similar to how I'm implementing a side menu in a React Native / Expo app
https://snack.expo.io/@wolverineks/withsidemenu
Background
I'm currently building a React Native / Expo app for a client. The app uses React Router, and React Native Drawer.
Some of the routes have a side menu, and some do not. So I wrote context like...
interface SideMenuContext {
open: () => void;
close: () => void;
enable: () => void;
disable: () => void;
enabled: boolean;
shouldOpen: boolean;
}
and a hook like...
export const useSideMenu = () => {
const sideMenu = React.useContext(SideMenuContext);
if (sideMenu === undefined) {
throw new Error("useSideMenu must used in a SideMenuProvider");
};
const { enable, disable, close } = sideMenu
React.useEffect(() => {
enable();
return () => {
disable();
close();
};
}, []);
return sideMenu;
};
and on the screens that have a side menu:
const SomeScreen = () => {
useSideMenu()
return ...yada...yada...yada
}
Can anyone spot the undesirable behavior?
So, I noticed a few things about this approach that I didn't like.
The imperative nature of the hook api meant that if (for some reason) multiple components that
useSideMenu
are mounted simultaneously, removing any of them would disable the side menu. The behavior I was looking for was, disabling the side menu only if all of the components were unmounted.When testing the screens in isolation, a
<SideMenuProvider />
would have to be mounted, or the hook would throw an error.
Next Steps
To overcome the second issue, I've written a component, <WithSideMenu />
, and moved the useSideMenu()
call from just inside the screens, to just outside of the screens...
<WithSideMenu>
<SomeComponent />
</WithSideMenu>
And, to overcome the first issue, I've rewritten the context to...
interface SideMenuContext {
open: () => void;
close: () => void;
register: () => () => void; // <- returns an "unregister"
enabled: boolean;
shouldOpen: boolean;
<Drawer />
}
to be used like...
const WithSideMenu: React.FC = ({ children ) => {
const sideMenu = useSideMenu()
const { register } = sideMenu;
React.useEffect(register, []);
return typeof children === "function"
? children(sideMenu)
: children;
};
Conclusion
- Declarative for the win.
- Composition for the win.
- Probably some other stuff...
Here is the snack again:
Top comments (0)