npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

redux-saga-modal

v1.3.2

Published

A wrapper to manage modals within redux-saga

Downloads

588

Readme

redux-saga-modal

Manage modals with redux-saga.

Live Demo

Install

npm i redux-saga-modal

Or

yarn add redux-saga-modal

Usage

Pass the reducer to your store. It keeps the state of all your modal components, so you only have to pass it once.

import { createStore, combineReducers } from 'redux';
import { reducer as modalReducer } from 'redux-saga-modal';

const rootReducer = combineReducers({
  // ...your other reducers
  // modalReducer should be mounted under 'modals' key,
  modals: modalReducer,
});

const store = createStore(rootReducer);

Connect a component to Redux Store with sagaModal.

import {
  sagaModal,
  createModal,
  SagaModalInjectedProps,
} from 'redux-saga-modal';
import ReactModal from 'react-modal';
import { ReactNode } from 'react';

ReactModal.setAppElement('#root');

export interface ConfirmModalOwnProps {
  title: ReactNode;
  text: ReactNode;
}

const Confirm: React.FC<ConfirmModalOwnProps & SagaModalInjectedProps> = ({
  isOpen,
  submit,
  hide,
  title,
  text,
}) => {
  const handleSubmit = () => {
    submit();
  };
  return (
    <ReactModal isOpen={isOpen} onRequestClose={hide} closeTimeoutMS={500}>
      <h3>{title}</h3>
      <p>{text}</p>
      <div>
        <button onClick={hide}>Cancel</button>
        <button onClick={handleSubmit}>Confirm</button>
      </div>
    </ReactModal>
  );
};

const modal = createModal('confirm');

const ConnectedConfirmModal = sagaModal({
  name: modal.name,
})(Confirm);

export { modal, ConnectedConfirmModal };

To create an instance use createModal and pass the modal name as the first argument.

import { createModal } from 'redux-saga-modal';
const modal = createModal('CONFIRM');

Result

{
  name: 'CONFIRM',
  selector: function (r) {},
  patterns: {
    show: function (n) {},
    hide: function (n) {},
    destroy: function (n) {},
    update: function (n) {},
    click: function (n) {},
    submit: function (n) {},
  },
  actions: {
    show: function (n) {},
    update: function (n) {},
    submit: function (n) {},
    click: function (n) {},
    hide: function () {},
    destroy: function () {},
  },
  takeShow: function (t) {},
  takeUpdate: function (t) {},
  takeClick: function (t) {},
  takeDestroy: function (t) {},
  takeSubmit: function (t) {},
  takeHide: function (t) {},
  show: function (r) {},
  update: function (r) {},
  click: function (r) {},
  submit: function (r) {},
  hide: function () {},
  destroy: function () {},
  select: function () {},
};

All methods already bound to the modal's name so you don't need manually pass it. The instance will receive properties actions, patterns, name, selector and high-level effects show, update, hide, submit, click, destroy, takeShow, takeUpdate, takeHide, takeSubmit, takeClick and takeDestroy.

Both patterns and actions have methods named show, update, hide, submit, click and destroy.

import { race } from 'redux-saga/effects';
import { createModal } from 'redux-saga-modal';

const modal = createModal('confirm');

const confirmModal = function* (initProps: ConfirmModalOwnProps) {
  yield modal.show(initProps);

  const [submit]: boolean[] = yield race([
    modal.takeSubmit(),
    modal.takeHide(),
  ]);

  yield modal.hide();

  return !!submit;
};

Alternatively if you don't need high-level effects you can use createModalHelpers. The example above can be rewritten like this:

import { race, put, take } from 'redux-saga/effects';
import { createModalHelpers } from 'redux-saga-modal';
import { ConfirmModalOwnProps } from './Modal';

const modal = createModalHelpers('confirm');
const confirmModal = function* (initProps: ConfirmModalOwnProps) {
  yield put(modal.actions.show(initProps));

  const [submit]: boolean[] = yield race([
    take(modal.patterns.submit()),
    take(modal.patterns.hide()),
  ]);

  yield put(modal.actions.hide());

  return !!submit;
};

For frequently repeated modals tasks create a config object with modals names as keys and tasks as values. Pass the config as the argument to sagas and fork it in your rootSaga.

Your tasks will be called with a this context every time when an action showModal has been dispatched with it's name and cancelled on destroyModal.

The context of your task will have properties actions, patterns, name, selector and high-level effects show, update, hide, submit, click, destroy, takeShow, takeUpdate, takeHide, takeSubmit, takeClick and takeDestroy.

Both patterns and actions have methods named show, update, hide, submit, click and destroy.

import { race, call, fork, all } from 'redux-saga/effects';
import {
  sagas as modalsSaga,
  SagaModalInstance,
  SagaModalAction,
} from 'redux-saga-modal';

interface CommentData {
  id: number;
  comment: string;
}

const saveComment = (data: CommentData) =>
  new Promise((resolve) => setTimeout(() => resolve({ ok: true }), 1000));

const saveCommentWorker = function* (this: SagaModalInstance) {
  const [submitAction]: SagaModalAction<CommentData>[] = yield race([
    this.takeSubmit(),
    this.takeHide(),
  ]);

  if (submitAction) {
    yield this.update({
      title: 'Saving',
      loading: true,
    });

    yield call(saveComment, submitAction.payload as CommentData);
    yield this.update({
      title: 'Changes saved',
      loading: false,
    });
  }

  yield this.hide();
};

const modalsTasks = {
  saveComment: saveCommentWorker,
};

export default function* rootSaga() {
  yield all([fork(modalsSaga, modalsTasks)]);
}

API

Actions Creators

Basic actions creators.

| Name | Arguments | Description | | ------------ | ------------------------------------ | ------------------------------------------------------------ | | showModal | (name: String, payload: Object = {}) | Triggers to show a modal and sets payload as props | | updateModal | (name: String, payload: Object = {}) | Updates the modal by merging payload with it's current props | | submitModal | (name: String, payload: any) | Triggers that a target's button was pressed | | clickModal | (name: String, payload: any) | A trigger for handling any click on modal | | hideModal | (name: String) | Hide modal without destroying its state in the redux store | | destroyModal | (name: String) | Close a modal by destroying its props |

Instance Creators

createModal

Creates a modal instance with properties name, selector, actions, patterns, and effects creators show, update, hide, submit, click, destroy, takeShow, takeUpdate, takeHide, takeSubmit, takeClick and takeDestroy.

Both patterns and actions includes methods named show, update, hide, submit, click and destroy. All methods refer to the modal's name so you don't need manually pass it.

Arguments

  • name(String)(Required) the name of a modal

  • config(Object)

    • getModalsState (Function) A selector that takes the Redux store and returns the slice which corresponds to where the redux-saga-modal reducer was mounted. By default, the reducer is mounted under the modals key.
import { createModal } from 'redux-saga-modal';

const {
  name,
  selector,
  //patterns creators used for filtering actions inside take effects
  patterns: { show, click, submit, update, hide, destroy },
  //action creators
  actions: { show, click, submit, update, hide, destroy },
  //put effects (already wrapped in redux-saga put)
  show,
  click,
  submit,
  update,
  hide,
  destroy,
  //take effects (already wrapped in redux-saga take)
  takeShow,
  takeClick,
  takeSubmit,
  takeUpdate,
  takeHide,
  takeDestroy,
} = createModal('CONFIRM');

createModalHelpers

Creates a modal instance with properties actions, patterns, selector and name.

Both patterns and actions have methods named show, update, hide, submit, click and destroy.

Arguments

  • name(String)(Required) the name of a modal

  • config(Object)

    • getModalsState (Function) A selector that takes the Redux store and returns the slice which corresponds to where the redux-saga-modal reducer was mounted. By default, the reducer is mounted under the modals key.
import { createModalHelpers } from 'redux-saga-modal';

const {
  name,
  selector,
  patterns: { show, click, submit, update, hide, destroy },
  actions: { show, click, submit, update, hide, destroy },
} = createModalHelpers('CONFIRM');

Instance properties

name

The name of the modal instance which was passed to createModal or createModalHelpers. Should be the same as passed one in sagaModal.

actions

Same as basic actions creators but the first argument is already bound to the modal name.

| Name | Arguments | Description | | ------- | ---------------------- | ------------------------------------------------------------ | | show | (payload: Object = {}) | Triggers to show a modal and sets payload as it's props | | update | (payload: Object = {}) | Updates the modal by merging payload with it's current props | | submit | (payload: any) | Triggers that a target's button was pressed | | click | (payload: any) | A trigger for handling any click on modal | | hide | () | Hide modal without destroying its state in the redux store | | destroy | () | Close a modal by destroying its props |

Example

import { createModal, createModalActions, showModal } from 'redux-saga-modal';

const modal = createModal('CONFIRM');
yield put(modal.actions.show({ title: 'Some title' }));

//or
const confirmActions = createModalActions('CONFIRM');
yield put(confirmActions.show({ title: 'Some title' }));

//or
yield put(showModal('CONFIRM', { title: 'Some title' }));

patterns

Matcher methods for filtering modal actions inside redux-saga take effects, such as take, takeLatest, takeEvery etc. Every pattern accepts optional argument for checking payload.

Payload pattern has the same meaning and rules as in the redux-saga but applies not for the whole action but only to its payload.

| Name | Arguments | Description | | ------- | ----------------------- | ---------------------------------------------------------------------------- | | show | payloadPattern?: String | Function | | update | payloadPattern?: String | Function | | submit | payloadPattern?: String | Function | | click | payloadPattern?: String | Function | | hide | | An action matcher for hideModal with the same name as it's instance has | | destroy | | An action matcher for destroyModal with the same name as it's instance has |

Example

import { createModal } from 'redux-saga-modal';

const { patterns } = createModal('CONFIRM');

//result: take(action => action.type === clickModal().type && action.meta.name === 'CONFIRM' && payload === 'value'
yield take(patterns.click(payload === 'value'));

//result: take(action => action.type === clickModal().type && action.meta.name === 'CONFIRM' && payload && payload.text === 'Some text'

yield take(patterns.show((payload) => payload && payload.text === 'Some text'));

//result: take(action => action.type === clickModal().type && action.meta.name === 'CONFIRM'
yield take(patterns.click);

selector

import { createModal } from 'redux-saga-modal';

const { selector, ...effects } = createModal('CONFIRM');

// the same
yield select(selector);
yield effects.select();

effects

Produced with createModal or createModalEffects. Returns scoped put, select and take effects.

Payload pattern in the take effects has the same meaning and rules as in's the redux-saga, but applies not for the whole action but only to the payload.

| Name | Arguments | Effect | | | ----------- | ------------------------ | -------- | --------------------------------------------------------------------- | | show | (payload: Object) | put | Triggers to show a modal and sets payload as it's props | | update | (payload: Object) | put | Updates the modal by merging payload with it's current props | | submit | (payload: any) | put | Triggers that a target's button was pressed | | click | (payload: any) | put | A trigger for handling any click on modal | | hide | (name: String) | put | Hide modal without destroying it's state in the redux store | | destroy | (name: String) | put | Close a modal by destroying it's props | | select | (name: String) | select | Select a modal state from the Redux Store | | takeShow | (payloadPattern?: String | Function | Array | | takeUpdate | (payloadPattern?: String | Function | Array | | takeSubmit | (payloadPattern?: String | Function | Array | | takeClick | (payloadPattern?: String | Function | Array | | takeHide | | take | Returns a take effect for an action hideModal with scoped name | | takeDestroy | | take | Returns a take effect for an action destroyModal with scoped name |

Example

import { createModal } from 'redux-saga-modal';

const { name, ...effects } = createModal('CONFIRM');

//result: put(showModal('CONFIRM', ({ text: 'Some text' }))
yield effects.show({ text: 'Some text' });

//result: take(action => action.type === clickModal().type && action.meta.name === 'CONFIRM' && payload === 'value'
yield effects.takeClick('value');

//result: take(action => action.type === clickModal().type && action.meta.name === 'CONFIRM' && payload && payload.text === 'Some text'
yield effects.takeClick((payload) => payload && payload.text === 'Some text');
//result: take(action => action.type === clickModal().type && action.meta.name === 'CONFIRM'
yield effects.takeClick();

sagas

Invoke your sagas with a this context whenever an action showModal has been dispatched with passed name and cancelled on destroyModal.

Arguments

  • config(Object)(Required)

    • <[key: ModalName], Saga: Generator>

sagaModal

Connects a component to Redux store and injects next props:

  • modal: { name }
  • name;
  • isOpen;
  • show;
  • update;
  • destroy;
  • click;
  • submit;
  • hide;
  • showModal;
  • updateModal;
  • submitModal;
  • clickModal;
  • hideModal;
  • destroyModal;

Arguments

  • name(String)(Required) the name of a modal

  • config(Object)

    • name (String)(Required) the name of a modal
    • getModalsState (Function) A selector that takes the Redux store and returns the slice which corresponds to where the redux-saga-modal reducer was mounted. By default reducer is mounted under the 'modals' key: state => state.modals
    • actions (Object) Custom actions to bind with redux dispatch
    • keepComponentOnHide (Boolean) Whether keep modal component when isOpen equals false By default equals false
    • destroyOnHide (Boolean) Whether automatically dispatch destroy to after hide. By default equals true

reducer

The modals reducer keeps state of the all modals. Should be mounted to redux store under name modals or any other but in this case you need to pass selector getModalsState to sagaModal, createModal and createModalHelpers.

createModalActions

Arguments

  • name(String)(Required) the name of a modal

Creates actions creators show, update, hide, submit, click and destroy bound to the name of the modal.

createModalEffects

Arguments

  • name (String)(Required) the name of a modal

Creates effects show, update, hide, submit, click and destroy bound to the name of the modal.

createModalPatterns

Arguments

  • name(String)(Required) the name of a modal

Creates patterns creators show, update, hide, submit, click and destroy bound to the name of modal. Every pattern accepts optional matcher for checking payload.

License

MIT © KarimAziev