Skip to content

Commit

Permalink
feat(hooks): create useLazy hook
Browse files Browse the repository at this point in the history
test for null on first render by copying react-hooks-testing-library's renderHooks method
  • Loading branch information
aneurysmjs committed Aug 5, 2019
1 parent 37cf5cb commit b007552
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 25 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"precss": "4.0.0",
"puppeteer": "1.19.0",
"react-dev-utils": "9.0.1",
"react-test-renderer": "16.8.6",
"rimraf": "2.6.3",
"run-sequence": "2.2.1",
"sass-loader": "7.1.0",
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/core/Header/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { useState } from 'react';

import Icon from '@/components/base/Icon/Icon';
import Navigation from '@/components/core/Navigation/Navigation';
import { useLazy } from '@/components/shared/LazyComponent/LazyComponent';
import { useLazy } from '@/hooks/useLazy';

import './Header.scss';

Expand Down
23 changes: 0 additions & 23 deletions src/app/components/shared/LazyComponent/LazyComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,3 @@ const LazyComponent = ({ getModule, ...rest }: PropsType) => {
};

export default LazyComponent;

export const useLazy = (
getModule: () => Promise<*>,
cond?: boolean = false,
) => {
const [AsyncModule, setAsyncModule] = useState(null);
useEffect(() => {
(async () => {
try {
if (!cond) {
return;
}
const module = await getModule();
setAsyncModule(() => module.default);
} catch (err) {
throw new Error(`LazyComponent error: ${err}`);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [cond]);

return AsyncModule;
};
6 changes: 6 additions & 0 deletions src/app/hooks/useLazy/Example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// @flow strict
import React from 'react';

const Example = () => <div> Some Component </div>;

export default Example;
4 changes: 4 additions & 0 deletions src/app/hooks/useLazy/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @flow strict

// eslint-disable-next-line import/prefer-default-export
export { default as useLazy } from './useLazy';
29 changes: 29 additions & 0 deletions src/app/hooks/useLazy/useLazy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// @flow strict
import { useState, useEffect } from 'react';

const useLazy = (
getModule: () => Promise<*>,
cond?: boolean = false,
) => {
const [AsyncModule, setAsyncModule] = useState(null);
useEffect(() => {
(async () => {
try {
if (!cond) {
return;
}
const module = await getModule();
// eslint-disable-next-line no-console
console.log('module', module);
setAsyncModule(() => module.default);
} catch (err) {
throw new Error(`LazyComponent error: ${err}`);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [cond]);

return AsyncModule;
};

export default useLazy;
14 changes: 14 additions & 0 deletions src/app/hooks/useLazy/useLazy.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// @flow strict
// $FlowFixMe
import { renderHook } from '@/utils/testing/renderHook';

import useLazy from './useLazy';

const getModule = () => import('./Example');

describe('LazyComponent', () => {
it('should render "null" at first', () => {
const { result } = renderHook(() => useLazy(getModule, false));
expect(result.current).toEqual(null);
});
});
103 changes: 103 additions & 0 deletions src/app/utils/testing/renderHook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// this is taken from
// @link https://raw.githubusercontent.com/testing-library/react-hooks-testing-library/master/src/index.js

import React, { Suspense } from 'react';
import { act, create } from 'react-test-renderer';

function TestHook({
callback, hookProps, onError, children,
}) {
try {
children(callback(hookProps));
} catch (err) {
if (err.then) {
throw err;
} else {
onError(err);
}
}
return null;
}

function Fallback() {
return null;
}

function resultContainer() {
let value = null;
let error = null;
const resolvers = [];

const result = {
get current() {
if (error) {
throw error;
}
return value;
},
get error() {
return error;
},
};

const updateResult = (val, err) => {
value = val;
error = err;
resolvers.splice(0, resolvers.length).forEach(resolve => resolve());
};

return {
result,
addResolver: (resolver) => {
resolvers.push(resolver);
},
setValue: val => updateResult(val),
setError: err => updateResult(undefined, err),
};
}

function renderHook(callback, { initialProps, wrapper } = {}) {
const {
result, setValue, setError, addResolver,
} = resultContainer();
const hookProps = { current: initialProps };

const wrapUiIfNeeded = innerElement => (
wrapper
? React.createElement(wrapper, null, innerElement)
: innerElement
);

const toRender = () => wrapUiIfNeeded(
<Suspense fallback={<Fallback />}>
<TestHook callback={callback} hookProps={hookProps.current} onError={setError}>
{setValue}
</TestHook>
</Suspense>,
);

let testRenderer;
act(() => {
testRenderer = create(toRender());
});

const { unmount, update } = testRenderer;

return {
result,
waitForNextUpdate: () => new Promise(resolve => addResolver(resolve)),
rerender: (newProps = hookProps.current) => {
hookProps.current = newProps;
act(() => {
update(toRender());
});
},
unmount: () => {
act(() => {
unmount();
});
},
};
}

export { renderHook, act };
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11539,7 +11539,7 @@ react-side-effect@^1.1.0:
exenv "^1.2.1"
shallowequal "^1.0.1"

react-test-renderer@^16.0.0-0:
react-test-renderer@16.8.6, react-test-renderer@^16.0.0-0:
version "16.8.6"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.8.6.tgz#188d8029b8c39c786f998aa3efd3ffe7642d5ba1"
integrity sha512-H2srzU5IWYT6cZXof6AhUcx/wEyJddQ8l7cLM/F7gDXYyPr4oq+vCIxJYXVGhId1J706sqziAjuOEjyNkfgoEw==
Expand Down

0 comments on commit b007552

Please sign in to comment.