{"id":18351384,"url":"https://github.com/saehun/react-functional-modal","last_synced_at":"2025-04-06T11:32:38.024Z","repository":{"id":42846576,"uuid":"262531239","full_name":"saehun/react-functional-modal","owner":"saehun","description":"FP friendly react modal component ","archived":false,"fork":false,"pushed_at":"2022-03-26T16:28:19.000Z","size":8784,"stargazers_count":22,"open_issues_count":5,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-01T21:29:18.513Z","etag":null,"topics":["component","functional","javascript","modal","react"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/saehun.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"license","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-05-09T09:03:32.000Z","updated_at":"2023-11-08T04:52:01.000Z","dependencies_parsed_at":"2022-08-23T14:51:13.607Z","dependency_job_id":null,"html_url":"https://github.com/saehun/react-functional-modal","commit_stats":null,"previous_names":["saehun/react-functional-modal","minidonut/react-functional-modal"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saehun%2Freact-functional-modal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saehun%2Freact-functional-modal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saehun%2Freact-functional-modal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saehun%2Freact-functional-modal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/saehun","download_url":"https://codeload.github.com/saehun/react-functional-modal/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247478151,"owners_count":20945257,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["component","functional","javascript","modal","react"],"created_at":"2024-11-05T21:30:42.694Z","updated_at":"2025-04-06T11:32:34.594Z","avatar_url":"https://github.com/saehun.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/minidonut/react-functional-modal/raw/master/docs/logo.png\" alt=\"logo\" width=\"600\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eReact Functional Modal\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://npmjs.org/package/react-functional-modal\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/v/react-functional-modal.svg\" alt=\"version\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://npmjs.org/package/react-functional-modal\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/dm/react-functional-modal.svg\" alt=\"downloads\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cb\u003eModal component doesn't needs to be mounted\u003c/b\u003e\u003c/br\u003e\n  \u003csub\u003eCall functions show, hide with arguments ReactNode and few options\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003cbr /\u003e\n\n- **Simple**: Only have 2 API. show and hide.\n- **Functional**: Let library manages the state. Just call the function.\n- **Flexible**: No restriction for how the modal to be shaped.\n- **Typed**: Built with typescript.\n- **Small**: 160 lines, ~1.8Kb gzipped. no deps.\n\n## Install\n\n``` shell\n$ npm install --save react-functional-modal\n```\n\n## How is it different?\n![comparison](https://github.com/minidonut/react-functional-modal/raw/master/docs/old-and-new.png)\n\n## Usage\n\nSimply Call the function `show` with react element(jsx):\n``` jsx\nshow(\u003cdiv\u003e{/* contents */}\u003c/div\u003e);\n```\n\nAnd hide it:\n``` jsx\nhide();\n```\n\nIf you want modals to be overlapped, do it:\n``` jsx\nshow(\u003cModalOne /\u003e);\nshow(\u003cModalTwo /\u003e);\n```\n\n`hide` function closes recently opened modal:\n``` jsx\nhide(); // hide ModalTwo\nhide(); // hide ModalOne\nhide(); // do nothing\n```\n\nIf you specified the key, you can explicitly close it:\n``` jsx\nshow(\u003cModal /\u003e, { key: \"1234\" });\nhide(\"1234\");\n```\n\nWhen you need fade-in, fade-out animation, there is a option:\n``` jsx\nshow(\u003cModal /\u003e, {\n  fading: true,\n  clickOutsideToClose: true,\n});\n```\n\nYou can override overlay style:\n``` jsx\nshow(\u003cModal /\u003e, {\n  style: {\n    justifyContent: \"flex-start\"     // Modal is placed left side of the page.\n    background: \"rgba(0, 0, 0, 0.1)\" // Darken overlay background color.\n  }\n});\n```\n\nProvide `onClose` callback:\n``` jsx\nshow(\u003cModal /\u003e, {\n  onClose: () =\u003e { /* called when the modal closed */ }\n});\n```\n\n\n`hide(key, ...args)` function pass the arguments to `onClose(...args)` callback:\n``` jsx\nshow(\u003cModal /\u003e, {\n  key: \"1234\" // required\n  onClose: (value1, value2) =\u003e { console.log(value1, value2) } // Hello World!\n});\n\nhide(\"1234\", \"Hello\", \"World!\");\n```\n\n\nYou can promisify the modal\n``` jsx\nconst getValue = new Promise((resolve) =\u003e {\n  show(\u003cModal someHandler={(value) =\u003e hide(\"1234\", value)} /\u003e, {\n    key: \"1234\" // required\n    onClose: (value) =\u003e { resolve(value); }\n  });\n});\n\n// inside some async function\n...\nconst value = await getValue();\n...\n```\n\n\n## Advanced\n\n### Message\n\n![message](https://github.com/minidonut/react-functional-modal/raw/master/docs/message.gif)\n``` jsx\nconst Message = ({ duration, title }) =\u003e {\n  const [remain, setRemain] = React.useState(duration / 1000);\n\n  React.useEffect(() =\u003e {\n    const interval = setInterval(() =\u003e { setRemain(remain =\u003e remain - 1) }, 1000);\n    return () =\u003e clearInterval(interval);\n  }, []);\n\n  return \u003cdiv style={wrapperStyle}\u003e\n    \u003ch3 style={{ whiteSpace: \"pre\" }}\u003e{title}\u003c/h3\u003e\n    \u003cp\u003e{`closed after ${remain}seconds..`}\u003c/p\u003e\n  \u003c/div\u003e;\n};\n\nconst message = (title, duration) =\u003e {\n  setTimeout(() =\u003e { hide(\"alert\") }, duration);\n  show(\u003cMessage title={title} duration={duration} /\u003e, {\n    key: \"alert\",\n    fading: true,\n    style: { background: \"rgba(27, 28, 37, 0.03)\" }\n  });\n}\n\n...\n\n\u003cbutton onClick={() =\u003e message(\n  `You have no test.\nSuccessfully deployed to production.\n\nHave a nice weekend.`,\n  3000,\n)}\u003e\n  show message modal\n\u003c/button\u003e\n```\n\n### Confirm\n\n\n![confirm](https://github.com/minidonut/react-functional-modal/raw/master/docs/confirm.gif)\n\n``` jsx\nconst confirm = () =\u003e new Promise((resolve) =\u003e {\n  show(\u003cdiv style={wrapperStyle}\u003e\n    \u003cp\u003eAre you sure to send this message to your ex at 3AM?\u003c/p\u003e\n    \u003cdiv style={{ textAlign: \"right\" }}\u003e\n      \u003cbutton onClick={() =\u003e hide(\"confirm\", true)} style={{ marginRight: \"8px\" }}\u003e\n        OK\n    \u003c/button\u003e\n      \u003cbutton onClick={() =\u003e hide(\"confirm\", false)}\u003e\n        CANCEL\n    \u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e, {\n    key: \"confirm\",\n    fading: true,\n    onClose: (result) =\u003e {\n      resolve(result);\n    },\n    style: {\n      background: \"rgba(27, 28, 37, 0.08)\"\n    }\n  })\n});\n```\n\n### Select\n\n\n![select](https://github.com/minidonut/react-functional-modal/raw/master/docs/select.gif)\n\n``` jsx\nconst select = (title, options) =\u003e new Promise((resolve) =\u003e {\n  show(\u003cdiv style={wrapperStyle}\u003e\n    \u003ch3\u003e{title}\u003c/h3\u003e\n    \u003cselect\n      value={options[0].toLowerCase()}\n      onChange={(e) =\u003e { e.persist(); hide(\"select\", e.target.value) }}\u003e\n      {options.map((o, i) =\u003e {\n        return \u003coption key={i} value={o.toLowerCase()}\u003e{o}\u003c/option\u003e\n      })}\n    \u003c/select\u003e\n  \u003c/div\u003e, {\n    key: \"select\",\n    fading: true,\n    style: { background: \"rgba(27, 28, 37, 0.08)\" },\n    onClose: (value) =\u003e {\n      resolve(value);\n    },\n  })\n});\n\n...\n\n\u003cbutton onClick={async () =\u003e {\n  const result = await select(\"What is your favorite language?\", [\n    \"Javscript\", \"Typescript\", \"Python\", \"Go\", \"C/C++\",\n  ]);\n  console.log(result);\n}}\u003e\n  show select modal\n\u003c/button\u003e\n```\n\n### Form\n\n\n![form](https://github.com/minidonut/react-functional-modal/raw/master/docs/form.gif)\n\n``` jsx\nconst Form = () =\u003e {\n  const [values, setValues] = React.useState({ name: \"\", email: \"\", phone: \"\" });\n  const onChange = React.useCallback((property) =\u003e (e) =\u003e {\n    e.persist();\n    setValues((prev) =\u003e ({ ...prev, [property]: e.target.value }));\n  }, [setValues]);\n\n  return \u003cform style={wrapperStyle}\u003e\n    \u003ch3\u003eGET FREE CHICKEN!\u003c/h3\u003e\n    \u003cInputGroup property=\"name\" onChange={onChange} value={values.name} /\u003e\n    \u003cInputGroup property=\"email\" onChange={onChange} value={values.email} /\u003e\n    \u003cInputGroup property=\"phone\" onChange={onChange} value={values.phone} /\u003e\n    \u003cdiv style={{ textAlign: \"center\" }}\u003e\n      \u003cbutton onClick={() =\u003e hide(\"form\", undefined)} style={{ marginRight: \"8px\" }}\u003e\n        CANCEL\n      \u003c/button\u003e\n      \u003cbutton onClick={() =\u003e hide(\"form\", values)}\u003e\n        SUBMIT\n      \u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/form\u003e\n};\n\nconst form = () =\u003e new Promise((resolve, reject) =\u003e {\n  show(\u003cForm /\u003e, {\n    key: \"form\",\n    fading: true,\n    style: { background: \"rgba(27, 28, 37, 0.08)\" },\n    onClose: (v) =\u003e {\n      if (!v) reject();\n      resolve(v);\n    },\n  });\n});\n```\n\n### Step\n\n\n![step](https://github.com/minidonut/react-functional-modal/raw/master/docs/step.gif)\n\n``` jsx\nconst step = (title, contents, index = 0) =\u003e {\n  if (index === contents.length || index \u003c 0) return hide(\"step\");\n\n  show(\u003cdiv style={wrapperStyle}\u003e\n    \u003ch3 style={{ textAlign: \"center\" }}\u003e{title}\u003c/h3\u003e\n    \u003cp\u003e{contents[index]}\u003c/p\u003e\n    \u003cdiv style={{ textAlign: \"center\" }}\u003e\n      \u003cbutton onClick={() =\u003e step(title, contents, index - 1)} style={{ marginRight: \"8px\" }}\u003e\n        PREV\n      \u003c/button\u003e\n      \u003cbutton onClick={() =\u003e step(title, contents, index + 1)}\u003e\n        NEXT\n      \u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e,\n    { key: \"step\", fading: true, style: { background: \"rgba(27, 28, 37, 0.08)\" } });\n}\n\n...\n\n\u003cbutton onClick={() =\u003e step(\n  \"How to debug code\", [\n  \"1. Make sure the code is saved\",\n  \"2. Reinstall node_modules\",\n  \"3. Restart your computer\",\n])}\u003e\n  show step modal\n\u003c/button\u003e\n```\n\n\n## API\n### show(ReactNode, Option)\n`ReactNode`: \u003cbr\u003e\nReact element. e.g. `\u003cdiv\u003e...\u003c/div\u003e`, `\u003cSomeComponent /\u003e`, `\u003c\u003e...\u003c/\u003e`\n\n`Option` (optional): \u003cbr\u003e\nSee [Option](https://github.com/minidonut/react-functional-modal#option) \u003cbr\u003e\n\n**return**: `void`\n\n### hide(string)\n`string` (optional): \u003cbr\u003e\nKey of the modal to hide. if not provided, hide modals in order from recent to old. \u003cbr\u003e\n\n**return**: `void`\n\n\n## `Option`\nAll properties follow are optional.\n| Key | Type | Description |\n| ----- | :--: | ----------- |\n| key | `string` | Unique key of modal. if ommited incremental number is assigned. |\n| onClose | `function` | callback when `hide` function called |\n| style | `object` | CSS properties object which will overrides the modal's overlay |\n| fading | `boolean` | enable fadeIn and fadeOut (default `false`) |\n| clickOutsideToClose | `boolean` | click overlay to close the modal (default `false`) |\n\n## Default overlay styles\n```\ndisplay: flex;\nposition: fixed;\ntop: 0; left: 0; right: 0; bottom: 0;\njustify-content: center;\nalign-items: center;\nbackground: rgba(255,255,255,0);\n```\n\n\n## How it works\nEvery 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.\n\nWhere is the React element given as first argument of `show` function mounted?\u003cbr\u003e\nThe answer is **another tree**:\u003cbr\u003e\n\u003cimg src=\"https://github.com/minidonut/react-functional-modal/raw/master/docs/how-it-works.png\" alt=\"how it works\" width=\"621\" height=\"405\" /\u003e\n\nAs 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.\n\nWhen the function `hide` called, `ReactDOM.unmountComponentAtNode` and `document.body.removeChild` API detroy and clear the modal.\n\n## Restriction\nAs our modal rendered in separated context, it has no access to context object such as `ReduxStore`, `React-Router Context`, `ThemeContext`.\n\n### Solution\n1. Wrap modal with context providers:\n``` jsx\nimport { Provider, useStore } from \"react-redux\";\n...\n// somewhere in react component\n...\n  const store = useStore();\n  const openModal = useCallback(() =\u003e {\n    show(\n      \u003cThemeProvider theme={theme}\u003e\n        \u003cProvider store={store}\u003e\n          \u003cModal /\u003e\n        \u003c/Provider\u003e\n      \u003c/ThemeProvider\u003e\n    );\n  }, [store, theme]);\n...\n```\n\n2. Pass functions directly (callback with closure)\n``` jsx\n...\n// somewhere in react component\n...\n  const updateSomething = useCallback((value) =\u003e {\n    dispatch(actions.updateSomething(value));\n  }, [dispatch]);\n\n  const openModal = useCallback(() =\u003e {\n    show(\u003cModal handler={updateSomething} /\u003e);\n  }, [updateSomething]);\n...\n```\n\n\n## License\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaehun%2Freact-functional-modal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsaehun%2Freact-functional-modal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaehun%2Freact-functional-modal/lists"}