Skip to content

Commit 0898aaa

Browse files
authored
Merge pull request #26813 from Hotell/mh/react/better-state-and-props-api
[react]: improve props and state type safety on Component
2 parents 76956cf + 98f3e2f commit 0898aaa

40 files changed

Lines changed: 593 additions & 708 deletions

File tree

types/create-react-class/create-react-class-tests.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const ClassicComponent: React.ClassicComponentClass<Props> = createReactClass<Pr
5656
shouldComponentUpdate(this: React.ClassicComponent<Props, State>, nextProps, nextState) {
5757
const newFoo: string = nextProps.foo;
5858
const newBar: number = nextState.bar;
59-
return newFoo !== this.props.foo && newBar !== this.state.bar;
59+
return newFoo !== this.props.foo && newBar !== this.state!.bar;
6060
},
6161
statics: {
6262
test: 1

types/draft-js/draft-js-tests.tsx

Lines changed: 120 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as React from "react";
22
import * as ReactDOM from "react-dom";
3-
import { Map } from "immutable";
43

54
import {
65
ContentBlock,
@@ -48,154 +47,141 @@ class HandleSpan extends React.Component {
4847
}
4948

5049
class RichEditorExample extends React.Component<{}, { editorState: EditorState }> {
51-
constructor() {
52-
super({});
53-
54-
const sampleMarkup =
55-
'<b>Bold text</b>, <i>Italic text</i><br/ ><br />' +
56-
'<a href="http://www.facebook.com">Example link</a><br /><br/ >' +
57-
'<img src="image.png" height="112" width="200" />';
58-
const blocksFromHTML = convertFromHTML(sampleMarkup);
59-
const state = ContentState.createFromBlockArray(
60-
blocksFromHTML.contentBlocks,
61-
blocksFromHTML.entityMap,
62-
);
63-
const decorator = new CompositeDecorator([{
64-
strategy: (
65-
block: ContentBlock,
66-
callback: (start: number, end: number) => void,
67-
contentState: ContentState
68-
) => {
69-
const text = block.getText();
70-
let matchArr, start;
71-
while ((matchArr = HANDLE_REGEX.exec(text)) !== null) {
72-
start = matchArr.index;
73-
callback(start, start + matchArr[0].length);
50+
static initState() {
51+
const sampleMarkup =
52+
'<b>Bold text</b>, <i>Italic text</i><br/ ><br />' +
53+
'<a href="http://www.facebook.com">Example link</a><br /><br/ >' +
54+
'<img src="image.png" height="112" width="200" />';
55+
const blocksFromHTML = convertFromHTML(sampleMarkup);
56+
const state = ContentState.createFromBlockArray(blocksFromHTML.contentBlocks, blocksFromHTML.entityMap);
57+
const decorator = new CompositeDecorator([
58+
{
59+
strategy: (
60+
block: ContentBlock,
61+
callback: (start: number, end: number) => void,
62+
contentState: ContentState
63+
) => {
64+
const text = block.getText();
65+
let matchArr, start;
66+
while ((matchArr = HANDLE_REGEX.exec(text)) !== null) {
67+
start = matchArr.index;
68+
callback(start, start + matchArr[0].length);
69+
}
70+
},
71+
component: HandleSpan
72+
}
73+
]);
74+
return { editorState: EditorState.createWithContent(state, decorator) };
75+
}
76+
state = RichEditorExample.initState()
77+
78+
onChange: (editorState: EditorState) => void = (editorState: EditorState) => this.setState({ editorState });
79+
80+
keyBindingFn(e: SyntheticKeyboardEvent): string {
81+
if (e.keyCode === KEYCODES.ENTER) {
82+
const { editorState } = this.state;
83+
const contentState = editorState.getCurrentContent();
84+
const selectionState = editorState.getSelection();
85+
86+
// only split headers into header and unstyled if we press 'Enter'
87+
// at the end of a header (without text selected)
88+
if (selectionState.isCollapsed()) {
89+
const endKey = selectionState.getEndKey();
90+
const endOffset = selectionState.getEndOffset();
91+
const endBlock = contentState.getBlockForKey(endKey);
92+
if (isHeaderBlock(endBlock) && endOffset === endBlock.getText().length) {
93+
return SPLIT_HEADER_BLOCK;
94+
}
95+
}
7496
}
75-
},
76-
component: HandleSpan,
77-
}]);
78-
this.state = { editorState: EditorState.createWithContent(state, decorator) };
79-
}
8097

81-
onChange: (editorState: EditorState) => void = (editorState: EditorState) => this.setState({ editorState });
82-
83-
keyBindingFn(e: SyntheticKeyboardEvent): string {
84-
if (e.keyCode === KEYCODES.ENTER) {
85-
const { editorState } = this.state;
86-
const contentState = editorState.getCurrentContent();
87-
const selectionState = editorState.getSelection();
88-
89-
// only split headers into header and unstyled if we press 'Enter'
90-
// at the end of a header (without text selected)
91-
if (selectionState.isCollapsed()) {
92-
const endKey = selectionState.getEndKey();
93-
const endOffset = selectionState.getEndOffset();
94-
const endBlock = contentState.getBlockForKey(endKey);
95-
if (isHeaderBlock(endBlock) && endOffset === endBlock.getText().length) {
96-
return SPLIT_HEADER_BLOCK;
97-
}
98-
}
98+
return getDefaultKeyBinding(e);
9999
}
100100

101-
return getDefaultKeyBinding(e);
102-
}
101+
handleKeyCommand = (command: string, editorState: EditorState) => {
102+
if (command === SPLIT_HEADER_BLOCK) {
103+
this.onChange(this.splitHeaderToNewBlock());
104+
return 'handled';
105+
}
103106

104-
handleKeyCommand = (command: string, editorState: EditorState) => {
105-
if (command === SPLIT_HEADER_BLOCK) {
106-
this.onChange(this.splitHeaderToNewBlock());
107-
return 'handled';
108-
}
107+
const newState = RichUtils.handleKeyCommand(editorState, command);
109108

110-
const newState = RichUtils.handleKeyCommand(editorState, command);
109+
if (newState) {
110+
this.onChange(newState);
111+
return 'handled';
112+
}
111113

112-
if (newState) {
113-
this.onChange(newState);
114-
return "handled";
115-
}
114+
return 'not-handled';
115+
};
116116

117-
return "not-handled";
118-
}
117+
toggleBlockType: (blockType: string) => void = (blockType: string) => {
118+
this.onChange(RichUtils.toggleBlockType(this.state.editorState, blockType));
119+
};
119120

120-
toggleBlockType: (blockType: string) => void = (blockType: string) => {
121-
this.onChange(RichUtils.toggleBlockType(this.state.editorState, blockType));
122-
}
121+
toggleInlineStyle: (inlineStyle: string) => void = (inlineStyle: string) => {
122+
this.onChange(RichUtils.toggleInlineStyle(this.state.editorState, inlineStyle));
123+
};
123124

124-
toggleInlineStyle: (inlineStyle: string) => void = (inlineStyle: string) => {
125-
this.onChange(RichUtils.toggleInlineStyle(this.state.editorState, inlineStyle));
126-
}
125+
splitHeaderToNewBlock(): EditorState {
126+
const { editorState } = this.state;
127+
const selection = editorState.getSelection();
127128

128-
splitHeaderToNewBlock(): EditorState {
129-
const { editorState } = this.state;
130-
const selection = editorState.getSelection();
131-
132-
// Add a new block after the cursor
133-
const contentWithBlock = Modifier.splitBlock(
134-
editorState.getCurrentContent(),
135-
selection,
136-
);
137-
138-
// Change the new block type to be normal 'unstyled' text,
139-
const newBlock = contentWithBlock.getBlockAfter(selection.getEndKey());
140-
const contentWithUnstyledBlock = Modifier.setBlockType(
141-
contentWithBlock,
142-
SelectionState.createEmpty(newBlock.getKey()),
143-
'unstyled',
144-
);
145-
146-
// push the new state with 'insert-characters' to preserve the undo/redo stack
147-
const stateWithNewline = EditorState.push(
148-
editorState,
149-
contentWithUnstyledBlock,
150-
'insert-characters'
151-
);
152-
153-
// manually move the cursor to the next line (as expected)
154-
const nextState = EditorState.forceSelection(
155-
stateWithNewline,
156-
SelectionState.createEmpty(newBlock.getKey()),
157-
);
158-
159-
return nextState;
160-
}
129+
// Add a new block after the cursor
130+
const contentWithBlock = Modifier.splitBlock(editorState.getCurrentContent(), selection);
161131

162-
render(): React.ReactElement<{}> {
163-
// If the user changes block type before entering any text, we can
164-
// either style the placeholder or hide it. Let's just hide it now.
165-
let className = 'RichEditor-editor';
166-
var contentState = this.state.editorState.getCurrentContent();
167-
if (!contentState.hasText()) {
168-
if (contentState.getBlockMap().first().getType() !== 'unstyled') {
169-
className += ' RichEditor-hidePlaceholder';
170-
}
132+
// Change the new block type to be normal 'unstyled' text,
133+
const newBlock = contentWithBlock.getBlockAfter(selection.getEndKey());
134+
const contentWithUnstyledBlock = Modifier.setBlockType(
135+
contentWithBlock,
136+
SelectionState.createEmpty(newBlock.getKey()),
137+
'unstyled'
138+
);
139+
140+
// push the new state with 'insert-characters' to preserve the undo/redo stack
141+
const stateWithNewline = EditorState.push(editorState, contentWithUnstyledBlock, 'insert-characters');
142+
143+
// manually move the cursor to the next line (as expected)
144+
const nextState = EditorState.forceSelection(stateWithNewline, SelectionState.createEmpty(newBlock.getKey()));
145+
146+
return nextState;
171147
}
172148

173-
return (
174-
<div className="RichEditor-root">
175-
<BlockStyleControls
176-
editorState={this.state.editorState}
177-
onToggle={this.toggleBlockType}
178-
/>
179-
<InlineStyleControls
180-
editorState={this.state.editorState}
181-
onToggle={this.toggleInlineStyle}
182-
/>
183-
<div className={className}>
184-
<Editor
185-
blockStyleFn={getBlockStyle}
186-
customStyleMap={styleMap}
187-
editorState={this.state.editorState}
188-
keyBindingFn={this.keyBindingFn}
189-
handleKeyCommand={this.handleKeyCommand}
190-
onChange={this.onChange}
191-
placeholder="Tell a story..."
192-
ref="editor"
193-
spellCheck={true}
194-
/>
195-
</div>
196-
</div>
197-
);
149+
render(): React.ReactElement<{}> {
150+
// If the user changes block type before entering any text, we can
151+
// either style the placeholder or hide it. Let's just hide it now.
152+
let className = 'RichEditor-editor';
153+
var contentState = this.state.editorState.getCurrentContent();
154+
if (!contentState.hasText()) {
155+
if (
156+
contentState
157+
.getBlockMap()
158+
.first()
159+
.getType() !== 'unstyled'
160+
) {
161+
className += ' RichEditor-hidePlaceholder';
162+
}
198163
}
164+
165+
return (
166+
<div className="RichEditor-root">
167+
<BlockStyleControls editorState={this.state.editorState} onToggle={this.toggleBlockType} />
168+
<InlineStyleControls editorState={this.state.editorState} onToggle={this.toggleInlineStyle} />
169+
<div className={className}>
170+
<Editor
171+
blockStyleFn={getBlockStyle}
172+
customStyleMap={styleMap}
173+
editorState={this.state.editorState}
174+
keyBindingFn={this.keyBindingFn}
175+
handleKeyCommand={this.handleKeyCommand}
176+
onChange={this.onChange}
177+
placeholder="Tell a story..."
178+
ref="editor"
179+
spellCheck={true}
180+
/>
181+
</div>
182+
</div>
183+
);
184+
}
199185
}
200186

201187
// Custom overrides for "code" style.

types/expo__vector-icons/expo__vector-icons-tests.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,9 @@ class Example extends React.Component {
4141
}
4242

4343
class TabTest extends React.Component<{}, { selectedTab: string }> {
44-
constructor() {
45-
super({});
46-
47-
this.state = {
48-
selectedTab: 'tab1'
49-
};
50-
}
44+
state = {
45+
selectedTab: 'tab1'
46+
};
5147

5248
render() {
5349
return (

types/fixed-data-table-2/fixed-data-table-2-tests.tsx

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,15 @@ interface MyTable3State {
4646
}
4747

4848
class MyTable3 extends React.Component<{}, MyTable3State> {
49-
constructor(props: {}) {
50-
super(props);
51-
52-
this.state = {
53-
myTableData: [
54-
{ name: "Rylan" },
55-
{ name: "Amelia" },
56-
{ name: "Estevan" },
57-
{ name: "Florence" },
58-
{ name: "Tressa" },
59-
]
60-
};
61-
}
49+
state = {
50+
myTableData: [
51+
{ name: "Rylan" },
52+
{ name: "Amelia" },
53+
{ name: "Estevan" },
54+
{ name: "Florence" },
55+
{ name: "Tressa" },
56+
]
57+
};
6258

6359
render() {
6460
return (
@@ -130,18 +126,15 @@ interface MyTable4State {
130126
}
131127

132128
class MyTable4 extends React.Component<{}, MyTable4State> {
133-
constructor(props: {}) {
134-
super(props);
135-
this.state = {
136-
tableData: [
137-
{ name: "Rylan", email: "Angelita_Weimann42@gmail.com" },
138-
{ name: "Amelia", email: "Dexter.Trantow57@hotmail.com" },
139-
{ name: "Estevan", email: "Aimee7@hotmail.com" },
140-
{ name: "Florence", email: "Jarrod.Bernier13@yahoo.com" },
141-
{ name: "Tressa", email: "Yadira1@hotmail.com" }
142-
]
143-
};
144-
}
129+
state = {
130+
tableData: [
131+
{ name: "Rylan", email: "Angelita_Weimann42@gmail.com" },
132+
{ name: "Amelia", email: "Dexter.Trantow57@hotmail.com" },
133+
{ name: "Estevan", email: "Aimee7@hotmail.com" },
134+
{ name: "Florence", email: "Jarrod.Bernier13@yahoo.com" },
135+
{ name: "Tressa", email: "Yadira1@hotmail.com" }
136+
]
137+
};
145138

146139
render() {
147140
return (

0 commit comments

Comments
 (0)