Tạo ứng dụng ghi chú với ReactJS và Redux

Trong bài viết này mình sẽ hướng dẫn xây dựng một ứng dụng ghi chú đơn giản sử dụng ReactJS và Redux. Đây cũng là một project nhỏ giúp làm quen với Redux.

Bạn cần chuẩn bị một vài kiến thức về Redux trước khi bắt tay vào xây dựng ứng dụng bằng cách tham khảo bài viết về tích hợp Redux vào ReactJS.

Ở đây ta sẽ xây dựng ứng dụng ghi chú đơn giản với chức năng bao gồm thêm, sửa, xóa ghi chú, có cấu trúc thư mục src như sau:

src/
....const/
        index.js
.....actions/
        index.js
....reducers/
        noteReducer.js
        index.js
----components/
       AddNote.css
       AddNote.js
       ShowNote.js
....App.js

Ngoài ra mình có sử dụng thư viện Bootstrap 4 để xây dựng giao diện, hãy tham khảo thêm về Bootstrap 4 để có thể hiểu rõ hơn nhé.

ung dung ghi chu react js JPG
Giao diện của ứng dụng ghi chú

1. Cài đặt thư viện cần thiết

Trước tiên cần phải cài đặt redux cho dự án của mình bằng npm với câu lệnh:

npm install redux react-redux --save

Để có thể xây dựng giao diện dễ dàng hơn ta sẽ sử dụng Bootstrap 4, tiến hành thêm thư viện bootstrap vào bên trong thẻ <head> của file public/index.html.

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">

Bước tiếp theo phải phân tích xem project cần những chức năng gì, để từ đó xây dựng cấu trúc redux một cách hợp lí hơn. Đối với project này bao gồm 3 chức năng chính đó là thêm, sửa, xóa ghi chú. Đồng nghĩa với đó là sẽ có 3 actions, 1 reducers và 3 const,..chúng ta sẽ triển khai ở bên dưới.

2. Triển khai Redux

Chúng ta sẽ triển khai mô hình Redux với 3 thư mục đó là actions, reducers, const,..Với từng thư mục sẽ có chức năng riêng.

Thư mục const sẽ chứa các hằng số hỗ trợ trong quá trình triển khai dự án. Ta sẽ khai báo các hằng số ở trong file const/index.js.

// const/index.js
export const ADD_NEW_NOTE = "ADD_NEW_NOTE";
export const REMOVE_NOTE = "REMOVE_NOTE";
export const EDIT_NOTE = "EDIT_NOTE";

Thư mục actions sẽ bao gồm các hàm chứa hành động để thực hiện dispatch tới reducers. Ở đây chúng ta sẽ triển khai các hàm ở file actions/index.js.

import { ADD_NEW_NOTE, REMOVE_NOTE, EDIT_NOTE } from "../const/index";
//action thêm note
export const actAddNote = (content) => {
  return {
    type: ADD_NEW_NOTE,
    content,
  };
};
//Xóa note
export const actRemoveNote = (id) => {
  return {
    type: REMOVE_NOTE,
    id,
  };
};
//Sửa note
export const actEditNote = (id, content) => {
  return {
    type: EDIT_NOTE,
    id,
    content,
  };
};

Có 3 hàm được export ở file actions/index.js đó là actAddNote, actRemoveNote, actEditNote dùng để tạo action cho chức năng thêm, sửa, xóa note.

Thư mục reducers sẽ chứa các hàm chứa các reducers trong Redux, có 2 files đó là

  • noteReducers.js : reducers hỗ trợ thực thi hành động với ghi chú như thêm, sửa, xóa. Ta có thể tạo nhiều reducers như này, và gộp nó vào bằng phương thức combineReducers trong redux.
  • index.js : trong trường hợp có nhiều reducers thì gộp tất cả reducers ở đây.
//reducesr/noteReducers.js

//Import các const
import { ADD_NEW_NOTE, REMOVE_NOTE, EDIT_NOTE } from "../const/index";

const noteReducers = (state = [], action) => {
  switch (action.type) {
    case ADD_NEW_NOTE:
      const generateID = new Date().getTime();
      state = [...state, { id: generateID, content: action.content }];
      return state;

    case EDIT_NOTE:
      const indexOfEditNote = state.findIndex((note) => note.id === action.id);
      if (indexOfEditNote !== -1)
        state[indexOfEditNote].content = action.content;
     
      return state;

    case REMOVE_NOTE:
      const idRemove = action.id;
      state = state.filter(note => {
        if (note.id === idRemove) 
          return false
        return true
      })
      return state;
    default:
      return state;
  }
};

export default noteReducers
//reducers/index.js
import {combineReducers} from 'redux'
import noteReducers from './noteReducer'

export default combineReducers({
    note: noteReducers
})

Để các component khác có thể tương tác với Store ta cần bọc root component bởi Provider. Ở file src/index.js sẽ chỉnh sửa lại như sau.

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";

import { Provider } from "react-redux";
import { createStore } from "redux";

//Gọi reducers
import reducers from "./reducers/index";


//Tạo store từ reducers
const store = createStore(reducers);

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
);

serviceWorker.unregister();

Vậy là các bước cài đặt redux xong, tiếp theo sẽ là đi xây dựng các component.

3. Xây dựng giao diện ứng dụng ghi chú

Bây giờ sẽ đi xây dựng giao diện và kết nối với store redux trong mỗi component. Ví dụ này sẽ có 2 components đó thêm và hiển thị ghi chú (nằm trong thư mục src/components).

Ở đây 2 component AddNoteShowNote sẽ được render ở trong file src/App.js

//file: src/App.js
import React from "react";
import "./App.css";
//Kết nối vơi redux
import { connect } from "react-redux";
import ShowNote from './components/ShowNote'
import AddNote from './components/AddNote'
function App(props) {
  return (
    <div className="row" style={{ marginTop: "3%" }}>
      <AddNote />
      {props.note.map((note, index) => {
          // Render ra lần lượt các ghi chú.
          return <ShowNote noteData = {note} key={note.id}/>
      })}
    </div>
  );
}

// Lấy state từ store bằng mapStateToProps
// Lúc này state nhận được sẽ gán vào props
const mapStateToProps = (state, ownProps) => {
  // Gán state nhận về từ store 
  // thành props có tên note (props.note)
  return {
    note: state.note,
  };
};

export default connect(mapStateToProps, null)(App);

Component AddNote sẽ có chức năng thêm ghi chú mới, file AddNote.js sẽ có nội dung như sau:

//file AddNote.js

import React, { useState, useRef } from "react";
import { connect } from "react-redux";
//Import actions vào đây
import { actAddNote } from "../actions/index";

function AddNote(props) {
  const [content, setContent] = useState();

  //Refs: Giúp chúng ta tương tác với DOM thật

  const noteInput = useRef(null)

  //Khi click vào nút thêm sẽ gọi hàm này,
  const handleAdd = () => {

    // Dispatch action.
    // Props này được tạo bởi hàm
    // mapDispatchToProps bên dưới
    props.addNote(content)

    //Gán giá trị cho input thành rỗng
    noteInput.current.value = ''

    //Cập nhật lại state content
    setContent('')
  };
  
  return (
    <div className="col-md-12" style={{ marginBottom: 15 }}>
      <div className="input-group mb-8">
        <input
          type="text"
          className="form-control"
          placeholder="Nội dung ghi chú"
          value =  {content}
          onChange={(e) => {
            setContent(e.target.value)
          }}
          ref={noteInput}
        />
        <div className="input-group-append">
          <button className="btn btn-primary" onClick={handleAdd}>
            Thêm
          </button>
        </div>
      </div>
    </div>
  );
}

//Chuyển dispatch thành props.
//Ở đây nếu mình muốn dispatch action actAddNote
//thì chỉ cần gọi props.addNote(content)
const mapDispatchToProps = (dispatch) => {
  return {
    addNote: (content) => {
      dispatch(actAddNote(content));
    },
  };
};
export default connect(null, mapDispatchToProps)(AddNote);

Component ShowNote sẽ nhận vào một props là dataNote chứ các thuộc tính của từng ghi chú. Component này sẽ có chức năng sửa và xóa các ghi chú.

//file: src/components/ShowNote.js
import React, { useState } from "react";
import "./ShowNote.css";
import { connect } from "react-redux";
import { actEditNote, actRemoveNote } from "../actions/index";

function ShowNote(props) {
  const [noteContent, setNoteContent] = useState(props.noteData.content)

  //Lấy ID của ghi chú 
  const noteID = props.noteData.id


  // Được gọi mỗi khi thay đổi giá trị
  // của ghi chú.
  const handleChange = (e) => {
    setNoteContent(e.target.value)
    props.editNote(noteID, e.target.value)
  }

  //Được gọi khi click vào 
  //xóa ghi chú.
  const handleRemoveNote = () => {
    props.removeNote(noteID)
  }
  return (
    <div className="col-md-4" style={{marginTop: 10}}>
      <div className="card bg-warning">
        <div className="card-body" style={{ height: 200 }}>
          <textarea value= {noteContent} onChange = {handleChange}></textarea>
        </div>
        <div className="card-footer">
          <button className="btn btn-danger btn-sm float-right" onClick={handleRemoveNote}>Xóa</button>
        </div>
      </div>
    </div>
  );
}

// chuyển dispatch thành props
const mapDispatchToProps = (dispatch) => {
  return {
    editNote: (id, content) => {
      dispatch(actEditNote(id, content));
    },
    removeNote: id => {
      dispatch(actRemoveNote(id));
    }
  };
};

// chuyển state từ store thành props
// của component
const mapStateToProps = (state, ownProps) => {
  return {
    note: state.note,
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(ShowNote);

Để giao diện của component ShowNote đẹp hơn 1 chút thì ta sẽ thêm một vài đoạn css ở file ShowNote.css

/* src/components/ShowNote.css */
textarea {
    background-color: rgba(0, 0, 0, 0);
    border-width: 0;
    overflow: hidden;
    resize: none;
}
textarea:focus{
    outline: none;
}

Đây cũng là buớc cuối cùng để xây dựng ứng dụng ghi chú, để khởi chạy dự án ta chỉ cần mở terminal và gõ dòng lệnh:

npm start

Trên đây là ví dụ về khởi tạo ứng dụng ghi chú sử dụng ReactJS và Redux, mong rằng ví dụ này sẽ giúp bạn có thể nắm chắc hơn về cách triển khai redux trong một dự án thực tế.

icon down gif Tải về tài nguyên của bài viết này.

DEMO

Chủ đề Học ReactJS

Bạn đang tìm Hosting / VPS để chạy dự án?

Hiện nay, Tinohost là một trong những nhà cung cấp Hosting tốt nhất Việt Nam. Với đội ngũ hỗ trợ nhanh chóng, giá cả phù hợp, nhiều dịch vụ miễn phí kèm theo như:

  • Hỗ trợ di chuyển dữ liệu
  • Cho dùng thử 7 ngày miễn phí
  • Tự động backup cho khách hàng (cả VPS cũng đc backup)

Sử dụng mã TINO30_2020 để được giảm 30% nhé

VÀO TINOHOST NGAY