|
5 | 5 | //////////////////////////////////////////////////////////////////////////////// |
6 | 6 | import "./styles.css"; |
7 | 7 |
|
8 | | -import React from "react"; |
| 8 | +import React, { useState, useEffect } from "react"; |
9 | 9 | import ReactDOM from "react-dom"; |
10 | | -import PropTypes from "prop-types"; |
11 | | - |
12 | | -class Select extends React.Component { |
13 | | - static propTypes = { |
14 | | - onChange: PropTypes.func, |
15 | | - value: PropTypes.any, |
16 | | - defaultValue: PropTypes.any |
17 | | - }; |
18 | | - |
19 | | - state = { |
20 | | - showOptions: false, |
21 | | - value: this.props.defaultValue |
22 | | - }; |
23 | | - |
24 | | - toggleOptions = () => { |
25 | | - this.setState(state => ({ |
26 | | - showOptions: !state.showOptions |
27 | | - })); |
28 | | - }; |
29 | | - |
30 | | - isControlled() { |
31 | | - return this.props.value != null; |
| 10 | + |
| 11 | +function Select({ children, defaultValue, value, onChange }) { |
| 12 | + const [showOptions, setShowOptions] = useState(false); |
| 13 | + const [currentValue, setCurrentValue] = useState(defaultValue); |
| 14 | + |
| 15 | + function toggleOptions() { |
| 16 | + setShowOptions(!showOptions); |
32 | 17 | } |
33 | 18 |
|
34 | | - componentWillMount() { |
35 | | - if (this.isControlled() && this.props.onChange == null) { |
36 | | - console.warn( |
37 | | - "You should provide an onChange to a controlled <Select> component!" |
38 | | - ); |
| 19 | + const isControlled = value != null; |
| 20 | + const displayValue = isControlled ? value : currentValue; |
| 21 | + |
| 22 | + function selectValue(value) { |
| 23 | + if (isControlled) { |
| 24 | + onChange(value); |
| 25 | + } else { |
| 26 | + setCurrentValue(value); |
| 27 | + |
| 28 | + if (onChange) { |
| 29 | + onChange(value); |
| 30 | + } |
39 | 31 | } |
40 | 32 | } |
41 | 33 |
|
42 | | - render() { |
43 | | - const { value } = this.isControlled() ? this.props : this.state; |
| 34 | + let label; |
| 35 | + React.Children.forEach(children, child => { |
| 36 | + if (child.props.value === displayValue) { |
| 37 | + label = child.props.children; |
| 38 | + } |
| 39 | + }); |
44 | 40 |
|
45 | | - let label; |
46 | | - const children = React.Children.map(this.props.children, child => { |
47 | | - if (child.props.value === value) { |
48 | | - label = child.props.children; |
49 | | - } |
| 41 | + useEffect(() => { |
| 42 | + if (isControlled && !onChange) { |
| 43 | + console.warn( |
| 44 | + "You rendered a <Select> with a `value` prop but no `onChange`, so it will be read-only..." |
| 45 | + ); |
| 46 | + } |
| 47 | + }, []); |
50 | 48 |
|
51 | | - return React.cloneElement(child, { |
52 | | - onSelect: () => { |
53 | | - if (this.isControlled()) { |
54 | | - if (this.props.onChange) { |
55 | | - this.props.onChange(child.props.value); |
56 | | - } |
57 | | - } else { |
58 | | - this.setState({ value: child.props.value }, () => { |
59 | | - if (this.props.onChange) { |
60 | | - this.props.onChange(this.state.value); |
61 | | - } |
62 | | - }); |
63 | | - } |
64 | | - } |
65 | | - }); |
66 | | - }); |
67 | | - |
68 | | - return ( |
69 | | - <div className="select" onClick={this.toggleOptions}> |
70 | | - <div className="label"> |
71 | | - {label} <span className="arrow">▾</span> |
72 | | - </div> |
73 | | - {this.state.showOptions && ( |
74 | | - <div className="options">{children}</div> |
75 | | - )} |
| 49 | + return ( |
| 50 | + <div className="select" onClick={toggleOptions}> |
| 51 | + <div className="label"> |
| 52 | + {label} <span className="arrow">▾</span> |
76 | 53 | </div> |
77 | | - ); |
78 | | - } |
| 54 | + {showOptions && ( |
| 55 | + <div className="options"> |
| 56 | + {React.Children.map(children, child => |
| 57 | + React.cloneElement(child, { |
| 58 | + onSelect: () => selectValue(child.props.value) |
| 59 | + }) |
| 60 | + )} |
| 61 | + </div> |
| 62 | + )} |
| 63 | + </div> |
| 64 | + ); |
79 | 65 | } |
80 | 66 |
|
81 | | -class Option extends React.Component { |
82 | | - render() { |
83 | | - return ( |
84 | | - <div className="option" onClick={this.props.onSelect}> |
85 | | - {this.props.children} |
86 | | - </div> |
87 | | - ); |
88 | | - } |
| 67 | +function Option({ children, onSelect }) { |
| 68 | + return ( |
| 69 | + <div className="option" onClick={onSelect}> |
| 70 | + {children} |
| 71 | + </div> |
| 72 | + ); |
89 | 73 | } |
90 | 74 |
|
91 | | -class App extends React.Component { |
92 | | - state = { |
93 | | - selectValue: "dosa" |
94 | | - }; |
95 | | - |
96 | | - setToMintChutney = () => { |
97 | | - this.setState({ selectValue: "mint-chutney" }); |
98 | | - }; |
99 | | - |
100 | | - render() { |
101 | | - return ( |
102 | | - <div> |
103 | | - <h1>Select</h1> |
104 | | - |
105 | | - <h2>Uncontrolled</h2> |
106 | | - |
107 | | - <Select defaultValue="tikka-masala"> |
108 | | - <Option value="tikka-masala">Tikka Masala</Option> |
109 | | - <Option value="tandoori-chicken">Tandoori Chicken</Option> |
110 | | - <Option value="dosa">Dosa</Option> |
111 | | - <Option value="mint-chutney">Mint Chutney</Option> |
112 | | - </Select> |
113 | | - |
114 | | - <h2>Controlled</h2> |
115 | | - |
116 | | - <pre>{JSON.stringify(this.state, null, 2)}</pre> |
117 | | - <p> |
118 | | - <button onClick={this.setToMintChutney}> |
119 | | - Set to Mint Chutney |
120 | | - </button> |
121 | | - </p> |
122 | | - |
123 | | - <Select |
124 | | - value={this.state.selectValue} |
125 | | - onChange={value => this.setState({ selectValue: value })} |
126 | | - > |
127 | | - <Option value="tikka-masala">Tikka Masala</Option> |
128 | | - <Option value="tandoori-chicken">Tandoori Chicken</Option> |
129 | | - <Option value="dosa">Dosa</Option> |
130 | | - <Option value="mint-chutney">Mint Chutney</Option> |
131 | | - </Select> |
132 | | - </div> |
133 | | - ); |
| 75 | +function App() { |
| 76 | + const [selectValue, setSelectValue] = useState("dosa"); |
| 77 | + |
| 78 | + function setToMintChutney() { |
| 79 | + setSelectValue("mint-chutney"); |
134 | 80 | } |
| 81 | + |
| 82 | + return ( |
| 83 | + <div> |
| 84 | + <h1>Select</h1> |
| 85 | + |
| 86 | + <h2>Uncontrolled</h2> |
| 87 | + |
| 88 | + <Select defaultValue="tikka-masala"> |
| 89 | + <Option value="tikka-masala">Tikka Masala</Option> |
| 90 | + <Option value="tandoori-chicken">Tandoori Chicken</Option> |
| 91 | + <Option value="dosa">Dosa</Option> |
| 92 | + <Option value="mint-chutney">Mint Chutney</Option> |
| 93 | + </Select> |
| 94 | + |
| 95 | + <h2>Controlled</h2> |
| 96 | + |
| 97 | + <p>Current value: {selectValue}</p> |
| 98 | + <p> |
| 99 | + <button onClick={setToMintChutney}>Set to Mint Chutney</button> |
| 100 | + </p> |
| 101 | + |
| 102 | + <Select value={selectValue} onChange={setSelectValue}> |
| 103 | + <Option value="tikka-masala">Tikka Masala</Option> |
| 104 | + <Option value="tandoori-chicken">Tandoori Chicken</Option> |
| 105 | + <Option value="dosa">Dosa</Option> |
| 106 | + <Option value="mint-chutney">Mint Chutney</Option> |
| 107 | + </Select> |
| 108 | + </div> |
| 109 | + ); |
135 | 110 | } |
136 | 111 |
|
137 | 112 | ReactDOM.render(<App />, document.getElementById("app")); |
0 commit comments