Skip to content

Commit

Permalink
September + October improvements (#418)
Browse files Browse the repository at this point in the history
* Add `logMessages` function, fix small bug

* Style examples, add example using functional component to print

* Added style tip about page orientation

* Added functional ComponentToPrint example, added a styling pitfall

* Add pitfall example regarding printing component arrays (#323)

* Upgrade all `devDependencies`

* Change class-only error to link how to use functional components

* Fix print to PDF filename not working in all major browsers

#391

* Add to README about known issues when printing from mobile WebViews
  • Loading branch information
MatthewHerbst authored Oct 20, 2021
1 parent 02c8ce1 commit c224cf7
Show file tree
Hide file tree
Showing 11 changed files with 1,382 additions and 2,966 deletions.
55 changes: 41 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ For functional components, use the `useReactToPrint` hook, which accepts an obje

`react-to-print` should be compatible with most major browsers. We also do our best to support IE11.

### Mobile Browsers in WebView

While printing on mobile browsers should work, printing within a WebView (when your page is opened by another app such as Facebook or Slack, but not by the full browser itself) is known to not work on many if not all mobile browsers. Some browsers don't make the correct API available. Others make it available but cause it to no-op when in WebView.

We are actively researching resolutions to this issue, but it likely requires changes by Google/Chromium and Apple/WebKit. See [#384](https://github.com/gregnb/react-to-print/issues/384) for more information. If you know of a way we can solve this, your help would be greatly appreciated.

### Known Incompatible Browsers

- Firefox Android (does not support [`window.print`](https://developer.mozilla.org/en-US/docs/Web/API/Window/print))
Expand All @@ -64,26 +70,23 @@ For functional components, use the `useReactToPrint` hook, which accepts an obje
For full examples please see the [`examples`](https://github.com/gregnb/react-to-print/tree/master/examples) folder.

```jsx
// Using a class component, everything works without issue
export class ComponentToPrint extends React.PureComponent {
render() {
return (
<table>
<thead>
<th>column 1</th>
<th>column 2</th>
<th>column 3</th>
</thead>
<tbody>
<tr>
<td>data 1</td>
<td>data 2</td>
<td>data 3</td>
</tr>
</tbody>
</table>
<div>My cool content here!</div>
);
}
}

// Using a functional component, you must wrap it in React.forwardRef, and then forward the ref to
// the node you want to be the root of the print (usually the outer most node in the ComponentToPrint)
// https://reactjs.org/docs/refs-and-the-dom.html#refs-and-function-components
export const ComponentToPrint = React.forwardRef((props, ref) => {
return (
<div ref={ref}>My cool content here!</div>
);
});
```

### Calling from class components
Expand Down Expand Up @@ -197,10 +200,24 @@ const componentRef = useRef(null);

## Common Pitfalls

- When printing, only styles that directly target the printed nodes will be applied, since the parent nodes will not exist in the DOM used for the print. For example, in the code below, if the `<p>` tag is the root of the `ComponentToPrint` then the red styling will *not* be applied. Be sure to target all printed content directly and not from unprinted parents.

```jsx
<div className="parent">
<p>Hello</p>
</div>
```

```css
div.parent p { color:red; }
```

- The `connect` method from `react-redux` returns a functional component that cannot be assigned a reference to be used within the `content` props' callback in `react-to-print`. To use a component wrapped in `connect` within `content` create an intermediate class component that simply renders your component wrapped in `connect`. See [280](https://github.com/gregnb/react-to-print/issues/280) for more.

- Using a custom component as the return for the `trigger` props is possible, just ensure you pass along the `onClick` prop. See [248](https://github.com/gregnb/react-to-print/issues/248) for an example.

- When rendering multiple components to print, for example, if you have a list of charts and want each chart to have its own print icon, ideally you will wrap each component to print + print button in its own component, and just render a list of those components. However, if you cannot do that for some reason, in your `.map` ensure that each component gets a unique `ref` value passed to it, otherwise printing any of the components will always print the last component. See [323](https://github.com/gregnb/react-to-print/issues/323) for more.

## FAQ

### Can the `ComponentToPrint` be a functional component?
Expand Down Expand Up @@ -233,6 +250,16 @@ Unfortunately there is no standard browser API for interacting with the print di

## Helpful Style Tips

### Set the page orientation

While you should be able to place these styles anywhere, sometimes the browser doesn't always pick them up. To force orientation of the page you can include the following in the component being printed:

```jsx
<style type="text/css" media="print">{"\
@page {\ size: landscape;\ }\
"}</style>
```

### Set custom margin to the page ([29](https://github.com/gregnb/react-to-print/issues/29))

To set custom margin to the page,
Expand Down
15 changes: 13 additions & 2 deletions examples/ComponentToPrint/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as React from "react";
import image from '../test_image.png';

type Props = {
text: string,
text?: string,
};

type State = {
Expand Down Expand Up @@ -38,8 +38,15 @@ export class ComponentToPrint extends React.PureComponent<Props, State> {
private setRef = (ref: HTMLCanvasElement) => this.canvasEl = ref;

public render() {
const {
text,
} = this.props;

return (
<div className="relativeCSS">
<style type="text/css" media="print">{"\
@page {\ size: landscape;\ }\
"}</style>
<div className="flash" />
<img alt="A test image" src={image as string} />
<img alt="This will warn but not block printing" />
Expand All @@ -52,7 +59,7 @@ export class ComponentToPrint extends React.PureComponent<Props, State> {
</thead>
<tbody>
<tr>
<td>{this.props.text}</td>
<td>{text ?? 'Custom Text Here'}</td>
<td>
<input
checked={this.state.checked}
Expand Down Expand Up @@ -119,3 +126,7 @@ export class ComponentToPrint extends React.PureComponent<Props, State> {
);
}
}

export const FunctionalComponentToPrint = React.forwardRef<ComponentToPrint | null, Props>((props, ref) => { // eslint-disable-line max-len
return <ComponentToPrint ref={ref} text={props.text} />;
});
4 changes: 2 additions & 2 deletions examples/FunctionalComponent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ export const FunctionalComponent = () => {
// to the root node of the returned component as it will be overwritten.

// Bad: the `onClick` here will be overwritten by `react-to-print`
// return <a href="#" onClick={() => alert('This will not work')}>Print this out!</a>;
// return <button onClick={() => alert('This will not work')}>Print this out!</button>;

// Good
return <a href="#">Print using a Functional Component</a>;
return <button>Print using a Functional Component</button>;
}, []);

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as React from "react";

import { FunctionalComponentToPrint } from "../ComponentToPrint";
import ReactToPrint from "../../src/index";

export const FunctionalComponentWithFunctionalComponentToPrint = () => {
const componentRef = React.useRef(null);

const onBeforeGetContentResolve = React.useRef<(() => void) | null>(null);

const [loading, setLoading] = React.useState(false);
const [text, setText] = React.useState("old boring text");

const handleAfterPrint = React.useCallback(() => {
console.log("`onAfterPrint` called"); // tslint:disable-line no-console
}, []);

const handleBeforePrint = React.useCallback(() => {
console.log("`onBeforePrint` called"); // tslint:disable-line no-console
}, []);

const handleOnBeforeGetContent = React.useCallback(() => {
console.log("`onBeforeGetContent` called"); // tslint:disable-line no-console
setLoading(true);
setText("Loading new text...");

return new Promise<void>((resolve) => {
onBeforeGetContentResolve.current = resolve;

setTimeout(() => {
setLoading(false);
setText("New, Updated Text!");
resolve();
}, 2000);
});
}, [setLoading, setText]);

React.useEffect(() => {
if (text === "New, Updated Text!" && typeof onBeforeGetContentResolve.current === "function") {
onBeforeGetContentResolve.current();
}
}, [onBeforeGetContentResolve.current, text]);

const reactToPrintContent = React.useCallback(() => {
return componentRef.current;
}, [componentRef.current]);

const reactToPrintTrigger = React.useCallback(() => {
// NOTE: could just as easily return <SomeComponent />. Do NOT pass an `onClick` prop
// to the root node of the returned component as it will be overwritten.

// Bad: the `onClick` here will be overwritten by `react-to-print`
// return <button onClick={() => alert('This will not work')}>Print this out!</button>;

// Good
return <button>Print a Functional Component (using `forwardRef`) using a Functional Component</button>; // eslint-disable-line max-len
}, []);

return (
<div>
<ReactToPrint
content={reactToPrintContent}
documentTitle="AwesomeFileName"
onAfterPrint={handleAfterPrint}
onBeforeGetContent={handleOnBeforeGetContent}
onBeforePrint={handleBeforePrint}
removeAfterPrint
trigger={reactToPrintTrigger}
/>
{loading && <p className="indicator">onBeforeGetContent: Loading...</p>}
<FunctionalComponentToPrint ref={componentRef} text={text} />
</div>
);
};
49 changes: 41 additions & 8 deletions examples/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import 'react-tabs/style/react-tabs.css';

import { ClassComponent } from "./ClassComponent";
import { ClassComponentContextConsumer } from "./ClassComponentContextConsumer";
import { ClassComponentText } from "./ClassComponentText";
import { FunctionalComponent } from "./FunctionalComponent";
import { FunctionalComponentWithHook } from "./FunctionalComponentWithHook";
import { ClassComponentText } from "./ClassComponentText";
import { FunctionalComponentWithFunctionalComponentToPrint } from './FunctionalComponentWithFunctionalComponentToPrint';
import "./relativecss/test.css";

type Props = Record<string, unknown>;
Expand All @@ -17,13 +20,43 @@ type State = {
class Example extends React.Component<Props, State> {
render() {
return (
<>
<ClassComponent />
<ClassComponentContextConsumer />
<FunctionalComponent />
<FunctionalComponentWithHook />
<ClassComponentText />
</>
<Tabs>
<TabList>
<Tab>Class Component</Tab>
<Tab>Functional Component</Tab>
<Tab>Raw Values</Tab>
</TabList>
<TabPanel>
<Tabs>
<TabList>
<Tab>Standard</Tab>
<Tab>With ContextConsumer</Tab>
</TabList>
<TabPanel><ClassComponent /></TabPanel>
<TabPanel><ClassComponentContextConsumer /></TabPanel>
</Tabs>
</TabPanel>
<TabPanel>
<Tabs>
<TabList>
<Tab>Standard</Tab>
<Tab>With Hook</Tab>
<Tab>With a functional ComponentToPrint</Tab>
</TabList>
<TabPanel><FunctionalComponent /></TabPanel>
<TabPanel><FunctionalComponentWithHook /></TabPanel>
<TabPanel><FunctionalComponentWithFunctionalComponentToPrint /></TabPanel>
</Tabs>
</TabPanel>
<TabPanel>
<Tabs>
<TabList>
<Tab>Text</Tab>
</TabList>
<TabPanel><ClassComponentText /></TabPanel>
</Tabs>
</TabPanel>
</Tabs>
);
}
}
Expand Down
2 changes: 2 additions & 0 deletions examples/relativecss/test.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@
background: rgba(76, 175, 80, 0.3);
}



Loading

0 comments on commit c224cf7

Please sign in to comment.