지난번 만든 간단한 카드 페이지에 Material UI를 입혀보려 합니다. 프론트에서 비주얼은 중요하니깐요!
Material-UI: A popular React UI framework
React components for faster and easier web development. Build your own design system, or start with Material Design.
material-ui.com
react 프로젝트에서 터미널을 열어 다음 명령어를 입력합니다.
npm install @material-ui/core
일단 제일 보기 싫었던 button과 input부터 손봅니다.
import { useState } from 'react';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
function CardForm({ onCreate = (v) => console.log(v) }) {
const [cardName, setName] = useState("")
const [description, setDescription] = useState("")
const handleSubmit = (e) => {
e.preventDefault()
onCreate({ name: cardName, description: description })
setName("")
setDescription("")
}
return (
<form onSubmit={handleSubmit} style={{
padding: '8px',
margin: '8px'
}}>
<TextField label="Outlined" variant="outlined" value={cardName} name="cardName" placeholder="name" onChange={e => setName(e.target.value)} />
<TextField label="Outlined" variant="outlined" value={description} name="cardDescription" placeholder="description" onChange={e => setDescription(e.target.value)} />
<Button variant="contained" type="submit"> Add </Button>
</form>
);
}
export default CardForm;
동작도 잘 되고, 변경된 스타일도 잘 적용이 되기는 하는데... 나온 ui가 생각보다 깔끔하게 떨어지질 않습니다.
그러던 중 material ui 홈페이지의 components 페이지의 코드를 보다 보니 makeStyles 라는 hook이 눈에 띕니다.
@material-ui/styles - Material-UI
You can use Material-UI's styling solution in your app, whether or not you are using Material-UI components.
material-ui.com
Material UI의 styles는 사용자 정의 스타일을 직관적으로 정의할수 있도록 makeStyles hook을 제공합니다.
makeStyles hook은 JSX 내에 덕지덕지 지저분할 css 파일을 한 데 모아주는 역할(?)을 수행할 수 있습니다.
다음과 같이 코드를 변경해줍니다.
import { useState } from 'react';
import { makeStyles, Button, TextField, FormGroup } from '@material-ui/core';
const useStyles = makeStyles((theme) => ({
formGroup: {
'& .MuiTextField-root': {
margin: theme.spacing(1),
width: '25ch',
},
alignItems: "center",
display: 'flex',
flexDirection: 'row'
}
}));
function CardForm({ onCreate = (v) => console.log(v) }) {
const [cardName, setName] = useState("")
const [description, setDescription] = useState("")
const classes = useStyles();
const handleSubmit = (e) => {
e.preventDefault()
onCreate({ name: cardName, description: description })
setName("")
setDescription("")
}
return (
<FormGroup className={classes.formGroup} onSubmit={handleSubmit}>
<TextField id="standard-required" label="name" value={cardName} name="cardName" onChange={e => setName(e.target.value)} />
<TextField id="standard-required" label="description" value={description} name="cardDescription" onChange={e => setDescription(e.target.value)} />
<Button size="large" variant="contained" type="submit"> Add </Button>
</FormGroup>
);
}
export default CardForm;
이제 상단의 ReactArrayExample도 Bar 형식으로 교체합니다.
import { Fragment, useState } from 'react';
import './App.css';
import CardForm from './CardForm';
import CardInfoListView from './CardInfoListView';
import {AppBar, Toolbar, Typography} from '@material-ui/core';
function App() {
const [cardId, setCardId] = useState(0)
const [cardList, setCardList] = useState([])
const handleOnCreate = (cardInfo) => {
setCardList(cardList.concat({ id: cardId, name: cardInfo.name, description: cardInfo.description }))
setCardId(c => c + 1)
}
const handleOnUpdate = (modifiedCard) => {
setCardList(cardList.map(card => (card.id === modifiedCard.id) ? modifiedCard : card))
}
const handleOnDelete = (cardId) => {
setCardList(cardList.filter(card => card.id !== cardId))
}
return (
<Fragment>
<AppBar position="sticky">
<Toolbar>
<Typography variant="h6">React Sample Card App</Typography>
</Toolbar>
</AppBar>
<CardForm onCreate={(cardInfo) => handleOnCreate(cardInfo)} />
<CardInfoListView cardList={cardList} handleOnUpdate={handleOnUpdate} handleOnDelete={handleOnDelete} />
</Fragment>
);
}
export default App;
이 때, Appbar의 position을 sticky로 주지 않으면 Appbar와 아래 내용이 겹쳐 보이니 주의하셔야 합니다.
CardInfoListView는 변경사항이 없으며, CardInfo는 다음과 같이 수정합니다.
import { Fragment, useEffect, useState } from "react";
import { Box, makeStyles, ButtonGroup, Button, TextField } from "@material-ui/core";
const useStyles = makeStyles((theme) => ({
root: {
'& .MuiTextField-root': {
margin: theme.spacing(1),
width: '25ch',
},
alignItems: "center",
display: 'flex',
flexDirection: 'row'
}
}));
function CardInfo({ info = { id: 0, name: "N/A", description: "N/A" }, handleOnUpdate, handleOnDelete }) {
const [editing, setEditing] = useState(false)
const [name, setName] = useState(info.name)
const [description, setDescription] = useState(info.description)
const classes = useStyles();
useEffect(() => {
console.log("CardInfo component created.")
return () => console.log("cardInfo component destroyed.")
}, [])
return (
<Box borderRadius={16} borderColor="gray.500" border={1} style={{
padding: '8px',
margin: '8px'
}}>
{
editing
?
<form className={classes.root} onSubmit={e => { e.preventDefault(); setEditing(false); handleOnUpdate({ id: info.id, name: name, description: description }) }} >
<TextField id="standard-required" label="name" name="cardName" value={name} onChange={e => setName(e.target.value)} />
<TextField id="standard-required" label="description" value={description} name="cardDescription" onChange={e => setDescription(e.target.value)} />
<ButtonGroup size="large">
<Button type="submit" color="primary"> Submit </Button>
<Button color="secondary" onClick={e => setEditing(false)}> Cancel </Button>
</ButtonGroup>
</form>
:
<Box className={classes.root}>
<TextField id="standard-required" label="name" name="cardName" value={info.name} disabled/>
<TextField id="standard-required" label="description" value={info.description} name="cardDescription" disabled/>
<ButtonGroup size="large">
<Button color="primary" onClick={e => setEditing(true)}> Edit </Button>
<Button color="secondary" onClick={e => handleOnDelete(info.id)}> Delete </Button>
</ButtonGroup>
</Box>
}
</Box>
);
}
export default CardInfo
동작하는 페이지는 다음과 같습니다.
'Tech > React' 카테고리의 다른 글
프론트 입문 리액트 삽질기 7 - 라우팅 : React router - 1 (0) | 2021.10.04 |
---|---|
프론트 입문 리액트 삽질기 5 - 배열을 활용한 컴포넌트 수정, 삭제 (0) | 2021.08.29 |
프론트 입문 리액트 삽질기 4 - 배열을 활용한 컴포넌트 생성 (0) | 2021.08.28 |
프론트 입문 리액트 삽질기 3 - Effect Hook으로 Lifecycle API 동작 구현하기 (0) | 2021.08.24 |
프론트 입문 리액트 삽질기 2 - Props, State Hook (0) | 2021.08.22 |