Skip to content

Commit

Permalink
πŸ“ [#3] React Hooks 아티클을 μΆ”κ°€ν•œλ‹€
Browse files Browse the repository at this point in the history
  • Loading branch information
hyoungqu23 committed Jan 30, 2024
1 parent 8a6a4ca commit dd99cfa
Show file tree
Hide file tree
Showing 7 changed files with 397 additions and 6 deletions.
280 changes: 280 additions & 0 deletions contents/articles/react-hooks.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
---
title: React Hooks 기초
description: React의 Hooks은 무엇이고, λŒ€ν‘œμ μΈ Hooksλ₯Ό μ•Œμ•„λ³΄μž
createdAt: 2022-06-10
category: React
tags:
- React
- TypeScript
---

## 🌊 React.js Hooks

**Hooks**λŠ” Componentμ—μ„œ 데이터λ₯Ό κ΄€λ¦¬ν•˜κ±°λ‚˜ 데이터가 변경될 λ•Œ μƒν˜Έμž‘μš©ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ˜λŠ” 것이닀. 기쑴의 React.jsμ—μ„œλŠ” Component λ‚΄λΆ€μ˜ State와 생λͺ…μ£ΌκΈ°λ₯Ό κ΄€λ¦¬ν•˜λ €λ©΄ Class Componentλ₯Ό μ‚¬μš©ν•΄μ•Ό ν–ˆμœΌλ‚˜, Function Componentμ—μ„œ μ΄λŸ¬ν•œ κΈ°λŠ₯을 κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄ 16.8 버전 μ΄ν›„λ‘œ μΆ”κ°€λœ μƒˆλ‘œμš΄ κΈ°λŠ₯이닀.

즉, Function Componentμ—μ„œ React state와 생λͺ…μ£ΌκΈ° κΈ°λŠ₯(lifecycle features)을 **연동(hook into)**Β ν•  수 있게 ν•΄μ£ΌλŠ” 것이 λ°”λ‘œ Hook이닀.

### Hooksλ₯Ό ν™œμš©ν•˜λŠ” 이유

**기쑴 React.js 문제점**

- Component μ‚¬μ΄μ—μ„œ State와 κ΄€λ ¨λœ λ‘œμ§μ„ μž¬μ‚¬μš©ν•˜κΈ° μ–΄λ ΅λ‹€.
- λ³΅μž‘ν•œ Component듀을 μ΄ν•΄ν•˜κΈ° μ–΄λ ΅λ‹€.
- Class Component의 `this`의 문제점

**Hooks의 μž₯점**

- Component의 ν•¨μˆ˜κ°€ λ§Žμ•„μ§ˆ λ•ŒλΆ€ν„° Class Component둜 λ¦¬νŒ©ν† λ§ν•  ν•„μš”κ°€ μ—†λ‹€.
- UI와 λ‘œμ§μ„ μˆ˜λΉ„κ²Œ 뢄리해 두 가지 λͺ¨λ‘ μž¬μ‚¬μš©ν•  수 μžˆλ‹€.
- κΈ°μ‘΄ μ½”λ“œλ₯Ό λ‹€μ‹œ μž‘μ„±ν•  ν•„μš” 없이 일뢀 Component λ‚΄λΆ€μ—μ„œ Hook을 μ‚¬μš©ν•  수 μžˆλ‹€.
- Hook을 μ‚¬μš©ν•˜λ©΄ Componentλ‘œλΆ€ν„° μƒνƒœ κ΄€λ ¨ λ‘œμ§μ„ 좔상화할 수 μžˆλ‹€. 즉, Componentλ³„λ‘œ 독립적인 ν…ŒμŠ€νŠΈμ™€ μž¬μ‚¬μš©μ΄ κ°€λŠ₯ν•˜κ³ , Component κ°„ 계측 λ³€ν™” 없이 μƒνƒœ κ΄€λ ¨ λ‘œμ§μ„ μž¬μ‚¬μš©ν•  수 μžˆλ‹€.

### Hooks μ‚¬μš© κ·œμΉ™

- HooksλŠ” λ°˜λ“œμ‹œ React.js ν•¨μˆ˜(Component, Hook) λ‚΄μ—μ„œλ§Œ μ‚¬μš©ν•  수 μžˆλ‹€.
- Hooks의 이름은 `use`둜 λ°˜λ“œμ‹œ μ‹œμž‘ν•œλ‹€.
- μ΅œμƒμœ„ λ ˆλ²¨μ—μ„œλ§Œ Hooksλ₯Ό ν˜ΈμΆœν•  수 μžˆλ‹€.(ex. `if`, `for`, 콜백 ν•¨μˆ˜, μ€‘μ²©λœ ν•¨μˆ˜ λ‚΄μ—μ„œλŠ” λΆˆκ°€λŠ₯)
즉, Component λ‚΄λΆ€μ˜ 첫 번째 μ€‘κ΄„ν˜Έ(`{}`) 내에 μž‘μ„±ν•΄μ•Ό ν•œλ‹€.

### State Hook

κ°„λ‹¨ν•œ μƒνƒœ 관리쑰차도 Class Component둜 μž‘μ„±ν•΄μ•Ό ν–ˆλŠ”λ°, μ΄λŠ” Function Component보닀 λ³΅μž‘ν•˜κ³  μ—λŸ¬κ°€ λ°œμƒν•˜κΈ° μ‰¬μš°λ©° μœ μ§€ λ³΄μˆ˜κ°€ νž˜λ“€λ‹€. λ”°λΌμ„œ State Hook을 ν™œμš©ν•΄ Function Componentμ—μ„œ κ°„λ‹¨νžˆ μ‚¬μš©ν•  수 μžˆλ‹€.

```jsx
const App = () => {
const [state, setState] = useState('μ΄ˆκΈ°κ°’');
};
```

Function Component λ‚΄λΆ€μ˜ 동적인 데이터λ₯Ό 관리할 수 있게 ν•΄μ£ΌλŠ” Hook.

- μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜κΈ° μœ„ν•œ μ½”λ“œ μ—­μ‹œ 맀우 κ°„λ‹¨ν•΄μ§€λŠ” μž₯점이 μžˆλ‹€.
- μ΄λ ‡κ²Œ State Hook으둜 μ„€μ •λœ `state` 값은 읽기 μ „μš©μ΄λ―€λ‘œ μˆ˜μ •ν•˜λ©΄ μ•ˆλœλ‹€.
- `state` 값을 λ³€κ²½ν•˜κΈ° μœ„ν•΄μ„œλŠ” `setState` ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.
- `state` 값이 λ³€κ²½λ˜λŠ” 경우 μžλ™μœΌλ‘œ Componentκ°€ λ‹€μ‹œ λ Œλ”λ§ λœλ‹€.
- `setState` ν•¨μˆ˜ ν™œμš©λ²•
- 직접 λ³€κ²½ν•  `state` 값을 인자둜 μž…λ ₯ν•˜λŠ” 방식.
```jsx
setState('λ³€κ²½ν•  κ°’');
```
- ν˜„μž¬ `state` 값을 λ§€κ°œλ³€μˆ˜λ‘œ λ°›λŠ” ν•¨μˆ˜λ₯Ό μ „λ‹¬ν•˜λŠ” 방식. πŸ‘
```jsx
setState((current) => {
return current + 1;
});
```

### Effect Hook

- ~~Component Life Cycle~~
![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/05d86aa0-ca32-498d-9fa3-0caeb7fe3e48/Untitled.png)

React Component μ•ˆμ—μ„œ 데이터λ₯Ό κ°€μ Έμ˜€κ±°λ‚˜ κ΅¬λ…ν•˜κ³ , DOM을 직접 μ‘°μž‘ν•˜λŠ” λ“±μ˜ λ‹€μ–‘ν•œ μž‘μ—…λ“€μ„ Side Effects λ˜λŠ” Effects 라고 ν•œλ‹€.

Side Effectsμ—λŠ” Clean-up이 ν•„μš”ν•˜μ§€ μ•Šμ€ 것이 μžˆλŠ”λ°, 즉, DOM을 μ—…λ°μ΄νŠΈ ν•œ λ’€ μΆ”κ°€λ‘œ μ½”λ“œλ₯Ό μ‹€ν–‰ν•΄μ•Ό ν•˜λŠ” κ²½μš°μ—λŠ” Clean-up이 ν•„μš”ν•˜μ§€ μ•Šλ‹€. ν•΄λ‹Ή ComponentλŠ” λ Œλ”λ§ 이후 계속 μ‚¬μš©λ˜κΈ° λ•Œλ¬Έμ΄λ‹€.

이와 달리 Clean-up이 ν•„μš”ν•œ Side Effects도 μžˆλ‹€. 예λ₯Ό λ“€μ–΄ λ©”λͺ¨λ¦¬λ₯Ό 많이 μ‚¬μš©ν•˜λŠ” Component의 κ²½μš°μ—λŠ” λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό 막기 μœ„ν•΄ μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” 경우 λ©”λͺ¨λ¦¬λ₯Ό ν•΄μ œν•΄μ£Όμ–΄μ•Ό ν•œλ‹€. 즉, DOM λ Œλ”λ§ 이후 μΆ”κ°€λ‘œ ν•„μš”ν•˜μ§€ μ•Šμ€ μ½”λ“œμ΄κ±°λ‚˜, λ©”λͺ¨λ¦¬λ₯Ό 많이 μ‚¬μš©ν•˜λŠ” λ“±μ˜ κ²½μš°μ—λŠ” Clean-up을 ν•΄μ£Όμ–΄μ•Ό ν•œλ‹€.

μ΄λŸ¬ν•œ 뢀뢄을 ν•΄κ²°ν•˜κΈ° μœ„ν•΄ Class Componentμ—μ„œλŠ” `componentWillUnmount()`λ“±μ˜ 생λͺ…μ£ΌκΈ° λ©”μ„œλ“œλ₯Ό ν™œμš©ν•˜μ§€λ§Œ, Function Componentμ—μ„œλŠ” Effect Hook을 μ‚¬μš©ν•œλ‹€.

즉, Function Component μ™ΈλΆ€μ—μ„œ 둜컬의 μƒνƒœ 값을 λ³€κ²½ν•˜λŠ” 것을 μ˜λ―Έν•œλ‹€. λ‹€λ§Œ, μ΄λŸ¬ν•œ 과정은 λ‹€λ₯Έ Component에 영ν–₯을 쀄 μˆ˜λ„ μžˆμ–΄ λ Œλ”λ§ κ³Όμ •μ—μ„œλŠ” κ΅¬ν˜„ν•  μˆ˜κ°€ μ—†λ‹€.

λ”°λΌμ„œ Effect Hook을 ν™œμš©ν•΄ Function Component λ‚΄μ—μ„œ side effectsλ₯Ό μˆ˜ν–‰ν•  수 있게 ν•΄μ€€λ‹€.

ν•„μˆ˜μ μΈ APIλ₯Ό λΆˆλŸ¬μ˜€κ±°λ‚˜ 데이터λ₯Ό κ°€μ Έμ˜¬ λ•Œ **`useEffect()`**λ₯Ό μ‚¬μš©ν•˜λ©΄ λͺ¨λ“  λ Œλ”λ§ν•˜λŠ” μš”μ†Œλ§ˆλ‹€ μ›ν•˜λŠ” μž‘μ—…μ„ μˆ˜ν–‰ν•  수 μžˆμ–΄ μ½”λ“œλ₯Ό 쀑볡할 ν•„μš”κ°€ μ—†λ‹€.

```jsx
const App = () => {
useEffect(effectCallback, Deps?);
// effectCallback: μ§€μ •λœ λŒ€μƒ λ³€μˆ˜κ°€ λ³€κ²½λ˜λŠ” 경우 μ‹€ν–‰ν•  ν•¨μˆ˜
// Deps: 변경을 감지할 λŒ€μƒ λ³€μˆ˜λ“€μ˜ λ°°μ—΄
}
```
Function Component λ‚΄λΆ€μ—μ„œ Side Effectλ₯Ό μˆ˜ν–‰ν•˜λŠ” Hook.
- Componentκ°€ 졜초둜 λ Œλ”λ§λ  λ•Œ, μ§€μ •ν•œ Stateλ‚˜ Propsκ°€ 변경될 λ•Œ λ“± λ‹€μ–‘ν•œ κ²½μš°μ— `effectCallback` ν•¨μˆ˜κ°€ ν˜ΈμΆœλœλ‹€.
- Depsκ°€ 빈 λ°°μ—΄`[]`이라면, Component 졜초 생성 μ‹œ ν•œλ²ˆλ§Œ μ‹€ν–‰ν•˜λŠ” 효과λ₯Ό μ§€μ •ν•˜λŠ” 것이닀.
- useEffect Hook λ‚΄μ—μ„œ λ‹€λ₯Έ ν•¨μˆ˜λ₯Ό λ°˜ν™˜ν•˜λŠ” 것은 `state` 값이 λ³€κ²½λ˜μ–΄ Componentκ°€ λ‹€μ‹œ λ Œλ”λ§λ˜κΈ° μ „κ³Ό Componentκ°€ μ—†μ–΄μ§ˆ λ•Œ ν˜ΈμΆœν•  ν•¨μˆ˜λ₯Ό λ°˜ν™˜ν•˜λŠ” 것이닀.
```jsx
const App = () => {
useEffect(() => {
// ... Stateκ°€ 변경될 λ•Œ, Componentλ₯Ό λ Œλ”λ§ν•  λ•Œ
}

return () => {
// ... Componentλ₯Ό λ‹€μ‹œ λ Œλ”λ§ν•  λ•Œ, Componentκ°€ μ—†μ–΄μ§ˆ λ•Œ
}
);
}
```
### Memo Hook
```jsx
const App = () => {
const [firstName, setFirstName] = useState('철수');
const [lastName, setLastName] = useState('κΉ€');

const fullName = useMemo(() => {
return `${lastName}${firstName}`;
}, [firstName, lastName]);
};
```
μ§€μ •ν•œ Stateλ‚˜ Propsκ°€ 변경될 경우 ν•΄λ‹Ή 값을 ν™œμš©ν•΄ κ³„μ‚°λœ 값을 λ©”λͺ¨μ΄μ œμ΄μ…˜ν•˜μ—¬ λ‹€μ‹œ λ Œλ”λ§ν•  λ•Œ λΆˆν•„μš”ν•œ 연산을 쀄여쀄 λ•Œ ν™œμš©ν•œλ‹€.
- Memo Hook 연산은 λ Œλ”λ§ λ‹¨κ³„μ—μ„œ 이루어진닀. λ”°λΌμ„œ 였래 κ±Έλ¦¬λŠ” λ‘œμ§μ€ μž‘μ„±ν•˜μ§€ μ•Šλ„λ‘ ꢌμž₯λœλ‹€. μ„±λŠ₯ ν•˜λ½ μ΄μŠˆκ°€ λ°œμƒν•  수 μžˆλ‹€.
```jsx
import React, { useState, useMemo } from 'react';

function App() {
const [foo, setFoo] = useState(0);
const [bar, setBar] = useState(0);

let multi = useMemo(() => {
return foo * bar;
}, [foo, bar]);

return (
<div className='App'>
<input
type='number'
value={foo}
onChange={(e) => setFoo(+e.target.value)}
/>
<input
type='number'
value={bar}
onChange={(e) => setBar(+e.target.value)}
/>
<div>{multi}</div>
</div>
);
}

export default App;
```
### Callback Hook
```jsx
const App = () => {
const [firstName, setFirstName] = useState('철수');
const [lastName, setLastName] = useState('κΉ€');

const getFullName = useCallback(() => {
return `${lastName}${firstName}`;
}, [firstName, lastName]);

return <>{getFullName()}</>;
};
```
Callback Hook은 ν•¨μˆ˜λ₯Ό λ©”λͺ¨μ΄μ œμ΄μ…˜ν•˜κΈ° μœ„ν•΄ μ‚¬μš©ν•˜λŠ” Hook.
- Componentκ°€ λ‹€μ‹œ λ Œλ”λ§λ  λ•Œ λΆˆν•„μš”ν•˜κ²Œ ν•¨μˆ˜κ°€ μž¬μƒμ„±λ˜λŠ” 것을 방지할 수 μžˆλ‹€.
- Memo Hook이 λ³€μˆ˜λ₯Ό λ©”λͺ¨μ΄μ œμ΄μ…˜ν•œλ‹€λ©΄, Callback Hook은 ν•¨μˆ˜λ₯Ό λ©”λͺ¨μ΄μ œμ΄μ…˜ ν•œλ‹€.
`useMemo(() β‡’ fn, deps)`와 `useCallback(fn, deps)`λŠ” λ™μΌν•˜λ‹€.
```jsx
import React, { useState, useCallback } from 'react';

function App() {
const [foo, setFoo] = useState(0);
const [bar, setBar] = useState(0);

let calc = useCallback(() => {
return foo + bar;
}, [foo, bar]);

return (
<div className='App'>
<input
type='number'
value={foo}
onChange={(e) => setFoo(+e.target.value)}
/>
<input
type='number'
value={bar}
onChange={(e) => setBar(+e.target.value)}
/>
<div>{calc()}</div>
</div>
);
}

export default App;
```
### Reference Hook
```jsx
const App = () => {
const inputRef = useRef(null);
const handleClickBtn = () => {
inputRef.current.focus();
};

return (
<div>
<input ref={inputRef} type='text' />
<button onClick={handleClickBtn}>input으둜 ν¬μ»€μŠ€ν•˜κΈ°</button>
</div>
);
};
```
Component 생애 μ£ΌκΈ° λ‚΄μ—μ„œ μœ μ§€ν•  ref 객체λ₯Ό λ°˜ν™˜ν•˜λŠ” Hook.
기본적으둜 Stateκ°€ λ³€κ²½λ˜λ©΄ λ‹€μ‹œ λ Œλ”λ§λœλ‹€. λ‹€λ§Œ, 값을 λ³€κ²½ν•˜λ”λΌλ„ λ‹€μ‹œ λ Œλ”λ§λ˜μ§€ μ•Šκ²Œλ” ν•΄μ•Ό ν•  상황이 μžˆμ„ 수 μžˆλ‹€. μ΄λ•Œ `let`을 ν™œμš©ν•΄ λ³€μˆ˜λ₯Ό μ„ μ–Έν•œλ‹€λ©΄ Componentλ₯Ό λ‹€μ‹œ λ Œλ”λ§ν•  λ•Œ `let` 선언문이 λ‹€μ‹œ μ„ μ–Έλ˜μ–΄ 값이 μ΄ˆκΈ°ν™”λ˜κ²Œ λœλ‹€.
λ”°λΌμ„œ 이런 μƒν™©μ—μ„œ Reference Hook을 ν™œμš©ν•œλ‹€.
- `ref` κ°μ²΄λŠ” `current` 속성을 가지며 이λ₯Ό 자유둭게 λ³€κ²½ν•  수 μžˆλ‹€.
- useRef에 μ˜ν•΄ λ°˜ν™˜λœ ref 객체가 λ³€κ²½λ˜λ”λΌλ„ Componentκ°€ λ‹€μ‹œ λ Œλ”λ§λ˜μ§€ μ•ŠλŠ”λ‹€.
- 일반적으둜 DOM μš”μ†Œμ— μ ‘κ·Όν•  λ•Œ ν•΄λ‹Ή μš”μ†Œμ— `ref` 속성을 μΆ”κ°€ν•˜μ—¬ μ‚¬μš©ν•œλ‹€.
즉, λ‹€μŒκ³Ό 같이 μž‘μ„±ν•˜λ©΄ `inputRef.current`에 ν•΄λ‹Ή `input` μš”μ†Œκ°€ ν• λ‹Ήλœλ‹€.
```jsx
<input ref={inputRef} type='text' />
```
```jsx
import React, { useRef } from 'react';

function App() {
const inputRef = useRef(null);
return (
<div className='App'>
<input ref={inputRef} />
<button
onClick={() => {
alert(inputRef.current.value);
}}
>
ν΄λ¦­ν•˜κΈ°
</button>
</div>
);
}

export default App;
```
### Custom Hook
μžμ‹ λ§Œμ˜ Hook을 λ§Œλ“€μ–΄ Component λ‘œμ§μ„ μž¬μ‚¬μš©ν•  수 μžˆλ‹€. 즉, UI μš”μ†Œμ˜ μž¬μ‚¬μš©μ„±μ„ 높이기 μœ„ν•΄ Componentλ₯Ό ν™œμš©ν•˜κ³ , 둜직의 μž¬μ‚¬μš©μ„±μ„ 높이기 μœ„ν•΄ Custom Hook을 ν™œμš©ν•œλ‹€.
```jsx
fucntion useCustomHook(args) {
const [status, setStatus] = useState(null);
// ...
return status;
}
```
- ν•˜λ‚˜μ˜ 둜직이 μ—¬λŸ¬ 번 ν™œμš©λ˜λŠ” 경우 ν•¨μˆ˜λ₯Ό λΆ„λ¦¬ν•˜λŠ” κ²ƒμ²˜λŸΌ Hook으둜 λΆ„λ¦¬ν•˜λŠ” 것이닀.
- use둜 μ‹œμž‘ν•΄μ•Ό ν•œλ‹€.
- ν•˜λ‚˜μ˜ Hook λ‚΄μ˜ `state`λŠ” κ³΅μœ λ˜μ§€ μ•ŠλŠ”λ‹€.
49 changes: 49 additions & 0 deletions src/app/articles/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { allArticles } from '@contentlayer';
import { useMDXComponent } from 'next-contentlayer/hooks';
import { notFound } from 'next/navigation';
import Image from 'next/image';

interface IArticlePageProps {
params: { slug: string };
}

const ArticlePage = ({ params: { slug } }: IArticlePageProps) => {
const article = allArticles.find((article) => article.slugAsParams === slug);

if (!article) notFound();

const MDXContent = useMDXComponent(article.body.code);

return (
<section className='flex flex-col px-4 py-10 items-center'>
<>
{article.thumbnail ? (
<Image
src={article.thumbnail}
alt={article.title}
width={400}
height={300}
/>
) : (
<div>{article.title}</div>
)}
</>
<h1 className='text-display2 font-extrabold'>{article.title}</h1>
<p>{article.description}</p>
<p>{new Date(article.createdAt).toLocaleDateString()}</p>
<p>{article.category}</p>

<ul>
{article.tags.map((tag) => (
<span key={tag}>{tag}</span>
))}
</ul>
<div className='w-full h-0.5 bg-primary-500 my-4' />
<article className='prose prose-primary max-w-3xl'>
<MDXContent />
</article>
</section>
);
};

export default ArticlePage;
30 changes: 30 additions & 0 deletions src/app/articles/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Card from '@/src/components/ui/Card';
import { allArticles } from '@contentlayer';

const ArticlesPage = () => {
return (
<section className='flex flex-col items-center justify-between px-4 py-10 tablet:py-40'>
<section className='flex flex-wrap gap-8'>
{allArticles.map((article) => (
<Card
key={article._id}
articlePreview={{
category: article.category,
title: article.title,
description: article.description,
createdAt: new Date(article.createdAt).toLocaleDateString(),
tags: article.tags,
slug: article.slug,
thumbnail: {
src: article.thumbnail,
alt: article.title,
},
}}
/>
))}
</section>
</section>
);
};

export default ArticlesPage;
17 changes: 14 additions & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import type { Metadata } from 'next';
import localFont from 'next/font/local';

import Footer from '@/src/components/ui/Footer';
import Header from '@/src/components/ui/Header';
import { ILayoutProps } from '@/src/interfaces';
import { cls } from '@/src/libs/utils';

import '../styles/globals.css';

export const Pretendard = localFont({
const Pretendard = localFont({
src: [
{
path: '../../public/fonts/Pretendard-Black.subset.woff2',
Expand Down Expand Up @@ -67,8 +69,17 @@ export const metadata: Metadata = {
const RootLayout = ({ children }: ILayoutProps) => {
return (
<html lang='ko'>
<body className={cls(Pretendard.className, 'bg-pink-100')}>
{children}
<body
className={cls(
Pretendard.className,
'relative bg-secondary-500 text-primary-50 w-screen flex flex-col min-h-screen overflow-x-hidden',
)}
>
<Header />
<main role='main' className='flex flex-col flex-1 gap-4 tablet:gap-10'>
{children}
</main>
<Footer />
</body>
</html>
);
Expand Down
Loading

0 comments on commit dd99cfa

Please sign in to comment.