Should I store function references in Redux store?

Solution 1:

No, you should not store function references in the redux store. They are not serializable, and as you mentioned state should be serializable at all times. The most redux-friendly approach I can think of is just to keep the map of hotkeys to their actionCreatorFuncNames.

Solution 2:

You don't. The store state must be serializable at all times (as Nathan answered). The Redux way is via enhancers, or the Redux-Observable way via dependencies.

Based on the Redux docs example, what you want is to pass the reference in your action (1), ignore it in your reducer (2) and use it in your enhancer (3):

//... in your action:
const data={val:1}, ref=()=>{};
const action = {type:'ACTION_WITH_REF', data, ref}; //(1)

//... in your reducer:
   case 'ACTION_WITH_REF':
   return {...state, data: action.data}; //(2)

//... and in your enhancer:
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
export const myRefStore= {};
 
function refHandler({ getState }) {
  return next => action => {
    switch(action.type){ 
       // this can be done more elegantly with a redux-observable
       case 'ACTION_WITH_REF':
         myRefStore.aRef = action.ref; // (3)
         break;
     }
    // be sure to maintain the chain of the store
    const returnValue = next(action);   
    // otherwise, your midddeware will break the store
    return returnValue;
  };
} 

const store = createStore(
  reducers,
  initialState,
  applyMiddleware(refHandler)
);

Note: As far as there are no side effects in your enhancers, you are good to go. Be aware that you could have obtained the refs directly in the reducers, but such an approach keeps the reference at the reducer level and misses the point of combineReducers(). With an enhancer, you keep them all in one place(myRefStore).

One final observation is that a redux store is not an any-data store but a state store, thus why we need to handle functions and other non-state-related stuff in enhancers. You can leverage the enhancer backbone to Redux-Observable and inject myRefStore via dependencies.

Solution 3:

I'm new to redux, but the way I see it, you could pass the key code and an action type. Then a reducer could be listening for that action type and make changes accordingly.

Here is an example using the library Mousetrap:

// On your Container
function registerShortcut(element, dispatch, keyCode, actionType) {
  Mousetrap(element).bind(keyCode, function(e) {
    dispatch({
      type: actionType,
      payload: {
        keyCode: keyCode,
        event: e
      }
    });
  });
});

mapDispatchToProps = function(dispatch) {
  return {
    onMount: function(element) {
      registerShortcut(element, dispatch, ['command+f', 'ctrl+f'], 'OPEN_SEARCH');
    },
    onUnmount: function(element) {
      Mousetrap(element).unbind(['command+f', 'ctrl+f']);
    }
  };
};


// On your Component
componentDidMount() {
  onMount(ReactDOM.findDOMNode(this));
};

componentWillUnmount() {
  onUnmount(ReactDOM.findDOMNode(this));
};



// On your reducer
function reducer(oldState, action)  {
  if (action.type == 'OPEN_SEARCH') {
    //... make changes ...//
    return newState;
  }
  return oldState;
};

This way, keyboard shortcuts will dispatch an action. The reducer will make the changes necessary to the state. And finally, the application can re-render.