React-redux: IoC and Redux

Created on 26 Jan 2018  路  2Comments  路  Source: reduxjs/react-redux

I'm fairly new to React/Redux, and I'm very very amazed with all of the amazing work you guys have all done. So thank you so much for the time you've put into this project.

I come from backend development and one very common pattern are IoC containers for configuring your app services at runtime to be used. One of the first things i noticed is that there is a lack of maybe documentation or even support for IoC with React/Redux. I wanted to create this issue as a discussion reviewing the different benefits/drawbacks and methods of implementing IoC within React/Redux apps.

The most common use case for IoC on the frontend for me is configuring an API client to interact with the backend API.

function ApiClient(axios) {
    return {
        createUser: (user) => {
            return axios.post('/users', user);
        }
    };
}

function MockApiClient(res) {
    return {
        createUser: (user) => {
            return Promise.resolve(res);
        }
    }
}

function createAppActions(apiClient) {
    return {
        createUser: (name) => {            
            return (dispatch, getState) => {
                dispatch({type: 'CREATE_USER_START', name: name});
                apiClient.createUser({name: name}).then((res) => {
                    dispatch({type: 'CREATE_USER_END', res: res});
                })
            }
        }
    }
}

const appActions = createAppActions(new ApiClient(axios));
const mockAppActions = createAppActions(new MockApiClient({id: 1}));

// in a test somewhere
dispatch(mockAppActions.createUser('Test'));

// we can now subscribe and verify the CREATE_USER_START and CREATE_USER_END were fired with the expected name and result.

Now typically in my apps, i'll use bottlejs to configure my services and actions, and then in my mapDispatchToProps, I access my global container to retrieve the configured actions which I can then be used in my components.

import {bindActionCreators} from 'redux';
import configuredBottle from './ioc'; // exports a configured service container

connect(null, (dispatch) => {
    return bindActionCreators(configuredBottle.container.appActions, dispatch);
})(SomeComponent);

Now, I know there are other ways of going about this, but I was curious on if there is a more idiomatic way to implement IoC in react/redux apps.

A few ways other ways to configure this would be to just have a specific js file used for creating the configured services in my app instead of using the container to inject the deps, but this would stubbing/mocking out the JS files with jest or something to implement testing, and I think it's overall a bit less flexible than using an IoC container, however it would get the job down.

I've also considered making a custom mapDispatchToPropsFactory that will inject a configured bottleJs container as a third argument, but not totally sure it's worth going down that route if IoC isn't something I should be even focusing on.

Most helpful comment

@ragboyjr : what I'd generally recommend is writing all logic as separate thunk action creators (or as using other side effect approaches, such as sagas and observables). From there, redux-thunk supports injecting an extra argument into thunk functions:

// configureStore.js
import thunkMiddleware from "redux-thunk";
import myApiClient from "./myApiClient";

const thunkWithMyApiInjected = thunkMiddleware.withExtraArgument({api : myApiClient});

const middlewareEnhancer = applyMiddleware(thunkWithMyApiInjected);
export default createStore(rootReducer, middlewareEnhancer);


// later
function thunkThatMakesApiCalls() {
    return (dispatch, getState, {api}) => }
        // use injected API object
    }
}

All 2 comments

I don't think we're going to be very prescriptive about how you choose to use our APIs. The base concept of a predictable state container is fairly novel and plugs into a lot of architectures. How that's done is really up to you.

If you're specifically concerned about testing, we have a lot of good documentation there: https://redux.js.org/docs/recipes/WritingTests.html And also our FAQ has expanded quite a bit recently: https://redux.js.org/docs/FAQ.html And I'd be remiss to not point out the Structuring Reducers docs, which can help with designing your actions as well: https://redux.js.org/docs/recipes/StructuringReducers.html

@ragboyjr : what I'd generally recommend is writing all logic as separate thunk action creators (or as using other side effect approaches, such as sagas and observables). From there, redux-thunk supports injecting an extra argument into thunk functions:

// configureStore.js
import thunkMiddleware from "redux-thunk";
import myApiClient from "./myApiClient";

const thunkWithMyApiInjected = thunkMiddleware.withExtraArgument({api : myApiClient});

const middlewareEnhancer = applyMiddleware(thunkWithMyApiInjected);
export default createStore(rootReducer, middlewareEnhancer);


// later
function thunkThatMakesApiCalls() {
    return (dispatch, getState, {api}) => }
        // use injected API object
    }
}
Was this page helpful?
0 / 5 - 0 ratings