반응형
지난번 만든 간단한 카드 페이지에 Material UI를 입혀보려 합니다. 프론트에서 비주얼은 중요하니깐요!
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는 사용자 정의 스타일을 직관적으로 정의할수 있도록 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 |