Skip to content

Commit

Permalink
Merge pull request #60 from haradakunihiko/feature/implement-to-use-c…
Browse files Browse the repository at this point in the history
…ontext

Enable to use context inside confirmation dialog
  • Loading branch information
haradakunihiko authored Mar 17, 2023
2 parents c5ca560 + aac8022 commit 9f64f77
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 48 deletions.
79 changes: 54 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,38 @@
# react-confirm
Small library which makes your Dialog component callable.
react-confirm is a lightweight library that simplifies the implementation of confirmation dialogs in React applications by offering a Promise-based API that works seamlessly with async/await syntax, similar to `window.confirm`.

This library does not provide any view component. Just adds a functionality to be callable like `window.confirm`.
One key feature of react-confirm is that it doesn't provide a specific view or component for the confirmation dialog, allowing you to easily customize the appearance of the dialog to match your application's design.

In the [example](https://github.com/haradakunihiko/react-confirm/tree/master/example), [react-bootstrap](https://react-bootstrap.github.io/components.html#modals) and [material-ui](http://www.material-ui.com/#/components/dialog) are used with.
In the [example](https://github.com/haradakunihiko/react-confirm/tree/master/example), [react-bootstrap](https://react-bootstrap-v3.netlify.app/components/modal/) and [material-ui](http://www.material-ui.com/#/components/dialog) are used with.

[![npm version](https://badge.fury.io/js/react-confirm.svg)](https://badge.fury.io/js/react-confirm)

## Motivation
React is great. And I respect the concept to render the view reactively only by it's state. However, it easily becomes really complex to manage all states which are only needed just temporarily like confirmation dialog. The question is... Is it worth to manage them inside your app? I guess the answer is not always yes.
React is a powerful library that allows for reactive rendering based on component state. However, managing temporary states like confirmation dialogs can quickly become complex. The question is: is it worth implementing these states within your app? The answer is not always a clear yes.

## What you can do
With this library,
- You can open a dialog component by calling function and it will be rendered outside your application. The function returns promise so that you can define callbacks to handle the confirmation result.
- You can pass arguments to the function and use them inside the dialog component.
- You can get values from the component in the promise.
- There is no limitation in the dialog. You can use input forms, multiple buttons, whatever you want (see demo site).
react-confirm library offers several benefits:

- You can open a dialog component by calling a function without appending it into your React tree. The function returns a promise, allowing you to handle confirmation results with callbacks.
- You can pass arguments to the function and use them inside the dialog component.
- You can retrieve values from the component in the promise.
- The library provides flexibility in designing the dialog. There is no limitation in the type of components you can use, whether it be input forms or multiple buttons. You can even check out the demo site to see examples of how to customize the dialog.

## Demo
https://codesandbox.io/s/react-confirm-with-react-bootstrap-kjju1

## Versions

- React 18+ users should use `react-confirm` version 0.2.x
- React 18+ users should use `react-confirm` version 0.2.x or 0.3.x
- React <=17 users should stick to `react-confirm` version 0.1.x

## Usage
1. create your dialog component.
2. apply `confirmable` to your component (optional, but usually recommended).
3. create function with `createConfirmation` by passing your confirmable component.
4. call it!
1. Create your dialog component.
2. Apply `confirmable` HOC to your component (Optional. See `confirmable` implementation).
3. Create a function using `createConfirmation` by passing your `confirmable` component.
4. Call it!

### create confirmable component
### Create your dialog component and Apply `confirmable` HOC to your component.

```js
import React from 'react';
Expand All @@ -56,16 +57,9 @@ YourDialog.propTypes = {

// confirmable HOC pass props `show`, `dismiss`, `cancel` and `proceed` to your component.
export default confirmable(YourDialog);

// or, use `confirmable` as decorator
@confirmable
class YourDialog extends React.Component {
}


```

### create confirm function
### Create a function using `createConfirmation`
```js
import { createConfirmation } from 'react-confirm';
import YourDialog from './YourDialog';
Expand All @@ -77,10 +71,9 @@ export const confirm = createConfirmation(YourDialog);
export function confirmWrapper(confirmation, options = {}) {
return confirm({ confirmation, options });
}

```

### use it!
### Call it!
Now, you can show dialog just like window.confirm with async-await. The most common example is onclick handler for submit buttons.

```js
Expand Down Expand Up @@ -108,6 +101,42 @@ const handleOnClick2 = async () => {

You can check more complex example in [codesandbox](https://codesandbox.io/s/react-confirm-with-react-bootstrap-kjju1)

## Using with Context
By default, this library renders the confirmation dialog without appending the component to your app's React component tree. While this can be useful, it may cause issues if you need to consume context in your component. To overcome this problem, you can use the `MountPoint` component to include your confirmation dialog within your app's tree, enabling it to access context and other data from the app.

Create your own `createConfirmation` using `createConfirmationCreater` and `createReactTreeMounter`.

```js
import { createConfirmationCreater, createReactTreeMounter, createMountPoint } from 'react-confirm';

const mounter = createReactTreeMounter();

export const createConfirmation = createConfirmationCreater(mounter);
export const MountPoint = createMountPoint(mounter);
```

Put `MountPoint` into your React tree.
```js
const YourRootComponent = () => {
return (
<YourContext.Provider>
<MountPoint />
<YourApp />
</YourContext.Provider>
)
}
```

use your `createConfirmation` as usual.
```js
export const confirm = createConfirmation(YourDialog);
```

To render the confirmation dialog within the React component tree but in a different part of the DOM, you can pass a DOM element to the `createReactTreeMounter` function. This will use the `createPortal` method to render the confirmation dialog in the specified DOM element while keeping it within the React component tree.

```js
const mounter = createReactTreeMounter(document.body);
```

## typescript

Expand Down
27 changes: 6 additions & 21 deletions src/createConfirmation.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import { createDomTreeMounter } from './mounter/domTree';

const createConfirmation = (Component, unmountDelay = 1000, mountingNode) => {
export const createConfirmationCreater = (mounter) => (Component, unmountDelay = 1000, mountingNode) => {
return (props) => {
const wrapper = (mountingNode || document.body).appendChild(document.createElement('div'));
const root = createRoot(wrapper);

let mountId;
const promise = new Promise((resolve, reject) => {
try {
root.render(
<Component
reject={reject}
resolve={resolve}
dispose={dispose}
{...props}
/>
);
mountId = mounter.mount(Component, { reject, resolve, dispose, ...props}, mountingNode)
} catch (e) {
console.error(e);
throw e;
Expand All @@ -24,12 +14,7 @@ const createConfirmation = (Component, unmountDelay = 1000, mountingNode) => {

function dispose() {
setTimeout(() => {
root.unmount();
setTimeout(() => {
if (wrapper && wrapper.parentNode) {
wrapper.parentNode.removeChild(wrapper);
}
});
mounter.unmount(mountId);
}, unmountDelay);
}

Expand All @@ -43,4 +28,4 @@ const createConfirmation = (Component, unmountDelay = 1000, mountingNode) => {
}
}

export default createConfirmation;
export default createConfirmationCreater(createDomTreeMounter());
6 changes: 4 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import confirmable from './confirmable';
import createConfirmation from './createConfirmation';
import createConfirmation, { createConfirmationCreater } from './createConfirmation';
import { createDomTreeMounter } from './mounter/domTree';
import { createReactTreeMounter, createMountPoint } from './mounter/reactTree';

export { confirmable, createConfirmation };
export { confirmable, createConfirmation, createConfirmationCreater, createDomTreeMounter, createReactTreeMounter, createMountPoint };
35 changes: 35 additions & 0 deletions src/mounter/domTree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import { createRoot } from 'react-dom/client';

export function createDomTreeMounter(defaultMountNode) {
const confirms = {};
const callbacks = {};

function mount(Component, props, mountNode){
const key = Math.floor(Math.random() * (1 << 30)).toString(16);
const wrapper = (mountNode || defaultMountNode || document.body).appendChild(document.createElement('div'));
confirms[key] = wrapper;

const root = createRoot(wrapper);

root.render(
<Component
{...props}
/>
);
callbacks.mounted && callbacks.mounted();
return key;
}

function unmount(key) {
const wrapper = confirms[key];
delete confirms[key];

if (wrapper && wrapper.parentNode) {
wrapper.parentNode.removeChild(wrapper);
}
}
return {
mount, unmount, options: {}
}
}
55 changes: 55 additions & 0 deletions src/mounter/reactTree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useState, useEffect } from 'react'
import { createPortal } from 'react-dom';

export function createReactTreeMounter(mountNode) {
const confirms = {};
const callbacks = {};

function mount(Component, props){
const key = Math.floor(Math.random() * (1 << 30)).toString(16);
confirms[key] = { Component, props};
callbacks.mounted && callbacks.mounted(confirms);
return key;
}
function unmount(key) {
delete confirms[key];
callbacks.mounted && callbacks.mounted(confirms);
}

function setMountedCallback(func) {
callbacks.mounted = func;
}

return {
mount, unmount,
options: {
setMountedCallback, mountNode
}
}
}

export function createMountPoint(reactTreeMounter) {
return () => {
const [confirmComponents, setConfirmComponents] = useState({});

useEffect(() => {
return reactTreeMounter.options.setMountedCallback((components) => {
setConfirmComponents({...components});
});
}, []);

let element = (
<>
{Object.keys(confirmComponents).map((key) => {
const { Component, props } = confirmComponents[key];
return <Component key={key} {...props} />
})}
</>
)
if (reactTreeMounter.options.mountNode) {
element = createPortal(element, reactTreeMounter.options.mountNode);
}

return element;
}
}

0 comments on commit 9f64f77

Please sign in to comment.