React-to-print: Need help to send component reference

Created on 26 Aug 2020  路  19Comments  路  Source: gregnb/react-to-print

Hi, I am trying to use the react-to-print library in my application but without satisfactory results so far. I don't know how I should send the reference to the ReactToPrint component, I appreciate any suggestions. Grateful in advance.
Note: the component I want to print is the FormContent of type class .

    <Paper>
      <Grid container direction="column" spacing={1}>
        <Grid item xs={12}>
          <Grid container direction="row">
            <Grid item xs={6}>
              <FormTopLeft />
            </Grid>
            <Grid item xs={6}>
              <ReactToPrint
                trigger={() => (
                  <Tooltip title="Print">
                    <IconButton>
                      <PrintIcon />
                    </IconButton>
                  </Tooltip>
                )}
                content={() => this.componentRef}
              />
            </Grid>
          </Grid>
          <Divider/>
        </Grid>
        <Grid item xs={12}>
          <FormContent ref={el => (this.componentRef = el)} />
        </Grid>
      </Grid>
    </Paper>
question

Most helpful comment

Thank you very much Matthew, with that intermediate class that you suggested to me, I solved the problem. Thank you!!!

https://codesandbox.io/s/interesting-cookies-k1bg9?file=/src/deliverySheet/ComponentToPrint.js

All 19 comments

Hello. Your usage looks correct. Could you please explain what's not working? Are there any errors?

Thank you very much for responding so quickly !! In console it shows me the following error when I click the print button:

index.js:1 For "react-to-print" to work only Class based components can be printed.

I don't understand why, because the component where ReactToPrint is mounted is of type class, just like FormContent.
I must be doing something wrong but I don't realize that.

Sometimes he shows me this other:
index.js:1 Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Is FormContent a class or functional component?

Yes, is a class component, which I am importing into the MainForm component.

Do you mind sharing the full code for FormContent and for the component that wraps ReactToPrint?

MainForm: component that wraps ReactToPrint.

import React, { Component } from "react";
import { Grid, Paper, Divider, Tooltip, IconButton } from "@material-ui/core";
import PrintIcon from "@material-ui/icons/Print";
import FormContent from "./components/FormContent";
import FormTopLeft from "./components/FormTopLeft";
import ReactToPrint from "react-to-print";

class MainForm extends Component {
  render() {
    return (
      <Paper>
        <Grid container direction="column" spacing={1}>
          <Grid item xs={12}>
            <Grid container direction="row">
              <Grid item xs={6}>
                <FormTopLeft />
              </Grid>
              <Grid item xs={6}>
                <ReactToPrint
                  trigger={() => (
                    <Tooltip title="Print">
                      <IconButton>
                        <PrintIcon />
                      </IconButton>
                    </Tooltip>
                  )}
                  content={() => this.componentRef}
                />
              </Grid>
            </Grid>
            <Divider />
          </Grid>
          <Grid item xs={12}>
            <FormContent ref={(el) => (this.componentRef = el)} />
          </Grid>
        </Grid>
      </Paper>
    );
  }
}
export default MainForm;
import React, { Component, Fragment } from "react";
import { withRouter } from 'react-router-dom';
import { connect } from "react-redux";
import { Grid, TextField } from "@material-ui/core";
import * as globalVisibilityActions from "../../../../../../../store/actions/sync";
import * as currentObjectActions from "../../../../../../../store/actions/sync";
import * as apiCallActions from "../../../../../../../store/actions/async/apiCalls/general"

class FormContent extends Component {
  state = {
    max_capacity: 0,   
  }
  componentDidMount() {
    this.props.updateGlobalVisibilityViewType();
    this.setState({
      ...this.state,
      max_capacity: this.props.boxData.max_capacity,
    })
  };
  handleChange = event => {
    const targetField = event.target.name
    const targetValue = event.target.value
    this.props.updateCurrentObjectFieldsOnChange(targetField, targetValue);
  };
  render() {
    const { box_number } = this.props.boxData;
    const readOnly = this.props.isEditMode;
    const zone = this.props.boxData.zone ? this.props.boxData.zone : {}  
    return (
      <Fragment>
        <form>
          <Grid
            container
            direction="row"
            spacing={6}
            style={{ textAlign: "left" }}      
          >
            <Grid item xs={6}>
              <TextField
                inputProps={{
                  readOnly: Boolean(readOnly),
                  disabled: Boolean(readOnly),
                }}
                fullWidth
                name="box_number"
                type="number"
                label="Box number"
                value={box_number || ""}
                onChange={this.handleChange}
              />
            </Grid>
            <Grid item xs={6}>
              <TextField
                inputProps={{
                  readOnly: Boolean(true),
                  disabled: Boolean(true),
                }}
                fullWidth
                name="max_capacity"
                type="number"
                label="Max capacity"
                value={this.state.max_capacity || 0}
              />
            </Grid>
          </Grid>
        </form>
      </Fragment>
    );
  }
}

const mapStateToProps = state => {
  return {
    boxData: state.currentObject.currentObject,
    isEditMode: state.globalVisibility.isEditModeDisabled,  
  };
};

const mapDispatchToProps = dispatch => {
  const viewType = 'formType';
  return {
    updateGlobalVisibilityViewType: () => dispatch(globalVisibilityActions.updateGlobalVisibilityViewType(viewType)),
    updateCurrentObjectFieldsOnChange: (targetField, targetValue) => 
    dispatch(currentObjectActions.updateCurrentObjectFieldsOnChange(targetField, targetValue)),
};

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(FormContent));

Hmm. There are a few possible culprits. It's possible that withRouter returns a functional component instead of a class one. It's also possible that for some reason the Fragment is causing a problem. Could you try exporting the component without the router and/or changing the Fragment to a div?

I made both changes, I used a div and not Fragment and I did without the withRouter library.
When the component mount, it shows me the following error in the console:
index.js:1 Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use
React.forwardRef()?

When clicking on the print button, the same error continues:
index.js: 1 For "react-to-print" to work only Class based components can be printed.

Very strange. Do you mind creating a codesandbox or similar with a full example so I can play with the code?

Today I prepare it.
I'm very sorry to be stealing your time.

No problem at all, looking forward to solving the problem

Hi Matthew, here is the link of the test I did in codesandbox: https://codesandbox.io/s/zen-nash-xgkvn?file=/src/App.js

The only difference it has with the FormContent component, which I have in my project, is the use of Redux.
I tried to comment "react-redux" in my project, and the print worked correctly.

Hello, I wanted to know if the react-to-print library has any problem to work, if Redux is being used in the component that you want to print?

Hi @maitedev not sure I follow? react-to-print uses no dependencies except for prop-types. It makes sense that connect from react-redux would be the problem. I think an easy solution would be to create a class component in the middle so that you can pass the class to ReactToPrint, and then the class will render the FormContent

Thank you very much Matthew, with that intermediate class that you suggested to me, I solved the problem. Thank you!!!

https://codesandbox.io/s/interesting-cookies-k1bg9?file=/src/deliverySheet/ComponentToPrint.js

Glad you got it working! In the future we'll be able to support functional components, but doing so will require a breaking change so I'm trying to delay it as long as is reasonable.

Thank you for this, I was in the same boat as @maitedev !

Would be great to add something on the README.md in order to inform about the issue with Redux :pray:

Awesome, thank you @MatthewHerbst !

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jogoool picture jogoool  路  8Comments

abdurrehman1998 picture abdurrehman1998  路  8Comments

k-jiang picture k-jiang  路  5Comments

sprietNathanael picture sprietNathanael  路  4Comments

404sand808s picture 404sand808s  路  7Comments