react, mui
MUI 테마 커스터 마이징(theme)
page.js
export default function App() {
return (
<>
<ThemeProvider theme={theme}>
<Button variant="contained">버튼</Button>
</ThemeProvider>
</>
);
}
theme.js
import { createTheme } from '@mui/material';
const theme = createTheme({
palette: {
type: 'dark',
primary: {
main: '#4caf50',
},
secondary: {
main: '#4caf50',
},
},
});
export default theme;
버튼 커스터 마이징(react-icons사용)
- cmd에 npm install react-icons --save 다운로드
- 그 다음 사용가능~
export default function App() {
return (
<>
<ThemeProvider theme={theme}>
<div className="tw-flex tw-items-center tw-gap-x-3">
<Button variant="text" endIcon={<MdDeleteForever />}>
Text
</Button>
<Button
variant="contained"
startIcon={<MdDeleteForever />}
onClick={() => confirm('삭제할거야?')}>
삭제
</Button>
<Button variant="outlined">Outlined</Button>
</div>
<div className="tw-flex tw-items-center tw-gap-x-3 tw-mt-3">
<Button
variant="text"
onClick={() => {
alert('버튼 클릭됨');
}}>
Text
</Button>
<Button variant="contained" disabled>
Contained
</Button>
<Button variant="outlined" href="sub/">
sub로 이동
</Button>
</div>
</ThemeProvider>
</>
);
}
MUI App Bar 컴포넌트 fixed
export default function App() {
return (
<>
<ThemeProvider theme={theme}>
<AppBar position="fixed">
<Toolbar>
<div className="tw-flex-1">
<FaBars className="tw-cursor-pointer" />
</div>
<div className="logo-box">
<a href="/" className="tw-font-bold">
NOTE!
</a>
</div>
<div className="tw-flex-1 tw-flex tw-justify-end">
<a href="/write">글쓰기</a>
</div>
</Toolbar>
</AppBar>
<Toolbar />
<section className="tw-h-screen tw-flex tw-items-center tw-justify-center tw-text-[5rem]">
section
</section>
</ThemeProvider>
</>
);
}
MUI snack Bar 컴포넌트, Alert
const Alert = React.forwardRef((props, ref) => {
return <MuiAlert {...props} ref={ref} variant="filled" />;
});
export default function App() {
const [open, setOpen] = React.useState(false);
const alertRef = React.useRef(null);
return (
<>
<ThemeProvider theme={theme}>
<AppBar position="fixed">
<Toolbar>
<div className="tw-flex-1">
<FaBars className="tw-cursor-pointer" />
</div>
<div className="logo-box">
<a href="/" className="tw-font-bold">
NOTE!
</a>
</div>
<div className="tw-flex-1 tw-flex tw-justify-end">
<a href="/write">글쓰기</a>
</div>
</Toolbar>
</AppBar>
<Toolbar />
<section className="tw-h-screen tw-flex tw-items-center tw-justify-center tw-text-[5rem]">
section
</section>
</ThemeProvider>
<section>
<Button onClick={() => setOpen(true)}>Open Snackbar</Button>
<Alert ref={alertRef} severity="error" varient="filled">
게시물이 삭제되었습니다.
</Alert>
<Alert severity="success" varient="outlined">
This is a success msg!!!!!
</Alert>
<Snackbar
open={open}
autoHideDuration={2000}
onClose={() => setOpen(false)}
message="Note archived">
<Alert severity="warning">게시물이 삭제됨</Alert>
</Snackbar>
</section>
</>
);
}
MUI backdrop 컴포넌트
export default function App() {
const [open, setOpen] = React.useState(false);
return (
<>
<ThemeProvider theme={theme}>
<AppBar position="fixed">
<Toolbar>
<div className="tw-flex-1">
<FaBars className="tw-cursor-pointer" />
</div>
<div className="logo-box">
<a href="/" className="tw-font-bold">
NOTE!
</a>
</div>
<div className="tw-flex-1 tw-flex tw-justify-end">
<a href="/write">글쓰기</a>
</div>
</Toolbar>
</AppBar>
<Toolbar />
<section className="tw-h-screen tw-flex tw-items-center tw-justify-center tw-text-[5rem]">
section
</section>
</ThemeProvider>
<Button onClick={() => setOpen(true)}>Show backdrop</Button>
<Backdrop
sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1 }}
open={open}
onClick={() => setOpen(false)}>
<CircularProgress color="inherit" />
</Backdrop>
</>
);
}
MUI Drawer 컴포넌트
문제 직면
- element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. you likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
- 이런식으로 에러가 뜨길래 대체 뭐지..하고 봤는데 import할때 @mui/material @mui/icons-material 이렇게 다른곳에서 import를 해야하는데 material에 한번에 import하려고 해서 오류가 났었다!
- 그 다음 prop-type에서 import해야하는데 material에서 또 import하려고 했다..
- 결론 import를 잘 했는지 꼭 확인해보자! 무작정 한꺼번에 import하려고 하진 않았는지!
export default function App() {
const [open, setOpen] = React.useState(false);
return (
<>
<ThemeProvider theme={theme}>
<AppBar position="fixed">
<Toolbar>
<div className="tw-flex-1">
<FaBars onClick={() => setOpen(true)} className="tw-cursor-pointer" />
</div>
<div className="logo-box">
<a href="/" className="tw-font-bold">
NOTE!
</a>
</div>
<div className="tw-flex-1 tw-flex tw-justify-end">
<a href="/write">글쓰기</a>
</div>
</Toolbar>
</AppBar>
<Toolbar />
<section className="tw-h-screen tw-flex tw-items-center tw-justify-center tw-text-[5rem]">
section
</section>
</ThemeProvider>
<Button onClick={() => setOpen(true)}>show drawer</Button>
<Drawer anchor="left" open={open} onClose={() => setOpen(false)}>
<List>
<ListItemButton>
<Link href="/write">글 쓰기</Link>
</ListItemButton>
<ListItemButton>사과</ListItemButton>
<ListItemButton>바나나</ListItemButton>
</List>
</Drawer>
</>
);
}
MUI Tab 컴포넌트
export default function App() {
const [tabCurrentIndex, setTabCurrentIndex] = React.useState(0);
return (
<>
<ThemeProvider theme={theme}>
<AppBar position="fixed">
<Toolbar>
<div className="tw-flex-1">
<FaBars onClick={() => setOpen(true)} className="tw-cursor-pointer" />
</div>
<div className="logo-box">
<a href="/" className="tw-font-bold">
NOTE!
</a>
</div>
<div className="tw-flex-1 tw-flex tw-justify-end">
<a href="/write">글쓰기</a>
</div>
</Toolbar>
</AppBar>
<Toolbar />
<section className="tw-h-screen tw-flex tw-items-center tw-justify-center tw-text-[5rem]">
section
</section>
</ThemeProvider>
<Tabs value={tabCurrentIndex} onChange={(_, newValue) => setTabCurrentIndex(newValue)}>
<Tab label="Item One" />
<Tab label="Item Two" />
<Tab label="Item Three" />
</Tabs>
{tabCurrentIndex == 0 && <div>내용1</div>}
{tabCurrentIndex == 1 && <div>내용2</div>}
{tabCurrentIndex == 2 && <div>내용3</div>}
</>
);
}
TodoList 적용, 할일 추가 폼, dateToStr
const useTodoStatus = () => {
const [todos, setTodos] = React.useState([]);
const lastTodoIdRef = React.useRef(0);
const addTodo = (newTitle) => {
const id = ++lastTodoIdRef.current;
const newTodo = {
id,
title: newTitle,
regDate: dateToStr(new Date()),
};
setTodos([...todos, newTodo]);
};
const removeTodo = (id) => {
const newTodos = todos.filter((todo) => todo.id != id);
setTodos(newTodos);
};
const modifyTodo = (id, title) => {
const newTodos = todos.map((todo) => (todo.id != id ? todo : { ...todo, title }));
setTodos(newTodos);
};
return {
todos,
addTodo,
removeTodo,
modifyTodo,
};
};
const NewTodoForm = ({ todoStatus }) => {
const [newTodoTitle, setNewTodoTitle] = useState('');
const addTodo = () => {
if (newTodoTitle.trim().length == 0) return;
const title = newTodoTitle.trim();
todoStatusaddTodo(title);
setNewTodoTitle('');
};
return (
<>
<div className="flex items-center gap-x-3">
<input
className="input input-bordered"
type="text"
placeholder="새 할일 입력해"
value={newTodoTitle}
onChange={(e) => setNewTodoTitle(e.target.value)}
/>
<button className="btn btn-primary" onClick={addTodo}>
할 일 추가
</button>
</div>
</>
);
};
const TodoListItem = ({ todo, todoStatus }) => {
const [editMode, setEditMode] = useState(false);
const [newTodoTitle, setNewTodoTitle] = useState(todo.title);
const readMode = !editMode;
const enableEditMode = () => {
setEditMode(true);
};
const removeTodo = () => {
todoStatus.removeTodo(todo.id);
};
const cancleEdit = () => {
setEditMode(false);
setNewTodoTitle(todo.title);
};
const commitEdit = () => {
if (newTodoTitle.trim().length == 0) return;
todoStatus.modifyTodo(todo.id, newTodoTitle.trim());
setEditMode(false);
};
return (
<li className="flex items-center gap-x-3 mb-3">
<span className="badge badge-accent badge-outline">{todo.id}</span>
{readMode ? (
<>
<span>{todo.title}</span>
<button className="btn btn-outline btn-accent" onClick={enableEditMode}>
수정
</button>
<button className="btn btn-accent" onClick={removeTodo}>
삭제
</button>
</>
) : (
<>
<input
className="input input-bordered"
type="text"
placeholder="할 일 써"
value={newTodoTitle}
onChange={(e) => setNewTodoTitle(e.target.value)}
/>
<button className="btn btn-accent" onClick={commitEdit}>
수정완료
</button>
<button className="btn btn-accent" onClick={cancleEdit}>
수정취소
</button>
</>
)}
</li>
);
};
const TodoList = ({ todoStatus }) => {
return (
<>
{todoStatus.todos.length == 0 ? (
<h4>할 일 없음</h4>
) : (
<>
<h4>할 일 목록</h4>
<ul>
{todoStatus.todos.map((todo) => (
<TodoListItem key={todo.id} todo={todo} todoStatus={todoStatus} />
))}
</ul>
</>
)}
</>
);
};
export default function App() {
const todoState = useTodoStatus();
const onSubmit = (e) => {
e.preventDefault();
const form = e.currentTarget;
form.title.value = form.title.value.trim();
if (form.title.value.length == 0) {
alert('할 일 써');
form.title.focus();
return;
}
todoState.addTodo(form.title.value);
form.title.value = '';
form.title.focus();
};
return (
<>
<ThemeProvider theme={theme}>
<AppBar position="fixed">
<Toolbar>
<div className="tw-flex-1">
<FaBars onClick={() => setOpen(true)} className="tw-cursor-pointer" />
</div>
<div className="logo-box">
<a href="/" className="tw-font-bold">
NOTE!
</a>
</div>
<div className="tw-flex-1 tw-flex tw-justify-end">
<a href="/write">글쓰기</a>
</div>
</Toolbar>
</AppBar>
<Toolbar />
<form onSubmit={onSubmit}>
<input type="text" name="title" autoComplete="off" placeholder="할 일 입력해" />
<button type="submit">추가</button>
<button type="reset">취소</button>
</form>
{todoState.todos.length}
</ThemeProvider>
</>
);
}
function dateToStr(d) {
const pad = (n) => {
return n < 10 ? '0' + n : n;
};
return (
d.getFullYear() +
'-' +
pad(d.getMonth() + 1) +
'-' +
pad(d.getDate()) +
' ' +
pad(d.getHours()) +
':' +
pad(d.getMinutes()) +
':' +
pad(d.getSeconds())
);
}
dateToStr 모듈화, App에 적용, theme를 App에서 분리, 공통 테마 적용
const App = () => {
const todoState = useTodoStatus();
const onSubmit = (e) => {
e.preventDefault();
const form = e.currentTarget;
form.title.value = form.title.value.trim();
if (form.title.value.length == 0) {
alert('할 일 써');
form.title.focus();
return;
}
todoState.addTodo(form.title.value);
form.title.value = '';
form.title.focus();
};
return (
<>
<AppBar position="fixed">
<Toolbar>
<div className="tw-flex-1">
<FaBars onClick={() => setOpen(true)} className="tw-cursor-pointer" />
</div>
<div className="logo-box">
<a href="/" className="tw-font-bold">
TODO!
</a>
</div>
<div className="tw-flex-1 tw-flex tw-justify-end">
<a href="/write">글쓰기</a>
</div>
</Toolbar>
</AppBar>
<Toolbar />
<form onSubmit={onSubmit}>
<input type="text" name="title" autoComplete="off" placeholder="할 일 입력해" />
<button type="submit">추가</button>
<button type="reset">취소</button>
</form>
{todoState.todos.length}
</>
);
};
export default function themeApp() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>
);
}
TODO
- forwardRef 찾아보기
- forwardRef
- 리액트 복습!(useRef, useEffect, useMemo, useState)
느낀점
- 뭔가 아직 낯선 모양이지만.. 자바스크립트라 그런지.. 뭔가 익숙한 냄새가 나지만 낯설다 후후.. 좀 더 보다보면 괜찮아질 듯 하다! 아직 에러잡아내는게 쉽지 않다 코드리뷰를 더 해보도록!
코드리뷰는 사랑입니다~