Want to use a reference to a dynamically imported component in next js as below. How do I do this?
Given
// component1.jsx
export default class Component1 extends React.Component {
constructor(props) {
super(props)
}
render() {
return (<p>something</p>)
}
doSomething() {
console.log('should work')
}
}
And
// component2.jsx
import dynamic from 'next/dynamic'
const Component1 = dynamic(import('./component1'), {ssr: false})
class Component2 extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
componentDidMount() {
this.myRef.current.doSomething(); // This fails!
}
render() {
return (<Component1 ref={this.myRef}/>);
}
}
Already looked at https://github.com/vercel/next.js/issues/4957 and the solution there just doesn't work?
I am trying to get props via state object.
import React, { Component } from 'react';
import Counter from './counter';
class Counters extends Component {
state = {
counters: [
{id:1, value:1},
{id:2, value:2},
]
};
render() {
return(
<div>
this.state.counters.map(counter => (
<Counter key={counter.id} value={counter.value}/>))}
</div>)
}
}
export default Counters;
import React, { Component } from 'react';
class Counter extends Component {
state = {
value: this.props.value
};
constructor(){
super();
}
render() {
console.log(this.props);
return (
<div>
</div>
);
}
}
export default Counter;
Why can't I get props within state object? I am seeing props in render.
I am getting TypeError: Cannot read property 'value' of undefined.
Can someone show me how to pass props correctly to the state object. I have seen props passed this way by other coders.
Thanks,
Rob.
You need to pass props to super and define the state in the constructor or remove the constructor and use state directly.
CODESANDBOX
import React, { Component } from "react";
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
value: this.props.value
};
}
render() {
console.log(this.props);
return <div>{this.state.value}</div>;
}
}
export default Counter;
or CODESANDBOX
import React, { Component } from "react";
class Counter extends Component {
state = {
value: this.props.value
};
render() {
console.log(this.props);
return <div>{this.state.value}</div>;
}
}
export default Counter;
I am trying to type the props of my component and use an URL param at the same time. I get the following error:
Property 'match' does not exist on type
'Readonly<{children?:ReactNode}> & Readonly'
Here is some of my code:
import Api from '../../api/Api';
interface MyProps {
api: Api
}
interface MyState {
someString: string,
loading: boolean
}
export class MyComponent extends React.Component<MyProps, MyState> {
constructor(props: MyProps) {
super(props);
this.state = {
someString: this.props.match.params.someString,//<-- Error is here on this line
loading: true
}
}
componentDidMount() {
this.props.api.getSomeInfo(this.state.someString, this.callback)
}
callback() {
let interval = setInterval(function () {
this.setState({loading: false});
clearInterval(interval)
}, 3000);
}
render() {
return (
<div>
<p>{this.someString}</p>
</div>
);
}
}
As you can see all I am trying to do is:
1- Go to:
http://localhost:8080/someStrings/:someString
2- Grab the value of :someString in my component's constructor and store in state
3- Use the value of someString in my state to be able to pass it as an argument to my API module to do stuff
4- When the callback is executed in the API I remove the loading animation
My question is basically, how do I declare my MyProps to be able to acheive this?
This is an open issue in type definition. https://github.com/DefinitelyTyped/DefinitelyTyped/issues/17355
Workaround
import { RouteProps } from 'react-router';
import React from 'react';
interface MyProps {
api: Api
}
interface MyState {
someString: string,
loading: boolean
}
class MyComponent extends React.Component<Props & RouteProps, State> // Pay attention here.
{
// your code...
}
ref: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/17355#issuecomment-336022780
This is how I solved it
import {RouteComponentProps} from 'react-router';
interface IMyProps {}
interface IReactRouterParams {
roomName: string;
username: string;
}
export class MyComponent extends React.Component<
IMyProps & RouteComponentProps<IReactRouterParams> {
constructor(props: any) {
super(props);
//everything works here
const {roomName, username} = this.props.match.params;
}
}
Try to add RouteComponentProps inteface to your props. Change:
export class MyComponent extends React.Component<MyProps, MyState> {
to
export class MyComponent extends React.Component<MyProps & RouteComponentProps, MyState> {
This is my script to handle the match problem, I believe you can borrow something from here.
class MyComponent extends React.Component<Props, State> {
private params: any;
constructor(props: any) {
super(props);
this.state = {
paramId: null
};
}
componentDidMount = () => {
this.getParams();
};
getParams = () => {
this.params = this.props;
this.setState({
paramId: this.params.match.params.id
});
};
render() {
const { paramId } = this.state;
return (
<React.Fragment></React.Fragment>
);
}
}
export default MyComponent;
I adapted the answer of Ritwick Dey into something I find a bit better.
Here it is:
import React, { Component } from 'react';
import { RouteComponentProps } from 'react-router';
interface Props {
// your custom props go here
api: Api
}
interface State {
someString: string,
isLoading: boolean
}
class MyComponent extends Component<RouteComponentProps<Props>, State>
{
// your code...
}
OR
import React, { Component } from 'react';
import { RouteComponentProps } from 'react-router';
interface Props
extends RouteComponentProps <{
// your custom props go here
api: Api
}> {}
interface State {
someString: string,
isLoading: boolean
}
class MyComponent extends Component<Props, State>
{
// your code...
}
They are the same thing. It depends on your preference.
I had a similar issue and that was due to the App.test.jsx file. There, there was already a test case(below code) that I had totally forgotten about and none of the props was utilized in that test case scenario.
When I introduced the props to the test case, it worked. So in nature test was definitely guiding to the right direction since it was trying to use props that do not exists. But after updating the test case with my default props, it works.
Hopefully this helps.
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App **propName1={propwasnotinthetest} propName2={propwasnotinthetest}**/>, div);
ReactDOM.unmountComponentAtNode(div);
});
I experienced a similar issue and found the least type breaking fix is to locally cast the params as any.
someString: (this.props.match.params as any).someString
import * as React from 'react';
import { RouteComponentProps } from 'react-router';
interface RouteComponetPath {
path?: string
}
interface ArticleContainerProps {
another: number
}
interface ArticleContainerState {
path?: string;
}
class ArticleContainer extends React.Component<ArticleContainerProps | RouteComponentProps<RouteComponetPath>, ArticleContainerState> {
constructor(props: ArticleContainerProps | RouteComponentProps<RouteComponetPath>) {
super(props);
this.state = {
path: (this.props as RouteComponentProps<RouteComponetPath>).match.params.path
};
}
componentDidMount() {
console.log("mount! Path is: ", this.state.path);
}
render() {
return (
<h1>This is a page with path {this.state.path} </h1>
)
}
}
export default ArticleContainer;
Which actually makes sense as you can have one interface to handle the paths and use a Union type and a Type Gueard as per TS documentation https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types
So, no hacks and remains strongly typed (not using any any anywhere).
Now I don't see any reason to pass both the path and another type of params, but you know... just to prove it can be done.
RouteComponentProps should help in typescript
import { RouteComponentProps } from 'react-router';
export class Edit extends React.Component<MyProps & RouteComponentProps, MyState> {
constructor(props: MyProps & RouteComponentProps) {
super(props);
...
}
...
(this.props.match.params as any).someString
or because you are using this.props.match but not adding match to the MyProps interface
Easy Workaround would be:
Home extends React.Component<any, any>
I'm mostly just curious what the significance of this pattern is. In almost every example I've looked at for using the flux architecture the getAppState() function is defined right above the react class definition. Why? Why is it not a function of the component?
So why is this:
import React from 'react';
getAppState = () => {
return {
something: SomeStore.getState()
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = getAppState();
}
}
Better than this:
import React from 'react';
class App extends React.Component {
constructor(props) {
super(props);
this.state = this.getAppState();
}
getAppState() {
return {
something: SomeStore.getState()
}
}
}
And what if I'm wanting to pass arguments from this.props into the getAppState() function?
I am trying to use react to recreate my currents components (written in pure typescript) but I can't find a way to give additional props to a component extending an other.
export interface DataTableProps {
columns: any[];
data: any[];
}
export class DataTable extends React.Component<DataTableProps, {}> {
render() {
// -- I can use this.props.columns and this.props.data --
}
}
export class AnimalTable extends DataTable {
render() {
// -- I would need to use a this.props.onClickFunction --
}
}
My problem is that I need to give AnimalTable some props that would be irrelevant to DataTable. How can I do that ?
You'll need to make DataTable generic so that you'll be able to use an interface which extends DataTableProps:
export interface AnimalTableProps extends DataTableProps {
onClickFunction: Function;
}
export class DataTable<T extends DataTableProps> extends React.Component<T, {}> { }
export class AnimalTable extends DataTable<AnimalTableProps> {
render() {
// this.props.onClickFunction should be available
}
}
as a rule of thumb it is probably better to avoid inheritance. luckily TS and react are great tools allowing that (unlike c# for example, where inheritance often saves you a bunch of boilerplate)
export interface DataTableProps {
columns: any[];
data: any[];
}
export class DataTable extends React.Component<DataTableProps, {}> {
render() {
// -- I can use this.props.columns and this.props.data --
}
}
export type AnimalTableProps = DataTableProps & {
onClickFunction: () => void;
};
export class AnimalTable extends React.Component<AnimalTableProps, {}> {
render() {
const {onClickFunction, ...tableProps} = this.props;
// use onClickFunction however you need it
return <DataTable {...tableProps}></DataTable>
}
}
The most elegant solution that I found (without extra generic class) is
interface IBaseProps {
name: string;
}
class Base<P> extends React.Component<P & IBaseProps, {}>{
}
interface IChildProps extends IBaseProps {
id: number;
}
class Child extends Base<IChildProps> {
render(): JSX.Element {
return (
<div>
{this.props.id}
{this.props.name}
</div>
);
}
}
For those who need, base classes can declare required/abstract methods that all instances must implement:
import { Component } from 'react'
abstract class TestComponent<P = {}, S = {}, SS = any> extends Component<P, S, SS> {
abstract test(): string
}
type Props = {
first: string,
last: string,
}
type State = {
fullName: string,
}
class MyTest extends TestComponent<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
fullName: `${props.first} ${props.last}`
}
}
test() {
const { fullName } = this.state
return fullName
}
}
Complete example of creating a component that you can extend off of and maintain state and props
import { Component } from "react";
// Props for the Base Component
export interface BaseComponentProps { }
// State for the Base Component
export interface BaseComponentState {
isLoaded?: boolean
}
// The Base Component that your components can extend
export class BaseComponent<Props extends BaseComponentProps, State extends BaseComponentState, SS = any> extends Component<Props, State, SS> {
State: BaseComponentState = {
isLoaded: false
}
constructor(props: Props) {
super(props);
}
componentDidMount() {
this.setState({ isLoaded: true })
}
}
// Props for your specialized component
export interface MainComponentProps extends BaseComponentProps {
}
// State for your specialized component
export interface MainComponentState extends BaseComponentState {
CanRead: boolean
}
// Your component which now extends the BaseComponent
export class MainComponent extends BaseComponent<MainComponentProps, MainComponentState> {
state: MainComponentState = {
CanRead: false
}
componentDidMount() {
super.componentDidMount();
if (this.state.isLoaded) {
this.setState({ CanRead: true })
}
}
}