Related
I'm trying to properly type the following scenario:
I have a manager/service that contains a collection of items, each item itself consisting of a React component and it's Props.
A method on the manager class takes a Component and the Props as parameters and adds it to the collection.
A third component subscribes to the collection and renders each component with it's props.
Each component that is to be added to the collection have some common properties including some that have generic types.
The code (without types to simply convey the idea) is something like this:
// ModalService.ts
class ModalService {
modals = [];
addModal(component, props) {
this.modals.push({ component, props });
}
}
// ModalManager.tsx
const ModalManager = () => {
const modalService = useInjector(ModalService); // our custom DI injector
return (
<div className="modal-collection">
{modalService.modals.forEach(({ Component, props }) => {
<Component {...props} />
})}
</div>
)
}
// AboutModal.tsx
const AboutModal = (props) => {
return (
<div className={`about-modal ${props.show ? 'is-visible' : ''}`}>
Hi {props.name}, welcome to this bizarre example
<button onClick={() => props.onHide(true)}>
Close
</button>
</div>
)
}
// RandomNumber.tsx
const RandomNumberModal = (props) => {
const randomNum = Math.round(Math.random() * (props.max - props.min)) + props.min;
return (
<div className={`random-modal ${props.show ? 'is-visible' : ''}`}>
Today's random number is: {randomNum}
<button onClick={() => props.onHide(randomNum)}>
Close
</button>
</div>
)
}
// elsewhere in the app.tsx?
modalService.addModal(AboutModal, {
show: false,
onHide: () => void
})
modalService.addModal(RandomNumberModal, {
show: true,
onHide: (num) => saveResult(num),
min: 500,
max: 1000
})
The problem I'm stuck with is coming up with properly typed definitions for the ModalService members when TypeScript is running strict mode. Right now, my types look like this:
interface SharedModalProps<TResult> {
show: boolean;
onHide?: (result: TResult) => void;
}
interface AboutModalProps extends SharedModalProps<boolean> {
name: string;
}
interface RandomNumberModalProps extends SharedModalProps<number> {
min: number;
max: number;
}
interface ModalItem {
// cant use ComponentType<SharedModalProps<TResult>> here because the type parameter for
// TResult can be anything here
// Ideally, I should be able to do something like ComponentType<T extends SharedModalProps<TResult>> or something similar
Component: React.ComponentType;
props: ComponentProps<ModalItem['Component']>;
}
export type ModalsArray = ModalItem[];
export type AddModalFn = <TModal extends React.ComponentType>(
component: TModal,
props: React.ComponentProps<TModal>
) => void;
With this configuration, the compiler complains about the modal components being passed to the addModal(component, props) function being incompatible with the defined type.
If I disable strict mode in tsconfig.json, then this error goes away and the correct types are resolved for the props parameter and completions for them work as well. But disabling Strict mode isn't really a solution.
What would be the correct way to type these components and the service so the compiler is able to infer the correct types and the editor is able to offer proper completions etc. as well?
I have this sample code up and running in a Code Sandbox here for reference.
Try this type. All errors now shown
export default class ModalManagerService {
#observable
modals: ModalItem[] = [];
addModal<TProps, TModal extends React.ComponentType<TProps>>(Component: TModal & React.ComponentType<TProps>, props: TProps) {
this.modals.push({ Component, props });
// expect props to have the shared types from SharedModalProps
}
}
I use this for reference: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-router/index.d.ts
If you don't need to keep track of the props, you could just store wrappers of the components you need to render instead of storing the component and props separately.
Service
interface ModalItem {
Component: React.ComponentType;
}
...
addModal(Component: React.ComponentType) {
this.modals.push({ Component });
}
...
App
...
service.addModal(() => (
<AboutModal show name="Sierra-117" onHide={(result: boolean) => console.log('test')} />
));
...
ModalManager
{managerService.modals.map(({ Component }, index) => {
return <Component />;
})}
here is your code with types, it validates that you pass the correct props as well.
You can do the same with interfaces if you prefer them.
and link to ts playground.
type ModalProps<T> = {
show: boolean,
onHide: (val: T) => void
}
class ModalService {
modals: Array<{Component:React.FunctionComponent<any>, props: ModalProps<any>}> = [];
addModal<T extends ModalProps<any>>(Component: React.FunctionComponent<T>, props: T) {
this.modals.push({ Component, props });
}
}
const modalService = useInjector(ModalService); // our custom DI injector
const ModalManager = () => {
return (
<div className="modal-collection">
{modalService.modals.map(({ Component, props }) => {
return <Component {...props} />
})}
</div>
)
}
const AboutModal = (props: {name: string} & ModalProps<boolean>) => {
return (
<div className={`about-modal ${props.show ? 'is-visible' : ''}`}>
Hi {props.name}, welcome to this bizarre example
<button onClick={() => props.onHide(true)}>
Close
</button>
</div>
)
}
const RandomNumberModal = (props: {max: number, min: number} & ModalProps<number>) => {
const randomNum = Math.round(Math.random() * (props.max - props.min)) + props.min;
return (
<div className={`random-modal ${props.show ? 'is-visible' : ''}`}>
Today's random number is: {randomNum}
<button onClick={() => props.onHide(randomNum)}>
Close
</button>
</div>
)
}
modalService.addModal(AboutModal, {
onHide: () => {},
show: false,
name: 'asdf',
});
function useInjector(Service: typeof ModalService) {
return new Service();
}
you can install #types/react as dependency in codesandbox.
after that, your mentioned error appears. (sandbox)
the errors says the type of AboutModal is FC<AboutModalProps> (a functional component with props of type AboutModalProps), but it's not assignable to type React.ComponentType<{}> (a react component with unspecified prop types).
one way is to accept any prop types in your component:
// CHANGE:
Component: React.ComponentType
// TO:
Component: React.ComponentType<any> // accept component with any props
With adding <any> (as component props), the component accepts the props of type AboutModalProps and the error is gone. (sandbox)
Now, we can replace any with a more specific type like allModalProps:
export interface allModalProps extends SharedModalProps<any> {
// put all of the possible modal props here.
name: string;
min: number;
max: number;
}
Now let's use the above declaration instead of React.ComponentType<any>:
Component: React.ComponentType<allModalProps> // replaced any with allModalProps
This way, we defined a modal props interface, and we used this interface as component props, instead of any. and we have no type errors.
Here is the sandbox: Sandbox
I'm using Typescript with React. I'm having trouble understanding how to use refs so as to get static typing and intellisense with respect to the react nodes referenced by the refs. My code is as follows.
import * as React from 'react';
interface AppState {
count: number;
}
interface AppProps {
steps: number;
}
interface AppRefs {
stepInput: HTMLInputElement;
}
export default class TestApp extends React.Component<AppProps, AppState> {
constructor(props: AppProps) {
super(props);
this.state = {
count: 0
};
}
incrementCounter() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div>
<h1>Hello World</h1>
<input type="text" ref="stepInput" />
<button onClick={() => this.incrementCounter()}>Increment</button>
Count : {this.state.count}
</div>
);
}}
If you’re using React 16.3+, the suggested way to create refs is using React.createRef().
class TestApp extends React.Component<AppProps, AppState> {
private stepInput: React.RefObject<HTMLInputElement>;
constructor(props) {
super(props);
this.stepInput = React.createRef();
}
render() {
return <input type="text" ref={this.stepInput} />;
}
}
When the component mounts, the ref attribute’s current property will be assigned to the referenced component/DOM element and assigned back to null when it unmounts. So, for example, you can access it using this.stepInput.current.
For more on RefObject, see #apieceofbart's answer or the PR createRef() was added in.
If you’re using an earlier version of React (<16.3) or need more fine-grained control over when refs are set and unset, you can use “callback refs”.
class TestApp extends React.Component<AppProps, AppState> {
private stepInput: HTMLInputElement;
constructor(props) {
super(props);
this.stepInput = null;
this.setStepInputRef = element => {
this.stepInput = element;
};
}
render() {
return <input type="text" ref={this.setStepInputRef} />
}
}
When the component mounts, React will call the ref callback with the DOM element, and will call it with null when it unmounts. So, for example, you can access it simply using this.stepInput.
By defining the ref callback as a bound method on the class as opposed to an inline function (as in a previous version of this answer), you can avoid the callback getting called twice during updates.
There used to be an API where the ref attribute was a string (see Akshar Patel's answer), but due to some issues, string refs are strongly discouraged and will eventually be removed.
Edited May 22, 2018 to add the new way of doing refs in React 16.3. Thanks #apieceofbart for pointing out that there was a new way.
React.createRef (class comp.)
class ClassApp extends React.Component {
inputRef = React.createRef<HTMLInputElement>();
render() {
return <input type="text" ref={this.inputRef} />
}
}
React.useRef (Hooks / function comp.)
a) Use readonly refs for React-managed DOM nodes:
const FunctionApp = () => {
// note the passed-in `null` arg ----------------v
const inputRef = React.useRef<HTMLInputElement>(null)
return <input type="text" ref={inputRef} />
}
inputRef.current becomes a readonly property by initializing its value with null.
b) Use mutable refs for arbitrary stored values akin to instance variables:
const FunctionApp = () => {
const renderCountRef = useRef(0)
useEffect(() => {
renderCountRef.current += 1
})
// ... other render code
}
Note: Don't initialize useRef with null in this case - it would make the renderCountRef type readonly (see example). If you need to provide null as initial value, do this:
const renderCountRef = useRef<number | null>(null)
Callback refs (both)
// Function component example, class analogue
const FunctionApp = () => {
const handleDomNodeChange = (domNode: HTMLInputElement | null) => {
// ... do something with changed dom node.
}
return <input type="text" ref={handleDomNodeChange} />
}
Note: String Refs are considered legacy and omitted for the scope of this answer.
Playground sample
One way (which I've been doing) is to setup manually :
refs: {
[string: string]: any;
stepInput:any;
}
then you can even wrap this up in a nicer getter function (e.g. here):
stepInput = (): HTMLInputElement => ReactDOM.findDOMNode(this.refs.stepInput);
Since React 16.3 the way to add refs is to use React.createRef as Jeff Bowen pointed in his answer. However you can take advantage of Typescript to better type your ref.
In your example you're using ref on input element. So they way I would do it is:
class SomeComponent extends React.Component<IProps, IState> {
private inputRef: React.RefObject<HTMLInputElement>;
constructor() {
...
this.inputRef = React.createRef();
}
...
render() {
<input type="text" ref={this.inputRef} />;
}
}
By doing this when you want to make use of that ref you have access to all input methods:
someMethod() {
this.inputRef.current.focus(); // 'current' is input node, autocompletion, yay!
}
You can use it on custom components as well:
private componentRef: React.RefObject<React.Component<IProps>>;
and then have, for example, access to props :
this.componentRef.current.props; // 'props' satisfy IProps interface
If you're using React.FC, add the HTMLDivElement interface:
const myRef = React.useRef<HTMLDivElement>(null);
And use it like the following:
return <div ref={myRef} />;
EDIT: This is no longer the right way to use refs with Typescript. Look at Jeff Bowen's answer and upvote it to increase its visibility.
Found the answer to the problem. Use refs as below inside the class.
refs: {
[key: string]: (Element);
stepInput: (HTMLInputElement);
}
Thanks #basarat for pointing in the right direction.
For those looking on how to do it when you have an array of elements:
const textInputRefs = useRef<(HTMLDivElement | null)[]>([])
...
const onClickFocus = (event: React.BaseSyntheticEvent, index: number) => {
textInputRefs.current[index]?.focus()
};
...
{items.map((item, index) => (
<textInput
inputRef={(ref) => textInputs.current[index] = ref}
/>
<Button
onClick={event => onClickFocus(event, index)}
/>
}
To use the callback style (https://facebook.github.io/react/docs/refs-and-the-dom.html) as recommended on React's documentation you can add a definition for a property on the class:
export class Foo extends React.Component<{}, {}> {
// You don't need to use 'references' as the name
references: {
// If you are using other components be more specific than HTMLInputElement
myRef: HTMLInputElement;
} = {
myRef: null
}
...
myFunction() {
// Use like this
this.references.myRef.focus();
}
...
render() {
return(<input ref={(i: any) => { this.references.myRef = i; }}/>)
}
For typescript user no constructor required.
...
private divRef: HTMLDivElement | null = null
getDivRef = (ref: HTMLDivElement | null): void => {
this.divRef = ref
}
render() {
return <div ref={this.getDivRef} />
}
...
If you wont to forward your ref, in Props interface you need to use RefObject<CmpType> type from import React, { RefObject } from 'react';
Lacking a complete example, here is my little test script for getting user input when working with React and TypeScript. Based partially on the other comments and this link https://medium.com/#basarat/strongly-typed-refs-for-react-typescript-9a07419f807#.cdrghertm
/// <reference path="typings/react/react-global.d.ts" />
// Init our code using jquery on document ready
$(function () {
ReactDOM.render(<ServerTime />, document.getElementById("reactTest"));
});
interface IServerTimeProps {
}
interface IServerTimeState {
time: string;
}
interface IServerTimeInputs {
userFormat?: HTMLInputElement;
}
class ServerTime extends React.Component<IServerTimeProps, IServerTimeState> {
inputs: IServerTimeInputs = {};
constructor() {
super();
this.state = { time: "unknown" }
}
render() {
return (
<div>
<div>Server time: { this.state.time }</div>
<input type="text" ref={ a => this.inputs.userFormat = a } defaultValue="s" ></input>
<button onClick={ this._buttonClick.bind(this) }>GetTime</button>
</div>
);
}
// Update state with value from server
_buttonClick(): void {
alert(`Format:${this.inputs.userFormat.value}`);
// This part requires a listening web server to work, but alert shows the user input
jQuery.ajax({
method: "POST",
data: { format: this.inputs.userFormat.value },
url: "/Home/ServerTime",
success: (result) => {
this.setState({ time : result });
}
});
}
}
From React type definition
type ReactInstance = Component<any, any> | Element;
....
refs: {
[key: string]: ReactInstance
};
So you can access you refs element as follow
stepInput = () => ReactDOM.findDOMNode(this.refs['stepInput']);
without redefinition of refs index.
As #manakor mentioned you can get error like
Property 'stepInput' does not exist on type '{ [key: string]:
Component | Element; }
if you redefine refs(depends on IDE and ts version you use)
Just to add a different approach - you can simply cast your ref, something like:
let myInputElement: Element = this.refs["myInput"] as Element
I always do this, in that case
to grab a ref
let input: HTMLInputElement = ReactDOM.findDOMNode<HTMLInputElement>(this.refs.input);
FIRST ADD AN IMPORT
import React, { useRef } from "react";
THEN THIS
const studentCapacityRef = useRef<HTMLInputElement>(null);
OR THIS
const studentCapacityRef = useRef<HTMLAreaElement>(null);
OR THIS
const studentCapacityRef = useRef<HTMLDivElement>(null);
etc...
class SelfFocusingInput extends React.Component<{ value: string, onChange: (value: string) => any }, {}>{
ctrls: {
input?: HTMLInputElement;
} = {};
render() {
return (
<input
ref={(input) => this.ctrls.input = input}
value={this.props.value}
onChange={(e) => { this.props.onChange(this.ctrls.input.value) } }
/>
);
}
componentDidMount() {
this.ctrls.input.focus();
}
}
put them in an object
I’m trying to use the AgGrid framework with a cellRenderer to render a div within a table and attach a tooltip to this div. The function that will render the tooltip that I’m using, showMyTooltip, expects an HTML element as an anchor. It works when attaching to other components, but for some reason doesn’t work when I try to attach to my cellRenderer. Here’s the cellRenderer component. When I log the value of this.anchor, I get “undefined.” Can anyone spot what I might be missing?
export class MyCellRenderer extends Component {
render() {
const { showTooltip, shouldOpenTooltip, tooltipContent } = this.props.value;
return
(<div>
<div ref={(el) => { this.anchor = el; }}>
<div> SHOULD ANCHOR TO THIS </div>
</div>
{shouldOpenTooltip &&
showMyTooltip({
anchor: this.anchor,
children: tooltipContent,
})
}
</div>);
}
}
export default MyCellRenderer;
I guess the cell renderer by default renders as a string. Below is an example for cell renderer using angular, maybe this will guide you for reactjs? In below example I have created a custom cell renderer and has assigned the name of the renderer in col def of ag-grid.
import { Component } from "#angular/core";
import { ICellRendererAngularComp } from "ag-grid-angular";
#Component({
selector: 'tooltip-cell',
template: `<ng-template #popTemplate>
<div class="tooltip-renderer">
Created By: {{creator}} <br/>
Created On: {{crDateTime | date:'short'}} <br/>
Revised By: {{revisor}} <br/>
Revised On: {{revDateTime | date:'short'}} <br/>
</div>
</ng-template>
<span [tooltip]="popTemplate" placement="left" container="body" triggers="mouseenter mouseleave dblclick">{{params.value}}</span>` })
export class ToolTipRenderer implements ICellRendererAngularComp {
public params: any;
public creator: any;
public crDateTime: any;
public revisor: any;
public revDateTime: any;
agInit(params: any): void {
this.params = params;
this.creator = params.data["Creator"];
this.crDateTime = params.data["CrDate"];
this.revisor = params.data["Revisor"];
this.revDateTime = params.data["RevDate"];
}
refresh(): boolean {
return false;
}
}
var colDef1 = {
cellRenderer: "tooltipRenderer",
...
}
I'm using Typescript with React. I'm having trouble understanding how to use refs so as to get static typing and intellisense with respect to the react nodes referenced by the refs. My code is as follows.
import * as React from 'react';
interface AppState {
count: number;
}
interface AppProps {
steps: number;
}
interface AppRefs {
stepInput: HTMLInputElement;
}
export default class TestApp extends React.Component<AppProps, AppState> {
constructor(props: AppProps) {
super(props);
this.state = {
count: 0
};
}
incrementCounter() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div>
<h1>Hello World</h1>
<input type="text" ref="stepInput" />
<button onClick={() => this.incrementCounter()}>Increment</button>
Count : {this.state.count}
</div>
);
}}
If you’re using React 16.3+, the suggested way to create refs is using React.createRef().
class TestApp extends React.Component<AppProps, AppState> {
private stepInput: React.RefObject<HTMLInputElement>;
constructor(props) {
super(props);
this.stepInput = React.createRef();
}
render() {
return <input type="text" ref={this.stepInput} />;
}
}
When the component mounts, the ref attribute’s current property will be assigned to the referenced component/DOM element and assigned back to null when it unmounts. So, for example, you can access it using this.stepInput.current.
For more on RefObject, see #apieceofbart's answer or the PR createRef() was added in.
If you’re using an earlier version of React (<16.3) or need more fine-grained control over when refs are set and unset, you can use “callback refs”.
class TestApp extends React.Component<AppProps, AppState> {
private stepInput: HTMLInputElement;
constructor(props) {
super(props);
this.stepInput = null;
this.setStepInputRef = element => {
this.stepInput = element;
};
}
render() {
return <input type="text" ref={this.setStepInputRef} />
}
}
When the component mounts, React will call the ref callback with the DOM element, and will call it with null when it unmounts. So, for example, you can access it simply using this.stepInput.
By defining the ref callback as a bound method on the class as opposed to an inline function (as in a previous version of this answer), you can avoid the callback getting called twice during updates.
There used to be an API where the ref attribute was a string (see Akshar Patel's answer), but due to some issues, string refs are strongly discouraged and will eventually be removed.
Edited May 22, 2018 to add the new way of doing refs in React 16.3. Thanks #apieceofbart for pointing out that there was a new way.
React.createRef (class comp.)
class ClassApp extends React.Component {
inputRef = React.createRef<HTMLInputElement>();
render() {
return <input type="text" ref={this.inputRef} />
}
}
React.useRef (Hooks / function comp.)
a) Use readonly refs for React-managed DOM nodes:
const FunctionApp = () => {
// note the passed-in `null` arg ----------------v
const inputRef = React.useRef<HTMLInputElement>(null)
return <input type="text" ref={inputRef} />
}
inputRef.current becomes a readonly property by initializing its value with null.
b) Use mutable refs for arbitrary stored values akin to instance variables:
const FunctionApp = () => {
const renderCountRef = useRef(0)
useEffect(() => {
renderCountRef.current += 1
})
// ... other render code
}
Note: Don't initialize useRef with null in this case - it would make the renderCountRef type readonly (see example). If you need to provide null as initial value, do this:
const renderCountRef = useRef<number | null>(null)
Callback refs (both)
// Function component example, class analogue
const FunctionApp = () => {
const handleDomNodeChange = (domNode: HTMLInputElement | null) => {
// ... do something with changed dom node.
}
return <input type="text" ref={handleDomNodeChange} />
}
Note: String Refs are considered legacy and omitted for the scope of this answer.
Playground sample
One way (which I've been doing) is to setup manually :
refs: {
[string: string]: any;
stepInput:any;
}
then you can even wrap this up in a nicer getter function (e.g. here):
stepInput = (): HTMLInputElement => ReactDOM.findDOMNode(this.refs.stepInput);
Since React 16.3 the way to add refs is to use React.createRef as Jeff Bowen pointed in his answer. However you can take advantage of Typescript to better type your ref.
In your example you're using ref on input element. So they way I would do it is:
class SomeComponent extends React.Component<IProps, IState> {
private inputRef: React.RefObject<HTMLInputElement>;
constructor() {
...
this.inputRef = React.createRef();
}
...
render() {
<input type="text" ref={this.inputRef} />;
}
}
By doing this when you want to make use of that ref you have access to all input methods:
someMethod() {
this.inputRef.current.focus(); // 'current' is input node, autocompletion, yay!
}
You can use it on custom components as well:
private componentRef: React.RefObject<React.Component<IProps>>;
and then have, for example, access to props :
this.componentRef.current.props; // 'props' satisfy IProps interface
If you're using React.FC, add the HTMLDivElement interface:
const myRef = React.useRef<HTMLDivElement>(null);
And use it like the following:
return <div ref={myRef} />;
EDIT: This is no longer the right way to use refs with Typescript. Look at Jeff Bowen's answer and upvote it to increase its visibility.
Found the answer to the problem. Use refs as below inside the class.
refs: {
[key: string]: (Element);
stepInput: (HTMLInputElement);
}
Thanks #basarat for pointing in the right direction.
For those looking on how to do it when you have an array of elements:
const textInputRefs = useRef<(HTMLDivElement | null)[]>([])
...
const onClickFocus = (event: React.BaseSyntheticEvent, index: number) => {
textInputRefs.current[index]?.focus()
};
...
{items.map((item, index) => (
<textInput
inputRef={(ref) => textInputs.current[index] = ref}
/>
<Button
onClick={event => onClickFocus(event, index)}
/>
}
To use the callback style (https://facebook.github.io/react/docs/refs-and-the-dom.html) as recommended on React's documentation you can add a definition for a property on the class:
export class Foo extends React.Component<{}, {}> {
// You don't need to use 'references' as the name
references: {
// If you are using other components be more specific than HTMLInputElement
myRef: HTMLInputElement;
} = {
myRef: null
}
...
myFunction() {
// Use like this
this.references.myRef.focus();
}
...
render() {
return(<input ref={(i: any) => { this.references.myRef = i; }}/>)
}
For typescript user no constructor required.
...
private divRef: HTMLDivElement | null = null
getDivRef = (ref: HTMLDivElement | null): void => {
this.divRef = ref
}
render() {
return <div ref={this.getDivRef} />
}
...
If you wont to forward your ref, in Props interface you need to use RefObject<CmpType> type from import React, { RefObject } from 'react';
Lacking a complete example, here is my little test script for getting user input when working with React and TypeScript. Based partially on the other comments and this link https://medium.com/#basarat/strongly-typed-refs-for-react-typescript-9a07419f807#.cdrghertm
/// <reference path="typings/react/react-global.d.ts" />
// Init our code using jquery on document ready
$(function () {
ReactDOM.render(<ServerTime />, document.getElementById("reactTest"));
});
interface IServerTimeProps {
}
interface IServerTimeState {
time: string;
}
interface IServerTimeInputs {
userFormat?: HTMLInputElement;
}
class ServerTime extends React.Component<IServerTimeProps, IServerTimeState> {
inputs: IServerTimeInputs = {};
constructor() {
super();
this.state = { time: "unknown" }
}
render() {
return (
<div>
<div>Server time: { this.state.time }</div>
<input type="text" ref={ a => this.inputs.userFormat = a } defaultValue="s" ></input>
<button onClick={ this._buttonClick.bind(this) }>GetTime</button>
</div>
);
}
// Update state with value from server
_buttonClick(): void {
alert(`Format:${this.inputs.userFormat.value}`);
// This part requires a listening web server to work, but alert shows the user input
jQuery.ajax({
method: "POST",
data: { format: this.inputs.userFormat.value },
url: "/Home/ServerTime",
success: (result) => {
this.setState({ time : result });
}
});
}
}
From React type definition
type ReactInstance = Component<any, any> | Element;
....
refs: {
[key: string]: ReactInstance
};
So you can access you refs element as follow
stepInput = () => ReactDOM.findDOMNode(this.refs['stepInput']);
without redefinition of refs index.
As #manakor mentioned you can get error like
Property 'stepInput' does not exist on type '{ [key: string]:
Component | Element; }
if you redefine refs(depends on IDE and ts version you use)
Just to add a different approach - you can simply cast your ref, something like:
let myInputElement: Element = this.refs["myInput"] as Element
I always do this, in that case
to grab a ref
let input: HTMLInputElement = ReactDOM.findDOMNode<HTMLInputElement>(this.refs.input);
FIRST ADD AN IMPORT
import React, { useRef } from "react";
THEN THIS
const studentCapacityRef = useRef<HTMLInputElement>(null);
OR THIS
const studentCapacityRef = useRef<HTMLAreaElement>(null);
OR THIS
const studentCapacityRef = useRef<HTMLDivElement>(null);
etc...
class SelfFocusingInput extends React.Component<{ value: string, onChange: (value: string) => any }, {}>{
ctrls: {
input?: HTMLInputElement;
} = {};
render() {
return (
<input
ref={(input) => this.ctrls.input = input}
value={this.props.value}
onChange={(e) => { this.props.onChange(this.ctrls.input.value) } }
/>
);
}
componentDidMount() {
this.ctrls.input.focus();
}
}
put them in an object
I want to call a method exposed by a React component from the instance of a React Element.
For example, in this jsfiddle. I want to call the alertMessage method from the HelloElement reference.
Is there a way to achieve this without having to write additional wrappers?
Edit (copied code from JSFiddle)
<div id="container"></div>
<button onclick="onButtonClick()">Click me!</button>
var onButtonClick = function () {
//call alertMessage method from the reference of a React Element! Something like HelloElement.alertMessage()
console.log("clicked!");
}
var Hello = React.createClass({displayName: 'Hello',
alertMessage: function() {
alert(this.props.name);
},
render: function() {
return React.createElement("div", null, "Hello ", this.props.name);
}
});
var HelloElement = React.createElement(Hello, {name: "World"});
React.render(
HelloElement,
document.getElementById('container')
);
There are two ways to access an inner function. One, instance-level, like you want, another, static level.
Instance
You need to call the function on the return from React.render. See below.
Static
Take a look at ReactJS Statics. Note, however, that a static function cannot access instance-level data, so this would be undefined.
var onButtonClick = function () {
//call alertMessage method from the reference of a React Element!
HelloRendered.alertMessage();
//call static alertMessage method from the reference of a React Class!
Hello.alertMessage();
console.log("clicked!");
}
var Hello = React.createClass({
displayName: 'Hello',
statics: {
alertMessage: function () {
alert('static message');
}
},
alertMessage: function () {
alert(this.props.name);
},
render: function () {
return React.createElement("div", null, "Hello ", this.props.name);
}
});
var HelloElement = React.createElement(Hello, {
name: "World"
});
var HelloRendered = React.render(HelloElement, document.getElementById('container'));
Then do HelloRendered.alertMessage().
You can do like
import React from 'react';
class Header extends React.Component{
constructor(){
super();
window.helloComponent = this;
}
alertMessage(){
console.log("Called from outside");
}
render(){
return (
<AppBar style={{background:'#000'}}>
Hello
</AppBar>
)
}
}
export default Header;
Now from outside of this component you can called like this below
window.helloComponent.alertMessage();
1. With React hooks - useImperativeHandle + useRef
const MyComponent = ({myRef}) => {
const handleClick = () => alert('hello world')
useImperativeHandle(myRef, () => ({
handleClick
}), [/* dependencies (if any) */])
return (<button onClick={handleClick}>Original Button</button>)
}
MyComponent.defaultProps = {
myRef: {current: {}}
}
const MyParentComponent = () => {
const myRef = React.useRef({})
return (
<>
<MyComponent
myRef={myRef}
/>
<button onClick={myRef.current.handleClick}>
Additional Button
</button>
</>
)
}
2. With only React hook - useRef
const MyComponent = ({myRef}) => {
const handleClick = () => alert('hello world')
myRef.current.handleClick = handleClick
return (<button onClick={handleClick}>Original Button</button>)
}
MyComponent.defaultProps = {
myRef: {current: {}}
}
const MyParentComponent = () => {
const myRef = React.useRef({})
return (
<>
<MyComponent
myRef={myRef}
/>
<button onClick={myRef.current.handleClick}>
Additional Button
</button>
</>
)
}
Good Luck...
I've done something like this:
class Cow extends React.Component {
constructor (props) {
super(props);
this.state = {text: 'hello'};
}
componentDidMount () {
if (this.props.onMounted) {
this.props.onMounted({
say: text => this.say(text)
});
}
}
render () {
return (
<pre>
___________________
< {this.state.text} >
-------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
</pre>
);
}
say (text) {
this.setState({text: text});
}
}
And then somewhere else:
class Pasture extends React.Component {
render () {
return (
<div>
<Cow onMounted={callbacks => this.cowMounted(callbacks)} />
<button onClick={() => this.changeCow()} />
</div>
);
}
cowMounted (callbacks) {
this.cowCallbacks = callbacks;
}
changeCow () {
this.cowCallbacks.say('moo');
}
}
I haven't tested this exact code, but this is along the lines of what I did in a project of mine and it works nicely :). Of course this is a bad example, you should just use props for this, but in my case the sub-component did an API call which I wanted to keep inside that component. In such a case this is a nice solution.
With the render method potentially deprecating the returned value, the recommended approach is now to attach a callback ref to the root element. Like this:
ReactDOM.render( <Hello name="World" ref={(element) => {window.helloComponent = element}}/>, document.getElementById('container'));
which we can then access using window.helloComponent, and any of its methods can be accessed with window.helloComponent.METHOD.
Here's a full example:
var onButtonClick = function() {
window.helloComponent.alertMessage();
}
class Hello extends React.Component {
alertMessage() {
alert(this.props.name);
}
render() {
return React.createElement("div", null, "Hello ", this.props.name);
}
};
ReactDOM.render( <Hello name="World" ref={(element) => {window.helloComponent = element}}/>, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
<button onclick="onButtonClick()">Click me!</button>
You can just add an onClick handler to the div with the function (onClick is React's own implementation of onClick) and you can access the property within { } curly braces, and your alert message will appear.
In case you wish to define static methods that can be called on the component class - you should use statics. Although:
"Methods defined within this block are static, meaning that you can run them before any component instances are created, and the methods do not have access to the props or state of your components. If you want to check the value of props in a static method, have the caller pass in the props as an argument to the static method." (source)
Some example code:
const Hello = React.createClass({
/*
The statics object allows you to define static methods that can be called on the component class. For example:
*/
statics: {
customMethod: function(foo) {
return foo === 'bar';
}
},
alertMessage: function() {
alert(this.props.name);
},
render: function () {
return (
<div onClick={this.alertMessage}>
Hello {this.props.name}
</div>
);
}
});
React.render(<Hello name={'aworld'} />, document.body);
Hope this helps you a bit, because i don't know if I understood your question correctly, so correct me if i interpreted it wrong:)
It appears statics are deprecated, and the other methods of exposing some functions with render seem convoluted. Meanwhile, this Stack Overflow answer about debugging React, while seeming hack-y, did the job for me.
If you are in ES6 just use the "static" keyword on your method from your example would be the following: static alertMessage: function() {
...
},
Hope can help anyone out there :)
I use this helper method to render components and return an component instance.
Methods can be called on that instance.
static async renderComponentAt(componentClass, props, parentElementId){
let componentId = props.id;
if(!componentId){
throw Error('Component has no id property. Please include id:"...xyz..." to component properties.');
}
let parentElement = document.getElementById(parentElementId);
return await new Promise((resolve, reject) => {
props.ref = (component)=>{
resolve(component);
};
let element = React.createElement(componentClass, props, null);
ReactDOM.render(element, parentElement);
});
}
class AppProvider extends Component {
constructor() {
super();
window.alertMessage = this.alertMessage.bind(this);
}
alertMessage() {
console.log('Hello World');
}
}
You can call this method from the window by using window.alertMessage().
method 1 using ChildRef:
public childRef: any = React.createRef<Hello>();
public onButtonClick= () => {
console.log(this.childRef.current); // this will have your child reference
}
<Hello ref = { this.childRef }/>
<button onclick="onButtonClick()">Click me!</button>
Method 2: using window register
public onButtonClick= () => {
console.log(window.yourRef); // this will have your child reference
}
<Hello ref = { (ref) => {window.yourRef = ref} }/>`
<button onclick="onButtonClick()">Click me!</button>
With React17 you can use useImperativeHandle hook.
useImperativeHandle customizes the instance value that is exposed to parent components when using ref. As always, imperative code using refs should be avoided in most cases. useImperativeHandle should be used with forwardRef:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
In this example, a parent component that renders would be able to call inputRef.current.focus().
Though this question is closed, I would like to share another approach.
Here's what worked for me:
Child Component
Child component accepts a prop, let's call it onExportedMethods, the aim is to return the set of instance methods that this component wants to give to consumers.
The decision of what needs to be exposed is done at constructor level.
Consumer Component
pass method for prop onExportedMethods & in the handler keep copy of the set of methods Child component exposes.
Whenever required, parent component can call the exposed method
Checkout the sample here
For dynamic components I used the getDerivedStateFromProps method with props.
You can create function that update the props of the child component, The getDerivedStateFromProps in the child component will handle the update of the props for you.
For example:
class Parent extends React.Component
{
constructor(props)
{
super(props);
this.state = { selectMachine: '1' };
this.setComponent = null;
}
handleMachineChange = (e) =>{
this.setState({selectMachine: e.target.value})
}
}
class Child extends React.Component
{
state = {
programForm: {
machine_id: '1',
}
}
constructor(props)
{
super(props);
}
static getDerivedStateFromProps(props, state) {
if(props.selectMachine !== state.programForm.machine_id){
//Change in props
return{
programForm: { ...state.programForm, machine_id: props.selectMachine }
};
}
return null; // No change to state
}
}