By reading the post Antd Basic – Create first ReactJs Component, You should understand the difference between props and state. In this post, we will introduce the two concepts of “controlled components” and “uncontrolled components“.
Uncontrolled components
Look at this simple example, there is an input component.
const MyInput = ({ onChange }) => (
<input onChange={onChange} />
);
The above component will display an input box, every time there is user input, it will call the passed parameter onChange.
Next, we will use the MyInput component in another component:
class Demo extends React.Component {
onTextChange = (event) => {
console.log(event.target.value);
}
render() {
return (
<MyInput onChange={this.onTextChange} />
);
}
}
In the above code, we encapsulate the MyInput component with a listener function onTextChange.
Now, a reset button is needed. When clicking it, the content of MyInput can be cleared. Then you can adjust it as follows.
<div>
<MyInput onChange={this.onChange} />
<button onClick={this.onTextReset}>Reset</button>
</div>
onTextReset = () => {
// What will we do next?
// Get the input element inside MyInput and set the value to it
}
It is not easy to modify the value of the MyInput component.
For such components that cannot directly control the state, we call them “uncontrolled components“.
Controlled component
Next, we make some adjustments. Transform it into a controlled component.
const MyInput = ({ value = '', onChange }) => (
<input value={value} onChange={onChange} />
);
At this time, the input of MyInput is completely determined by the value attribute.
You will find that you cannot enter anything in the input box with the new code (because the value is always empty).
Let’s modify the demo to make it work again:
class Demo extends React.Component {
state = {
text: '',
}
onTextChange = (event) => {
this.setState({ text: event.target.value });
}
render() {
return (
<MyInput value={this.state.text} onChange={this.onTextChange} />
);
}
}
Well, resetting becomes a breeze:
onTextReset = () => {
this.setState({ text: '' });
}
The two concepts of “controlled” and “uncontrolled” differ in whether the state of this component can be modified externally.
A properly designed component should support both “controlled” and “uncontrolled” forms, that is, when the developer does not control the properties of the component, the component manages its own state, and when the developer controls the properties of the component, the component should be controlled by the properties control.
The development of a complex component needs to pay more attention to this point, to avoid that only some of the properties are controlled, making it a semi-controlled component.
Tabs component
For a typical component example, you can refer to the Antd tabs component:
<Tabs>
<TabPane tab="Tab 1" key="1">Content of Tab Pane 1</TabPane>
<TabPane tab="Tab 2" key="2">Content of Tab Pane 2</TabPane>
</Tabs>
In most cases, developers don’t need to consider how to control which tab page the tabs stay on, and users can click on them when needed. In this case, tabs will run as “uncontrolled components“.
When the activeKey attribute is passed, the tabs component will be transformed into a “controlled component“. Label switching needs to be controlled by code:
<Tabs activeKey={this.state.activeKey} onChange={this.onTabChange}>
<TabPane tab="Tab 1" key="1">Content of Tab Pane 1</TabPane>
<TabPane tab="Tab 2" key="2">Content of Tab Pane 2</TabPane>
</Tabs>
state = {
activeKey: '1',
}
onTabChange = (activeKey) => {
this.setState({ activeKey });
}
Tree component
By controlling the state of the component, we can realize some functions that the original component design did not realize.
For example, in the tree component. We expand/close by clicking the small triangle on the left of the node, and clicking the text part selects the node:
<Tree>
<TreeNode title="parent 1" key="0-0">
<TreeNode title="leaf" key="0-0-0" />
<TreeNode title="leaf" key="0-0-1" />
</TreeNode>
</Tree>
If we want to change to click on the text part, and also expand/close the node, what should we do?
First, we need to query the document to find out what attributes are related to this requirement.
• expandedKeys: set expanded nodes
• selectedKeys: set the selected node
• onExpand: trigger when the node is expanded/closed
• onSelect: trigger when the node is selected
It’s easy to think of how to adjust: when the node is selected, change the original selectedKeys to update expandedKeys.
Converted into the corresponding code:
<Tree
expandedKeys={this.state.expandedKeys}
selectedKeys={[]}
onExpand={this.onExpand}
onSelect={this.onSelect}
>
<TreeNode title="parent 1" key="0-0">
<TreeNode title="leaf" key="0-0-0" />
<TreeNode title="leaf" key="0-0-1" />
</TreeNode>
</Tree>
state = {
expandedKeys: [],
}
// Receive the original expansion event and record expandedKeys in the state
onExpand = (expandedKeys) => {
this.setState({ expandedKeys });
}
// Receive selected event, modify expandedKeys
onSelect = (selectedKeys) => {
const { expandedKeys } = this.state;
const key = selectedKeys[0];
if (expandedKeys.includes(key)) {
// Remove key
this.setState({
expandedKeys: expandedKeys.filter(k => k !== key),
});
} else {
// Add key
this.setState({ expandedKeys: [...expandedKeys, key] });
}
}
Discussion about this post