From 86be480d565d6da60e1237858e0cc2ff11592fbe Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 18 Dec 2024 08:13:58 +0000
Subject: [PATCH 01/11] Create draft PR for #963
From 21ba4f921967eb5e40e4f5d60670ef6aa2e57fea Mon Sep 17 00:00:00 2001
From: Jeongwoo Park
Date: Wed, 18 Dec 2024 22:42:12 +0900
Subject: [PATCH 02/11] =?UTF-8?q?test:=20useLocalStorageState=20=ED=85=8C?=
=?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../useLocalStorageState.test.tsx | 87 +++++++++++++++++++
1 file changed, 87 insertions(+)
create mode 100644 frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx
diff --git a/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx b/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx
new file mode 100644
index 00000000..f912c326
--- /dev/null
+++ b/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx
@@ -0,0 +1,87 @@
+/* eslint-disable react/function-component-definition */
+/* eslint-disable react/jsx-one-expression-per-line */
+/* eslint-disable react/button-has-type */
+import { render, fireEvent, screen } from '@testing-library/react';
+import useLocalStorageState from '.';
+import '@testing-library/jest-dom';
+
+// 1. 원시값을 테스트하는 컴포넌트
+const PrimitiveValueComponent = () => {
+ const [value, setValue] = useLocalStorageState('primitiveKey', 0);
+
+ return (
+
+
Value: {value}
+
+
+
+ );
+};
+
+// 2. 객체를 테스트하는 컴포넌트
+const ObjectValueComponent = () => {
+ const [user, setUser] = useLocalStorageState<{ name: string; age: number }>('objectKey', {
+ name: 'lurgi',
+ age: 30,
+ });
+
+ return (
+
+
+ Name: {user.name}, Age: {user.age}
+
+
+
+ );
+};
+
+describe('useLocalStorageState의 값이 원시값인 경우에 대한 테스트', () => {
+ beforeEach(() => {
+ window.localStorage.clear();
+ });
+
+ // 1. 원시값 테스트
+ describe('Primitive Value Tests', () => {
+ it('초기 상태를 설정한다', () => {
+ render();
+ expect(screen.getByText('Value: 0')).toBeInTheDocument();
+ });
+
+ it('상태 변경 시 localStorage에 저장한다', () => {
+ render();
+ fireEvent.click(screen.getByText('Increment'));
+ expect(screen.getByText('Value: 1')).toBeInTheDocument();
+ expect(window.localStorage.getItem('primitiveKey')).toBe('1');
+ });
+
+ it('localStorage에 값이 있으면 초기 상태로 사용한다', () => {
+ window.localStorage.setItem('primitiveKey', '10');
+ render();
+ expect(screen.getByText('Value: 10')).toBeInTheDocument();
+ });
+ });
+
+ describe('useLocalStorageState의 값이 객체인 경우에 대한 테스트', () => {
+ it('초기 상태를 설정한다', () => {
+ render();
+ expect(screen.getByText('Name: lurgi, Age: 30')).toBeInTheDocument();
+ });
+
+ it('상태 변경 시 localStorage에 저장한다', () => {
+ render();
+ fireEvent.click(screen.getByText('Increase Age'));
+ expect(screen.getByText('Name: lurgi, Age: 31')).toBeInTheDocument();
+
+ const storedValue = window.localStorage.getItem('objectKey');
+ expect(storedValue).toBe(JSON.stringify({ name: 'lurgi', age: 31 }));
+ });
+
+ it('localStorage에 값이 있으면 초기 상태로 사용한다', () => {
+ const storedObject = JSON.stringify({ name: 'Jane Doe', age: 28 });
+ window.localStorage.setItem('objectKey', storedObject);
+
+ render();
+ expect(screen.getByText('Name: Jane Doe, Age: 28')).toBeInTheDocument();
+ });
+ });
+});
From 3b0dfdb776f4c413d87598b6216e3b317c34bb0f Mon Sep 17 00:00:00 2001
From: Jeongwoo Park
Date: Wed, 18 Dec 2024 22:42:24 +0900
Subject: [PATCH 03/11] =?UTF-8?q?feat:=20useLocalStorageState=20hook=20?=
=?UTF-8?q?=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/hooks/useLocalStorageState/index.ts | 32 +++++++++++++++++++
1 file changed, 32 insertions(+)
create mode 100644 frontend/src/hooks/useLocalStorageState/index.ts
diff --git a/frontend/src/hooks/useLocalStorageState/index.ts b/frontend/src/hooks/useLocalStorageState/index.ts
new file mode 100644
index 00000000..a4053cad
--- /dev/null
+++ b/frontend/src/hooks/useLocalStorageState/index.ts
@@ -0,0 +1,32 @@
+import { useState } from 'react';
+
+/**
+ * useLocalStorageState
+ * @param key - LocalStorage에 저장될 키 값
+ * @param initialValue - 초기 상태 값
+ * @returns [상태 값, 상태를 변경하는 함수] useState의 반환값과 동일합니다.
+ */
+function useLocalStorageState(key: string, initialValue: T): [T, (value: T | ((prev: T) => T)) => void] {
+ const [state, _setState] = useState(() => {
+ try {
+ const storedValue = window.localStorage.getItem(key);
+ return storedValue !== null ? JSON.parse(storedValue) : initialValue;
+ } catch (error) {
+ return initialValue;
+ }
+ });
+
+ const setState = (value: T | ((prev: T) => T)) => {
+ try {
+ const newState = value instanceof Function ? value(state) : value;
+ _setState(newState);
+ window.localStorage.setItem(key, JSON.stringify(newState));
+ } catch (error) {
+ console.error(`"${key}":`, error);
+ }
+ };
+
+ return [state, setState];
+}
+
+export default useLocalStorageState;
From c709bcda61f68aaaafa038530e86ae87627f70e4 Mon Sep 17 00:00:00 2001
From: Jeongwoo Park
Date: Wed, 18 Dec 2024 22:54:09 +0900
Subject: [PATCH 04/11] =?UTF-8?q?test:=20useLocalStorageState=20=ED=85=8C?=
=?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94?=
=?UTF-8?q?=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../useLocalStorageState.test.tsx | 33 +++++++++++++++----
1 file changed, 26 insertions(+), 7 deletions(-)
diff --git a/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx b/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx
index f912c326..02d99604 100644
--- a/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx
+++ b/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx
@@ -5,7 +5,6 @@ import { render, fireEvent, screen } from '@testing-library/react';
import useLocalStorageState from '.';
import '@testing-library/jest-dom';
-// 1. 원시값을 테스트하는 컴포넌트
const PrimitiveValueComponent = () => {
const [value, setValue] = useLocalStorageState('primitiveKey', 0);
@@ -13,12 +12,12 @@ const PrimitiveValueComponent = () => {
Value: {value}
+
);
};
-// 2. 객체를 테스트하는 컴포넌트
const ObjectValueComponent = () => {
const [user, setUser] = useLocalStorageState<{ name: string; age: number }>('objectKey', {
name: 'lurgi',
@@ -31,6 +30,7 @@ const ObjectValueComponent = () => {
Name: {user.name}, Age: {user.age}
+
);
};
@@ -40,24 +40,32 @@ describe('useLocalStorageState의 값이 원시값인 경우에 대한 테스트
window.localStorage.clear();
});
- // 1. 원시값 테스트
describe('Primitive Value Tests', () => {
it('초기 상태를 설정한다', () => {
render();
expect(screen.getByText('Value: 0')).toBeInTheDocument();
});
- it('상태 변경 시 localStorage에 저장한다', () => {
+ it('[setState 인자 원시값] 상태 변경 시 localStorage에 저장한다', () => {
render();
fireEvent.click(screen.getByText('Increment'));
expect(screen.getByText('Value: 1')).toBeInTheDocument();
expect(window.localStorage.getItem('primitiveKey')).toBe('1');
});
+ it('[setState 인자 함수] 상태 변경 시 localStorage에 저장한다', () => {
+ render();
+ fireEvent.click(screen.getByText('Increment2'));
+ expect(screen.getByText('Value: 1')).toBeInTheDocument();
+ expect(window.localStorage.getItem('primitiveKey')).toBe('1');
+ });
+
it('localStorage에 값이 있으면 초기 상태로 사용한다', () => {
window.localStorage.setItem('primitiveKey', '10');
render();
expect(screen.getByText('Value: 10')).toBeInTheDocument();
+ fireEvent.click(screen.getByText('Increment2'));
+ expect(window.localStorage.getItem('primitiveKey')).toBe('11');
});
});
@@ -67,7 +75,7 @@ describe('useLocalStorageState의 값이 원시값인 경우에 대한 테스트
expect(screen.getByText('Name: lurgi, Age: 30')).toBeInTheDocument();
});
- it('상태 변경 시 localStorage에 저장한다', () => {
+ it('[setState 인자 원시값] 상태 변경 시 localStorage에 저장한다', () => {
render();
fireEvent.click(screen.getByText('Increase Age'));
expect(screen.getByText('Name: lurgi, Age: 31')).toBeInTheDocument();
@@ -76,12 +84,23 @@ describe('useLocalStorageState의 값이 원시값인 경우에 대한 테스트
expect(storedValue).toBe(JSON.stringify({ name: 'lurgi', age: 31 }));
});
+ it('[setState 인자 함수] 상태 변경 시 localStorage에 저장한다', () => {
+ render();
+ fireEvent.click(screen.getByText('Increase Age2'));
+ expect(screen.getByText('Name: lurgi, Age: 31')).toBeInTheDocument();
+
+ const storedValue = window.localStorage.getItem('objectKey');
+ expect(storedValue).toBe(JSON.stringify({ name: 'lurgi', age: 31 }));
+ });
+
it('localStorage에 값이 있으면 초기 상태로 사용한다', () => {
- const storedObject = JSON.stringify({ name: 'Jane Doe', age: 28 });
+ const storedObject = JSON.stringify({ name: 'jeong woo', age: 28 });
window.localStorage.setItem('objectKey', storedObject);
render();
- expect(screen.getByText('Name: Jane Doe, Age: 28')).toBeInTheDocument();
+ expect(screen.getByText('Name: jeong woo, Age: 28')).toBeInTheDocument();
+ fireEvent.click(screen.getByText('Increase Age2'));
+ expect(screen.getByText('Name: jeong woo, Age: 29')).toBeInTheDocument();
});
});
});
From cad53675bfc3dbcf10def49d762b3fee0ab5afe0 Mon Sep 17 00:00:00 2001
From: Jeongwoo Park
Date: Wed, 18 Dec 2024 22:54:28 +0900
Subject: [PATCH 05/11] =?UTF-8?q?refactor:=20setState=20=EC=9D=B8=EC=9E=90?=
=?UTF-8?q?=20=EA=B0=92=20=EB=AA=85=EC=8B=9C=EC=A0=81=EC=9C=BC=EB=A1=9C=20?=
=?UTF-8?q?=EB=B3=80=ED=99=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/hooks/useLocalStorageState/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/hooks/useLocalStorageState/index.ts b/frontend/src/hooks/useLocalStorageState/index.ts
index a4053cad..a87611b2 100644
--- a/frontend/src/hooks/useLocalStorageState/index.ts
+++ b/frontend/src/hooks/useLocalStorageState/index.ts
@@ -19,7 +19,7 @@ function useLocalStorageState(key: string, initialValue: T): [T, (value: T |
const setState = (value: T | ((prev: T) => T)) => {
try {
const newState = value instanceof Function ? value(state) : value;
- _setState(newState);
+ _setState(value);
window.localStorage.setItem(key, JSON.stringify(newState));
} catch (error) {
console.error(`"${key}":`, error);
From f9b52cda2db974b863c843880f994347c7b45ed1 Mon Sep 17 00:00:00 2001
From: Jeongwoo Park
Date: Thu, 19 Dec 2024 16:14:03 +0900
Subject: [PATCH 06/11] =?UTF-8?q?test:=20=EC=BB=B4=ED=8F=AC=EB=84=8C?=
=?UTF-8?q?=ED=8A=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=ED=9B=85=20=ED=85=8C?=
=?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A1=9C=20=EB=B3=80=EA=B2=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../useLocalStorageState.test.tsx | 117 ++++++++----------
1 file changed, 55 insertions(+), 62 deletions(-)
diff --git a/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx b/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx
index 02d99604..ba2168d2 100644
--- a/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx
+++ b/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx
@@ -1,39 +1,6 @@
-/* eslint-disable react/function-component-definition */
-/* eslint-disable react/jsx-one-expression-per-line */
-/* eslint-disable react/button-has-type */
-import { render, fireEvent, screen } from '@testing-library/react';
+import { act } from 'react';
+import { renderHook } from '@testing-library/react';
import useLocalStorageState from '.';
-import '@testing-library/jest-dom';
-
-const PrimitiveValueComponent = () => {
- const [value, setValue] = useLocalStorageState('primitiveKey', 0);
-
- return (
-
-
Value: {value}
-
-
-
-
- );
-};
-
-const ObjectValueComponent = () => {
- const [user, setUser] = useLocalStorageState<{ name: string; age: number }>('objectKey', {
- name: 'lurgi',
- age: 30,
- });
-
- return (
-
-
- Name: {user.name}, Age: {user.age}
-
-
-
-
- );
-};
describe('useLocalStorageState의 값이 원시값인 경우에 대한 테스트', () => {
beforeEach(() => {
@@ -42,65 +9,91 @@ describe('useLocalStorageState의 값이 원시값인 경우에 대한 테스트
describe('Primitive Value Tests', () => {
it('초기 상태를 설정한다', () => {
- render();
- expect(screen.getByText('Value: 0')).toBeInTheDocument();
+ const { result } = renderHook(() => useLocalStorageState('primitiveKey', 0));
+ expect(result.current[0]).toBe(0);
});
it('[setState 인자 원시값] 상태 변경 시 localStorage에 저장한다', () => {
- render();
- fireEvent.click(screen.getByText('Increment'));
- expect(screen.getByText('Value: 1')).toBeInTheDocument();
+ const { result } = renderHook(() => useLocalStorageState('primitiveKey', 0));
+
+ act(() => {
+ result.current[1]((prev) => prev + 1);
+ });
+
+ expect(result.current[0]).toBe(1);
expect(window.localStorage.getItem('primitiveKey')).toBe('1');
});
it('[setState 인자 함수] 상태 변경 시 localStorage에 저장한다', () => {
- render();
- fireEvent.click(screen.getByText('Increment2'));
- expect(screen.getByText('Value: 1')).toBeInTheDocument();
+ const { result } = renderHook(() => useLocalStorageState('primitiveKey', 0));
+
+ act(() => {
+ result.current[1]((prev) => prev + 1);
+ });
+
+ expect(result.current[0]).toBe(1);
expect(window.localStorage.getItem('primitiveKey')).toBe('1');
});
it('localStorage에 값이 있으면 초기 상태로 사용한다', () => {
window.localStorage.setItem('primitiveKey', '10');
- render();
- expect(screen.getByText('Value: 10')).toBeInTheDocument();
- fireEvent.click(screen.getByText('Increment2'));
+
+ const { result } = renderHook(() => useLocalStorageState('primitiveKey', 0));
+ expect(result.current[0]).toBe(10);
+
+ act(() => {
+ result.current[1]((prev) => prev + 1);
+ });
+
+ expect(result.current[0]).toBe(11);
expect(window.localStorage.getItem('primitiveKey')).toBe('11');
});
});
describe('useLocalStorageState의 값이 객체인 경우에 대한 테스트', () => {
it('초기 상태를 설정한다', () => {
- render();
- expect(screen.getByText('Name: lurgi, Age: 30')).toBeInTheDocument();
+ const initialObject = { name: 'lurgi', age: 30 };
+ const { result } = renderHook(() => useLocalStorageState('objectKey', initialObject));
+ expect(result.current[0]).toEqual(initialObject);
});
it('[setState 인자 원시값] 상태 변경 시 localStorage에 저장한다', () => {
- render();
- fireEvent.click(screen.getByText('Increase Age'));
- expect(screen.getByText('Name: lurgi, Age: 31')).toBeInTheDocument();
+ const initialObject = { name: 'lurgi', age: 30 };
+ const { result } = renderHook(() => useLocalStorageState('objectKey', initialObject));
+
+ act(() => {
+ result.current[1]({ name: 'lurgi', age: 31 });
+ });
- const storedValue = window.localStorage.getItem('objectKey');
- expect(storedValue).toBe(JSON.stringify({ name: 'lurgi', age: 31 }));
+ expect(result.current[0]).toEqual({ name: 'lurgi', age: 31 });
+ expect(window.localStorage.getItem('objectKey')).toBe(JSON.stringify({ name: 'lurgi', age: 31 }));
});
it('[setState 인자 함수] 상태 변경 시 localStorage에 저장한다', () => {
- render();
- fireEvent.click(screen.getByText('Increase Age2'));
- expect(screen.getByText('Name: lurgi, Age: 31')).toBeInTheDocument();
+ const initialObject = { name: 'lurgi', age: 30 };
+ const { result } = renderHook(() => useLocalStorageState('objectKey', initialObject));
- const storedValue = window.localStorage.getItem('objectKey');
- expect(storedValue).toBe(JSON.stringify({ name: 'lurgi', age: 31 }));
+ act(() => {
+ result.current[1]((prev) => ({ ...prev, age: prev.age + 1 }));
+ });
+
+ expect(result.current[0]).toEqual({ name: 'lurgi', age: 31 });
+ expect(window.localStorage.getItem('objectKey')).toBe(JSON.stringify({ name: 'lurgi', age: 31 }));
});
it('localStorage에 값이 있으면 초기 상태로 사용한다', () => {
const storedObject = JSON.stringify({ name: 'jeong woo', age: 28 });
window.localStorage.setItem('objectKey', storedObject);
- render();
- expect(screen.getByText('Name: jeong woo, Age: 28')).toBeInTheDocument();
- fireEvent.click(screen.getByText('Increase Age2'));
- expect(screen.getByText('Name: jeong woo, Age: 29')).toBeInTheDocument();
+ const { result } = renderHook(() => useLocalStorageState('objectKey', { name: 'default', age: 0 }));
+ expect(result.current[0]).toEqual({ name: 'jeong woo', age: 28 });
+
+ act(() => {
+ result.current[1]((prev) => ({ ...prev, age: prev.age + 1 }));
+ });
+
+ expect(result.current[0]).toEqual({ name: 'jeong woo', age: 29 });
+ expect(window.localStorage.getItem('objectKey')).toBe(JSON.stringify({ name: 'jeong woo', age: 29 }));
});
});
});
From f9478114e4910bb9d9bb7af28d9df81bd5699ec6 Mon Sep 17 00:00:00 2001
From: Jeongwoo Park
Date: Thu, 19 Dec 2024 16:17:18 +0900
Subject: [PATCH 07/11] =?UTF-8?q?test:=20enableStorage=EC=98=B5=EC=85=98?=
=?UTF-8?q?=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../useLocalStorageState.test.tsx | 20 +++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx b/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx
index ba2168d2..655d030b 100644
--- a/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx
+++ b/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx
@@ -48,6 +48,15 @@ describe('useLocalStorageState의 값이 원시값인 경우에 대한 테스트
expect(result.current[0]).toBe(11);
expect(window.localStorage.getItem('primitiveKey')).toBe('11');
});
+
+ it('enableStorage 옵션을 false로 지정한 경우 localStorage를 사용하지 않는다', () => {
+ window.localStorage.setItem('primitiveKey', '10');
+
+ const { result } = renderHook(() => useLocalStorageState('primitiveKey', 0, { enableStorage: false }));
+
+ expect(result.current[0]).toBe(0);
+ expect(window.localStorage.getItem('primitiveKey')).toBe('10');
+ });
});
describe('useLocalStorageState의 값이 객체인 경우에 대한 테스트', () => {
@@ -95,5 +104,16 @@ describe('useLocalStorageState의 값이 원시값인 경우에 대한 테스트
expect(result.current[0]).toEqual({ name: 'jeong woo', age: 29 });
expect(window.localStorage.getItem('objectKey')).toBe(JSON.stringify({ name: 'jeong woo', age: 29 }));
});
+
+ it('enableStorage 옵션을 false로 지정한 경우 localStorage를 사용하지 않는다', () => {
+ const storedObject = JSON.stringify({ name: 'jeong woo', age: 28 });
+ window.localStorage.setItem('objectKey', storedObject);
+
+ const initialObject = { name: 'lurgi', age: 30 };
+ const { result } = renderHook(() => useLocalStorageState('objectKey', initialObject, { enableStorage: false }));
+
+ expect(result.current[0]).toEqual(initialObject);
+ expect(window.localStorage.getItem('objectKey')).toBe(storedObject);
+ });
});
});
From 60c5c6d31fd74add17037f7eaa151358f9df7312 Mon Sep 17 00:00:00 2001
From: Jeongwoo Park
Date: Thu, 19 Dec 2024 16:17:38 +0900
Subject: [PATCH 08/11] =?UTF-8?q?feat:=20enableStorage=20=EC=98=B5?=
=?UTF-8?q?=EC=85=98=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/hooks/useLocalStorageState/index.ts | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/frontend/src/hooks/useLocalStorageState/index.ts b/frontend/src/hooks/useLocalStorageState/index.ts
index a87611b2..e0e93332 100644
--- a/frontend/src/hooks/useLocalStorageState/index.ts
+++ b/frontend/src/hooks/useLocalStorageState/index.ts
@@ -1,13 +1,24 @@
import { useState } from 'react';
+interface OptionProp {
+ enableStorage: boolean;
+}
+
/**
* useLocalStorageState
* @param key - LocalStorage에 저장될 키 값
* @param initialValue - 초기 상태 값
+ * @param option - { use: boolean }
* @returns [상태 값, 상태를 변경하는 함수] useState의 반환값과 동일합니다.
*/
-function useLocalStorageState(key: string, initialValue: T): [T, (value: T | ((prev: T) => T)) => void] {
+function useLocalStorageState(
+ key: string,
+ initialValue: T,
+ option: OptionProp = { enableStorage: true },
+): [T, (value: T | ((prev: T) => T)) => void] {
const [state, _setState] = useState(() => {
+ if (!option.enableStorage) return initialValue;
+
try {
const storedValue = window.localStorage.getItem(key);
return storedValue !== null ? JSON.parse(storedValue) : initialValue;
From e7956f2a7fd3086bc5ae24631054bc0b24279e44 Mon Sep 17 00:00:00 2001
From: Jeongwoo Park
Date: Thu, 19 Dec 2024 16:21:51 +0900
Subject: [PATCH 09/11] =?UTF-8?q?refactor:=20useLocalStorageState=20?=
=?UTF-8?q?=EC=BD=94=EB=93=9C=20=EA=B0=9C=EC=84=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/hooks/useLocalStorageState/index.ts | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/frontend/src/hooks/useLocalStorageState/index.ts b/frontend/src/hooks/useLocalStorageState/index.ts
index e0e93332..fcabe462 100644
--- a/frontend/src/hooks/useLocalStorageState/index.ts
+++ b/frontend/src/hooks/useLocalStorageState/index.ts
@@ -8,7 +8,7 @@ interface OptionProp {
* useLocalStorageState
* @param key - LocalStorage에 저장될 키 값
* @param initialValue - 초기 상태 값
- * @param option - { use: boolean }
+ * @param option - { enableStorage: boolean }
* @returns [상태 값, 상태를 변경하는 함수] useState의 반환값과 동일합니다.
*/
function useLocalStorageState(
@@ -27,16 +27,19 @@ function useLocalStorageState(
}
});
- const setState = (value: T | ((prev: T) => T)) => {
+ const saveToLocalStorage = (value: T) => {
try {
- const newState = value instanceof Function ? value(state) : value;
- _setState(value);
- window.localStorage.setItem(key, JSON.stringify(newState));
+ window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(`"${key}":`, error);
}
};
+ const setState = (value: T | ((prev: T) => T)) => {
+ _setState(value);
+ saveToLocalStorage(value instanceof Function ? value(state) : value);
+ };
+
return [state, setState];
}
From 938a8379bb8f8573f1a6f60c630220da67bb0974 Mon Sep 17 00:00:00 2001
From: Jeongwoo Park
Date: Fri, 20 Dec 2024 08:33:10 +0900
Subject: [PATCH 10/11] =?UTF-8?q?chore:=20useLocalStorageState=EC=9D=98=20?=
=?UTF-8?q?Prop=20=EA=B0=9C=EC=84=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/hooks/useLocalStorageState/index.ts | 16 ++++++-------
.../useLocalStorageState.test.tsx | 23 +++++++++++--------
2 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/frontend/src/hooks/useLocalStorageState/index.ts b/frontend/src/hooks/useLocalStorageState/index.ts
index fcabe462..9df2f6bc 100644
--- a/frontend/src/hooks/useLocalStorageState/index.ts
+++ b/frontend/src/hooks/useLocalStorageState/index.ts
@@ -1,23 +1,21 @@
import { useState } from 'react';
interface OptionProp {
- enableStorage: boolean;
+ key: string;
+ enableStorage?: boolean;
}
/**
* useLocalStorageState
- * @param key - LocalStorage에 저장될 키 값
* @param initialValue - 초기 상태 값
- * @param option - { enableStorage: boolean }
+ * @param option - { key: LocalStorage에 저장될 키 값, enableStorage: LocalStorage의 값을 사용할지 여부}
* @returns [상태 값, 상태를 변경하는 함수] useState의 반환값과 동일합니다.
*/
-function useLocalStorageState(
- key: string,
- initialValue: T,
- option: OptionProp = { enableStorage: true },
-): [T, (value: T | ((prev: T) => T)) => void] {
+function useLocalStorageState(initialValue: T, option: OptionProp): [T, (value: T | ((prev: T) => T)) => void] {
+ const { key, enableStorage = true } = option;
+
const [state, _setState] = useState(() => {
- if (!option.enableStorage) return initialValue;
+ if (!enableStorage) return initialValue;
try {
const storedValue = window.localStorage.getItem(key);
diff --git a/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx b/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx
index 655d030b..4cbee9dc 100644
--- a/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx
+++ b/frontend/src/hooks/useLocalStorageState/useLocalStorageState.test.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable function-paren-newline */
import { act } from 'react';
import { renderHook } from '@testing-library/react';
import useLocalStorageState from '.';
@@ -9,12 +10,12 @@ describe('useLocalStorageState의 값이 원시값인 경우에 대한 테스트
describe('Primitive Value Tests', () => {
it('초기 상태를 설정한다', () => {
- const { result } = renderHook(() => useLocalStorageState('primitiveKey', 0));
+ const { result } = renderHook(() => useLocalStorageState(0, { key: 'primitiveKey' }));
expect(result.current[0]).toBe(0);
});
it('[setState 인자 원시값] 상태 변경 시 localStorage에 저장한다', () => {
- const { result } = renderHook(() => useLocalStorageState('primitiveKey', 0));
+ const { result } = renderHook(() => useLocalStorageState(0, { key: 'primitiveKey' }));
act(() => {
result.current[1]((prev) => prev + 1);
@@ -25,7 +26,7 @@ describe('useLocalStorageState의 값이 원시값인 경우에 대한 테스트
});
it('[setState 인자 함수] 상태 변경 시 localStorage에 저장한다', () => {
- const { result } = renderHook(() => useLocalStorageState('primitiveKey', 0));
+ const { result } = renderHook(() => useLocalStorageState(0, { key: 'primitiveKey' }));
act(() => {
result.current[1]((prev) => prev + 1);
@@ -38,7 +39,7 @@ describe('useLocalStorageState의 값이 원시값인 경우에 대한 테스트
it('localStorage에 값이 있으면 초기 상태로 사용한다', () => {
window.localStorage.setItem('primitiveKey', '10');
- const { result } = renderHook(() => useLocalStorageState('primitiveKey', 0));
+ const { result } = renderHook(() => useLocalStorageState(0, { key: 'primitiveKey' }));
expect(result.current[0]).toBe(10);
act(() => {
@@ -52,7 +53,7 @@ describe('useLocalStorageState의 값이 원시값인 경우에 대한 테스트
it('enableStorage 옵션을 false로 지정한 경우 localStorage를 사용하지 않는다', () => {
window.localStorage.setItem('primitiveKey', '10');
- const { result } = renderHook(() => useLocalStorageState('primitiveKey', 0, { enableStorage: false }));
+ const { result } = renderHook(() => useLocalStorageState(0, { key: 'primitiveKey', enableStorage: false }));
expect(result.current[0]).toBe(0);
expect(window.localStorage.getItem('primitiveKey')).toBe('10');
@@ -62,13 +63,13 @@ describe('useLocalStorageState의 값이 원시값인 경우에 대한 테스트
describe('useLocalStorageState의 값이 객체인 경우에 대한 테스트', () => {
it('초기 상태를 설정한다', () => {
const initialObject = { name: 'lurgi', age: 30 };
- const { result } = renderHook(() => useLocalStorageState('objectKey', initialObject));
+ const { result } = renderHook(() => useLocalStorageState(initialObject, { key: 'objectKey' }));
expect(result.current[0]).toEqual(initialObject);
});
it('[setState 인자 원시값] 상태 변경 시 localStorage에 저장한다', () => {
const initialObject = { name: 'lurgi', age: 30 };
- const { result } = renderHook(() => useLocalStorageState('objectKey', initialObject));
+ const { result } = renderHook(() => useLocalStorageState(initialObject, { key: 'objectKey' }));
act(() => {
result.current[1]({ name: 'lurgi', age: 31 });
@@ -80,7 +81,7 @@ describe('useLocalStorageState의 값이 원시값인 경우에 대한 테스트
it('[setState 인자 함수] 상태 변경 시 localStorage에 저장한다', () => {
const initialObject = { name: 'lurgi', age: 30 };
- const { result } = renderHook(() => useLocalStorageState('objectKey', initialObject));
+ const { result } = renderHook(() => useLocalStorageState(initialObject, { key: 'objectKey' }));
act(() => {
result.current[1]((prev) => ({ ...prev, age: prev.age + 1 }));
@@ -94,7 +95,7 @@ describe('useLocalStorageState의 값이 원시값인 경우에 대한 테스트
const storedObject = JSON.stringify({ name: 'jeong woo', age: 28 });
window.localStorage.setItem('objectKey', storedObject);
- const { result } = renderHook(() => useLocalStorageState('objectKey', { name: 'default', age: 0 }));
+ const { result } = renderHook(() => useLocalStorageState({ name: 'default', age: 0 }, { key: 'objectKey' }));
expect(result.current[0]).toEqual({ name: 'jeong woo', age: 28 });
act(() => {
@@ -110,7 +111,9 @@ describe('useLocalStorageState의 값이 원시값인 경우에 대한 테스트
window.localStorage.setItem('objectKey', storedObject);
const initialObject = { name: 'lurgi', age: 30 };
- const { result } = renderHook(() => useLocalStorageState('objectKey', initialObject, { enableStorage: false }));
+ const { result } = renderHook(() =>
+ useLocalStorageState(initialObject, { key: 'objectKey', enableStorage: false }),
+ );
expect(result.current[0]).toEqual(initialObject);
expect(window.localStorage.getItem('objectKey')).toBe(storedObject);
From f0fdad72ff55644adc5d6245e61aa80064789907 Mon Sep 17 00:00:00 2001
From: Jeongwoo Park
Date: Fri, 20 Dec 2024 08:51:25 +0900
Subject: [PATCH 11/11] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=20?=
=?UTF-8?q?=ED=95=B8=EB=93=A4=EB=A7=81=20=EA=B0=9C=EC=84=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/hooks/useLocalStorageState/index.ts | 37 ++++++++++++++-----
1 file changed, 27 insertions(+), 10 deletions(-)
diff --git a/frontend/src/hooks/useLocalStorageState/index.ts b/frontend/src/hooks/useLocalStorageState/index.ts
index 9df2f6bc..94715952 100644
--- a/frontend/src/hooks/useLocalStorageState/index.ts
+++ b/frontend/src/hooks/useLocalStorageState/index.ts
@@ -5,6 +5,28 @@ interface OptionProp {
enableStorage?: boolean;
}
+const safeParseJSON = (value: string | null, fallback: T): T => {
+ try {
+ return value !== null ? JSON.parse(value) : fallback;
+ } catch (error) {
+ if (process.env.NODE_ENV === 'development') {
+ console.error('JSON 파싱 실패:', error);
+ }
+ return fallback;
+ }
+};
+
+const safeStringifyJSON = (value: T): string | null => {
+ try {
+ return JSON.stringify(value);
+ } catch (error) {
+ if (process.env.NODE_ENV === 'development') {
+ console.error('JSON 직렬화 실패:', error);
+ }
+ return null;
+ }
+};
+
/**
* useLocalStorageState
* @param initialValue - 초기 상태 값
@@ -17,19 +39,14 @@ function useLocalStorageState(initialValue: T, option: OptionProp): [T, (valu
const [state, _setState] = useState(() => {
if (!enableStorage) return initialValue;
- try {
- const storedValue = window.localStorage.getItem(key);
- return storedValue !== null ? JSON.parse(storedValue) : initialValue;
- } catch (error) {
- return initialValue;
- }
+ const storedValue = window.localStorage.getItem(key);
+ return safeParseJSON(storedValue, initialValue);
});
const saveToLocalStorage = (value: T) => {
- try {
- window.localStorage.setItem(key, JSON.stringify(value));
- } catch (error) {
- console.error(`"${key}":`, error);
+ const stringifiedValue = safeStringifyJSON(value);
+ if (stringifiedValue !== null) {
+ window.localStorage.setItem(key, stringifiedValue);
}
};