Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Connecting nested Redux smart components with reducers

Similar to the ToDo example for Redux, my project structure is similar - just a container with an array of child components to display. The store would look something like the following:

{
    workspace: {
        widgets: [1, 2, 3]
    }
    widgets: {
        1: {
            id: 1,
            title: 'First Widget',
            lastPrice: 123.324,
            lastUpdate: '2015-11-12'
        },
        2: {
            id: 2,
            title: 'Second Widget',
            lastPrice: 1.624,
            lastUpdate: '2015-11-12'
        },
        3: {
            id: 3,
            title: 'Third Widget',
            lastPrice: 4.345,
            lastUpdate: '2015-11-12'
        }
    }
}

Each widget item is a complex component in it's own right - lots of possible actions, many properties, and many child dumb components. Hence, I've made each widget a connected smart component with a dedicated WidgetReducer to keep those state changes separate.

I'd like the WidgetReducer to be concerned with just one widget at a time, so the state handled by it would just be a single widget node like:

{   id: 3,
    title: 'Third Widget',
    lastPrice: 4.345,
    lastUpdate: '2015-11-12'
}

My Workspace component currently iterates over all Widgets, passing the Widget ID to each Widget smart component:

@connect(state => ({ workspace: state.workspace }))
export default class Workspace extends React.Component {
    render() {
        return (
            <div>
                {this.props.workspace.widgets.map(id =>
                    (<Widget key={id} widgetId={id} />)
                )}
            </div>
        );
    }
}

My Widget component is like so:

@connect((state, ownProps) => ({ widget: state.widgets[ownProps.widgetId]}))
export default class Widget extends React.Component {
    componentWillMount() {
        this.props.dispatch(subscribeToUpdates(this.props.widgetId));
    }

    render() {
        return (
            <div>
                <header>{this.props.widget.title}</header>
                <div> /* Other widget stuff goes here */</div>
            </div>
        );
    }
}

How can I connect each Widget component so it receives only state updates on it's own Widget object, and the connected Widget Reducer is only concerned with individual Widget objects, rather than a collection of them?

like image 566
LJW Avatar asked Nov 16 '15 05:11

LJW


1 Answers

First of all take a look at Reselect: https://github.com/rackt/reselect

If I understand the question, this is what you are looking for. Let's say the state of widget-id 2 changes, widget 1 and widget 3 don't care the state of widget-id 2 changed. Only widget 2 needs to reflect that change. Reselect gives you the power to build 'memoized' select functions. This may sound like a fancy word, but in practice it means that only if the input value of a selector changes, it'll calculate the next state.

In the widget example, you would build select functions which only recalculate the state if the slice of state it's concerned with has changed.

Possible example:

import { createSelector } from 'reselect'

const widgetByIdSelector = (state, props) => state.widgets[props.widgetId]
// this is the `input` function for you selector: as input we take the   
// specific slice of state a specific widgetId requests.
// the main idea is: only if the `input` of this function changes, it'll 'reselect' that state.

export const widgetSelector = createSelector(
   widgetByIdSelector,
    (widgetById) => { 
         return { widgetById } 
   }

@connect(widgetSelector)
export default class Widget extends Component { ... }

I have no idea if this code will work, wrote this on a train ride with shaky internet connection, but this is the principal idea...

As for a reducer & action creators (you'll probably doing something like this):

export function createActionForSpecificWidgetById(widgetId, payload) {
    return {
       type: ACTIONS.HERE_COMES_YOUR_ACTION_TYPE,
       widgetId,
       payload
    }
}

function reducer(state = initialState, action) {
  switch(action.type) {
  case HERE_COMES_YOUR_ACTION_TYPE:
      //now you can modify the state of the specific widgetId because you have access to the id at action.widgetId
}

Is this what you were looking for?

like image 159
Seneca Avatar answered Nov 09 '22 22:11

Seneca