Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

impl(lazy-initialize): set content for handlePrint() and handleClick() via second optional argument using [content] #679

Merged
merged 14 commits into from
Feb 9, 2024
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"typescript.tsdk": "./node_modules/typescript/lib"
"typescript.tsdk": "./node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
108 changes: 108 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,114 @@ const Example = () => {
};
```

### Calling from functional components within a custom hook

>./hooks/useUICommands.jsx
```jsx
import React, { useRef, useMemo } from 'react';
import { useReactToPrint } from 'react-to-print';

/* NOTE: `navigator.clipboard` is undefined in Safari 12.1.x as well as the earlier versions
of other browsers like Chrome (Webkit), Firefox, Edge (EdgeHTML) */
/* SEE: https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#clipboard_availability */
import "clipboard-polyfill/overwrite-globals"; /* @_SHIM: # */

/**
* copyTextFactory
*
* @param {void}
* @returns {void}
*/
function copyTextFactory() {
return (textString = "") => {
/* NOTE: `navigator.clipboard.writeText(...)` throws vague error in Safari 13.1.x (and above) even when called in a real user context */
/* SEE: https://developer.apple.com/forums/thread/691873 */
try {
navigator.clipboard.writeText(textString);
} catch(_) { }
};
}

/**
* printPageFactory
*
* @param {Function} printer
* @returns {void}
*/
function printPageFactory(printer) {
return (event, componentRef = null) => {
if (!componentRef) {
window.print();
} else {
printer(event, () => componentRef.current)
}
};
}

/* @NOTE: A ReactJS hook to house command design pattern actions for copying, printing e.t.c on a React app */
export const useUICommands = (options = {
print: {}
}) => {
/* @HINT: COMMAND DESIGN PATTERN - ReactJS hook */

const printer = useReactToPrint(
Object.assign({
documentTitle: ".",
onBeforeGetContent: () => Promise.resolve(undefined),
onBeforePrint: () => undefined,
onAfterPrint: () => undefined,
removeAfterPrint: true,
}, options.print)
);
const commands = useRef({
copy: copyTextFactory(),
print: printPageFactory(printer)
}).current;

return useMemo(
() => ({
hub: {
execute(commandName = "", ...args) {
if (typeof commands[commandName] === "function") {
const commandRoutine = commands[commandName];
return commandRoutine.apply(null, args);
}
}
}
}),
/* eslint-disable-next-line react-hooks/exhaustive-deps */
[]
);
};
```
>./src/AnotherExample.jsx
```jsx
import React, { useRef } from 'react';
import { useUICommands, PRINT_COMMAND } from 'react-busser';

export const AnotherExample = () => {
const contentToPrint = useRef(null);
const uiCommands = useUICommands({
print: {
documentTitle: "Print - Document",
onBeforePrint: () => console.log("before printing..."),
onAfterPrint: () => console.log("after printing..."),
}
});

return (
<>
<div ref={contentToPrint}>Hello Again</div>
<button onClick={(event) => {
uiCommands.hub.execute(PRINT_COMMAND, event, contentToPrint);
}}>
PRINT
</button>
</>
)
}
```

**Note ([401](https://github.com/gregnb/react-to-print/issues/401)):** In TypeScript, if you encounter `componentRef.current` error such as: `Type 'undefined' is not assignable to type 'ReactInstance | null'.`, add `null` inside the `useRef()`:

```ts
Expand Down
69 changes: 56 additions & 13 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ const contextEnabled = Object.prototype.hasOwnProperty.call(React, "createContex
const hooksEnabled = Object.prototype.hasOwnProperty.call(React, "useMemo") && Object.prototype.hasOwnProperty.call(React, "useCallback");

export interface IPrintContextProps {
handlePrint: () => void,
handlePrint: (event: React.MouseEvent, content?: (() => React.ReactInstance | null)) => void,
MatthewHerbst marked this conversation as resolved.
Show resolved Hide resolved
}
const PrintContext = contextEnabled ? React.createContext({} as IPrintContextProps) : null;
export const PrintContextConsumer = PrintContext ? PrintContext.Consumer : () => null;

export interface ITriggerProps<T> {
onClick: () => void;
onClick: (event: React.MouseEvent) => void;
ref: (v: T) => void;
}

Expand All @@ -25,6 +25,26 @@ type Font = {

type PropertyFunction<T> = () => T;

/**
* This function helps to curry arguments to a bound function
* and partially apply them at the end of the argument list.
*
* @param {Object} thisArg
* @param {Function} callback
* @param {Array.<*>} predefinedArgs
*
* @returns {*}
*/
function wrapCallbackWithArgs<CallbackReturnValue, BoundObject>(
thisArg: BoundObject,
callback: (...curryArgs: any[]) => CallbackReturnValue,
...predefinedArgs: unknown[]
) {
return function (...args: unknown[]) {
return callback.apply(thisArg, [...args, ...predefinedArgs]);
};
}

// NOTE: https://github.com/Microsoft/TypeScript/issues/23812
const defaultProps = {
copyStyles: true,
Expand All @@ -50,7 +70,7 @@ export interface IReactToPrintProps {
bodyClass?: string;
children?: React.ReactNode;
/** Content to be printed */
content: () => React.ReactInstance | null;
content?: () => React.ReactInstance | null;
/** Copy styles over into print window. default: true */
copyStyles?: boolean;
/**
Expand Down Expand Up @@ -186,7 +206,7 @@ export default class ReactToPrint extends React.Component<IReactToPrintProps> {
}
}

public handleClick = () => {
public handleClick (event: React.MouseEvent, content?: (() => React.ReactInstance | null)) {
const {
onBeforeGetContent,
onPrintError,
Expand All @@ -196,21 +216,23 @@ export default class ReactToPrint extends React.Component<IReactToPrintProps> {
const onBeforeGetContentOutput = onBeforeGetContent();
if (onBeforeGetContentOutput && typeof onBeforeGetContentOutput.then === "function") {
onBeforeGetContentOutput
.then(this.handlePrint)
.then(() => this.handlePrint(content))
.catch((error: Error) => {
if (onPrintError) {
onPrintError("onBeforeGetContent", error);
}
});
} else {
this.handlePrint();
this.handlePrint(content);
}
} else {
this.handlePrint();
this.handlePrint(content);
}

return Boolean(event);
}

public handlePrint = () => {
public handlePrint = (optionalContent?: (() => React.ReactInstance | null)) => {
const {
bodyClass,
content,
Expand All @@ -220,7 +242,11 @@ export default class ReactToPrint extends React.Component<IReactToPrintProps> {
nonce,
} = this.props;

const contentEl = content();
let contentEl = typeof optionalContent === "function" ? optionalContent() : null;

if (!contentEl && typeof content === "function") {
contentEl = content();
}

if (contentEl === undefined) {
this.logMessages(['To print a functional component ensure it is wrapped with `React.forwardRef`, and ensure the forwarded ref is used. See the README for an example: https://github.com/gregnb/react-to-print#examples']);
Expand Down Expand Up @@ -566,7 +592,7 @@ export default class ReactToPrint extends React.Component<IReactToPrintProps> {

if (trigger) {
return React.cloneElement(trigger(), {
onClick: this.handleClick,
onClick: this.handleClick.bind(this),
});
} else {
if (!PrintContext) {
Expand All @@ -575,7 +601,14 @@ export default class ReactToPrint extends React.Component<IReactToPrintProps> {
return null;
}

const value = { handlePrint: this.handleClick };
const value = {
handlePrint: (
event: React.MouseEvent,
content: (() => React.ReactInstance | null)
) => {
return this.handleClick(event, content);
}
};

return (
<PrintContext.Provider value={value as IPrintContextProps}>
Expand All @@ -586,7 +619,10 @@ export default class ReactToPrint extends React.Component<IReactToPrintProps> {
}
}

type UseReactToPrintHookReturn = () => void;
type UseReactToPrintHookReturn = (
event: React.MouseEvent,
content?: (() => React.ReactInstance | null)
) => void;

export const useReactToPrint = (props: IReactToPrintProps): UseReactToPrintHookReturn => {
if (!hooksEnabled) {
Expand All @@ -605,5 +641,12 @@ export const useReactToPrint = (props: IReactToPrintProps): UseReactToPrintHookR
[props]
);

return React.useCallback(() => reactToPrint.handleClick(), [reactToPrint]);
return React.useCallback(
(event: React.MouseEvent, content?: (() => React.ReactInstance | null)) => {
/* eslint-disable-next-line @typescript-eslint/unbound-method */
const triggerPrint = wrapCallbackWithArgs(reactToPrint, reactToPrint.handleClick, content);
// NOTE: `event` is a no-use argument
// (necessary for backward compatibility with older versions)
return triggerPrint(event);
}, [reactToPrint]);
};