Skip to content

Commit b91ee64

Browse files
committed
Update select exercise + solution to use hooks
1 parent 1992289 commit b91ee64

File tree

2 files changed

+132
-177
lines changed

2 files changed

+132
-177
lines changed

subjects/20-Select/exercise.js

Lines changed: 44 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -5,79 +5,59 @@
55
////////////////////////////////////////////////////////////////////////////////
66
import "./styles.css";
77

8-
import React from "react";
8+
import React, { useState, useEffect } from "react";
99
import ReactDOM from "react-dom";
10-
import PropTypes from "prop-types";
1110

12-
class Select extends React.Component {
13-
static propTypes = {
14-
onChange: PropTypes.func,
15-
value: PropTypes.any,
16-
defaultValue: PropTypes.any
17-
};
18-
19-
render() {
20-
return (
21-
<div className="select">
22-
<div className="label">
23-
label <span className="arrow"></span>
24-
</div>
25-
<div className="options">{this.props.children}</div>
11+
function Select({ children }) {
12+
return (
13+
<div className="select">
14+
<div className="label">
15+
label <span className="arrow"></span>
2616
</div>
27-
);
28-
}
17+
<div className="options">{children}</div>
18+
</div>
19+
);
2920
}
3021

31-
class Option extends React.Component {
32-
render() {
33-
return <div className="option">{this.props.children}</div>;
34-
}
22+
function Option({ children }) {
23+
return <div className="option">{children}</div>;
3524
}
3625

37-
class App extends React.Component {
38-
state = {
39-
selectValue: "dosa"
40-
};
41-
42-
setToMintChutney = () => {
43-
this.setState({ selectValue: "mint-chutney" });
44-
};
45-
46-
render() {
47-
return (
48-
<div>
49-
<h1>Select</h1>
50-
51-
<h2>Uncontrolled</h2>
26+
function App() {
27+
const [selectValue, setSelectValue] = useState("dosa");
5228

53-
<Select defaultValue="tikka-masala">
54-
<Option value="tikka-masala">Tikka Masala</Option>
55-
<Option value="tandoori-chicken">Tandoori Chicken</Option>
56-
<Option value="dosa">Dosa</Option>
57-
<Option value="mint-chutney">Mint Chutney</Option>
58-
</Select>
59-
60-
<h2>Controlled</h2>
61-
62-
<pre>{JSON.stringify(this.state, null, 2)}</pre>
63-
<p>
64-
<button onClick={this.setToMintChutney}>
65-
Set to Mint Chutney
66-
</button>
67-
</p>
68-
69-
<Select
70-
value={this.state.selectValue}
71-
onChange={value => this.setState({ selectValue: value })}
72-
>
73-
<Option value="tikka-masala">Tikka Masala</Option>
74-
<Option value="tandoori-chicken">Tandoori Chicken</Option>
75-
<Option value="dosa">Dosa</Option>
76-
<Option value="mint-chutney">Mint Chutney</Option>
77-
</Select>
78-
</div>
79-
);
29+
function setToMintChutney() {
30+
setSelectValue("mint-chutney");
8031
}
32+
33+
return (
34+
<div>
35+
<h1>Select</h1>
36+
37+
<h2>Uncontrolled</h2>
38+
39+
<Select defaultValue="tikka-masala">
40+
<Option value="tikka-masala">Tikka Masala</Option>
41+
<Option value="tandoori-chicken">Tandoori Chicken</Option>
42+
<Option value="dosa">Dosa</Option>
43+
<Option value="mint-chutney">Mint Chutney</Option>
44+
</Select>
45+
46+
<h2>Controlled</h2>
47+
48+
<p>Current value: {selectValue}</p>
49+
<p>
50+
<button onClick={setToMintChutney}>Set to Mint Chutney</button>
51+
</p>
52+
53+
<Select value={selectValue} onChange={setSelectValue}>
54+
<Option value="tikka-masala">Tikka Masala</Option>
55+
<Option value="tandoori-chicken">Tandoori Chicken</Option>
56+
<Option value="dosa">Dosa</Option>
57+
<Option value="mint-chutney">Mint Chutney</Option>
58+
</Select>
59+
</div>
60+
);
8161
}
8262

8363
ReactDOM.render(<App />, document.getElementById("app"));

subjects/20-Select/solution.js

Lines changed: 88 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -5,133 +5,108 @@
55
////////////////////////////////////////////////////////////////////////////////
66
import "./styles.css";
77

8-
import React from "react";
8+
import React, { useState, useEffect } from "react";
99
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);
3217
}
3318

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+
}
3931
}
4032
}
4133

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+
});
4440

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+
}, []);
5048

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>
7653
</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+
);
7965
}
8066

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+
);
8973
}
9074

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");
13480
}
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+
);
135110
}
136111

137112
ReactDOM.render(<App />, document.getElementById("app"));

0 commit comments

Comments
 (0)