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:
- 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; }, {}); - 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); } ); }; - 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);
