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

@surglogs/with-dispatch-on-update

v1.2.2

Published

HOC that allows you to conditionally dispatch a Redux action

Downloads

419

Readme

withDispatchOnUpdate

Build Status

withDispatchOnUpdate is a Higher Order Component that allows you to conditionally dispatch a Redux action

Instalation

npm i @surglogs/with-dispatch-on-update

Import:

import withDispatchOnUpdate from '@surglogs/with-dispatch-on-update'

What is this library good for?

Often you need to dispatch an action in your component (usually to call API or navigate to other screen) when some situation arises (missing data, incoming props changed somehow etc.). The question is: where and how should you do this? We created a higher order component that helps to achieve that.

Example

Following the classic example app, we will create a component that fetches todos if necessary and show it to the user.

We need to tweak our Redux store a little bit to handle asynchronous actions. To achieve that we use Redux Promise Middleware. You can use it too if want, but you are encouraged to use any other solution, which allows you to get the pending state of API call.

Action

We will start by defining our action to fetch todos:

// action

import api from './api'

export const LOAD_TODOS = 'LOAD_TODOS'

export const loadTodos = () => {
  const promise = api.getTodos()

  return {
    type: LOAD_TODOS,
    payload: promise,
  }
}

Our payload is in form of a Promise - to be able to handle such an action with ease, we use the aforementioned Redux Promise Middleware.

Reducer

Next we move on to define our reducer:

//reducer

const initialState = {
  todos: null,
  pending: false,
}

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case LOAD_TODOS_PENDING:
      return { ...state, pending: true }
    case LOAD_TODOS_FULLFILLED:
      const { todos } = action.payload
      return { todos, pending: false }
    default:
      return state
  }
}

Redux Promise Middleware helps us here to distinguish between two states - API call was fired (that is the LOAD_TODOS_PENDING action) and API call returned successfully (LOAD_TODOS_FULLFILLED action).

As you can see, we are storing the pending state of the API call. It will come handy in the component itself.

Component

Finally, we will write our component:

// component

import { connect } from 'react-redux'
import { compose } from 'redux'

import TodoList from '../components/TodoList'

const shouldLoadTodos = (state, props) => {
  const todos = state.todos
  const isTodosRequestPending = state.pending

  return !todos && !isTodosRequestPending
}

const mapStateToProps = (state, props) => ({
  todos: state.todos,
})

const VisibleTodoList = compose(
  withDispatchOnUpdate({
    action: loadTodos,
    condition: shouldLoadTodos,
  }),
  connect(mapStateToProps),
)(TodoList)

export default VisibleTodoList

In connect(mapStateToProps) we get the todos from the store that we want to show to the user.

The fetching of todos is managed by our HOC withDispatchOnUpdate

withDispatchOnUpdate({
  action: loadTodos,
  condition: shouldLoadTodos,
})

We only need to provide the action that should be dispatched, and condition function that tells whether the action should be dispatched. Our condition function, which is here called shouldLoadTodos, gets both state and props and returns a Boolean. In our example, the condition is: todos do not exist in the store and the API call is not pending. Whenever this condition is fullfilled, the action loadTodos is dispatched.

Passing arguments to action

We will extend our previous example a little bit. Let's say our app now can have multiple todo lists (shopping list, bucket list, movies to watch etc.). User can choose a list and we need to fetch the right todos.

We have to adjust our action that fetches the todos:

export const loadTodos = listId => {
  const promise = api.getTodos(listId)

  return {
    type: LOAD_TODOS,
    payload: promise,
  }
}

Now we have to pass the listId to the action somehow. How to do that?

Passing argument via props

Let's pretend we passed the listId to the component. In that case we will adjust our data loading like this:

withDispatchOnUpdate({
  action: loadTodos,
  condition: shouldLoadTodos,
  args: ['listId']
}),

By writing args: ['listId'] we say that the first argument is a prop with name listId. That was pretty easy!

However if the listId was null or undefined, the action won't be dispatched. This is on purpose since you usually do not want to dispatch an action if some argument is missing - which usually happens when the value is null or undefined. If you don't want this behaviour you can pass a mapping function instead of a string - read on!

Alternatively, you can pass a function that maps the props to value:

const listIdMapper = (props) => props.listId

...

withDispatchOnUpdate({
  action: loadTodos,
  condition: shouldLoadTodos,
  args: [listIdMapper]
}),

It is up to you which method you prefer. Passing a string is shorter, but passing a function is more flexible - you can for example provide a default value.

You can of course pass more than one argument to the action. Take a look at one more example:

withDispatchOnUpdate({
  action: loadData,
  condition: shouldLoadData,
  args: [
    'a',
    (props) => props.b || [], // if there is no value, default to empty array
    'c'
  ]
}),

Passing argument from store

Now let's pretend our listId is stored in the store. We might get the prop using connect and then write withDispatchOnUpdate the same way as last time:

compose(
  connect((state, props) => ({
    listId: state.listId
  })),
  withDispatchOnUpdate({
    action: loadTodos,
    condition: shouldLoadTodos,
    args: ['listId']
  }),
  ...
)

However, there is shorter way. You can use connector, to specify the props that you need to get from the store:

compose(
  withDispatchOnUpdate({
    action: loadTodos,
    condition: shouldLoadTodos,
    args: ['listId'],
    connector: {
      listId: (state, props) => state.listId
    }
  }),
  ...
)

It is just a shortcut for using connect, therefore use whatever you like more.

Options

The provided action will be fired only if none of the arguments in the args array is null or undefined. If you want to prevent this, you can pass an optional param shouldRequireAllProps: false:

withDispatchOnUpdate({
  action: myAction,
  condition: myCondition,
  args: ['a', 'b', 'c'],
  shouldRequireAllProps: false
})

Now the action will be fired even if a, b or c prop is null or undefined and myCondition is satisfied.