함께 성장하는 프로독학러

Memo_app 05. 메모(Memo) - Create 기능 구현 (메모작성) 본문

Programming/tutorials

Memo_app 05. 메모(Memo) - Create 기능 구현 (메모작성)

프로독학러 2018. 6. 26. 05:50

안녕하세요, 프로독학러 입니다.


이번 포스팅에서는 메모에 관련된 기능들 중 Create 를 구현해 보도록 하겠습니다.

메모의 기능들을 CRUD 라고도 하는데요, 이는 Create, Retrieve, Update, Delete 의 줄임말으로, 쓰기, 읽기, 수정하기, 삭제하기입니다.

(이 중 첫번째인 Create 를 구현하도록 하겠습니다)


*본 튜토리얼은 Velopert 님의 'React.js Codelab 2016' 을 기반으로 되었습니다.

(여러 모듈들의 버전업에 따라 작성방법이 조금씩 달라진 코드를 버전에 맞게 수정하고, 제가 튜토리얼을 따라함에 있어 이해가 쉽지 않았던 부분에 설명을 추가하는 방식으로 진행합니다.)

* Velopert 님의 원본 튜토리얼을 보고싶으신 분들은 아래의 링크를 참고해 주세요.

<React.js codelab 2016 - Velopert>


0. Memo 기능을 위한 설정


Memo 기능을 구현하기에 앞서 인증 때와 마찬가지로 데이터 모델링을 하고, 기본적인 BACK-END 라우터를 설정하도록 하겠습니다.


0-1) Memo 데이터 모델링


가장 먼저 할 것은 Memo 도큐먼트가 데이터베이스에 저장될 형태를 만들어(Schema) 모델로 export 하는 파일을 만드는 것입니다.

server - models 디렉토리에 memo.js 파일을 생성해 주세요.


(./server/models)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import mongoose from 'mongoose';
 
const Schema = mongoose.Schema;
 
const Memo = new Schema({
    writer: String,
    contents: String,
    starred: [String],
    date: {
        created: { type: DatedefaultDate.now },
        edited: { type: DatedefaultDate.now }
    },
    is_edited: { type: Booleandefaultfalse }
});
 
export default mongoose.model('memo', Memo);
cs


코드의 5 번째 줄에서 memo 도큐먼트의 형식을 new Schema 를 통해서 지정했습니다.

16 번째 줄을 통해 model 을 export 했습니다. ('memo' 의 복수형인 'memos' 를 콜렉션 명으로 사용하겠다고 선언)

다른 파일에서 해당 model 을 통해 데이터베이스의 'memos' collection 에 접근할 수 있습니다.


0-2) BACK-END 라우터 파일 생성


memo 에 관련된 BACK-END API 들을 정의할 파일을 만들어 줍시다.

server - routes 디렉토리에 memo.js 파일을 생성해 주세요.


(./server/routes/memo.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import express from 'express';
import Memo from '../models/memo';
import mongoose from 'mongoose';
 
const router = express.Router();
 
// WRITE MEMO
router.post('/', (req, res) => {
 
});
 
// MODIFY MEMO
router.put('/:id', (req, res) => {
 
});
 
// DELETE MEMO
router.delete('/:id', (req, res) => {
 
});
 
// GET MEMO LIST
router.get('/', (req, res) => {
 
});
 
export default router;
cs


코드의 두 번째 줄에서 0-1) 에서 만든 model 을 Memo 로 import 했습니다.

이 파일안에서 Memo 를 통해 DB 에 접근할 수 있습니다. ('memos' collection)


나머지 라우터들은 차차 구현해 나가도록 하겠습니다.


0-3) routes 디렉토리의 index 에 memo 라우터 추가


memo 라우터를 routes 디렉토리의 index 에 추가 시켜줍니다.


(./server/routes/index.js)

1
2
3
4
5
6
7
8
9
import express from 'express';
import account from './account';
import memo from './memo';
 
const router = express.Router();
router.use('/account', account);
router.use('/memo', memo);
 
export default router;
cs


코드의 세 번째 줄에서 memo 라우터를 불러왔습니다.

코드의 7 번째 줄에서 /memo 로 들어오는 접근을 memo 라우터로 위임했습니다.

이를 통해 /api/memo/:id 와 같은 경로로 들어오는 접근을 memo 라우터에서 처리할 수 있게 되었습니다.

(./server/main.js 에서 '/api' 로 들어오는 접근을 routes 디렉토리로 위임시킨 바 있음)


1. Create 기능 구현 (메모 작성하기)


memo 기능의 가장 처음으로 구현할 기능은 쓰기 기능입니다.

쓰기 기능은 어떻게 작동하는지 아래 그림을 통해 살펴보도록 하겠습니다.



가장 먼저 BACK-END 의 API 부터 살펴보도록 하겠습니다.

API 는 POST 형식으로 '/api/memo' 에 접근한 요청을 처리하는 라우터로, 입력받은 데이터의 형식과 로그인여부를 확인하고 로그인된 유저를 작성자로하여 memos 콜렉션(DB)에 새로운 도큐먼트를 추가합니다. 이 과정에서 에러가 있으면 에러 객체를, 없다면 성공 객체를 리턴합니다.

두 번째로 살펴볼 것은 FRONT-END 의 Redux 입니다.

리덕스에서는 API 와 통신을 통하여 그 결과에 따라 다른 액션객체를 전달하는 thunk 함수와 전달받은 액션 객체의 type 값에 따라 리덕스 state 를 다르게 변경하는 리듀서가 있습니다.

세 번쨰로 살펴볼 것은 HOME 컨테이너 컴포넌트입니다.

HOME 컨테이너는 '/' 라우트에 렌더링되는 컴포넌트인데, 이는 전역이 아니라 '/' 에만 렌더링 되는 컴포넌트 입니다. (/register 등에서는 작동되지 않는 라우트) 또한 Write 컴포넌트를 렌더링 합니다.

네 번째로 살펴볼 것은 Write 컴포넌트입니다.

Write 컴포넌트에서 사용자로 부터 입력받은 내용을 thunk 의 인자로 하여 thunk 함수를 실행시킵니다.


데이터 이동은 다음과 같이 진행됩니다.

1. 리덕스에서 정의한 thunk 함수와 리덕스 state 가 Home 컴포넌트로 전달(connect 를 통해)

2. Home 컨테이너에서 thunk 실행 메소드를 정의하여 Write 컴포넌트에 props 로 전달.

3. Write 컴포넌트에서 컴포넌트 상태(state)를 통해 thunk 함수의 인자를 결정해 실행

4. thunk 를 통해 BACK-END API 로 데이터가 전달됨

5. API 가 DB 와 통신을 통해 오류가 생기면 오류객체를, 오류가 없다면 DB 에 저장 후 성공객체를 전달

6. API 가 리턴한 값에 따라 state 를 변경, 다시 Home 컨테이너로 전달


1-1) BACK-END API 구현


가장 먼저 서버측에서 동작하는 API 를 구현해 보도록 하겠습니다.


(./server/routes/memo.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// WRITE MEMO
/*
    WRITE MEMO: POST /api/memo
    BODY SAMPLE: { contents: "sample "}
    ERROR CODES
        1: NOT LOGGED IN
        2: CONTENTS IS NOT STRING
        3: EMPTY CONTENTS
*/
router.post('/', (req, res) => {
    // CHECK LOGIN STATUS
    // 세션확인 (로그인 여부 확인)
    if(typeof req.session.loginInfo === 'undefined') {
        return res.status(403).json({
            error: "NOT LOGGED IN",
            code: 1
        });
    }
 
    // CHECK CONTENTS VALID
    // 입력받은 콘텐츠의 데이터 타입이 문자열이 아닐 경우
    if(typeof req.body.contents !== 'string') {
        return res.status(400).json({
            error: "CONTENTS IS NOT STRING",
            code: 2
        });
    }
 
    // 입력받은 콘텐츠가 비어있는 경우
    if(req.body.contents === "") {
        return res.status(400).json({
            error: "EMPTY CONTENTS",
            code: 3
        });
    }
 
    // CREATE NEW MEMO
    // 위의 결격사항이 없을 경우 뉴 모델을 통하여 DB에 저장
    let memo = new Memo({
        writer: req.session.loginInfo.username,
        contents: req.body.contents
    });
 
    // SAVE IN DATABASE
    memo.save( err => {
        if(err) throw err;
        return res.json({ success: true });
    });
});
cs


API 는 전달 받은 데이터 ({ contents: "sample" } 와 같은) 를 req.body 를 통해 접근할 수 있습니다.

POST 방식으로 '/api/memo' 로 들어온 접근에 대하여 처음 수행하는 동작은 코드 13 번째 줄과 같이 로그인 여부를 확인하는 것입니다.

로그인의 확인은 session 을 통해 이루어집니다. (로그인 확인 기능과 동일)

만약 로그인 상태가 아니라면 HTTP status 403 과 함께 에러 객체를 리턴합니다.

* HTTP status 403 은 금지됨(Forbidden)의 의미입니다. (권한 없음)


코드의 22 번째 줄에서는 전달받은 데이터의 contents 값의 데이터 형식이 문자열인지 검사합니다.

만약 문자열이 아니라면 HTTP status 400 과 함께 에러 객체를 리턴합니다.

* HTTP status 400 은 잘못된 요청이라는 의미입니다.


코드의 30 번째 줄에서는 전달받은 데이터의 contents 값이 비어있는 경우인지를 검사합니다.

만약 비어있는 문자열이 전달 되었다면 HTTP status 400 과 함께 에러 객체를 리턴합니다.


만약 위의 에러상황들이 있는 경우가 아니라면, 코드의 39 번째 줄과 같이 새로운 model 을 생성하여 DB 에 저장합니다.

이 때 작성자는 세션에 있는 로그인정보가 됩니다. 그리고 마지막으로 성공 객체를 리턴합니다.


1-2) Redux 구현


다음으로는 Redux 를 통해 ActionType, thunk 및 액션 생성자 함수, 리듀서를 구현해 보도록 하겠습니다.


1-2-1) ActionTypes


(./src/acions/ActionTypes.js)

1
2
3
4
5
/* MEMO */
// Post memo
export const MEMO_POST = "MEMO_POST";
export const MEMO_POST_SUCCESS = "MEMO_POST_SUCCESS";
export const MEMO_POST_FAILURE = "MEMO_POST_FAILURE";
cs


memo 작성과 관련된, 문자열을 export 하는 actiontypes 를 추가했습니다.


1-2-2) thunk 및 액션 생성자 함수 구현


thunk 및 액션 생성자 함수를 구현하기 위해 actions 디렉토리에 memo.js 파일을 생성해 줍니다.


(./src/actions/memo.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import {
    MEMO_POST,
    MEMO_POST_SUCCESS,
    MEMO_POST_FAILURE
} from './ActionTypes';
import axios from 'axios';
 
/* MEMO POST */
export function memoPostRequest(contents) {
    return (dispatch) => {
        // inform MEMO POST API is starting
        dispatch(memoPost());
 
        return axios.post('/api/memo/', { contents })
        .then((response) => {
            dispatch(memoPostSuccess());
        }).catch((error) => {
            dispatch(memoPostFailure(error.response.data.code));
        });
    };
}
 
export function memoPost() {
    return {
        type: MEMO_POST
    };
}
 
export function memoPostSuccess() {
    return {
        type: MEMO_POST_SUCCESS
    };
}
 
export function memoPostFailure(error) {
    return {
        type: MEMO_POST_FAILURE,
        error
    };
}
cs


코드의 첫 번째 줄에서 위에서 생성한 액션타입들을 import 했습니다.


코드의 23, 29, 35 번째 줄은 액션 생성자 함수로, action 객체를 리턴합니다. 각각 메모 작성 요청중, 성공, 실패를 의미하는 action 객체를 리턴합니다. 실패했을 경우, 인자로 들어오는 값을 error 필드의 값으로 취합니다.


코드의 9 번째 줄은 thunk 함수입니다. 함수의 인자로는 content 가 들어옵니다.

thunk 함수가 실행되면 맨 처음 리듀서에게 메모작성 요청이 들어왔다는 객체를 전달해주고(12 번째 줄),

axios 를 통해 API 와 통신합니다. 통신할 때 API 에 객체를 전달하는데, 이 객체는 contents 필드의 값이 thunk 함수의 인자로 들어온 값인 객체입니다. API 와의 통신을 마치고 API 에서 객체를 리턴한 뒤에, 그 객체가 성공 객체이면 성공 action 객체를 리듀서에 전달하고, 에러객체가 리턴되면 실패 action 객체를 리듀서에 전달합니다. (이때 API 로부터 전달받은 에러객체의 코드값을 action.error 의 값으로 취합니다)


1-2-3) 리듀서 구현


위에서 정의한 thunk 함수로 부터 액션 객체를 전달받아 state 를 수정하는 리듀서를 구현해 보겠습니다.

reducers 디렉토리에 memo.js 파일을 생성해 줍니다.


(./src/reducers/memo.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import * as types from 'actions/ActionTypes';
 
const initialState = {
    post: {
        status: 'INIT',
        error: -1
    },
    list: {
        status: 'INIT',
        data: [],
        isLast: false
    },
    edit: {
        status: 'INIT',
        error: -1,
    },
    remove: {
        status: 'INIT',
        error: -1
    },
    star: {
        status: 'INIT',
        error: -1
    }
};
 
export default function memo(state = initialState, action) {
  switch(action.type) {
    case types.MEMO_POST:
        return {
          ...state,
          post: {
            ...state.post,
            status: 'WAITING',
            error: -1
          }
        };
    case types.MEMO_POST_SUCCESS:
        return {
          ...state,
          post: {
            ...state.post,
            status: 'SUCCESS'
          }
        };
    case types.MEMO_POST_FAILURE:
        return {
          ...state,
          post: {
            ...state.post,
            status: 'FAILURE',
            error: action.error
          }
        };
    default:
        return state;
    }
}
cs


코드의 첫 번째 줄에서 ActionTypes 를 import 했습니다.

세 번째 줄에서 initialState 를 정의 했습니다. 

initialState 는 27 번째 줄에서 리듀서 함수의 첫번째 인자인 state 의 default parameter 로 쓰였습니다.

* ES6 의 default parameter 에 대해서 익숙하지 않으신 분들은 아래의 링크를 참고해주세요

<ES6 의 default parameter>


리듀서 함수는 두 개의 인자를 취하는데, 하나는 이전 state 이고, 다른 하나는 action 객체입니다.

전달 받은 action 객체의 type 값에 따라 다음 state 를 다르게 변경하는 코드를 switch 조건문을 이용하여 작성하였습니다.

* JS 의 switch 조건문에 익숙하지 않으신 분들은 아래의 링크를 참고해주세요.

<javascript 의 switch 조건문>


memo 리듀서가 생성되었으므로 reducers 디렉토리의 index 에 memo 리듀서를 추가해 줍시다.


(./src/reducers/index.js)

1
2
3
4
5
6
7
8
9
import authentication from './authentication';
import memo from './memo';
 
import { combineReducers } from 'redux';
 
export default combineReducers({
    authentication,
    memo
});
cs


1-3) Home 컨테이너 구현


이번 단계에서는 '/' 라우트에 렌더링될 컨테이너 컴포넌트인 Home 컴포넌트를 구현해 보도록 하겠습니다.

containers 디렉토리에 Home.js 파일을 생성해 줍니다.


(./src/containers/Home.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { Component } from 'react';
 
class Home extends Component {
 
    render() {
      return (
          <div>
            exact Home
          </div>
      );
    }
}
 
export default Home;
cs


일단은 exact Home 이라는 문자를 렌더링하는 단순한 컴포넌트를 생성했습니다.


Home 컨테이너를 새로 생성했으니 containers 디렉토리에 Home 을 추가해 주도록 하겠습니다.


(./src/containers/index.js)

1
2
3
4
5
6
import Register from './Register';
import Login from './Login';
import App from './App';
import Home from './Home';
 
export { Register, Login, App, Home };
cs


1-3-1) Home 컨테이너 클라이언트 사이드 라우트에 추가


'/' 경로의 접근에 대하여 Home 컨테이너 컴포넌트를 렌더링하도록 라우트를 추가해 주겠습니다.


(./src/index.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import { Register, Login, App, Home } from 'containers'
 
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import reducers from 'reducers';
import thunk from 'redux-thunk';
 
const store = createStore(reducers, applyMiddleware(thunk));
 
const title = 'Memo_App!';
 
ReactDOM.render(
  <Provider store={store}>
    <Router>
      <div>
        <Route path="/" component={App}/>
        <Route exact path="/" component={Home}/>
        <Route path="/register" component={Register}/>
        <Route path="/login" component={Login}/>
      </div>
    </Router>
  </Provider>
  ,
  document.getElementById('root')
);
 
module.hot.accept();
cs


코드의 4 번째 줄에서 container 디렉토리로 부터 Home 을 import 했습니다.


코드의 20 번째 줄에서 '/' 경로의 접근에 Home 컨테이너를 렌더링 하도록 설정했습니다.

이때 Route 의 path 속성 앞에 'exact' 속성을 붙였습니다. exact 속성을 추가하게 되면 정확한 path 경로에만 적용되는 라우트 라는 뜻입니다.

바로 윗줄의 App 컨테이너를 렌더링하는 라우트와는 비교가 되죠.

20 번째 줄의 라우트는 '/' 경로에서만 작동하는 라우트입니다.


서버를 재시작해 확인해 봅시다.

어떤가요? '/' 경로에서만 exact Home 이라는 글자가 렌더링 되고 있나요?

exact 속성에 대해 의문이 생긴다면 한번 exact 속성을 지워보세요. exact Home 이라는 글자가 전역에서 렌더링 될 것입니다.


1-4) Write 컴포넌트


그럼 이제 Home 컨테이너에 렌더링될 Write 컴포넌트를 구현해 보도록 하겠습니다.

components 디렉토리에 Write.js 파일을 생성해주세요.


(./src/components/Write.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { Component } from 'react';
 
class Write extends Component {
 
  render() {
      return (
        <div className="container write">
            <div className="card">
                <div className="card-content">
                    <textarea className="materialize-textarea"
                              placeholder="Write down your memo"></textarea>
                </div>
                <div className="card-action">
                    <a>POST</a>
                </div>
            </div>
        </div>
      );
  }
}
 
export default Write;
cs


Materializecss 를 이용해 렌더링될 뷰를 구성했습니다.


Write 컴포넌트를 새롭게 생성했으니 components 디렉토리의 index 에 Write 를 추가해 줍시다.


(./src/components/index.js)

1
2
3
4
5
import Authentication from './Authentication';
import Header from './Header';
import Write from './Write';
 
export { Authentication, Header, Write };
cs


위의 코드의 세 번째 줄에서 Write 컴포넌트를 import 하고 5 번째 줄에서 export 했습니다.


추가적으로 write 컴포넌트의 스타일을 추가해줍시다.


(./src/style.css)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* WRITE */
.write .materialize-textarea {
    padding: 0px;
    padding-bottom: 36px;
    margin-bottom: 0px;
    font-size: 18px;
}
.write .card-content {
    padding-bottom: 10px;
}
.write .card-action {
    text-align: right;
}
cs


1-5) Home 컨테이너에서 Write 컴포넌트 렌더링하기


정확한 '/' 경로에서 Write 컴포넌트가 렌더링 될 수 있도록 Home 컨테이너에서 Write 컴포넌트를 렌더링 하도록 합시다.


(./src/containers/Home.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { Component } from 'react';
import { Write } from 'components';
 
class Home extends Component {
 
    render() {
      return (
          <div>
            <Write />
          </div>
      );
    }
}
 
export default Home;
cs


코드의 두 번째 줄에서 Write 컴포넌트를 import 하고 9 번째 줄에서 렌더링 했습니다.


서버를 재시작하여 잘 구현되었는지 확인해 보도록 하겠습니다.



잘 구현되고 있네요. 그런에 Header 와 Write 사이의 간격이 조금 좁아 답답해 보입니다.

이를 해결해 보도록 하겠습니다.


이는 style 에서 위쪽 여백을 주면되는데요, Write 컴포넌트에 주는것이 아니라 Home 컨테이너에 주도록 하겠습니다.

이유는 로그인이 안 된 상태거나 담벼락인 상태일 경우 Write 컴포넌트가 나오지 않고 바로 메모들이 표시될 것이기 때문입니다.


(./src/containers/Home.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { Component } from 'react';
import { Write } from 'components';
 
class Home extends Component {
 
    render() {
      return (
          <div className="wrapper">
            <Write />
          </div>
      );
    }
}
 
export default Home;
cs


Home 컨터이너가 렌더링 되는 부분의 div 태그에 wrapper 라는 class 를 추가했습니다.

* class 가아니라 className 인 이유는 JSX 문법에 따라야하기 때문입니다. 이에 익숙하지 않으신 분들은 아래의 링크를 참조해 주세요.

<React component, JSX>


wrapper class 에 대한 스타일도 추가해 줍시다.


(./src/style.css)

1
2
3
4
/* Home 위쪽 여백 20px */
.wrapper {
    margin-top: 20px;
}
cs


어때요? 위쪽 여백이 좀 늘어났나요?


1-6) Home 컨테이너에 Redux 연결하기


이제 Redux 에서 구현한 thunk 함수와 리덕스 state 를 Home 컨테이너에 연결하도록 하겠습니다.

연결을 통해서 Home 컴포넌트에서 thunk 함수와 리덕스 state 를 props 처럼 사용할 수 있습니다.


(./src/containers/Home.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React, { Component } from 'react';
import { Write } from 'components';
import { connect } from 'react-redux';
import { memoPostRequest } from 'actions/memo';
 
class Home extends Component {
 
    render() {
      return (
          <div className="wrapper">
            <Write />
          </div>
      );
    }
}
 
const mapStateToProps = (state) => {
    return {
        isLoggedIn: state.authentication.status.isLoggedIn,
        postStatus: state.memo.post
    };
};
 
const mapDispatchToProps = (dispatch) => {
    return {
        memoPostRequest: (contents) => {
            return dispatch(memoPostRequest(contents));
        }
    };
};
 
export default connect(mapStateToProps, mapDispatchToProps)(Home);
cs


코드의 4 번째 줄에서 Redux 에서 정의한 thunk 함수인 memoPostRequest 를 import 했습니다.


코드의 17~32 번째 줄을 통하여 리덕스 state 와 thunk 함수를 Home 컨테이너가 전달받은 props 처럼 사용할 수 있도록 설정했습니다.


1-7) thunk 실행 메소드 Write 컴포넌트에 전달


Home 컨테이너에서 thunk 를 실행하는 메소드를 만들어 Write 컴포넌트로 전달해 주도록 하겠습니다.


(./src/containers/Home.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
handlePost = (contents) => {
    return this.props.memoPostRequest(contents).then(
        () => {
            if(this.props.postStatus.status === "SUCCESS") {
                // TRIGGER LOAD NEW MEMO
                Materialize.toast('Success!'2000);
            } else {
                /*
                  ERROR CODES
                      1: NOT LOGGED IN
                      2: CONTENTS IS NOT STRING
                      3: EMPTY CONTENTS
                */
                let $toastContent;
                switch(this.props.postStatus.error) {
                    case 1:
                        // IF NOT LOGGED IN, NOTIFY AND REFRESH AFTER
                        $toastContent = $('<span style="color: #FFB4BA">You are not logged in</span>');
                        Materialize.toast($toastContent, 2000);
                        setTimeout(()=> {location.reload(false);}, 2000);
                        break;
                    case 2:
                        $toastContent = $('<span style="color: #FFB4BA">Contents should be string</span>');
                        Materialize.toast($toastContent, 2000);
                        break;
                    case 3:
                        $toastContent = $('<span style="color: #FFB4BA">Please write Something</span>');
                        Materialize.toast($toastContent, 2000);
                        break;
                    default:
                        $toastContent = $('<span style="color: #FFB4BA">Something Broke</span>');
                        Materialize.toast($toastContent, 2000);
                        break;
                }
            }
        }
    );
}
...
render() {
      const write = ( <Write onPost={this.handlePost}/> );
      return (
          <div className="wrapper">
            { this.props.isLoggedIn ? write : undefined }
          </div>
      );
    }
...
 
cs


코드의 첫 번째 줄에서 정의된 handlePost 메소드는 thunk 함수를 실행시키고 Redux state 가 변경된 이후를 정의하고 있습니다. (.then)

업데이트 된 state (리덕스 스테이트지만 연결을 통해 Home 에서 this.props.postStatus.status 로 접근가능) 의 값이 "SUCCESS" 라면 메모작성이 성공했다는 메시지를 띄웁니다. 

* 메모 읽기 기능을 구현한 뒤에 새 메모를 불러오는 메소드의 실행을 추가할 예정입니다.

만약 업데이트 된 state 의 값이 "SUCCESS" 가 아니라면 API 가 리턴한 에러코드의 값으로 업데이트 된 state.error 에 맞게 경고창을 띄웁니다.


41 번째 줄에서 Write 컴포넌트에 handlePost 메소드를 onPost props 로 전달하고, write 라는 상수에 담았습니다.

그리고 44 번째 줄을 통해 로그인 된 상태에서만 Write 컴포넌트를 렌더링하도록 설정했습니다.


1-8) Write 컴포넌트에서 인자를 결정해 thunk 사용


Write 컴포넌트에서 onPost props 로 전달받은 thunk 를 사용하도록 구현합니다.

이 때 thunk 의 인자 contents 를 Write 컴포넌트의 state 를 통해 결정합니다.


(./src/components/Write.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import React, { Component } from 'react';
import PropTypes from 'prop-types';
 
class Write extends Component {
  state = {
    contents: ''
  }
 
  handleChange = (e) => {
      this.setState({
          contents: e.target.value
      });
  }
 
  handlePost = () => {
      let contents = this.state.contents;
 
      this.props.onPost(contents).then(
          () => {
              this.setState({
                  contents: ""
              });
          }
      );
  }
 
  render() {
      return (
        <div className="container write">
            <div className="card">
                <div className="card-content">
                    <textarea className="materialize-textarea"
                              placeholder="Write down your memo"
                              onChange={this.handleChange}
                              value={this.state.contents}></textarea>
                </div>
                <div className="card-action">
                    <a onClick={this.handlePost}>POST</a>
                </div>
            </div>
        </div>
      );
  }
}
 
Write.propTypes = {
    onPost: PropTypes.func
};
 
Write.defaultProps = {
    onPost: (contents) => { console.error("post function not defined"); }
};
 
export default Write;
cs


props 를 전달 받았으므로 propTypes 와 defaultProps 를 46~52 번째 줄과 같이 추가했습니다.


Write 컴포넌트에서 사용자가 입력하는 textarea 에 onChange 이벤트로 handleChange 메소드를 등록했습니다.

handleChange 메소드는 9 번째 줄에 정의한 메소드로, 사용자가 textarea 태그에 입력한 값을 Write 컴포넌트의 state 로 지정하는 메소드입니다. 이와 동시에 textarea 의 value 값을 this.state.contents 로 설정하여 입력한 값과 state 와 textarea 의 value 값이 실시간으로 일치하도록 설정했습니다. (코드의 35 번째 줄)


15 번째 줄에서 컴포넌트 state 를 인자로 하여 thunk 를 실행하고, textarea 를 비우도록 설정했습니다. (handlePost 메소드)


그리고 38 번째 줄에서 POST 버튼(a 태그)을 클릭하면 handlePost 메소드가 실행되도록 onClick 이벤트에 등록하였습니다.


서버를 재시작 한 뒤에 '/' (메인 화면) 에서 로그인을 한 뒤 메모를 작성해 보세요.

SUCCESS 라는 알림이 잘 뜨던가요?

* 작성한 메모를 보는 작업은 다음 포스팅에서 하겠습니다.


여기까지...


Memo 에 관련된 기능 충 첫 번째인 Create 를 구현해 보았습니다.

전체적인 작업의 메커니즘은 인증때와 크게 다르지 않습니다.

여기까지 따라오시면서 조금 익숙해지셨나요? 그러셨길 바라면서 이만 줄이겠습니다.


감사합니다.


**참고 자료 (항상 감사드립니다)

<React.js codelab 2016 - Velopert>


*이 포스팅이 도움이 되셨다면 다녀가셨다는 표시로 공감 부탁드릴게요! (로그인 하지 않으셔도 공감은 가능합니다 ㅎㅎ)

Comments