An open API service indexing awesome lists of open source software.

https://github.com/saehun/react-functional-modal

FP friendly react modal component
https://github.com/saehun/react-functional-modal

component functional javascript modal react

Last synced: over 1 year ago
JSON representation

FP friendly react modal component

Awesome Lists containing this project

README

          


logo

React Functional Modal



version


downloads


Modal component doesn't needs to be mounted
Call functions show, hide with arguments ReactNode and few options


- **Simple**: Only have 2 API. show and hide.
- **Functional**: Let library manages the state. Just call the function.
- **Flexible**: No restriction for how the modal to be shaped.
- **Typed**: Built with typescript.
- **Small**: 160 lines, ~1.8Kb gzipped. no deps.

## Install

``` shell
$ npm install --save react-functional-modal
```

## How is it different?
![comparison](https://github.com/minidonut/react-functional-modal/raw/master/docs/old-and-new.png)

## Usage

Simply Call the function `show` with react element(jsx):
``` jsx
show(

{/* contents */}
);
```

And hide it:
``` jsx
hide();
```

If you want modals to be overlapped, do it:
``` jsx
show();
show();
```

`hide` function closes recently opened modal:
``` jsx
hide(); // hide ModalTwo
hide(); // hide ModalOne
hide(); // do nothing
```

If you specified the key, you can explicitly close it:
``` jsx
show(, { key: "1234" });
hide("1234");
```

When you need fade-in, fade-out animation, there is a option:
``` jsx
show(, {
fading: true,
clickOutsideToClose: true,
});
```

You can override overlay style:
``` jsx
show(, {
style: {
justifyContent: "flex-start" // Modal is placed left side of the page.
background: "rgba(0, 0, 0, 0.1)" // Darken overlay background color.
}
});
```

Provide `onClose` callback:
``` jsx
show(, {
onClose: () => { /* called when the modal closed */ }
});
```

`hide(key, ...args)` function pass the arguments to `onClose(...args)` callback:
``` jsx
show(, {
key: "1234" // required
onClose: (value1, value2) => { console.log(value1, value2) } // Hello World!
});

hide("1234", "Hello", "World!");
```

You can promisify the modal
``` jsx
const getValue = new Promise((resolve) => {
show( hide("1234", value)} />, {
key: "1234" // required
onClose: (value) => { resolve(value); }
});
});

// inside some async function
...
const value = await getValue();
...
```

## Advanced

### Message

![message](https://github.com/minidonut/react-functional-modal/raw/master/docs/message.gif)
``` jsx
const Message = ({ duration, title }) => {
const [remain, setRemain] = React.useState(duration / 1000);

React.useEffect(() => {
const interval = setInterval(() => { setRemain(remain => remain - 1) }, 1000);
return () => clearInterval(interval);
}, []);

return


{title}


{`closed after ${remain}seconds..`}


;
};

const message = (title, duration) => {
setTimeout(() => { hide("alert") }, duration);
show(, {
key: "alert",
fading: true,
style: { background: "rgba(27, 28, 37, 0.03)" }
});
}

...

message(
`You have no test.
Successfully deployed to production.

Have a nice weekend.`,
3000,
)}>
show message modal

```

### Confirm

![confirm](https://github.com/minidonut/react-functional-modal/raw/master/docs/confirm.gif)

``` jsx
const confirm = () => new Promise((resolve) => {
show(


Are you sure to send this message to your ex at 3AM?



hide("confirm", true)} style={{ marginRight: "8px" }}>
OK

hide("confirm", false)}>
CANCEL


, {
key: "confirm",
fading: true,
onClose: (result) => {
resolve(result);
},
style: {
background: "rgba(27, 28, 37, 0.08)"
}
})
});
```

### Select

![select](https://github.com/minidonut/react-functional-modal/raw/master/docs/select.gif)

``` jsx
const select = (title, options) => new Promise((resolve) => {
show(


{title}


{ e.persist(); hide("select", e.target.value) }}>
{options.map((o, i) => {
return {o}
})}

, {
key: "select",
fading: true,
style: { background: "rgba(27, 28, 37, 0.08)" },
onClose: (value) => {
resolve(value);
},
})
});

...

{
const result = await select("What is your favorite language?", [
"Javscript", "Typescript", "Python", "Go", "C/C++",
]);
console.log(result);
}}>
show select modal

```

### Form

![form](https://github.com/minidonut/react-functional-modal/raw/master/docs/form.gif)

``` jsx
const Form = () => {
const [values, setValues] = React.useState({ name: "", email: "", phone: "" });
const onChange = React.useCallback((property) => (e) => {
e.persist();
setValues((prev) => ({ ...prev, [property]: e.target.value }));
}, [setValues]);

return

GET FREE CHICKEN!






hide("form", undefined)} style={{ marginRight: "8px" }}>
CANCEL

hide("form", values)}>
SUBMIT



};

const form = () => new Promise((resolve, reject) => {
show(, {
key: "form",
fading: true,
style: { background: "rgba(27, 28, 37, 0.08)" },
onClose: (v) => {
if (!v) reject();
resolve(v);
},
});
});
```

### Step

![step](https://github.com/minidonut/react-functional-modal/raw/master/docs/step.gif)

``` jsx
const step = (title, contents, index = 0) => {
if (index === contents.length || index < 0) return hide("step");

show(


{title}


{contents[index]}



step(title, contents, index - 1)} style={{ marginRight: "8px" }}>
PREV

step(title, contents, index + 1)}>
NEXT


,
{ key: "step", fading: true, style: { background: "rgba(27, 28, 37, 0.08)" } });
}

...

step(
"How to debug code", [
"1. Make sure the code is saved",
"2. Reinstall node_modules",
"3. Restart your computer",
])}>
show step modal

```

## API
### show(ReactNode, Option)
`ReactNode`:

React element. e.g. `

...
`, ``, `<>...>`

`Option` (optional):

See [Option](https://github.com/minidonut/react-functional-modal#option)

**return**: `void`

### hide(string)
`string` (optional):

Key of the modal to hide. if not provided, hide modals in order from recent to old.

**return**: `void`

## `Option`
All properties follow are optional.
| Key | Type | Description |
| ----- | :--: | ----------- |
| key | `string` | Unique key of modal. if ommited incremental number is assigned. |
| onClose | `function` | callback when `hide` function called |
| style | `object` | CSS properties object which will overrides the modal's overlay |
| fading | `boolean` | enable fadeIn and fadeOut (default `false`) |
| clickOutsideToClose | `boolean` | click overlay to close the modal (default `false`) |

## Default overlay styles
```
display: flex;
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
justify-content: center;
align-items: center;
background: rgba(255,255,255,0);
```

## How it works
Every React element must be mounted somewhere in vdom tree. No react component can be rendered outside of the flow. In this point of view, package `react-functional-modal` have no sense. because it doesn't be mounted but called with argument.

Where is the React element given as first argument of `show` function mounted?

The answer is **another tree**:

how it works

As the function `show` called, it creates HTML `div` element and appends it as a child of document's body. New `ReactDOM.render` API called with the HTML `div` element as a container, given react element wrapped by overlay as a root.

When the function `hide` called, `ReactDOM.unmountComponentAtNode` and `document.body.removeChild` API detroy and clear the modal.

## Restriction
As our modal rendered in separated context, it has no access to context object such as `ReduxStore`, `React-Router Context`, `ThemeContext`.

### Solution
1. Wrap modal with context providers:
``` jsx
import { Provider, useStore } from "react-redux";
...
// somewhere in react component
...
const store = useStore();
const openModal = useCallback(() => {
show(





);
}, [store, theme]);
...
```

2. Pass functions directly (callback with closure)
``` jsx
...
// somewhere in react component
...
const updateSomething = useCallback((value) => {
dispatch(actions.updateSomething(value));
}, [dispatch]);

const openModal = useCallback(() => {
show();
}, [updateSomething]);
...
```

## License
MIT