[Series] Các mẫu thiết kế component trong React – Phần 6: Giới thiệu về Control Props

Control Props Pattern:

  • cho phép user có thể quản lý 1 hay nhiều props trong state component,
  • nếu user không quản lý, tự bản thân component sẽ quản lý  state.
  • ngược lại user sẽ phải tự quản lý sự thay đổi giá trị mà user đã cung cấp cho component

Thực hiện:

  1. Do giá trị state của component có thế từ phía user truyền vào hoặc không nên ta cần hàm getState để trả về state mà component cần sử dụng.
      isControlled = key => this.props[key] !== undefined;
    
      getState = (state = this.state) =>
        Object.entries(state).reduce((combineState, [key, value]) => {
          if (this.isControlled(key)) {
            combineState[key] = this.props[key];
          } else {
            combineState[key] = value;
          }
          return combineState;
        }, {});
  2. Component cung cấp 1 hàm update state:
      • mỗi khi state thay đổi thì gọi hàm handleChangeState với tham số là state đã thay đổi để người dùng có thể update lại props của mình theo ý muốn.
      • chỉ update state nếu các thay đổi không bao gồm props mà user control
      internalSetState = (changes, callback = emptyFnc) => {
        let allChanges;
        this.setState(
          state => {
            const combineState = this.getState(state);
            const changesObject =
              typeof changes === "function" ? changes(combineState) : changes;
            allChanges = changesObject;
            const nonControlledChanges = Object.entries(changesObject).reduce(
              (newChanges, [key, value]) => {
                if (!this.isControlled(key)) {
                  newChanges[key] = value;
                }
                return newChanges;
              },
              {}
            );
            return Object.keys(nonControlledChanges).length > 0
              ? nonControlledChanges
              : null;
          },
          () => {
            this.props.onStateChange(allChanges);
            callback(allChanges);
          }
        );
      };
  3. Cập nhật state ở phía user
      handleStateChange = ({ checked }) => {
        this.setState({ appChecked: checked });
      };

Full code:

import React from "react";
import ReactDOM from "react-dom";

import { Input } from "reactstrap";

import "./styles.css";

function emptyFnc() {}

class CheckBox extends React.Component {
  static defaultProps = {
    onStateChange: () => {}
  };
  state = { checked: false };

  internalSetState = (changes, callback = emptyFnc) => {
    let allChanges;
    this.setState(
      state => {
        const combineState = this.getState(state);
        const changesObject =
          typeof changes === "function" ? changes(combineState) : changes;
        allChanges = changesObject;
        const nonControlledChanges = Object.entries(changesObject).reduce(
          (newChanges, [key, value]) => {
            if (!this.isControlled(key)) {
              newChanges[key] = value;
            }
            return newChanges;
          },
          {}
        );
        return Object.keys(nonControlledChanges).length > 0
          ? nonControlledChanges
          : null;
      },
      () => {
        this.props.onStateChange(allChanges);
        callback(allChanges);
      }
    );
  };

  onCheck = () =>
    this.internalSetState(
      ({ checked }) => ({ checked: !checked }),
      allChanges => console.log("click", allChanges)
    );

  isControlled = key => this.props[key] !== undefined;

  getState = (state = this.state) =>
    Object.entries(state).reduce((combineState, [key, value]) => {
      if (this.isControlled(key)) {
        combineState[key] = this.props[key];
      } else {
        combineState[key] = value;
      }
      return combineState;
    }, {});

  render() {
    return (
      <Input
        type="checkbox"
        checked={this.getState().checked}
        onClick={this.onCheck}
      />
    );
  }
}

class App extends React.Component {
  initialState = { appChecked: false };
  state = this.initialState;

  handleStateChange = ({ checked }) => {
    this.setState({ appChecked: checked });
  };

  render() {
    const { appChecked } = this.state;
    return (
      <React.Fragment>
        <CheckBox checked={appChecked} onStateChange={this.handleStateChange} />
        <CheckBox checked={appChecked} onStateChange={this.handleStateChange} />
      </React.Fragment>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

 

[Series] Các mẫu thiết kế component trong React – Phần 5: Giới thiệu về Render Props Patern và State Reducer

Render props:

  • 1 hàm render JSX dựa trên state và helper argument.
  • simple and powerful pattern
  • flexible trong việc chia sẻ component logic mà vẫn giữ được UI flexibility

State Reducer: là 1 pattern cho phép user có thể quản lý được state của component

Implement:

Ở component ta viết 1 hàm internalSetState dùng để update state ( gồm state đã thay đổi sau khi thực thi component logic và state mà user mong muốn )

  internalSetState = (changes, callback) => {
    this.setState(state => {
      const changesObject =
        typeof changes === "function" ? changes(state) : changes;
      const reducerChange = this.props.stateReducer(state, changesObject);
      return reducerChange;
    }, callback);
  };

Component cung cấp 1 hàm cho phép user định nghĩa state mong muốn thay đổi

  updateStateReducer = (state, changes) => {
    if (this.state.timesClicked > 4) {
      return {
        ...changes,
        checked: false
      };
    }
    return changes;
  };

Full code:

import React from "react";
import ReactDOM from "react-dom";

import { Input } from "reactstrap";

import "./styles.css";

class CheckBox extends React.Component {
  static defaultProps = {
    initialCheck: false,
    onReset: () => {}
  };
  initialState = { checked: this.props.initialCheck };
  state = this.initialState;

  internalSetState = (changes, callback) => {
    this.setState(state => {
      const changesObject =
        typeof changes === "function" ? changes(state) : changes;
      const reducerChange = this.props.stateReducer(state, changesObject);
      return reducerChange;
    }, callback);
  };

  onCheck = () =>
    this.internalSetState(
      ({ checked }) => ({ checked: !checked }),
      () => this.props.handleCheck(this.state.checked)
    );

  reset = () =>
    this.internalSetState(this.initialState, () =>
      this.props.onReset(this.state.checked)
    );

  getStateAndHelper = () => ({
    checked: this.state.checked,
    onCheck: this.onCheck,
    reset: this.reset
  });

  render() {
    return this.props.children(this.getStateAndHelper());
  }
}

class App extends React.Component {
  initialState = { timesClicked: 0 };
  state = this.initialState;

  handleCheck = (...args) => {
    this.setState(({ timesClicked }) => ({ timesClicked: timesClicked + 1 }));
  };

  handleReset = (...args) => this.setState(this.initialState);

  updateStateReducer = (state, changes) => {
    if (this.state.timesClicked > 4) {
      return {
        ...changes,
        checked: false
      };
    }
    return changes;
  };

  render() {
    const { timesClicked } = this.state;
    return (
      <CheckBox
        handleCheck={this.handleCheck}
        stateReducer={this.updateStateReducer}
        onReset={this.handleReset}
      >
        {({ checked, onCheck, reset }) => (
          
{timesClicked > 4 ? "Click reset để active component" : timesClicked > 0 ? `Time Clicked: ${timesClicked}` : null} Reset
)} </CheckBox> ); } } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);