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
- Host: GitHub
- URL: https://github.com/saehun/react-functional-modal
- Owner: saehun
- License: mit
- Created: 2020-05-09T09:03:32.000Z (about 6 years ago)
- Default Branch: master
- Last Pushed: 2022-03-26T16:28:19.000Z (over 4 years ago)
- Last Synced: 2025-04-01T21:29:18.513Z (over 1 year ago)
- Topics: component, functional, javascript, modal, react
- Language: TypeScript
- Homepage:
- Size: 8.38 MB
- Stars: 22
- Watchers: 2
- Forks: 0
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- License: license
Awesome Lists containing this project
README
React Functional Modal
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?

## 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

``` 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

``` 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

``` 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

``` 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

``` 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**:

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