Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Refs with Material-ui 4, React 16.8, TypeScript 3.5

I currently am migrating to these versions and now I am having a forwardRef isssue with many components. There are many examples for Function components using the forwardRef for them but what about HOC class components. Also very large nested HOC components.

Base component is just an input using material ui input.

import React, {Component} from 'react'
import {FormControl, Input, withStyles, createStyles} from '@material-ui/core'


const styles = (theme: any) => createStyles({
    container: {

    },
    inputField: {
        marginTop: 10,
        marginBottom: -2,
        fontSize: 18,
        borderWidth: 1,
        paddingLeft: 15,
        paddingTop: 5,
        paddingBottom: 5,
        boxSizing: 'border-box' as any,
        borderStyle: 'groove',
        width: '100%',
    },
    textField: {
        width: '100%',
        margin: 0,
    },
})

/*interface IStyles extends WithStyles<typeof styles> {
    container: any
    inputField: any
    textField: any
}*/

interface ICommentBoxNoHEInjectedProps {
    disabled?: boolean
    path: string
    value: any
    placeholder?: string
    rows: number
    ref?: any
    onChange: (newValue: any, path: string) => any
    other?: any
}

interface ICommentBoxNoHEState {
    value: any
}

interface ICommentBoxNoHEStyles {
    classes: any
}

type CommentBoxNOHEProps = ICommentBoxNoHEInjectedProps & ICommentBoxNoHEStyles

class CommentBoxNoHE extends Component<CommentBoxNOHEProps, ICommentBoxNoHEState> {
    constructor(props: CommentBoxNOHEProps) {
        super(props)
        this.state = {
            value: '',
        }
    }

    public componentWillUpdate(nextProps: any) {
        if (nextProps.value !== this.props.value) {
            this.setState(this.setValue(nextProps.value))
        }
    }

    public componentWillMount() {
        if (this.props.value) {
            this.setState(this.setValue(this.props.value))
        }
    }

    public setValue(newValue: any) {
        return (state: Readonly<ICommentBoxNoHEState>, props: CommentBoxNOHEProps): any => {
            return {value: newValue}
        }
    }

    public handleOnChange(event: any) {
        const value = event.target.value
        this.props.onChange(value, this.props.path)
        this.setState(this.setValue(value))
    }

    public render() {
        const {classes, disabled, ref, placeholder, rows} = this.props
        return (
            <FormControl
                className={classes.textField}
                margin="dense"
                fullWidth={true}
            >
                <Input className={classes.inputField}
                       disabled={disabled}
                       placeholder={placeholder}
                       value={this.state.value}
                       inputRef={ref}
                       fullWidth={true}
                       multiline={true}
                       type="text"
                       rows={rows}
                       onChange={this.handleOnChange}
                />
            </FormControl>
        )
    }
}
// CommentBoxNo_H_E_component
const CommentBoxNo_H_E_component = withStyles(styles)(CommentBoxNoHE)
export {CommentBoxNo_H_E_component}

The component that seems to fail or is it this one I have no idea. Next component on top of this input component is a notes component

import React, {Component} from 'react'
import {IworkOrder} from '../../../api/models'
import Button from '@material-ui/core/Button'
import {withStyles, createStyles} from '@material-ui/core/styles'
import Slide from '@material-ui/core/Slide'
import DialogActions from '@material-ui/core/DialogActions'
import DialogContent from '@material-ui/core/DialogContent'
import DialogTitle from '@material-ui/core/DialogTitle'
import Dialog from '@material-ui/core/Dialog'
import {updateWO} from '../helpers'
import {debounce} from 'tedb-utils'
import {CommentBoxNo_H_E_component} from '../../../components'

const styles = (theme: any) => createStyles({
    root: {
        flexGrow: 1,
    },
    dialogPaper: {
        minWidth: 600,
    },
    dialogTitle: {
        fontSize: 30,
    },
    flatButton: {
        width: '100%',
        fontSize: 20,
    },
    dialogContent: {
        fontSize: 20,
    },
    commentBox: {
        margin: 10,
    },
    actionButtons: {
        marginRight: 20,
        marginBottom: 30,
    },
})

interface INotesComponentStyles {
    classes: any
}

interface INotesComponentInjectedProps {
    keyValue: string
    title: string
    rows: number
    currentWO: IworkOrder
    open: boolean
    placeholder: string
    handleDialogClose: () => any
}

interface INotesComponentState {
    note: any
}

export type NotesComponentProps = INotesComponentInjectedProps & INotesComponentStyles

/**
 * New Feature in Material-UI. Need to include Slide as a component
 * to be implemented in the Dialog component
 * @param props
 * @constructor
 */
function Transition(props: any) {
    return <Slide direction="up" {...props} />
}

class NotesComponent extends Component<NotesComponentProps, INotesComponentState> {
    public updateWO = updateWO
    constructor(props: NotesComponentProps) {
        super(props)
        this.state = {
            note: '',
        }
        this.isDisabled = this.isDisabled.bind(this)
        this.handleInputChange = this.handleInputChange.bind(this)
        this.clear = this.clear.bind(this)
    }

    public componentWillUpdate(nextProps: any, nextState: any) {
        if (this.props.currentWO.misc[this.props.keyValue] !== this.state.note) {
            this.setState(this.setValue(this.props.currentWO.misc[this.props.keyValue]))
        }
    }

    public setValue(value: any) {
        return (state: Readonly<INotesComponentState>, props: NotesComponentProps): any => ({note: value})
    }

    public isDisabled(): boolean {
        return !!this.props.currentWO.timeSubmitted
    }

    public save(wo: any) {
        this.updateWO(wo, wo._id)
            .then((res) => {
                console.log('saved')
            })
            .catch((err: any) => {
                console.log(err)
            })

    }

    public handleInputChange(value: string, key: string) {
        const {currentWO} = this.props
        currentWO.misc[key] = value
        this.save(currentWO)
        this.setState(this.setValue(value))

    }

    public clear() {
        const {currentWO, keyValue} = this.props
        currentWO.misc[keyValue] = ''
        this.save(currentWO)
        this.setState(this.setValue(''))
    }

    public render() {
        const {classes} = this.props
        return (
            <Dialog
                maxWidth="md"
                classes={{
                    root: classes.root,
                    paper: classes.dialogPaper,
                }}
                onClose={this.props.handleDialogClose()}
                open={this.props.open}
                TransitionComponent={Transition}
            >
                <DialogTitle>
                    <div className={classes.dialogTitle}>
                        {this.props.title}
                    </div>
                </DialogTitle>
                <DialogContent className={classes.dialogContent}>
                    <div className={classes.commentBox}>
                        <CommentBoxNo_H_E_component
                            disabled={this.isDisabled()}
                            rows={this.props.rows}
                            placeholder={this.props.placeholder}
                            path={this.props.keyValue}
                            value={this.state.note}
                            onChange={debounce(this.handleInputChange, 500)}
                        />
                    </div>
                </DialogContent>
                <DialogActions className={classes.actionButtons}>
                    <Button
                        className={classes.flatButton}
                        onClick={this.clear}
                        disabled={this.isDisabled()}
                        color="secondary"
                        variant={'contained'}
                        >
                        Clear
                    </Button>
                    <Button
                        className={classes.flatButton}
                        onClick={this.props.handleDialogClose()}
                        color="primary"
                        variant={'contained'}
                        >
                        Close
                    </Button>
                </DialogActions>
            </Dialog>
        )
    }
}
// NotesComponent_dialog
const NotesComponent_dialog = withStyles(styles)(NotesComponent)
export {NotesComponent_dialog}

This is just a modal popup in a much larger component housing hundreds of other components like this.

This is the given error

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Check the render method of `TrapFocus`.
    in Transition (created by TrapFocus)
    in TrapFocus (created by ForwardRef(Modal))
    in div (created by ForwardRef(Modal))
    in ForwardRef(Portal) (created by ForwardRef(Modal))
    in ForwardRef(Modal) (created by ForwardRef(Dialog))
    in ForwardRef(Dialog) (created by WithStyles(ForwardRef(Dialog)))
    in WithStyles(ForwardRef(Dialog)) (created by NotesComponent)
    in NotesComponent (created by WithStyles(NotesComponent))
    in WithStyles(NotesComponent) (created by Misc)
    in div (created by Misc)
    in Misc (created by WithStyles(Misc))
    in WithStyles(Misc) (created by WO)
    in div (created by ForwardRef(Grid))
    in ForwardRef(Grid) (created by WithStyles(ForwardRef(Grid)))
    in WithStyles(ForwardRef(Grid)) (created by WO)
    in div (created by ForwardRef(Grid))
    in ForwardRef(Grid) (created by WithStyles(ForwardRef(Grid)))
    in WithStyles(ForwardRef(Grid)) (created by WO)
    in div (created by ForwardRef(Grid))
    in ForwardRef(Grid) (created by WithStyles(ForwardRef(Grid)))
    in WithStyles(ForwardRef(Grid)) (created by WO)
    in div (created by ForwardRef(Grid))
    in ForwardRef(Grid) (created by WithStyles(ForwardRef(Grid)))
    in WithStyles(ForwardRef(Grid)) (created by WO)
    in div (created by ForwardRef(Grid))
    in ForwardRef(Grid) (created by WithStyles(ForwardRef(Grid)))
    in WithStyles(ForwardRef(Grid)) (created by WO)
    in div (created by WO)
    in WO (created by ConnectFunction)
    in ConnectFunction (created by WithStyles(undefined))
    in WithStyles(undefined) (created by Context.Consumer)
    in Route (created by Routes)
    in Switch (created by Routes)
    in div (created by App)
    in App (created by Routes)
    in Routes (created by Root_container)
    in ThemeProvider (created by Root_container)
    in Router (created by ConnectedRouter)
    in ConnectedRouter (created by Context.Consumer)
    in ConnectedRouterWithContext (created by ConnectFunction)
    in ConnectFunction (created by Root_container)
    in Provider (created by Root_container)
    in Root_container (created by App)
    in div (created by App)
    in App
like image 394
mjwrazor Avatar asked Mar 04 '23 15:03

mjwrazor


1 Answers

The issue (or at least one issue) is with the Slide transition. It needs to use forwardRef.

The example in the documentation looks like the following:

import React from 'react';
import Slide from '@material-ui/core/Slide';
const Transition = React.forwardRef(function Transition(props, ref) {
  return <Slide direction="up" ref={ref} {...props} />;
});

And the current (2020-03-05) TypeScript version in the documentation:

import React from 'react';
import Slide from '@material-ui/core/Slide';
import { TransitionProps } from '@material-ui/core/transitions';

const Transition = React.forwardRef<unknown, TransitionProps>(function Transition(props, ref) {
  return <Slide direction="up" ref={ref} {...props} />;
});
like image 124
Ryan Cogswell Avatar answered Mar 23 '23 02:03

Ryan Cogswell