1

PROBLEM:

I'm trying to make an example from react-cropper work in TypeScript. The example works fine with .JSX and there are Typings for it, so I thought it would just work sublime, with some changes to the .JSX code.

One of the tricky things to get my head around was the reference pattern in TypeScript and React. However, I found this amazing question on SO which I've followed to set everything up.

EXPECTED:

That the ref-pattern would work as intended and I'd have access to the underlying functions (from the object).

I.e. I'm expecting to see that this.cropRef has access to getCroppedCanvas (defined in react-cropper).

ACTUAL:

console.log(this) in the cropImage() function shows that refs are empty, even though I'm following the above example.

Trying to use the now deprecated ref-pattern of using strings show me that this has the reference heading into the cropImage() function. However, because TS is strongly typed, I dont' have access to getCroppedCanvas().

CODE:

import * as React from "react";
import Cropper from 'react-cropper';
import 'cropperjs/dist/cropper.css';

export class ImageEditor extends React.Component<any, any> {
    private cropRef : React.RefObject<Cropper>;

    constructor(props : any) {
        super(props);
        this.state = {
            src : "",
            cropResult : ""
        };

        this.cropRef = React.createRef();  
        this.cropImage = this.cropImage.bind(this);
        this.onChange = this.onChange.bind(this);
    }

    onChange(e : any) {
        e.preventDefault();
        let files;
        if (e.dataTransfer) {
            files = e.dataTransfer.files;
        } else if (e.target) { 
            files = e.target.files;
        }
        const reader = new FileReader();
        reader.onload = () => {
            this.setState({ src : reader.result });
        };
        reader.readAsDataURL(files[0]);
    }

    cropImage() {
        console.log(this.refs);
        if (typeof this.cropRef.current.getCroppedCanvas() === 'undefined') {
                return;
        }
        this.setState({
            cropResult: this.cropRef.current.getCroppedCanvas().toDataURL(),
        });
    }

    render() {
        return (
            <div>
                <div style={{ width: '100%' }}>
                    <input type="file" onChange={this.onChange} />
                    <br />
                    <br />
                    <Cropper
                        style={{ height: 400, width: '100%' }}
                        aspectRatio={16 / 9}
                        preview=".img-preview"
                        guides={false}
                        src={this.state.src} 
                        ref={(cropper : any) => { this.cropRef = cropper}}
                    />
                </div>
                    <div>
                        <div className="box" style={{ width: '50%', float: 'right' }}>
                            <h1>Preview</h1>
                            <div className="img-preview" style={{ width: '100%', float: 'left', height: 300 }} />
                        </div>
                    <div className="box" style={{ width: '50%', float: 'right' }}>
                        <h1>
                        <span>Crop</span>
                        <button onClick={this.cropImage} style={{ float: 'right' }}>
                            Crop Image
                        </button>
                        </h1>
                        <img style={{ width: '100%' }} src={this.state.cropResult} alt="cropped image" />
                    </div>
                </div>
            </div>
        );
    }
} 

EDIT:

edited index.d.ts for react-cropper:

import * as cropperjs from 'cropperjs';
import * as React from 'react';

import Data = cropperjs.Data;
import ContainerData = cropperjs.ContainerData;
import ImageData = cropperjs.ImageData;
import CanvasData = cropperjs.CanvasData;
import CropBoxData = cropperjs.CropBoxData;
import CroppedCanvasOptions = cropperjs.CroppedCanvasOptions;
type ReactCropperProps = cropperjs.CropperOptions & React.AllHTMLAttributes<HTMLImageElement>;

interface ReactCropper extends cropperjs {} // tslint:disable-line no-empty-interface
declare class ReactCropper extends React.Component<ReactCropperProps> {
    on(eventname: string, callback: () => void): void;
}
export default ReactCropper;

package.json

{
  "name": "name",
  "version": "1.0.0",
  "description": "packages for content",
  "main": "webpack.config.js",
  "scripts": {
    "build": "webpack",
    "test": "karma start karma.unit.build.js",
    "build-test": "karma start karma.unit.js",
    "coverage": "karma start karma.coverage.js"
  },
  "author": "Joakim Bajoul Kakaei",
  "license": "ISC",
  "devDependencies": {
    "@types/chai": "^4.1.4",
    "@types/mocha": "^5.2.5",
    "@types/react": "^16.4.7",
    "@types/react-cropper": "./node_modules/react-cropper.fixed",
    "@types/react-dom": "^16.0.6",
    "@types/react-dropzone": "^4.2.0",
    "@types/sinon": "^5.0.1",
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.5",
    "chai": "^4.1.2",
    "css-loader": "^1.0.0",
    "istanbul-instrumenter-loader": "^3.0.1",
    "karma": "^2.0.5",
    "karma-chai": "^0.1.0",
    "karma-coverage": "^1.1.2",
    "karma-mocha": "^1.3.0",
    "karma-phantomjs-launcher": "^1.0.4",
    "karma-sinon": "^1.0.5",
    "karma-typescript-preprocessor2": "^1.2.1",
    "karma-webpack": "^3.0.0",
    "mocha": "^5.2.0",
    "mocha-webpack": "^1.1.0",
    "phantomjs-prebuilt": "^2.1.16",
    "sinon": "^6.1.4",
    "style-loader": "^0.21.0",
    "ts-loader": "^4.4.2",
    "ts-node": "^7.0.0",
    "typescript": "^2.9.2",
    "typings": "^2.1.1",
    "webpack": "^4.16.2",
    "webpack-cli": "^3.1.0"
  },
  "dependencies": {
    "npm": "^6.3.0",
    "react": "^16.4.1",
    "react-cropper": "^1.0.1",
    "react-dom": "^16.4.1",
    "react-dropzone": "^4.2.13"
  }
}

node_modules/react-cropper/package.json:

{
    "name": "@types/react-cropper",
    "dependencies": {
    }
}

1 Answer 1

1

It looks like you are using a strange mix of the callback pattern and the RefObject pattern. If you want to use the RefObject, you should just pass the RefObject as the ref attribute, i.e.:

ref={this.cropRef}

If you want to use a callback, then the declaration should be:

private cropRef : Cropper;

and you would use this.cropRef.getCroppedCanvas() rather than this.cropRef.current.getCroppedCanvas().

Sign up to request clarification or add additional context in comments.

12 Comments

That's what I thought too, but I'm getting errors basically EVERYWHERE when I do it just like that. If I use the RefObject pattern, my code breaks on this.cropRef.getCroppedCanvas() because the method does not exist on the type RefObject<Cropper> and on my ref in the Cropper element. This because the RefObject is missing properties (such as align). This is a part of the error Type 'ReactCropper' is not assignable to type 'HTMLImageElement'. Property 'align' is missing in type 'ReactCropper'.
If you are using the RefObject pattern, then you should keep the current to dereference the RefObject in this.cropRef.current.getCroppedCanvas(). Which way are you trying?
Using the RefObject pattern I'm using current, which solves the issue with having access go getCroppedCanvas(). However, I'm unable to set up the reference on the element
Did you use ref={this.cropRef}? What error did you get?
Finally I actually tried this myself and it looks like there is a mistake in the type definition for react-cropper: React.HTMLProps<HTMLImageElement> should be replaced with React.AllHTMLAttributes<HTMLImageElement>. You can use this procedure to modify the type definition locally.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.