리액트, 스프링부트 연동하여 CRUD 구현
#7 리액트 앱에 Material UI 적용하기
React Material UI는 리액트 앱을 빠르고 쉽게 디자인할 수 있는 프레임워크입니다. 자세한 내용은 공식 홈페이지를 참고하세요. 보통 CSS, Styled-Components, 혹은 BootStrap을 사용합니다. 하지만 우리는 리액트를 공부하고 있기에, 리액트 프레임워크 중 하나인 Material UI를 사용하겠습니다. Material UI의 장점으로는 쉽고 빠르게 앱을 꾸밀 수 있다는 겁니다. 그리고 레이아웃, 내비게이션 등 다양한 요소들을 컴포넌트 형식으로 지원합니다.
Material UI를 사용하기 위해서는 Material UI를 CLI, 즉 터미널이나 명령프롬포트를 통해 다운받아야 합니다. 아래의 명령어 2개를 입력하세요.
$ yarn add @material-ui/core
$ yarn add @material-ui/icons
위 명령어를 입력하면 아래와 같이 설치가 진행됩니다.
'@material-ui/core'는 Layout, Inputs, Navigation 등 pre-built에 필요한 요소들을 컴포넌트 형식으로 제공합니다. '@material-ui/icons'는 SVG 형식의 아이콘을 제공합니다. 설치를 완료했다면, 이제 Material UI를 우리가 만든 컴포넌트에 적용해봅시다. 그 전에, 다른 컴포넌트들 CSS를 정리하겠습니다.
RouterComponent.jsx
import React from 'react';
import { BrowserRouter, Route, Switch} from 'react-router-dom';
import UserListComponent from "../user/UserListComponent";
import AddUserComponent from "../user/AddUserComponent";
import EditUserComponent from "../user/EditUserComponent";
const AppRouter = () => {
return(
<div style={style}>
<BrowserRouter>
<Switch>
<Route exact path="/" component={UserListComponent} />
<Route path="/users" component={UserListComponent} />
<Route path="/edit-user" component={EditUserComponent} />
<Route path="/add-user" component={AddUserComponent} />
</Switch>
</BrowserRouter>
</div>
);
}
const style = {
marginTop: '20px'
}
export default AppRouter;
Material UI를 적용해봅시다.
UserListComponent.jsx
import React, { Component } from 'react';
import ApiService from "../../ApiService";
import Table from '@material-ui/core/Table'
import TableBody from '@material-ui/core/TableBody'
import TableCell from '@material-ui/core/TableCell'
import TableHead from '@material-ui/core/TableHead'
import TableRow from '@material-ui/core/TableRow'
import Button from '@material-ui/core/Button'
import Typography from '@material-ui/core/Typography'
import CreateIcon from '@material-ui/icons/Create'
import DeleteIcon from '@material-ui/icons/Delete'
class UserListComponent extends Component{
constructor(props){
super(props);
this.state = {
users: [],
message: null
}
}
componentDidMount(){
this.reloadUserList();
}
reloadUserList = () => {
ApiService.fetchUsers()
.then( res => {
this.setState({
users: res.data
})
})
.catch(err => {
console.log('reloadUserList() Error!', err);
})
}
deleteUser = (userID) => {
ApiService.deleteUser(userID)
.then( res => {
this.setState({
message: 'User Deleted Successfully.'
});
this.setState({
users: this.state.users.filter( user =>
user.id !== userID)
});
})
.catch(err => {
console.log('deleteUser() Error!', err);
})
}
editUser = (ID) => {
window.localStorage.setItem("userID", ID);
this.props.history.push('/edit-user');
}
addUser = () => {
window.localStorage.removeItem("userID");
this.props.history.push('/add-user');
}
render(){
return(
<div>
<Typography variant="h4" style={style}>User List</Typography>
<Button variant="contained" color="primary" onClick={this.addUser}> Add User </Button>
<Table>
<TableHead>
<TableRow>
<TableCell>ID</TableCell>
<TableCell>FistName</TableCell>
<TableCell align="right">LastName</TableCell>
<TableCell align="right">UserName</TableCell>
<TableCell align="right">Age</TableCell>
<TableCell align="right">Salary</TableCell>
<TableCell align="right">Edit</TableCell>
<TableCell align="right">Delete</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.state.users.map( user =>
<TableRow key={user.id}>
<TableCell component="th" scope="user">{user.id}</TableCell>
<TableCell align="right">{user.firstName}</TableCell>
<TableCell align="right">{user.lastName}</TableCell>
<TableCell align="right">{user.username}</TableCell>
<TableCell align="right">{user.age}</TableCell>
<TableCell align="right">{user.salary}</TableCell>
<TableCell align="right" onClick={()=> this.editUser(user.id)}>
<CreateIcon />
</TableCell>
<TableCell align="right" onClick={()=> this.deleteUser(user.id)}>
<DeleteIcon />
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
);
}
}
const style = {
display: 'flex',
justifyContent: 'center'
}
export default UserListComponent;
AddUserComponent.jsx
import React, { Component } from 'react';
import ApiService from "../../ApiService";
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
class AddUserComponent extends Component{
constructor(props){
super(props);
this.state = {
username: '',
password: '',
firstName: '',
lastName: '',
age: '',
salary: '',
message: null
}
}
onChange = (e) => {
this.setState({
[e.target.name] : e.target.value
})
}
saveUser = (e) => {
e.preventDefault();
let user = {
username: this.state.username,
password: this.state.password,
firstName: this.state.firstName,
lastName: this.state.lastName,
age: this.state.age,
salary: this.state.salary,
}
ApiService.addUser(user)
.then( res => {
this.setState({
message: user.username + '님이 성공적으로 등록되었습니다.'
})
console.log(this.state.message);
this.props.history.push('/users');
})
.catch( err => {
console.log('saveUser() 에러', err);
});
}
render(){
return(
<div>
<Typography variant="h4" style={style}>Add User</Typography>
<form style={formContainer}>
<TextField type="text" placeholder="please input your username" name="username"
fullWidth margin="normal" value={this.state.username} onChange={this.onChange} />
<TextField type="password" placeholder="please input your password" name="password"
fullWidth margin="normal" value={this.state.password} onChange={this.onChange} />
<TextField placeholder="please input your first name" name="firstName"
fullWidth margin="normal" value={this.state.firstName} onChange={this.onChange} />
<TextField placeholder="please input your last name" name="lastName"
fullWidth margin="normal" value={this.state.lastName} onChange={this.onChange} />
<TextField type="number" placeholder="please input your age" name="age"
fullWidth margin="normal" value={this.state.age} onChange={this.onChange} />
<TextField type="number" placeholder="please input your salary" name="salary"
fullWidth margin="normal" value={this.state.salary} onChange={this.onChange} />
<Button variant="contained" color="primary" onClick={this.saveUser}>Save</Button>
</form>
</div>
);
}
}
const formContainer = {
display: 'flex',
flexFlow: 'row wrap'
}
const style = {
display: 'flex',
justifyContent: 'center'
}
export default AddUserComponent;
EditUserComponent.jsx
import React, { Component } from 'react';
import ApiService from "../../ApiService";
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
class EditUserComponent extends Component{
constructor(props){
super(props);
this.state = {
id: '',
username: '',
firstName: '',
lastName: '',
age: '',
salary: '',
message: null
}
}
componentDidMount(){
this.loadUser();
}
loadUser = () => {
ApiService.fetchUserByID(window.localStorage.getItem("userID"))
.then( res => {
let user = res.data;
this.setState({
id: user.id,
username: user.username,
firstName: user.firstName,
lastName: user.lastName,
age: user.age,
salary: user.salary
})
})
.catch(err => {
console.log('loadUser() 에러', err);
});
}
onChange = (e) => {
this.setState({
[e.target.name] : e.target.value
});
}
saveUser = (e) => {
e.preventDefault();
let user = {
id: this.state.id,
password: this.state.password,
firstName: this.state.firstName,
lastName: this.state.lastName,
age: this.state.age,
salary: this.state.salary
}
ApiService.editUser(user)
.then( res => {
this.setState({
message : user.lastName + '님 정보가 수정되었습니다.'
})
this.props.history.push('/users');
})
.catch(err => {
console.log('saveUser() 에러', err);
})
}
render(){
return(
<div>
<Typography variant="h4" style={style}>Edit User</Typography>
<form>
<TextField type="text" name="username" readOnly={true}
fullWidth margin="normal" value={this.state.username} />
<TextField placeholder="Edit your first name" name="firstName"
fullWidth margin="normal" value={this.state.firstName} onChange={this.onChange} />
<TextField placeholder="Edit your last name" name="lastName"
fullWidth margin="normal" value={this.state.lastName} onChange={this.onChange} />
<TextField type="number" placeholder="Edit your age" name="age"
fullWidth margin="normal" value={this.state.age} onChange={this.onChange} />
<TextField type="number" placeholder="Edit your salary" name="salary"
fullWidth margin="normal" value={this.state.salary} onChange={this.onChange} />
<Button variant="contained" color="primary" onClick={this.saveUser}>Save</Button>
</form>
</div>
);
}
}
const style = {
display: 'flex',
justifyContent: 'center'
}
export default EditUserComponent;
이제 내비게이션을 만들 겁니다. 저는 이 위치에 NavBar.jsx를 생성했습니다. 그리고 아래와 같이 코드를 작성하세요.
NavBar.jsx
import React from 'react';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import MenuIcon from '@material-ui/icons/Menu';
const NavBar = () => {
return(
<div>
<AppBar position="static">
<Toolbar>
<IconButton edge="start" color="inherit" aria-label="Menu">
<MenuIcon />
</IconButton>
<Typography variant="h6" style={style}>
React User Application
</Typography>
<Button color="inherit">Login</Button>
</Toolbar>
</AppBar>
</div>
);
}
const style = {
flexGrow: 1
}
export default NavBar;
이제 App.js에서 NavBar.jsx를 불러오고 Material UI를 추가합니다.
App.js
import React from 'react';
import AppRouter from './component/route/RouterComponent';
import NavBar from "./component/route/NavBar";
import Container from '@material-ui/core/Container';
function App() {
return (
<div>
<NavBar />
<Container>
<AppRouter />
</Container>
</div>
);
}
export default App;
잘 작성하셨나요? 이제 우리가 작성한 리액트 앱의 디자인이 어떻게 변했는지 확인해보겠습니다. 먼저 Material UI를 적용하기 전 우리의 리액트 앱입니다.
껄껄껄.... 이젠 적용 후의 모습입니다. 밑의 명령어로 노드 서버를 켜서 확인해보겠습니다. 이미 켜져 있다면 http://localhost:3000/ 으로 들어가서 확인해보세요.
wow... 정말 깔끔해졌죠? 이게 Material UI입니다. 컴포넌트 형식으로 props를 지원해주어 비교적 쉽게 리액트 앱을 꾸밀 수 있었습니다. 리액트, 즉 프론트앤드에서 해야 할 작업은 여기에서 끝났습니다. 반 정도 온 셈이에요. 여기까지 오신 여러분, 자신에게 잘했다고 칭찬해주세요. 이제 다음 글부터 백앤드(스프링부트, MySQL)을 다룰 겁니다. 조금만 더 힘내자구요!
댓글