Skip to content

Commit

Permalink
Life cycle hook 개발 (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
hyesungoh authored Oct 31, 2022
1 parent cee2353 commit cffdd6d
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 1 deletion.
6 changes: 5 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ module.exports = {
'import/no-unresolved': 'error',
'react/react-in-jsx-scope': 'off',
'no-use-before-define': 'off',
'no-restricted-exports': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'simple-import-sort/imports': [
'error',
Expand All @@ -94,7 +95,10 @@ module.exports = {
'import/first': 'error',
'import/newline-after-import': 'error',
'import/no-duplicates': 'error',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{ ignoreRestSiblings: true, argsIgnorePattern: '_', varsIgnorePattern: '_' },
],
'unused-imports/no-unused-imports': 'error',
'unused-imports/no-unused-vars': [
'error',
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"start": "next start",
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}' --fix",
"test:unit": "jest",
"test:watch": "jest --watch",
"test:coverage": "yarn test:unit --coverage",
"cypress:open": "cypress open",
"cypress:run": "cypress run",
Expand Down
60 changes: 60 additions & 0 deletions src/hooks/life-cycle/useDidMount.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useState } from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';

import useDidMount from './useDidMount';

describe('useDidMount', () => {
it('default export이여야 한다', () => {
expect(useDidMount).toBeDefined();
});

it('effectCallback이 실행되어야 한다', () => {
const effectCallback = jest.fn();
renderHook(() => useDidMount(effectCallback));
expect(effectCallback).toBeCalled();
});

it('rerender 시 1번 실행되어야 한다', () => {
const effectCallback = jest.fn();
const { rerender } = renderHook(() => useDidMount(effectCallback));
rerender();
expect(effectCallback).toBeCalledTimes(1);
});

describe('useDidMount Component', () => {
const STATE_CHANGE_BUTTON_TEXT = 'change';
const mockCallback = jest.fn();

const App = () => {
const [_, setState] = useState(false);

useDidMount(mockCallback);

return (
<div>
<button type="button" onClick={() => setState((prev) => !prev)}>
{STATE_CHANGE_BUTTON_TEXT}
</button>
</div>
);
};

afterEach(() => {
jest.clearAllMocks();
});

it('mockCallback이 실행되어야 한다', () => {
render(<App />);
expect(mockCallback).toBeCalledTimes(1);
});

it('mockCallback은 상태가 변해도 1번 실행되어야 한다', () => {
render(<App />);
expect(mockCallback).toBeCalledTimes(1);
const setStateButton = screen.getByText(STATE_CHANGE_BUTTON_TEXT);
fireEvent.click(setStateButton);
expect(mockCallback).toBeCalledTimes(1);
});
});
});
13 changes: 13 additions & 0 deletions src/hooks/life-cycle/useDidMount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useEffect, useRef } from 'react';

const useDidMount = (callback: VoidFunction) => {
const didMountRef = useRef<boolean>(false);

useEffect(() => {
if (didMountRef.current) return;
didMountRef.current = true;
callback();
}, []);
};

export default useDidMount;
54 changes: 54 additions & 0 deletions src/hooks/life-cycle/useDidUpdate.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useState } from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';

import useDidUpdate from './useDidUpdate';

describe('useDidUpdate', () => {
it('default export이여야 한다', () => {
expect(useDidUpdate).toBeDefined();
});

it('첫 호출 시 effectCallback이 실행되면 안된다', () => {
const mockCallback = jest.fn();
renderHook(() => useDidUpdate(mockCallback, []));
expect(mockCallback).not.toBeCalled();
});

describe('useDidUpdate Component', () => {
const STATE_CHANGE_BUTTON_TEXT = 'change';
const mockCallback = jest.fn();

const App = () => {
const [state, setState] = useState<number>(0);

useDidUpdate(mockCallback, [state]);

return (
<div>
<button type="button" onClick={() => setState((prev) => prev + 1)}>
{STATE_CHANGE_BUTTON_TEXT}
</button>
</div>
);
};

afterEach(() => {
jest.clearAllMocks();
});

it('마운트 시 effectCallback이 실행되면 안된다', () => {
render(<App />);
expect(mockCallback).not.toBeCalled();
});

it('dependency list 업데이트 시 effectCallback이 실행되어야 한다', () => {
render(<App />);
const setStateButton = screen.getByText(STATE_CHANGE_BUTTON_TEXT);
fireEvent.click(setStateButton);
expect(mockCallback).toBeCalledTimes(1);
fireEvent.click(setStateButton);
expect(mockCallback).toBeCalledTimes(2);
});
});
});
16 changes: 16 additions & 0 deletions src/hooks/life-cycle/useDidUpdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { DependencyList, useEffect, useRef } from 'react';

const useDidUpdate = (callback: VoidFunction, dependencyList: DependencyList) => {
const didMountRef = useRef<boolean>(false);

useEffect(() => {
if (!didMountRef.current) {
didMountRef.current = true;
return;
}

callback();
}, [...dependencyList]);
};

export default useDidUpdate;
63 changes: 63 additions & 0 deletions src/hooks/life-cycle/useWillUnmount.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useState } from 'react';
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';

import useWillUnmount from './useWillUnmount';

describe('useWillUnmount', () => {
it('default export이어야 한다', () => {
expect(useWillUnmount).toBeDefined();
});

it('첫 호출 시 callback이 실행되면 안된다', () => {
const mockCallback = jest.fn();
renderHook(() => useWillUnmount(mockCallback));
expect(mockCallback).not.toBeCalled();
});

describe('useWillUnmount Component', () => {
const TOGGLE_BUTTON_TEXT = 'toggle';
const mockCallback = jest.fn();

const Child = () => {
useWillUnmount(mockCallback);

return <div />;
};

const App = () => {
const [state, setState] = useState<boolean>(true);

return (
<div>
<button type="button" onClick={() => setState((prev) => !prev)}>
{TOGGLE_BUTTON_TEXT}
</button>

{state && <Child />}
</div>
);
};

afterEach(() => {
cleanup();
jest.clearAllMocks();
});

it('마운트 시 callback이 실행되면 안된다', () => {
render(<App />);
expect(mockCallback).not.toBeCalled();
});

it('Child가 unmount시 callback이 실행된다', () => {
render(<App />);
const toggleButton = screen.getByText(TOGGLE_BUTTON_TEXT);
fireEvent.click(toggleButton); // false
expect(mockCallback).toBeCalledTimes(1);

fireEvent.click(toggleButton); // true
fireEvent.click(toggleButton); // false
expect(mockCallback).toBeCalledTimes(2);
});
});
});
9 changes: 9 additions & 0 deletions src/hooks/life-cycle/useWillUnmount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useEffect } from 'react';

const useWillUnmount = (callback: VoidFunction) => {
useEffect(() => {
return callback;
}, []);
};

export default useWillUnmount;

0 comments on commit cffdd6d

Please sign in to comment.