Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions apps/todo-app/src/components/TodoForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { autobind, BaseComponent, IBaseProps } from 'office-ui-fabric-react/lib/Utilities';
import { autobind, BaseComponent, IBaseProps, createRef, RefObject } from 'office-ui-fabric-react/lib/Utilities';
import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import { TextField, ITextField } from 'office-ui-fabric-react/lib/TextField';
import * as stylesImport from './Todo.scss';
Expand Down Expand Up @@ -42,7 +42,7 @@ export interface ITodoFormState {
* Button: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/button
*/
export default class TodoForm extends BaseComponent<ITodoFormProps, ITodoFormState> {
private _textField!: ITextField;
private _textField: RefObject<ITextField> = createRef<ITextField>();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it necessary to type this? Isn't it inferred by the assignment?


constructor(props: ITodoFormProps) {
super(props);
Expand All @@ -62,7 +62,7 @@ export default class TodoForm extends BaseComponent<ITodoFormProps, ITodoFormSta
<TextField
className={ styles.textField }
value={ this.state.inputValue }
componentRef={ this._resolveRef('_textField') }
componentRef={ this._textField }
placeholder={ strings.inputBoxPlaceholder }
onBeforeChange={ this._onBeforeTextFieldChange }
autoComplete='off'
Expand All @@ -82,18 +82,23 @@ export default class TodoForm extends BaseComponent<ITodoFormProps, ITodoFormSta
private _onSubmit(event: React.FormEvent<HTMLElement>): void {
event.preventDefault();

if (!this._getTitleErrorMessage(this._textField.value || '')) {
const { value: textField } = this._textField;
if (!textField) {
return;
}

if (!this._getTitleErrorMessage(textField.value || '')) {
this.setState({
inputValue: ''
} as ITodoFormState);

this.props.onSubmit(this._textField.value || '');
this.props.onSubmit(textField.value || '');
} else {
this.setState({
errorMessage: this._getTitleErrorMessage(this.state.inputValue)
} as ITodoFormState);

this._textField.focus();
textField.focus();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@uifabric/utilities",
"comment": "Adds createRef polyfil to prepare for object refs.",
"type": "minor"
}
],
"packageName": "@uifabric/utilities",
"email": "mark@thedutchies.com"
}
8 changes: 5 additions & 3 deletions ghdocs/BESTPRACTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,15 @@ public render() {
}
```

Best, use _resolveRef in BaseComponent:
Best, use createRef:
```typescript
import { createRef, RefObject } from 'office-ui-fabric-react/lib/Utilities';

class Foo extends BaseComponent<...> {
private _root: HTMLElement;
private _root: RefObject<HTMLElement> = createRef<HTMLElement>();

public render() {
return <div ref={ this._resolveRef('_root') } />;
return <div ref={ _root } />;
}
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import { registerLanguage, highlightBlock } from 'highlight.js';
import * as javascript from 'highlight.js/lib/languages/javascript';
import { BaseComponent } from 'office-ui-fabric-react/lib/Utilities';
import { RefObject, createRef } from '@uifabric/utilities/lib/createRef';

registerLanguage('javascript', javascript);

Expand All @@ -10,13 +11,13 @@ export interface IHighlightProps extends React.HTMLAttributes<HTMLDivElement> {
}

export class Highlight extends BaseComponent<IHighlightProps, {}> {
private _codeElement: HTMLElement;
private _codeElement: RefObject<HTMLElement> = createRef<HTMLElement>();

public render(): JSX.Element {
return (
<pre>
<code
ref={ this._resolveRef('_codeElement') }
ref={ this._codeElement }
className='javascript'
>
{ this.props.children }
Expand All @@ -30,6 +31,8 @@ export class Highlight extends BaseComponent<IHighlightProps, {}> {
}

public componentDidMount(): void {
highlightBlock(this._codeElement);
if (this._codeElement.value) {
highlightBlock(this._codeElement.value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import { OverflowSet, IOverflowSet } from 'office-ui-fabric-react/lib/OverflowSe
import { ResizeGroup, IResizeGroup } from 'office-ui-fabric-react/lib/ResizeGroup';
import { TooltipHost } from 'office-ui-fabric-react/lib/Tooltip';
import {
classNamesFunction
classNamesFunction,
createRef,
RefObject
} from '../../Utilities';

const getClassNames = classNamesFunction<ICommandBarStyleProps, ICommandBarStyles>();
Expand Down Expand Up @@ -54,8 +56,8 @@ export class CommandBarBase extends BaseComponent<ICommandBarProps, {}> implemen
elipisisIconProps: { iconName: 'More' }
};

private _overflowSet: IOverflowSet;
private _resizeGroup: IResizeGroup;
private _overflowSet: RefObject<IOverflowSet> = createRef<IOverflowSet>();
private _resizeGroup: RefObject<IResizeGroup> = createRef<IResizeGroup>();
private _classNames: {[key in keyof ICommandBarStyles]: string };

public render(): JSX.Element {
Expand Down Expand Up @@ -88,7 +90,7 @@ export class CommandBarBase extends BaseComponent<ICommandBarProps, {}> implemen

return (
<ResizeGroup
componentRef={ this._resolveRef('_resizeGroup') }
componentRef={ this._resizeGroup }
className={ className }
data={ commandBardata }
onReduceData={ onReduceData }
Expand All @@ -100,7 +102,7 @@ export class CommandBarBase extends BaseComponent<ICommandBarProps, {}> implemen

{/*Primary Items*/ }
<OverflowSet
componentRef={ this._resolveRef('_overflowSet') }
componentRef={ this._overflowSet }
className={ css(this._classNames.primarySet) }
items={ data.primaryItems }
overflowItems={ data.overflowItems.length ? data.overflowItems : undefined }
Expand Down Expand Up @@ -134,11 +136,13 @@ export class CommandBarBase extends BaseComponent<ICommandBarProps, {}> implemen
}

public focus(): void {
this._overflowSet.focus();
const { value: overflowSet } = this._overflowSet;

overflowSet && overflowSet.focus();
}

public remeasure(): void {
this._resizeGroup.remeasure();
this._resizeGroup.value && this._resizeGroup.value.remeasure();
}

private _computeCacheKey(primaryItems: ICommandBarItemProps[], farItems: ICommandBarItemProps[], overflow: boolean): string {
Expand Down Expand Up @@ -235,6 +239,6 @@ export class CommandBarBase extends BaseComponent<ICommandBarProps, {}> implemen
@autobind
private _onRenderButton(props: ICommandBarItemProps): JSX.Element {
// tslint:disable-next-line:no-any
return <CommandBarButton {...props as any} />;
return <CommandBarButton { ...props as any } />;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export class BaseExtendedPicker<T, P extends IBaseExtendedPickerProps<T>> extend
this.focusZone.focus();
}

public get inputElement(): HTMLInputElement {
public get inputElement(): HTMLInputElement | null {
return this.input.inputElement;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
KeyCodes,
autobind,
css,
getRTL
getRTL,
createRef,
RefObject
} from '../../Utilities';
import { Callout, DirectionalHint } from 'office-ui-fabric-react/lib/Callout';
import { Suggestions, ISuggestionsProps, SuggestionsController, IBasePickerSuggestionsProps, ISuggestionModel }
Expand All @@ -30,12 +32,12 @@ export class BaseFloatingPicker<T, P extends IBaseFloatingPickerProps<T>> extend
implements IBaseFloatingPicker {
protected selection: Selection;

protected root: HTMLElement;
protected suggestionElement: Suggestions<T>;
protected root: RefObject<HTMLDivElement> = createRef<HTMLDivElement>();
protected suggestionElement: RefObject<Suggestions<T>> = createRef<Suggestions<T>>();

protected suggestionStore: SuggestionsController<T>;
protected SuggestionOfProperType: new (props: ISuggestionsProps<T>) => Suggestions<T> =
Suggestions as new (props: ISuggestionsProps<T>) => Suggestions<T>;
Suggestions as new (props: ISuggestionsProps<T>) => Suggestions<T>;
protected loadingTimer: number | undefined;
// tslint:disable-next-line:no-any
protected currentPromise: PromiseLike<any>;
Expand Down Expand Up @@ -154,7 +156,7 @@ export class BaseFloatingPicker<T, P extends IBaseFloatingPickerProps<T>> extend
let { className } = this.props;
return (
<div
ref={ this._resolveRef('root') }
ref={ this.root }
className={ css('ms-BasePicker', className ? className : '') }
>
{ this.renderSuggestions() }
Expand Down Expand Up @@ -187,15 +189,15 @@ export class BaseFloatingPicker<T, P extends IBaseFloatingPickerProps<T>> extend
onSuggestionClick={ this.onSuggestionClick }
onSuggestionRemove={ this.onSuggestionRemove }
suggestions={ this.suggestionStore.getSuggestions() }
ref={ this._resolveRef('suggestionElement') }
ref={ this.suggestionElement }
onGetMoreResults={ this.onGetMoreResults }
moreSuggestionsAvailable={ this.state.moreSuggestionsAvailable }
isLoading={ this.state.suggestionsLoading }
isSearching={ this.state.isSearching }
isMostRecentlyUsedVisible={ this.state.isMostRecentlyUsedVisible }
isResultsFooterVisible={ this.state.isResultsFooterVisible }
refocusSuggestions={ this.refocusSuggestions }
{...this.props.pickerSuggestionsProps as IBasePickerSuggestionsProps}
{ ...this.props.pickerSuggestionsProps as IBasePickerSuggestionsProps }
/>
</Callout>
) : null;
Expand Down Expand Up @@ -324,7 +326,7 @@ export class BaseFloatingPicker<T, P extends IBaseFloatingPickerProps<T>> extend
ev: React.MouseEvent<HTMLElement>,
item: T,
index: number
): void {
): void {
this.onChange(item);
}

Expand All @@ -333,7 +335,7 @@ export class BaseFloatingPicker<T, P extends IBaseFloatingPickerProps<T>> extend
ev: React.MouseEvent<HTMLElement>,
item: T,
index: number
): void {
): void {
if (this.props.onRemoveSuggestion) {
(this.props.onRemoveSuggestion as ((item: T) => void))(item);
}
Expand All @@ -348,6 +350,7 @@ export class BaseFloatingPicker<T, P extends IBaseFloatingPickerProps<T>> extend
return;
}
let keyCode = ev.which;
let { value: suggestionElement } = this.suggestionElement;
switch (keyCode) {
case KeyCodes.escape:
this.setState({ suggestionsVisible: false });
Expand All @@ -357,8 +360,8 @@ export class BaseFloatingPicker<T, P extends IBaseFloatingPickerProps<T>> extend

case KeyCodes.tab:
case KeyCodes.enter:
if (this.suggestionElement.hasSuggestedActionSelected()) {
this.suggestionElement.executeSelectedAction();
if (suggestionElement && suggestionElement.hasSuggestedActionSelected()) {
suggestionElement.executeSelectedAction();
} else if (!ev.shiftKey &&
!ev.ctrlKey &&
this.suggestionStore.hasSelectedSuggestion()) {
Expand Down Expand Up @@ -386,16 +389,16 @@ export class BaseFloatingPicker<T, P extends IBaseFloatingPickerProps<T>> extend
break;

case KeyCodes.up:
if (this.suggestionElement.tryHandleKeyDown(keyCode, this.suggestionStore.currentIndex)) {
if (suggestionElement && suggestionElement.tryHandleKeyDown(keyCode, this.suggestionStore.currentIndex)) {
ev.preventDefault();
ev.stopPropagation();
} else {
if (this.suggestionElement.hasSuggestedAction() &&
if (suggestionElement && suggestionElement.hasSuggestedAction() &&
this.suggestionStore.currentIndex === 0
) {
ev.preventDefault();
ev.stopPropagation();
this.suggestionElement.focusAboveSuggestions();
suggestionElement.focusAboveSuggestions();
this.suggestionStore.deselectAllSuggestions();
this.forceUpdate();
} else {
Expand All @@ -409,18 +412,19 @@ export class BaseFloatingPicker<T, P extends IBaseFloatingPickerProps<T>> extend
break;

case KeyCodes.down:
if (this.suggestionElement.tryHandleKeyDown(keyCode, this.suggestionStore.currentIndex)) {
if (suggestionElement && suggestionElement.tryHandleKeyDown(keyCode, this.suggestionStore.currentIndex)) {
ev.preventDefault();
ev.stopPropagation();
} else {
if (
this.suggestionElement.hasSuggestedAction() &&
suggestionElement &&
suggestionElement.hasSuggestedAction() &&
this.suggestionStore.currentIndex + 1 ===
this.suggestionStore.suggestions.length
) {
ev.preventDefault();
ev.stopPropagation();
this.suggestionElement.focusBelowSuggestions();
suggestionElement.focusBelowSuggestions();
this.suggestionStore.deselectAllSuggestions();
this.forceUpdate();
} else {
Expand Down Expand Up @@ -496,7 +500,7 @@ export class BaseFloatingPicker<T, P extends IBaseFloatingPickerProps<T>> extend
) => ISuggestionModel<T>))(
this.state.queryString,
(this.props.onValidateInput as ((input: string) => boolean))(this.state.queryString)
);
);
this.suggestionStore.createGenericSuggestion(itemToConvert);
this.completeSuggestion();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface IBaseFloatingPicker {
// displaying persona's than type T could either be of Persona or Ipersona props
// tslint:disable-next-line:no-any
export interface IBaseFloatingPickerProps<T> extends React.Props<any> {
componentRef?: (component?: IBaseFloatingPicker) => void;
componentRef?: (component?: IBaseFloatingPicker | null) => void;

/** The suggestions controller */
suggestionsController: SuggestionsController<T>;
Expand All @@ -34,7 +34,7 @@ export interface IBaseFloatingPickerProps<T> extends React.Props<any> {
/**
* The input element to listen on events
*/
inputElement?: HTMLElement;
inputElement?: HTMLElement | null;

/**
* Function that specifies how an individual suggestion item will appear.
Expand Down
Loading