How to handle action dispatching for a nested React Redux component

Viewed 412

I'm doing something like this for my UI component in a React Redux app:

// Outer.js
import Inner from './path'

export const Outer = () => {
    return (
        <div>
            ...
            <Inner />
            ...
        </div>
    )
}
// Inner.js
const param = ''
export const Inner = () => {
    return (
        <div>
            <TextField 
                input={param}
                onChange = {(param) => {
                    Function(param)
                }}
            />
        </div>
    )
}

I also set up a Container component for Outer.js:

// OuterContainer.js
import Outer from './path'

const mapStateToProps = (state) => {
    paramToBeUpdated: ???
}

const mapDispatchToProps = (dispatch) => {
    Function: (param) => dispatch(Function(param))
}

export default connect(mapStateToProps, mapDispatchToProps)(Outer)

My action created for this step:

action/index.js
export const Function = (param) => (dispatch, getState) => {
    dispatch({ type: 'FUNCTION', param })
}

And my reducer included the following function:

// reducer.js
export default reducer = (state="", action) => {
    switch(action.type) {
        case 'FUNCTION':
            return {
                ...state,
                param: action.param
            }
        ...
    }
}

I'm trying to update the variable paramToBeUpdated's value from the Inner UI component. But it didn't work.

Can Inner and Outer components share a container component connected with Outer?

How should I do it without making too much changes to my current setup? Is it possible to avoid creating a new Inner container, which will basically be a copy of the Outer container?

2 Answers

If you can't connect Inner with the state value and or the action then you must have done something wrong, here is a working example:

const { Provider, connect } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { produce } = immer;

const initialState = {
  value: '',
};
//action types
const CHANGE = 'CHANGE';
//action creators

const change = (value) => ({
  type: CHANGE,
  payload: value,
});
const reducer = (state, { type, payload }) => {
  if (type === CHANGE) {
    return produce(state, (draft) => {
      draft.value = payload;
    });
  }
  return state;
};
//creating store with redux dev tools
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  reducer,
  initialState,
  composeEnhancers(
    applyMiddleware(() => (next) => (action) =>
      next(action)
    )
  )
);
//components
const Inner = connect((state) => state.value, {
  Function: change,
})(({ Function, value }) => {
  return (
    <input
      type="text"
      value={value}
      onChange={(e) => Function(e.target.value)}
    />
  );
});

const Outer = () => {
  return (
    <div>
      <Inner />
    </div>
  );
};
ReactDOM.render(
  <Provider store={store}>
    <Outer />
  </Provider>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://unpkg.com/immer@7.0.5/dist/immer.umd.production.min.js"></script>
<div id="root"></div>

Only Outer is connected to the redux store. If you want to dispatch an action from Inner you may do:

  1. Connect Inner to the redux store
// Inner.js
const Inner = (props) => {
    return (
        <div>
            <TextField 
                input={param}
                onChange = {(param) => {
                    props.Function(param)
                }}
            />
        </div>
    )
}
export default connect(null, mapDispatchToProps)(Inner)

no need to create any InnerContainer

  1. Pass dispatch function from Outer (+ no need for Container)
// Outer.js
import Inner from './path'

export const Outer = (props) => {
    return (
        <div>
            ...
            <Inner Function={props.Function} />
            ...
        </div>
    )
}

const mapStateToProps = (state) => {
    paramToBeUpdated: ???
}

const mapStateToProps = (dispatch) => {
    Function: (param) => dispatch(Function(param))
}

export default connect(mapStateToProps, mapDispatchToProps)(Outer)
// Inner.js
const Inner = (props) => {
    return (
        <div>
            <TextField 
                input={param}
                onChange = {(param) => {
                    props.Function(param)
                }}
            />
        </div>
    )
}
Related