Skip to content

Instantly share code, notes, and snippets.

@joenoon
Last active March 26, 2019 15:54
Show Gist options
  • Save joenoon/bd60647a2ca9e96bc6bc3030c4807c13 to your computer and use it in GitHub Desktop.
Save joenoon/bd60647a2ca9e96bc6bc3030c4807c13 to your computer and use it in GitHub Desktop.
needs some custom setup, but here is the idea
import {MobxReactLiteHooks} from './jn-mobx-lite';
declare module 'jn-mobx-lite.macro';
export function useObserver<T>(baseComponentName?: string): MobxReactLiteHooks;
const { createMacro } = require('babel-plugin-macros');
const IMPORT_SOURCE = '@jn-mobx-lite';
module.exports = createMacro(useObserverMacro);
function useObserverMacro({
references,
state: {
file: {
opts: { filename },
},
},
babel: { types: t, template },
}) {
const { useObserver = [] } = references;
useObserver.forEach(referencePath => {
const program = referencePath.scope.getProgramParent().path;
const line = referencePath.getStatementParent();
const lineVars = line.node.declarations
? line.node.declarations[0].id
: null;
const linesAfter = line.container.splice(
line.key + 1,
line.container.length - line.key - 1
);
line.insertAfter(
t.returnStatement(
t.callExpression(t.identifier('useObserver'), [
t.functionExpression(
t.identifier('useObserverRender'),
lineVars ? [lineVars] : [],
t.blockStatement(linesAfter),
false,
false
),
])
)
);
line.remove();
const foundImport =
program.get('body').filter(x => {
if (t.isImportDeclaration(x) && x.node.source.value === IMPORT_SOURCE) {
return !!x.node.specifiers.filter(
s => s.local.name === 'useObserverRender'
);
}
return false;
}).length > 0;
if (!foundImport) {
program.unshiftContainer(
'body',
t.importDeclaration(
[
t.importSpecifier(
t.identifier('useObserver'),
t.identifier('useObserver')
),
],
t.stringLiteral(IMPORT_SOURCE)
)
);
}
});
}
import {useComputed, useObservable, useObserver as useObserverInternal} from 'mobx-react-lite';
const HOOKS = {
useComputed,
useObservable,
};
export type MobxReactLiteHooks = typeof HOOKS;
// This is for my convenience, not neccessary for the pattern.
export * from 'mobx';
/**
* Any functional component needing observability or using useObservable/useComputed
* should be defined similar to:
*
* ```
* export const MyComponent: React.FunctionComponent<{}> = props =>
* useObserver(function useHooks({useObservable, useComputed}) {
* const someObservable = useObservable({foo: 10});
*
* const myComputedVal = useComputed(() => {
* return someObservable.foo + 10;
* });
*
* return (
* <div>
* MyComponent: {myComputedVal}
* <button onClick={() => (someObservable.foo += 10)}>Bump</button>
* </div>
* );
* });
* ```
*
* Enforcing this pattern and requiring `'useObservable` and `useComputed` are provided
* by a `useObserver` helps ensure expected behavior and the inner `useHooks` named
* function enables properly linting the rules of hooks.
*
* Note: `observer` HOC and the `Observer` component are not exposed. Components can be
* broken down into smaller units that can use `useObserver` with what React already
* provides us, simplifying the usage.
*/
export function useObserver<T>(func: (hooks: MobxReactLiteHooks) => T, baseComponentName?: string): T {
return useObserverInternal(() => func(HOOKS), baseComponentName);
}
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`macros 1. macros: 1. macros 1`] = `
"
import {useObserver} from '../useObserver.macro'
export const MyComponent = (props) => {
console.log('here');
const {useObservable, useComputed} = useObserver();
const foo = useObservable({foo: 1});
const bar = useComputed(() => foo.foo + 1);
return null;
};
export const MyComponent2 = (props) => {
console.log('here');
const {useObservable} = useObserver();
const foo = useObservable({foo: 1});
return null;
};
export const MyComponent3 = (props) => {
console.log('here');
useObserver();
return null;
};
↓ ↓ ↓ ↓ ↓ ↓
import { useObserver } from \\"@jn-mobx-lite\\";
export const MyComponent = props => {
console.log('here');
return useObserver(function useObserverRender({
useObservable,
useComputed
}) {
const foo = useObservable({
foo: 1
});
const bar = useComputed(() => foo.foo + 1);
return null;
});
};
export const MyComponent2 = props => {
console.log('here');
return useObserver(function useObserverRender({
useObservable
}) {
const foo = useObservable({
foo: 1
});
return null;
});
};
export const MyComponent3 = props => {
console.log('here');
return useObserver(function useObserverRender() {
return null;
});
};
"
`;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment