Tag: react hooks

  • [React] Sử dụng mẫu container trong React như thế nào?

    [React] Sử dụng mẫu container trong React như thế nào?

    Chắc hẳn ai cũng biết tới nguyên lý đầu tiên của SOLID đó là trách nhiệm đơn nhất. Nghĩa là mỗi một đối tượng chỉ làm một nhiệm vụ duy nhất. Đơn giản vì với tính đơn nhất chúng ta sẽ dễ dàng kiểm soát mã nguồn hơn (cho cả người code lần người review). Chưa kể tới việc với tính đơn nhất thì mã nguồn của bạn cũng trở nên trong sáng hơn. Với anh em lập trình React cũng vậy, bài toán khá đau đầu là làm sao tách logic ra khỏi phần mã nguồn kết xuất HTML. Cách làm như vậy giúp chúng ta thay vì viết một React Component để là cả hai việc thì bây giờ chúng ta sẽ viết hai React Component để làm hai nhiệm vụ khác nhau. Trong bài viết này tôi sẽ hướng dẫn các bạn sử dụng mấu container để làm việc đó.

    Container component là gì?

    Chúng ta không có một định nghĩa chính xác nào về container component cả, container component đơn giản là một React Component làm nhiệm vụ goi API để lấy dữ liệu và sau đó truyền nó xuống một React Component để kết xuất mã HTML.

    Định nghĩa container

    Để các bạn dễ hình dung tôi sẽ viết thử một container, container này sẽ thực hiện lấy thông tin của octocat

    src/containers/OctocatContainer.jsx

    import React, { useState, useEffect } from 'react';
    import { Octocat } from '../components/Octocat';
    export function OctocatContainer() {
      const [octocat, setOctocat] = useState(null);
    
      useEffect(() => {
        async function getOctocat() {
          // gọi hàm fetch với phướng thực mặc định là GET
          const response = await fetch('https://api.github.com/users/octocat');
          const body = await response.json();
          // sử dụng React Hook để thông tin octocat vào state
          setOctocat(body);
        }
    
        getOctocat();
      }, []); // tham số mảng rỗng thể hiện hàm effect không phụ thuộc vào đối tượng nào, nó tương đương với hàm componentDidMount
    
      return <Octocat {...octocat} />;
    }
    

    Trong ví dụ trên tôi có sử dụng thêm JSX spread attributes để tránh việc phải khai báo props cho Octocat component. Mẫu này đặc biệt có ý nghĩa đối với các component mà bạn cần khai báo nhiều props.

    Định nghĩa component để kết xuất mã HTML

    Sau khi có dữ liệu rồi thì chúng ta sẽ kết xuất nó ra HTML như sau:

    src/components/Octocat/Octocat.jsx

    import React from 'react';
    
    export function Octocat(props) {
      const { login, avatar_url, name } = props;
      return (
        <ul>
          <li>{login}</li>
          <li>{name}</li>
          <li>
            <img src={avatar_url} alt={login} />
          </li>
        </ul>
      );
    }
    

    Như các bạn thấy, component trên chỉ làm nhiệm vụ bind dữ liệu lên màn hình mà thôi.

    Cùng xem kết quả nhé 🙂

    octocat

  • [React] Tôi đã làm toast component như thế nào?

    [React] Tôi đã làm toast component như thế nào?

    Với bất kỳ ứng dụng nào việc hiển thị thông báo lỗi là không thể thiếu được. Các bạn có thể có rất nhiều cách làm để hiển thị được các thông báo lỗi này. Trong bài viết này tôi xin chia sẻ cách làm của tôi để các bạn cùng tham khảo nhé.

    Với bất kỳ ứng dụng nào việc hiển thị thông báo lỗi là không thể thiếu được. Các bạn có thể có rất nhiều cách làm để hiển thị được các thông báo lỗi này. Trong bài viết này tôi xin chia sẻ cách làm của tôi để các bạn cùng tham khảo nhé.

    Mục đích của tôi khi viết component này phải đảm bảo nó dễ dùng như hàm window.alert() vậy. Trong bài viết này tôi sẽ sử dụng Material UI để hiển thị thông báo lỗi.

    Toast Context

    Vì chúng ta cần hàm hiển thị thông báo lỗi có thể sử dụng như window.alert() nên chúng ta cần khai báo một nơi lưu trữ để từ đó chúng ta có thể gọi được ở bất kỳ đâu trong ứng dụng. Có nhiều bạn sẽ nghĩ tới Redux nhưng trong bài viết này tôi sẽ sử dụng React Context API để làm việc đó. Chúng ta bắt đầu bằng việc khai báo ToastContext nào.

    src/contexts/toast.js

    import React, { useContext } from 'react';
    
    export const ToastContext = React.createContext();
    
    export const useToast = () => useContext(ToastContext);
    

    Toast Provider

    Trong React Context API có định nghĩa Provider để khi có sử thay đổi trong component thì các component bên trong có thể nhận biết sử thay đổi đó và phản anh kết quả thay đổi lên màn hình. Ở đây chúng ta cần hiển thị message lỗi mỗi khi có component nào đó thông báo về việc hiển thị message lỗi.

    import React, { useState } from 'react';
    import { Snackbar } from '@material-ui/core';
    import { Alert } from '@material-ui/lab';
    import { ToastContext } from '../contexts/toast';
    
    export const ToastProvider = (props) => {
      const { children } = props;
      const [state, setState] = useState({ isOpen: false });
    
      const show = (message) => {
        setState({ isOpen: true, message });
      };
    
      const hide = () => setState({ isOpen: false });
    
      const error = (message) => {
        show({ type: 'error', text: message });
      };
    
      const warn = (message) => {
        show({ type: 'warning', text: message });
      };
    
      const info = (message) => {
        show({ type: 'info', text: message });
      };
    
      const success = (message) => {
        show({ type: 'success', text: message });
      };
      const { isOpen, message } = state;
      return (
        <ToastContext.Provider
          value={{
            error: error,
            warn: warn,
            info: info,
            success: success,
            hide: hide
          }}>
          {children}
          {message && (
            <Snackbar open={isOpen} autoHideDuration={6000} onClose={hide}>
              <Alert elevation={6} variant="filled" onClose={hide} severity={message.type}>
                {message.text}
              </Alert>
            </Snackbar>
          )}
        </ToastContext.Provider>
      );
    };
    

    Sử dụng các hàm error, warn, info, success để hiển thị thông báo

    Để sử dụng được các hàm này trong ứng dụng bạn cần khai báo nó trong component chính của ứng dụng

    import React from 'react';
    import { ToastProvider } from './providers/ToastProvider';
    import { useToast } from './contexts/toast';
    import './App.css';
    
    function ButtonList() {
      const { error, warn, info, success } = useToast();
      return (
        <>
          <button onClick={() => error('error message!')}>error</button>
          <button onClick={() => warn('warn message!')}>warn</button>
          <button onClick={() => info('info message!')}>info</button>
          <button onClick={() => success('success message!')}>success</button>
        </>
      );
    }
    
    function App() {
      return (
        <ToastProvider>
          <ButtonList />
        </ToastProvider>
      );
    }
    export default App;
    

    Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết đã cung cấp cho các bạn thêm một các để hiển thị thông báo tốt hơn cho ứng dụng của bạn.

  • [React] Class Component và Function Component, bạn chọn viết theo cách nào?

    [React] Class Component và Function Component, bạn chọn viết theo cách nào?

    Trong thế giới React thì chắc hẳn ai cũng biết đến Class Component và Function Component. Tuy nhiên có thể có những hiểu nhầm về hai loại component này. Trong bài viết này tôi sẽ thử so sánh hai cách viết này để giúp bạn có thể lựa chọn viết theo cách nào. Chúng ta cùng bắt đầu nhé.

    Cú pháp

    Khác nhau đầu tiên giữa Class ComponentFunction Component thể hiện ngay ở cách khai báo.

    Class Component

    import React, { Component } from 'react';
    
    class TestComponent extends Components {
      // phương pháp này bắt buộc phải khai báo hàm để kết xuất mã HTML
      render() {
        return <div>TestComponent</div>;
      }
    }
    

    Cách khai báo này khá quen thuộc với các bạn có nền tảng lập trình hướng đối tượng (OOP). Với những bạn mới học React hoặc chuyển sang học React thì phương pháp tiếp cận này có vẻ phù hợp và dễ hiểu.

    Function Component

    import React from 'react';
    
    export function TestComponent() {
      // phương pháp xem kết xuất mã HTML như là giá trị trả về của hàm
      return <div>TestComponent</div>;
    }
    

    Function component sử dụng cách tiếp cận khác đó là sử dụng pure function để khai báo component. Ban đầu function component được sử dụng để viết các component chỉ với mục đích kết xuất HTML mà thôi. Với các component với theo hướng tiếp cận này thì bạn sẽ không can thiệp được vào lifecycle của component. Do đó nó thướng được biết đến với tên gọi Stateless Component.

    Props

    Class Component

    props trong Class Component được xem như giá trị truyển vào cho hàm khởi tạo class.

    import React, { Component } from 'react';
    
    class TestComponent extends Components {
      constructor(props) {
        super(props); // bắt buộc phải có dòng này để gọi hàm khởi tạo của class cha nhé
      }
    
      render() {
        return <div>TestComponent</div>;
      }
    }
    

    Function Component

    props trong Function Component thì được xem như là giá trị truyền vào hàm pure function khi định nghĩa component.

    import React from 'react';
    
    export function TestComponent(props) {
      return <div>TestComponent</div>;
    }
    

    Định nghĩa defaultPropspropTypes thì không có sự khác biệt giữa Class Component và Function Component.

    TestComponent.defaultProps = {};
    
    TestComponent.propTypes = {};
    

    State

    Trước khi React Hooks ra đời thì như đã nói ở trên Function Component con được biết đến với tên gọi Stateless Component. Nghĩa là nó không có state. Khi React Hooks ra đời thì Function Component cũng có state của riêng nó.

    Class Component

    state trong Class Component dược định nghĩa như sau:

    import React, { Component } from 'react';
    
    class TestComponent extends Components {
      constructor(props) {
        super(props);
        // khởi tạo giá trị state
        this.state = { isLoading: false };
      }
    
      render() {
        return <div>TestComponent</div>;
      }
    }
    

    Khi muốn thay đổi giá trị state bạn gọi phương thức setState của component:

    this.setState((state) => ({ isLoading: true }));
    

    Function Component

    state trong Function Component được định nghĩa như sau:

    import React, { useState } from 'react';
    
    export function TestComponent(props) {
      // giá trị khởi tạo state được truyền vào trong useState hook
      const [state, setState] = useState({ isLoading: false });
    
      return <div>TestComponent</div>;
    }
    

    Các bạn để ý hàm useState trả về giá trị của component state trong biến state và hàm setState. Khi muốn thay đổi giá trị của state thì bạn có thể gọi hàm setState.

    setState({ isLoading: true });
    

    Component Lifecycle

    Với Class component các bạn sẽ thấy component lifecycle khá rõ ràng với các hàm như componentDidMount, componentDidUpdate. Function Component thì không như vậy, toàn bộ việc sử lý lifecycle đều thông qua useEffect hook.

    // componentDidMount
    useEffect(() => {
      return () => {}; // componentWillUnmount
    }, []);
    
    // componentDidUpdate
    useEffect(() => {
      return () => {}; // componentWillUnmount
    }, [state]);
    

    Như các bạn thấy thì componentDidMountcomponentDidUpdate không chỉ định rõ khi nào thì hàm được gọi. Việc gọi hàm thì tự chúng ta hiểu dựa theo lifecycle của React component thôi. Với Function Component và useEffect thì khác, bạn có thể thấy [][state] chị định rõ ràng đối tượng phụ thuộc mà khi chúng thay đổi thì hàm truyển vào useEffect sẽ được gọi. Có vẻ như nó lại tường mình hơn là các hàm lifecycle trong class Component.

    Sau sự có mặt của TypeScript thì có lẽ Class Component và React Hooks thì có lẽ bạn Class Component chiếm ưu thế tuyệt đối. Thời điểm đó anh em thi nhau viết Class Component với TypeScript và tôi cũng thế :). Ngay đến cả facebook cũng support TypeScript với create-react-app nữa cơ mà. Thế nhưng khi React Hooks xuất hiện thì có vẻ gió đã đổi chiều, việc xử lý state và lifecycle với hook có vẻ đơn giản hơn rất nhiều. Các bạn thì thấy thế nào. Bạn sẽ chọn cách nào với dự án của mình?

  • [React] Loading indicator with React Context API & Hooks

    [React] Loading indicator with React Context API & Hooks

    Loading indicator có lẽ là component huyền thoại mà dự án nào cũng cần đến. Hôm nay mình sẽ hướng dẫn các bạn viết nó thành dạng global component để có thẻ được gọi tại bất kỳ đâu trong ứng dụng của bạn. Chúng ta cùng bắt đầu nhé.

    Để hiểu thêm về React Context API & Hooks các bạn tham khảo thêm tại đây:

    Chúng ta bắt đầu bằng việc tạo một dự án mới sử dụng React nhé. Trong bài viết này mình sẽ sử dụng create-react-app cùng với yarn để tạo project mới. Các bạn xem hướng dẫn cài yarn tại đây nhé.

    Tạo một project mới

    • Cài create-react-app package:
    hieunv@HieuNV ~ % yarn global add create-react-app
    yarn global v1.22.0
    [1/4] ?  Resolving packages...
    [2/4] ?  Fetching packages...
    [3/4] ?  Linking dependencies...
    [4/4] ?  Building fresh packages...
    success Installed "[email protected]" with binaries:
          - create-react-app
    ✨  Done in 14.08s.
    
    • Sau khi cài xong thì các bạn tạo project mới như sau:
    yarn create react-app loading
    
    Cấu trúc project được tạo bằng create-react-app

    Tạo React context mới để lưu trạng thái của Loading component

    context/loading.js

    import React, { useContext } from 'react';
    
    export const LoadingContext = React.createContext();
    
    export const useLoading = () => useContext(LoadingContext);
    

    Tạo LoadingProvider để lưu trạng thái loading như là một global state.

    providers/LoadingProvider.jsx

    import React, { useState } from 'react';
    import PropTypes from 'prop-types';
    import { LoadingContext } from '../../contexts/loading';
    
    export function LoadingProvider(props) {
      const [loading, setLoading] = useState(false);
    
      return (
        <LoadingContext.Provider
          value={{
            loading: loading,
            show: () => setLoading(true),
            hide: () => setLoading(false)
          }}>
          {props.children}
        </LoadingContext.Provider>
      );
    }
    
    LoadingProvider.propTypes = {
      children: PropTypes.node
    };
    

    Như các bạn nhìn thấy trong đoạn mã trên chúng ta đã sử dụng LoadingContext truyền trang thái loading và các hàm showhide xuống các component con thông qua React Context.

    Tại App component bạn đặt LoadingProvider là component cao nhất để tất các các component con bên trong có thể gọi các hàm showhide khi cần thiết.

    App.js

    import React from 'react';
    import { LoadingProvider } from './providers/LoadingProvider';
    import { useLoading } from './context/loading.js';
    import logo from './logo.svg';
    
    import './App.css';
    
    function App() {
      const { show, hide } = useLoading();
      return (
        <LoadingProvider>
          <div className="App">
            <header className="App-header">
              <img src={logo} className="App-logo" alt="logo" />
              <p>
                Edit <code>src/App.js</code> and save to reload.
              </p>
              <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">
                Learn React
              </a>
            </header>
          </div>
        </LoadingProvider>
      );
    }
    
    export default App;
    

    Tại component bạn muốn gọi show hay hide loading thì chỉ cần thực hiện như sau:

    • import hàm useLoading() được khai báo trong context/loading.js
    import { useLoading } from './context/loading.js';
    
    • Trong component bạn sử dụng hook useLoading để lấy các hàm showhide:
    const { show, hide } = useLoading();
    

    Giờ chúng ta sửa lại một chút ở LoadingProvider để có thể show loading indicator

    import React, { useState } from 'react';
    import PropTypes from 'prop-types';
    import { LoadingContext } from '../../contexts/loading';
    
    function Loading() {
      return <div>Loading...</div>;
    }
    
    export function LoadingProvider(props) {
      const [loading, setLoading] = useState(false);
    
      return (
        <LoadingContext.Provider
          value={{
            loading: loading,
            show: () => setLoading(true),
            hide: () => setLoading(false)
          }}>
          <>
            {loading && <Loading />}
            {props.children}
          </>
        </LoadingContext.Provider>
      );
    }
    
    LoadingProvider.propTypes = {
      children: PropTypes.node
    };
    

    Các bạn để ý component Loading nhé. Chúng ta chỉ cần thêm một chút kỹ thuật css để cho component này đề lên trên tất cả các component khác bằng z-index đã có được loading indicator có thể được gọi ở bất kỳ đâu rồi.

    Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết có thể giúp ích cho dự án của các bạn.