0.7.0 • Published 1 year ago

@rashedmakkouk/redux-toolkit v0.7.0

Weekly downloads
-
License
BSD 3-Clause
Repository
github
Last release
1 year ago

Redux Toolkit

Opinionated toolset for efficient Redux development.

Installation

Install package in your project.

# NPM
npm install @rashedmakkouk/redux-toolkit

# Yarn
yarn add @rashedmakkouk/redux-toolkit

Usage

Using ES6 module syntax

import { module } from '@rashedmakkouk/redux-toolkit';

module();

Using CommonJS syntax

const { module } = require('@rashedmakkouk/redux-toolkit');

module();

Definitions

For an abstract implementation, a more generic terminology is used.

ParamRequiredDescription
folderYesRefers to the slice name in the root Redux state object.
subFolderNoRefers to the sub slice name in the root Redux state object.

Action Types

This package exports a handful of Action Types that you can use in your project.

  • APPEND_RECORDS
  • REMOVE_PROPS
  • SET_PROPS
  • UPDATE_PROPS
  • UPDATE_PROPS_BY_KEY
  • REMOVE_RECORDS_BY_KEY
  • PURGE_STORE
  • RESET_REDUCER
  • RESET_STORE
  • USER_AUTHENTICATED
  • USER_UNAUTHENTICATED
  • SIGN_IN
  • SIGN_UP
  • REFRESH_TOKEN
  • INIT_APP
  • STARTUP_APP
  • SHUTDOWN_APP
  • APP_READY
  • NETWORK_CONNECTED
  • NETWORK_DISCONNECTED
  • NETWORK_ONLINE
  • NETWORK_OFFLINE
  • CONNECT_WEBSOCKET
  • SEND_WEBSOCKET_MESSAGE

Redux Store Setup

Create a Redux Store

app/store.js

Follow the instructions from Create a Redux Store to configure your app's root Redux Store; also see configureStore.

Create a Redux State Slice

app/reducers/slice-name.reducer.js

The implementation described below introduces some complexity to your existing Action Creators and Slice Reducers, make sure to check out Caveats section and the changes you need to apply to your app.

app/reducers/posts.reducer.js

// Set Store slice initial state data structure and values.
const initialState = {
  value: 0
};

// Create slice reducer function.
const postsReducer = (state = initialState, action) => {
  const { type } = action;

  switch (type) {
    ...

    default:
      return state;
  }
};

export default postsReducer;

Add Slice Reducers to the Store

Follow these steps to Add Slice Reducers to the Store.

Provide the Redux Store

index.js

Follow the instructions from Provide the Redux Store to React to connect the Store to your React or React Native app.

Restrict processing a dispatched action by Slice

This implementation makes use of the same Action Creator and dispatches the same Action Reducer in all of the Redux Store slice reducers.

To process the dispatched actions only within the intended reducer function, add the following condition to each of your slice reducers:

app/reducers/posts.reducer.js

const initialState = {
  value: 0
};

const postsReducer = (state = initialState, action) => {
  const { folder, type } = action;

  // Add this <--
  if (folder !== 'posts') {
    return state;
  }

  switch (type) {
    ...

    default:
      return state;
  }
};

export default postsReducer;

Add Action Reducers to Redux Store Slice

You can add as many shared or explicit Action Reducers as needed.

import { actionReducers, actionTypes } from '@rashedmakkouk/redux-toolkit';

// Import action reducers.
const { setProps, updateProps } = actionReducers;

// Import action types.
const { SET_PROPS, UPDATE_PROPS } = actionTypes;

const initialState = {
  value: 0
};

const postsReducer = (state = initialState, action) => {
  const { folder, type } = action;

  if (folder !== 'posts') {
    return state;
  }

  switch (type) {
    ...

    // Add Action Reducers as needed.
    case SET_PROPS:
      return setProps(state, action, initialState);

    case UPDATE_PROPS:
      return updateProps(state, action);

    default:
      return state;
  }
};

export default postsReducer;

Use Action Creators in React Components

Action Creators can be triggered from anywhere in your app.

import { actionCreators } from '@rashedmakkouk/redux-toolkit';

// Import Action Creators.
const { appendRecords } = actionCreators;

// In React Functional components, use dispatch hook.
import { useDispatch } from 'react-redux';
const dispatch = useDispatch();

// In React Class components, use your configured app store.
import store from 'app/store.js';
const { dispatch } = store;

// Dispatch Action Creators in your app's components.
dispatch(
  appendRecords({
    folder: 'slice-name', // Root Redux store slice.
    payload: [ ... ], // Data to be processed.
    subFolder: 'sub-slice-name', // Optional: Root Redux store sub slice.
  })
);

Action Creators

Commonly used Redux Store actions for processing payloads.

IMPORTANT: No data types validations are made to any of the action types, arguments or payload. Make sure your data is correct and consistent to avoid thrown exceptions or unexpected outcome.

appendRecords (Array)

Appends Array payload to the end of an existing array.

Use case:

  • Slice Reducer data type: Array.
  • Slice Reducer data type: Object, Sub Slice data type: Array.

Parameters

ParamTypeRequiredDefaultDescription
args.folderstringYes-Slice name in the Store.
args.subFolderstringNo-Sub slice name in the Store.
args.payloadArrayYes-Data array.

Configuration

app/reducers/notifications.reducer.js

import { actionReducers, actionTypes } from '@rashedmakkouk/redux-toolkit';

const { appendRecords } = actionReducers;

const { APPEND_RECORDS } = actionTypes;

const initialState = [];

const notificationsReducer = (state = initialState, action) => {
  const { folder, type } = action;

  if (folder !== 'notifications') {
    return state;
  }

  switch (type) {
    ...

    case APPEND_RECORDS:
      return appendRecords(state, action);

    default:
      return state;
  }
};

export default notificationsReducer;

Example

// Redux Store slice data.
notifications: [
  { uid: 1 }
]

// Component.
dispatch(
  appendRecords({
    folder: 'notifications',
    payload: [
      { uid: 2 },
      { uid: 3 }
    ]
  })
)

// Result.
notifications: [
  { uid: 1 },
  { uid: 2 },
  { uid: 3 }
]

removeRecordsByKey (Array)

Removes Object records from Array by specified key with values matching supplied payload.

Use case:

  • Slice Reducer data type: Array.
  • Slice Reducer data type: Object, Sub Slice data type: Array.

Parameters

ParamTypeRequiredDefaultDescription
args.folderstringYes-Slice name in the Store.
args.subFolderstringNo-Sub slice name in the Store.
args.keyArrayYes-Object property to filter by.
args.payloadArray\ | Array\Yes-List of values to filter by.

Configuration

app/reducers/posts.reducer.js

import { actionReducers, actionTypes } from '@rashedmakkouk/redux-toolkit';

const { removeRecordsByKey } = actionReducers;

const { REMOVE_RECORDS_BY_KEY } = actionTypes;

const initialState = {};

const postsReducer = (state = initialState, action) => {
  const { folder, type } = action;

  if (folder !== 'posts') {
    return state;
  }

  switch (type) {
    ...

    case REMOVE_RECORDS_BY_KEY:
      return removeRecordsByKey(state, action);

    default:
      return state;
  }
};

export default postsReducer;

Example

// Redux Store slice data.
posts: [
  { id: 1, title: 'Title #1' },
  { id: 2, title: 'Title #2' },
  { id: 3, title: 'Title #3' }
]

// Component.
dispatch(
  removeRecordsByKey({
    folder: 'posts',
    key: 'id',
    payload: [ 1, 2 ]
  })
)

// Result.
posts: [
  { id: 3, title: 'Title #3' }
]

setProps (Object)

Resets Store slice Object to initial state and updates object properties with supplied key/value pairs payload.

Parameters

ParamTypeRequiredDefaultDescription
args.folderstringYes-Slice name in the Store.
args.subFolderstringNo-Sub slice name in the Store.
args.payloadobjectYes-Object key/value pairs to set.

Configuration

app/reducers/network.reducer.js

import { actionReducers, actionTypes } from '@rashedmakkouk/redux-toolkit';

const { setProps } = actionReducers;

const { SET_PROPS } = actionTypes;

const initialState = {
  isConnected: undefined,
  connectionType: undefined,
  secure: undefined
};

const networkReducer = (state = initialState, action) => {
  const { folder, type } = action;

  if (folder !== 'network') {
    return state;
  }

  switch (type) {
    ...

    case SET_PROPS:
      return setProps(state, action, initialState);

    default:
      return state;
  }
};

export default networkReducer;

Example

// Redux Store slice data.
network: {
  isConnected: false,
  connectionType: 'WiFi',
  secure: false
}

// Component.
dispatch(
  setProps({
    folder: 'network',
    payload: {
      isConnected: true,
      connectionType: '4G'
    }
  })
)

// Result.
network: {
  isConnected: true,
  connectionType: '4G',
  secure: undefined
}

updateProps (Object)

Updates object key/value pairs with supplied payload.

Parameters

ParamTypeRequiredDefaultDescription
args.folderstringYes-Slice name in the Store.
args.subFolderstringNo-Sub slice name in the Store.
args.payloadobjectYes-Object key/value pairs to update.

Configuration

app/reducers/config.reducer.js

import { actionReducers, actionTypes } from '@rashedmakkouk/redux-toolkit';

const { updateProps } = actionReducers;

const { UPDATE_PROPS } = actionTypes;

const initialState = {
  network: {
    isConnected: undefined,
    connectionType: undefined,
    secure: undefined
  }
};

const configReducer = (state = initialState, action) => {
  const { folder, type } = action;

  if (folder !== 'config') {
    return state;
  }

  switch (type) {
    ...

    case UPDATE_PROPS:
      return updateProps(state, action);

    default:
      return state;
  }
};

export default configReducer;

Example

// Redux Store slice data.
config: {
  network: {
    isConnected: false,
    connectionType: 'WiFi',
    secure: false
  }
}

// Component.
dispatch(
  updateProps({
    folder: 'config',
    subFolder: 'network',
    payload: {
      isConnected: true,
      connectionType: '4G'
    }
  })
)

// Result.
config: {
  network: {
    isConnected: true,
    connectionType: '4G',
    secure: false
  }
}

removeProps (Object)

Removes object properties by supplied key(s) payload.

Use case:

  • Object properties.
  • Normalized Object records.

Parameters

ParamTypeRequiredDefaultDescription
args.folderstringYes-Slice name in the Store.
args.subFolderstringNo-Sub slice name in the Store.
args.payloadstring | Array\Yes-List of object property names to remove.

Configuration

app/reducers/temp.reducer.js

import { actionReducers, actionTypes } from '@rashedmakkouk/redux-toolkit';

const { removeProps } = actionReducers;

const { REMOVE_PROPS } = actionTypes;

const initialState = {};

const tempReducer = (state = initialState, action) => {
  const { folder, type } = action;

  if (folder !== 'temp') {
    return state;
  }

  switch (type) {
    ...

    case REMOVE_PROPS:
      return removeProps(state, action);

    default:
      return state;
  }
};

export default tempReducer;

Example

// Redux Store slice data.
temp: {
  1: { id: 1 },
  2: { id: 2 },
  uid: "abc",
}

// Component.
dispatch(
  removeProps({
    folder: 'temp',
    payload: [ 2, 'uid' ]
  })
)

// Result.
temp: {
  1: { id: 1 }
}

updatePropsByKey (Object)

Iterates over normalized data structures by Object keys, updates child objects properties if exist, else adds a new record.

Parameters

ParamTypeRequiredDefaultDescription
args.folderstringYes-Slice name in the Store.
args.subFolderstringNo-Sub slice name in the Store.
args.payloadobjectYes-Store slice initial state.

Configuration

app/reducers/posts.reducer.js

import { actionReducers, actionTypes } from '@rashedmakkouk/redux-toolkit';

const { updatePropsByKey } = actionReducers;

const { UPDATE_PROPS_BY_KEY } = actionTypes;

const initialState = {};

const postsReducer = (state = initialState, action) => {
  const { folder, type } = action;

  if (folder !== 'posts') {
    return state;
  }

  switch (type) {
    ...

    case UPDATE_PROPS_BY_KEY:
      return updatePropsByKey(state, action);

    default:
      return state;
  }
};

export default postsReducer;

Example

// Redux Store slice data.
posts: {
  1: { id: 1, title: 'Post #1', category: 'category-1' },
  2: { id: 2, title: 'Post #2', category: 'category-2' }
}

// Component.
dispatch(
  updatePropsByKey({
    folder: 'posts',
    payload: {
      2: { id: 2, title: 'Post #2.1' }
      3: { id: 3, title: 'Post #3', category: 'category-3' }
    }
  })
)

// Result.
posts: {
  1: { id: 1, title: 'Post #1', category: 'category-1' },
  2: { id: 2, title: 'Post #2.1', category: 'category-2' },
  3: { id: 3, title: 'Post #3', category: 'category-3' }
}

resetReducer

Resets Store slice to initial state and default values.

Parameters

ParamTypeRequiredDefaultDescription
args.folderstringYes-Slice name in the Store.

Configuration

app/reducers/network.reducer.js

import { actionReducers, actionTypes } from '@rashedmakkouk/redux-toolkit';

const { resetReducer } = actionReducers;

const { RESET_REDUCER } = actionTypes;

const initialState = {
  isConnected: undefined,
  connectionType: undefined,
  secure: undefined
};

const networkReducer = (state = initialState, action) => {
  const { folder, type } = action;

  if (folder !== 'network') {
    return state;
  }

  switch (type) {
    ...

    case RESET_REDUCER:
      return resetReducer(initialState);

    default:
      return state;
  }
};

export default networkReducer;

Example

// Redux Store slice data.
network: {
  isConnected: true,
  connectionType: 'WiFi',
  secure: true
}

// Component.
dispatch(
  resetReducer({ folder: 'network' })
)

// Result.
network: {
  isConnected: undefined,
  connectionType: undefined,
  secure: undefined
}

resetStore

Dispatches action to reset and/or purge Store state.

Parameters

ParamTypeRequiredDefaultDescription
purgebooleanNofalseIf true, returns PURGE_STORE instead of the default RESET_STORE action type.

Configuration

app/reducers/index.js

Update your Redux Root Reducer function to listen to dispatched reset action:

import { actionTypes } from '@rashedmakkouk/redux-toolkit';

import postsReducer from './posts.reducer.js'

const { PURGE_STORE, RESET_STORE } = actionTypes;

const appReducer = combineReducers({
  // List of your app reducer functions.
  ...
  posts: postsReducer,
});

const rootReducer = (state, action) => {
  // Listen to specified action types to clear Store state.
  if (action.type === RESET_STORE || action.type === PURGE_STORE) {
    state = undefined;
  }

  return appReducer(state, action);
};

export default rootReducer;

Example

// Component.
dispatch(
  resetStore(true)
)

The reducer parameter passed to configureStore function supports composed slice reducers using combineReducers function.

redux-persist

If you are using redux-persist, you can use this Action Creator with the argument purge set to true to trigger the built-in Action Type used to clear persisted Redux Store state in local storage.

Caveats

Using shared Action Creators

When using shared Action Creators across multiple reducers, the payload will get processed in every reducer that has the action configured. See Restrict processing a dispatched action by Slice for required configuration.

Adding custom properties to Action Creators

Refactor your Action Creators to include two new properties: folder and subFolder. See Definitions section for details.

This allows for handling which dispatched actions being processed in which slice reducer.

Changelog

Check the Changelog for a detailed list of new features and changes.

Community

Head over to Discussions where you can ask questions, request new features or voice your ideas and suggestions.

Head over to Issues to report discovered bugs or code enhancement suggestions.

License

This package is available under the BSD 3-Clause license. It also includes external libraries that are available under a variety of licenses. See LICENSE for the full license text.

0.7.0

1 year ago

0.6.7

2 years ago

0.6.6

2 years ago

0.6.8

2 years ago

0.6.5

2 years ago

0.6.4

2 years ago

0.6.3

2 years ago

0.6.2

2 years ago

0.6.1

2 years ago

0.5.0

2 years ago