diff --git a/frontend/app/@types/markdown-toolbar-element/index.d.ts b/frontend/app/@types/markdown-toolbar-element/index.d.ts deleted file mode 100644 index f4ed19d2..00000000 --- a/frontend/app/@types/markdown-toolbar-element/index.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -declare namespace JSX { - interface IntrinsicElements { - 'markdown-toolbar': { - for: string; - children?: any; - className: string; - }; - 'md-bold': any; - 'md-header': any; - 'md-italic': any; - 'md-quote': any; - 'md-code': any; - 'md-link': any; - 'md-unordered-list': any; - 'md-ordered-list': any; - } -} diff --git a/frontend/app/@types/preact/index.d.ts b/frontend/app/@types/preact/index.d.ts new file mode 100644 index 00000000..442ccaa8 --- /dev/null +++ b/frontend/app/@types/preact/index.d.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import * as preact from 'preact/src/jsx'; + +declare module 'preact/src/jsx' { + namespace JSXInternal { + interface IntrinsicElements { + 'markdown-toolbar': { + for: string; + children?: any; + className: string; + }; + 'md-bold': any; + 'md-header': any; + 'md-italic': any; + 'md-quote': any; + 'md-code': any; + 'md-link': any; + 'md-unordered-list': any; + 'md-ordered-list': any; + } + } +} diff --git a/frontend/app/components/auth-panel/__anonymous-login-form/auth-panel__anonymous-login-form.tsx b/frontend/app/components/auth-panel/__anonymous-login-form/auth-panel__anonymous-login-form.tsx index a3ad4783..856ae95d 100644 --- a/frontend/app/components/auth-panel/__anonymous-login-form/auth-panel__anonymous-login-form.tsx +++ b/frontend/app/components/auth-panel/__anonymous-login-form/auth-panel__anonymous-login-form.tsx @@ -1,5 +1,5 @@ -/** @jsx h */ -import { h, Component, RenderableProps } from 'preact'; +/** @jsx createElement */ +import { createElement, Component, createRef } from 'preact'; import b from 'bem-react-helper'; import { Theme } from '@app/common/types'; @@ -17,7 +17,7 @@ interface State { export class AnonymousLoginForm extends Component { static usernameRegex = /^[a-zA-Z][\w ]+$/; - inputRef?: HTMLInputElement; + inputRef = createRef(); constructor(props: Props) { super(props); @@ -60,11 +60,12 @@ export class AnonymousLoginForm extends Component { componentDidUpdate() { setTimeout(() => { - this.inputRef && this.inputRef.focus(); + this.inputRef.current && this.inputRef.current.focus(); }, 100); } - render(props: RenderableProps) { + render() { + const props = this.props; // TODO: will be great to `b` to accept `string | undefined | (string|undefined)[]` as classname let className = b('auth-panel-anonymous-login-form', {}, { theme: props.theme }); if (props.className) { @@ -77,7 +78,7 @@ export class AnonymousLoginForm extends Component {
(this.inputRef = ref)} + ref={this.inputRef} type="text" placeholder="Username" value={this.state.inputValue} diff --git a/frontend/app/components/auth-panel/__email-login-form/auth-panel__email-login-form.test.tsx b/frontend/app/components/auth-panel/__email-login-form/auth-panel__email-login-form.test.tsx index 55c9926a..a8db5efe 100644 --- a/frontend/app/components/auth-panel/__email-login-form/auth-panel__email-login-form.test.tsx +++ b/frontend/app/components/auth-panel/__email-login-form/auth-panel__email-login-form.test.tsx @@ -1,5 +1,5 @@ -/** @jsx h */ -import { h } from 'preact'; +/** @jsx createElement */ +import { createElement } from 'preact'; import { mount } from 'enzyme'; import { EmailLoginForm, Props, State } from './auth-panel__email-login-form'; import { User } from '@app/common/types'; diff --git a/frontend/app/components/auth-panel/__email-login-form/auth-panel__email-login-form.tsx b/frontend/app/components/auth-panel/__email-login-form/auth-panel__email-login-form.tsx index 544e4b3a..1d4bb7ad 100644 --- a/frontend/app/components/auth-panel/__email-login-form/auth-panel__email-login-form.tsx +++ b/frontend/app/components/auth-panel/__email-login-form/auth-panel__email-login-form.tsx @@ -1,10 +1,11 @@ -/** @jsx h */ -import { h, Component, RenderableProps } from 'preact'; +/** @jsx createElement */ +import { createElement, Component, createRef } from 'preact'; +import { forwardRef } from 'preact/compat'; import b from 'bem-react-helper'; +import { useSelector } from 'react-redux'; import { Theme, User } from '@app/common/types'; import { sendEmailVerificationRequest } from '@app/common/api'; import { extractErrorMessageFromResponse } from '@app/utils/errorUtils'; -import { connect } from 'preact-redux'; import { getHandleClickProps } from '@app/common/accessibility'; import { sleep } from '@app/utils/sleep'; import TextareaAutosize from '@app/components/input/textarea-autosize'; @@ -13,12 +14,14 @@ const mapStateToProps = () => ({ sendEmailVerification: sendEmailVerificationRequest, }); -export type Props = { +interface OwnProps { onSignIn(token: string): Promise; onSuccess?(user: User): Promise; theme: Theme; className?: string; -} & ReturnType; +} + +export type Props = OwnProps & ReturnType; export interface State { usernameValue: string; @@ -33,8 +36,8 @@ export class EmailLoginForm extends Component { static usernameRegex = /^[a-zA-Z][\w ]+$/; static emailRegex = /[^@]+@[^.]+\..+/; - inputRef?: HTMLInputElement; - tokenRef?: TextareaAutosize; + inputRef = createRef(); + tokenRef = createRef(); constructor(props: Props) { super(props); @@ -59,11 +62,11 @@ export class EmailLoginForm extends Component { async focus() { await sleep(100); - if (this.inputRef) { - this.inputRef.focus(); + if (this.inputRef.current) { + this.inputRef.current.focus(); return; } - this.tokenRef && this.tokenRef.textareaRef && this.tokenRef.textareaRef.select(); + this.tokenRef.current && this.tokenRef.current.textareaRef && this.tokenRef.current.textareaRef.select(); } async onVerificationSubmit(e: Event) { @@ -73,7 +76,7 @@ export class EmailLoginForm extends Component { await this.props.sendEmailVerification(this.state.usernameValue, this.state.addressValue); this.setState({ verificationSent: true }); setTimeout(() => { - this.tokenRef && this.tokenRef.focus(); + this.tokenRef.current && this.tokenRef.current.focus(); }, 100); } catch (e) { this.setState({ error: extractErrorMessageFromResponse(e) }); @@ -119,7 +122,7 @@ export class EmailLoginForm extends Component { verificationSent: false, }); setTimeout(() => { - this.inputRef && this.inputRef.focus(); + this.inputRef.current && this.inputRef.current.focus(); }, 100); } @@ -141,11 +144,11 @@ export class EmailLoginForm extends Component { componentDidMount() { setTimeout(() => { - this.inputRef && this.inputRef.focus(); + this.inputRef.current && this.inputRef.current.focus(); }, 100); } - render(props: RenderableProps) { + render(props: Props) { // TODO: will be great to `b` to accept `string | undefined | (string|undefined)[]` as classname let className = b('auth-panel-email-login-form', {}, { theme: props.theme }); if (props.className) { @@ -173,7 +176,7 @@ export class EmailLoginForm extends Component { (this.inputRef = ref)} + ref={this.inputRef} type="text" placeholder="Username" value={this.state.usernameValue} @@ -193,7 +196,7 @@ export class EmailLoginForm extends Component { title={form1InvalidReason || ''} disabled={form1InvalidReason !== null} /> - {this.state.error && } + {this.state.error &&
{this.state.error}
} ); @@ -207,7 +210,7 @@ export class EmailLoginForm extends Component { (this.tokenRef = ref)} + ref={this.tokenRef} placeholder="Token" value={this.state.tokenValue} onInput={this.onTokenChange} @@ -221,15 +224,15 @@ export class EmailLoginForm extends Component { title={form2InvalidReason || ''} disabled={form2InvalidReason !== null} /> - {this.state.error && } + {this.state.error &&
{this.state.error}
} ); } } -export const EmailLoginFormConnected = connect( - mapStateToProps, - null, - null, - { withRef: true } -)(EmailLoginForm); +export type EmailLoginFormRef = EmailLoginForm; + +export const EmailLoginFormConnected = forwardRef((props, ref) => { + const connectedProps = useSelector(mapStateToProps); + return ; +}); diff --git a/frontend/app/components/auth-panel/__user-id/auth-panel__user-id.tsx b/frontend/app/components/auth-panel/__user-id/auth-panel__user-id.tsx index 9eb97bcc..60962192 100644 --- a/frontend/app/components/auth-panel/__user-id/auth-panel__user-id.tsx +++ b/frontend/app/components/auth-panel/__user-id/auth-panel__user-id.tsx @@ -1,20 +1,15 @@ -/** @jsx h */ -import { h } from 'preact'; +/** @jsx createElement */ +import { createElement, JSX, FunctionComponent } from 'preact'; import b from 'bem-react-helper'; import { Theme } from '@app/common/types'; -import { exclude } from '@app/utils/exclude'; -interface Props { +type Props = { id: string; theme: Theme; -} +} & JSX.HTMLAttributes; -export const UserID = (props: JSX.HTMLAttributes & Props) => ( -
- {props.id} +export const UserID: FunctionComponent = ({ id, theme, ...props }) => ( +
+ {id}
); diff --git a/frontend/app/components/auth-panel/auth-panel.test.tsx b/frontend/app/components/auth-panel/auth-panel.test.tsx index 07c0de00..f0712e3d 100644 --- a/frontend/app/components/auth-panel/auth-panel.test.tsx +++ b/frontend/app/components/auth-panel/auth-panel.test.tsx @@ -1,5 +1,5 @@ -/** @jsx h */ -import { h } from 'preact'; +/** @jsx createElement */ +import { createElement } from 'preact'; import { mount } from 'enzyme'; import { Props, AuthPanel } from './auth-panel'; import { User, PostInfo } from '../../common/types'; diff --git a/frontend/app/components/auth-panel/auth-panel.tsx b/frontend/app/components/auth-panel/auth-panel.tsx index 0ed6d820..94181689 100644 --- a/frontend/app/components/auth-panel/auth-panel.tsx +++ b/frontend/app/components/auth-panel/auth-panel.tsx @@ -1,5 +1,5 @@ -/** @jsx h */ -import { h, Component, RenderableProps } from 'preact'; +/** @jsx createElement */ +import { createElement, Component, createRef } from 'preact'; import b from 'bem-react-helper'; import { PROVIDER_NAMES, IS_STORAGE_AVAILABLE, IS_THIRD_PARTY } from '@app/common/constants'; @@ -11,11 +11,12 @@ import Dropdown, { DropdownItem } from '@app/components/dropdown'; import { Button } from '@app/components/button'; import { UserID } from './__user-id'; import { AnonymousLoginForm } from './__anonymous-login-form'; -import { EmailLoginForm, EmailLoginFormConnected } from './__email-login-form'; +import { EmailLoginFormConnected } from './__email-login-form'; import { StoreState } from '@app/store'; import { ProviderState } from '@app/store/provider/reducers'; import debounce from '@app/utils/debounce'; import postMessage from '@app/utils/postMessage'; +import { EmailLoginFormRef } from './__email-login-form/auth-panel__email-login-form'; export interface Props { user: User | null; @@ -44,7 +45,7 @@ interface State { } export class AuthPanel extends Component { - emailLoginRef?: EmailLoginForm; + emailLoginRef = createRef(); constructor(props: Props) { super(props); @@ -85,7 +86,7 @@ export class AuthPanel extends Component { }, 100); onEmailTitleClick() { - this.emailLoginRef && this.emailLoginRef.focus(); + this.emailLoginRef.current && this.emailLoginRef.current.focus(); } onSortChange(e: Event) { @@ -208,7 +209,7 @@ export class AuthPanel extends Component { > (this.emailLoginRef = ref ? ref.getWrappedInstance() : null)} + ref={this.emailLoginRef} onSignIn={this.onEmailSignIn} theme={this.props.theme} className="auth-panel__email-login-form" @@ -305,7 +306,7 @@ export class AuthPanel extends Component {
Disable third-party cookies blocking to sign in or open comments in{' '} @@ -373,7 +374,7 @@ export class AuthPanel extends Component { ); }; - render(props: RenderableProps, { isBlockedVisible }: State) { + render(props: Props, { isBlockedVisible }: State) { const { user, postInfo: { read_only }, diff --git a/frontend/app/components/avatar-icon/avatar-icon.tsx b/frontend/app/components/avatar-icon/avatar-icon.tsx index 2802d8cf..c11635b9 100644 --- a/frontend/app/components/avatar-icon/avatar-icon.tsx +++ b/frontend/app/components/avatar-icon/avatar-icon.tsx @@ -1,5 +1,5 @@ -/** @jsx h */ -import { h } from 'preact'; +/** @jsx createElement */ +import { createElement, JSX } from 'preact'; import b from 'bem-react-helper'; import { Theme } from '@app/common/types'; diff --git a/frontend/app/components/button/button.tsx b/frontend/app/components/button/button.tsx index a82a7474..cbff6e06 100644 --- a/frontend/app/components/button/button.tsx +++ b/frontend/app/components/button/button.tsx @@ -1,11 +1,11 @@ -/** @jsx h */ -import { Component, h, RenderableProps } from 'preact'; +/** @jsx createElement */ +import { createElement, JSX, Component } from 'preact'; import b from 'bem-react-helper'; import noop from '@app/utils/noop'; import { Theme } from '@app/common/types'; -interface Props { +type Props = { type?: string; kind?: string; theme: Theme; @@ -14,14 +14,14 @@ interface Props { onClick?: (e: MouseEvent) => void; onFocus?: (e: FocusEvent) => void; onBlur?: (e: FocusEvent) => void; -} +} & JSX.HTMLAttributes; interface State { isClicked: boolean; isFocused: boolean; } -export class Button extends Component { +export class Button extends Component { constructor(props: Props) { super(props); @@ -62,7 +62,7 @@ export class Button extends Component { this.props.onFocus!(e); } - render(props: RenderableProps, state: State) { + render(props: Props, state: State) { const { children, className } = props; const { isClicked, isFocused } = state; diff --git a/frontend/app/components/comment/comment.test.tsx b/frontend/app/components/comment/comment.test.tsx index 7f86b714..d6ae81dd 100644 --- a/frontend/app/components/comment/comment.test.tsx +++ b/frontend/app/components/comment/comment.test.tsx @@ -1,5 +1,5 @@ -/** @jsx h */ -import { h } from 'preact'; +/** @jsx createElement */ +import { createElement } from 'preact'; import { mount } from 'enzyme'; import { Props, Comment } from './comment'; import { User, Comment as CommentType, PostInfo } from '@app/common/types'; diff --git a/frontend/app/components/comment/comment.tsx b/frontend/app/components/comment/comment.tsx index 2fe104c2..a252a288 100644 --- a/frontend/app/components/comment/comment.tsx +++ b/frontend/app/components/comment/comment.tsx @@ -1,8 +1,8 @@ -/** @jsx h */ +/** @jsx createElement */ import './styles'; -import { h, Component, RenderableProps } from 'preact'; +import { createElement, JSX, Component, createRef } from 'preact'; import b from 'bem-react-helper'; import { getHandleClickProps } from '@app/common/accessibility'; @@ -72,7 +72,7 @@ export interface State { export class Comment extends Component { votingPromise: Promise; /** comment text node. Used in comment text copying */ - textNode?: HTMLDivElement; + textNode = createRef(); constructor(props: Props) { super(props); @@ -305,7 +305,7 @@ export class Comment extends Component { copyComment = () => { const username = this.props.data.user.name; const time = this.props.data.time; - const text = this.textNode!.textContent || ''; + const text = this.textNode.current!.textContent || ''; copy(`${username} ${time}
${text.replace(/\n+/g, '
')}`); @@ -447,7 +447,7 @@ export class Comment extends Component { return controls; }; - render(props: RenderableProps, state: State) { + render(props: Props, state: State) { const isAdmin = this.isAdmin(); const isGuest = this.isGuest(); const isCurrentUser = this.isCurrentUser(); @@ -547,7 +547,9 @@ export class Comment extends Component { } if (!props.editMode && this.props.inView === false) { - const [width, height] = this.base ? [this.base.scrollWidth, this.base.scrollHeight] : [100, 100]; + const [width, height] = this.base + ? [(this.base as Element).scrollWidth, (this.base as Element).scrollHeight] + : [100, 100]; return (
{ {(!props.collapsed || props.view === 'pinned') && (
(this.textNode = r)} + ref={this.textNode} dangerouslySetInnerHTML={{ __html: o.text }} /> )} diff --git a/frontend/app/components/comment/connected-comment.ts b/frontend/app/components/comment/connected-comment.tsx similarity index 64% rename from frontend/app/components/comment/connected-comment.ts rename to frontend/app/components/comment/connected-comment.tsx index 994288b3..ebc79da4 100644 --- a/frontend/app/components/comment/connected-comment.ts +++ b/frontend/app/components/comment/connected-comment.tsx @@ -3,11 +3,15 @@ * and should be importded explicitly */ +/** @jsx createElement */ + import './styles'; +import { createElement, FunctionComponent } from 'preact'; + import { Comment as CommentType } from '@app/common/types'; -import { connect } from 'preact-redux'; +import { useStore } from 'react-redux'; import { StoreState } from '@app/store'; import { @@ -26,27 +30,30 @@ import { getCommentMode } from '@app/store/comments/getters'; import { uploadImage, getPreview } from '@app/common/api'; import { getThreadIsCollapsed } from '@app/store/thread/getters'; import { bindActions } from '@app/utils/actionBinder'; +import { useActions } from '@app/hooks/useAction'; + +type ProvidedProps = Pick< + Props, + | 'editMode' + | 'user' + | 'isUserBanned' + | 'post_info' + | 'isCommentsDisabled' + | 'theme' + | 'collapsed' + | 'getPreview' + | 'uploadImage' +>; const mapStateToProps = (state: StoreState, cprops: { data: CommentType }) => { - const props: Pick< - Props, - | 'editMode' - | 'user' - | 'isUserBanned' - | 'post_info' - | 'isCommentsDisabled' - | 'theme' - | 'collapsed' - | 'getPreview' - | 'uploadImage' - > = { - editMode: getCommentMode(state, cprops.data.id), + const props: ProvidedProps = { + editMode: getCommentMode(cprops.data.id)(state), user: state.user, isUserBanned: cprops.data.user.block || state.bannedUsers.find(u => u.id === cprops.data.user.id) !== undefined, post_info: state.info, isCommentsDisabled: state.info.read_only || false, theme: state.theme, - collapsed: getThreadIsCollapsed(state, cprops.data), + collapsed: getThreadIsCollapsed(cprops.data)(state), getPreview, uploadImage, }; @@ -67,8 +74,8 @@ export const boundActions = bindActions({ setVerifyStatus: setVerifiedStatus, }); -/** Comment component connected to redux */ -export const ConnectedComment = connect( - mapStateToProps, - boundActions as Partial -)(Comment); +export const ConnectedComment: FunctionComponent> = props => { + const providedProps = mapStateToProps(useStore().getState(), props); + const actions = useActions(boundActions); + return ; +}; diff --git a/frontend/app/components/countdown/index.tsx b/frontend/app/components/countdown/index.tsx index 518f385f..6a72aec7 100644 --- a/frontend/app/components/countdown/index.tsx +++ b/frontend/app/components/countdown/index.tsx @@ -1,5 +1,5 @@ -/** @jsx h */ -import { h, Component, RenderableProps } from 'preact'; +/** @jsx createElement */ +import { createElement, JSX, Component, createRef } from 'preact'; import { exclude } from '@app/utils/exclude'; type Props = { @@ -14,7 +14,7 @@ interface State { /** Component which uses plain DOM mutation instead of rerendering react reactive reactivity */ export default class Countdown extends Component { - elemRef?: HTMLSpanElement; + elemRef = createRef(); intervalID?: number; constructor(props: Props) { super(props); @@ -41,7 +41,7 @@ export default class Countdown extends Component { tick() { if (this.elemRef) { const value = Math.max(0, (this.state.time - new Date().getTime()) / 1000).toFixed(0); - this.elemRef!.innerText = value; + this.elemRef.current!.innerText = value; if (value === '0') { this.props.onTimePassed && this.props.onTimePassed(); window.clearInterval(this.intervalID); @@ -56,7 +56,7 @@ export default class Countdown extends Component { this.tick(); }, 1000); } - render(props: RenderableProps) { - return (this.elemRef = ref)} />; + render(props: Props) { + return ; } } diff --git a/frontend/app/components/dropdown/__item/dropdown__item.tsx b/frontend/app/components/dropdown/__item/dropdown__item.tsx index dcacf804..0e008683 100644 --- a/frontend/app/components/dropdown/__item/dropdown__item.tsx +++ b/frontend/app/components/dropdown/__item/dropdown__item.tsx @@ -1,5 +1,5 @@ -/** @jsx h */ -import { h, RenderableProps } from 'preact'; +/** @jsx createElement */ +import { createElement, JSX, RenderableProps } from 'preact'; import b from 'bem-react-helper'; interface Props { diff --git a/frontend/app/components/dropdown/dropdown.tsx b/frontend/app/components/dropdown/dropdown.tsx index b4ffdda0..17872368 100644 --- a/frontend/app/components/dropdown/dropdown.tsx +++ b/frontend/app/components/dropdown/dropdown.tsx @@ -1,5 +1,5 @@ -/** @jsx h */ -import { Component, h, RenderableProps } from 'preact'; +/** @jsx createElement */ +import { createElement, Component, createRef } from 'preact'; import b from 'bem-react-helper'; import { Button } from '@app/components/button'; @@ -24,7 +24,7 @@ interface State { } export default class Dropdown extends Component { - rootNode?: HTMLDivElement; + rootNode = createRef(); constructor(props: Props) { super(props); @@ -52,10 +52,10 @@ export default class Dropdown extends Component { await this.__adjustDropDownContent(); if (isActive) { this.__onOpen(); - this.props.onOpen && this.props.onOpen(this.rootNode!); + this.props.onOpen && this.props.onOpen(this.rootNode.current!); } else { this.__onClose(); - this.props.onClose && this.props.onClose(this.rootNode!); + this.props.onClose && this.props.onClose(this.rootNode.current!); } if (this.props.onTitleClick) { @@ -71,8 +71,8 @@ export default class Dropdown extends Component { __onOpen() { const isChildOfDropDown = (() => { - if (!this.rootNode) return false; - let parent = this.rootNode.parentElement!; + if (!this.rootNode.current) return false; + let parent = this.rootNode.current.parentElement!; while (parent !== document.body) { if (parent.classList.contains('dropdown')) return true; parent = parent.parentElement!; @@ -87,10 +87,10 @@ export default class Dropdown extends Component { let prevDcBottom: number | null = null; this.checkInterval = window.setInterval(() => { - if (!this.rootNode || !this.state.isActive) return; + if (!this.rootNode.current || !this.state.isActive) return; const windowHeight = window.innerHeight; const dcBottom = (() => { - const dc = Array.from(this.rootNode.children).find(c => c.classList.contains('dropdown__content')); + const dc = Array.from(this.rootNode.current.children).find(c => c.classList.contains('dropdown__content')); if (!dc) return 0; const rect = dc.getBoundingClientRect(); return window.scrollY + Math.abs(rect.top) + dc.scrollHeight + 10; @@ -111,8 +111,8 @@ export default class Dropdown extends Component { } async __adjustDropDownContent() { - if (!this.rootNode) return; - const dc = this.rootNode.querySelector('.dropdown__content'); + if (!this.rootNode.current) return; + const dc = this.rootNode.current.querySelector('.dropdown__content'); if (!dc) return; await sleep(10); const rect = dc.getBoundingClientRect(); @@ -144,14 +144,14 @@ export default class Dropdown extends Component { }, () => { this.__onClose(); - this.props.onClose && this.props.onClose(this.rootNode!); + this.props.onClose && this.props.onClose(this.rootNode.current!); } ); } catch (e) {} } onOutsideClick(e: MouseEvent) { - if (!this.rootNode || this.rootNode.contains(e.target as Node) || !this.state.isActive) return; + if (!this.rootNode.current || this.rootNode.current.contains(e.target as Node) || !this.state.isActive) return; this.setState( { contentTranslateX: 0, @@ -159,7 +159,7 @@ export default class Dropdown extends Component { }, () => { this.__onClose(); - this.props.onClose && this.props.onClose(this.rootNode!); + this.props.onClose && this.props.onClose(this.rootNode.current!); } ); } @@ -176,11 +176,12 @@ export default class Dropdown extends Component { window.removeEventListener('message', this.receiveMessage); } - render(props: RenderableProps, { isActive }: State) { - const { title, titleClass, heading, children, mix } = props; + render() { + const { title, titleClass, heading, children, mix, theme } = this.props; + const { isActive } = this.state; return ( -
(this.rootNode = r)}> +