Martin Dawson
Martin Dawson

Reputation: 7656

Redux Accessing correct item in object list in mapStateToProps

Here is the problem I am having. I have to pass id={this.props.id} to each component so that I can get the correct player from the list of players in mapStateToProps by looking at ownProps.

  1. These AudioPlayer components are going to be provided by the developer so I don't want them having to pass in the id like I am doing currently to every component.
  2. I can't just use React's context because mapStateToProps doesn't expose the context due to it being experimental.
  3. I know I can just do React.CloneElement in each of the components (GUI, Media etc) but this seems meh as well.

My question is:

How can I get the correct player out of the list without having to pass the id to each of my components like in the code below?

If mapStateToProps accepted a context param then I could just get the id from the context in each of the components and pass it down once.

Is their a better way of doing this?

AudioPlayers that the developer defines:

class AudioPlayer extends React.Component {
    render() {
        return (
            <Player className="jp-default" id={this.props.id}>
                <Gui id={this.props.id}>
                    <Media id={this.props.id}>
                        <Audio id={this.props.id}>                           
                        </Audio>
                    </Media>
                    <div className="poster-container">
                        <Poster id={this.props.id}/>
                        <Title id={this.props.id}/>
                    </div>
                </Gui>             
            </Player>
        );
    }
}

AudioPlayer.options = {
    id: "audio-player",
    paused: true,
    //...etc
};

class AudioPlayerTwo extends React.Component {
    render() {
        return (
            <Player className="jp-default-two" id={this.props.id}>
                <Gui id={this.props.id}>
                    <Media id={this.props.id}>
                        <Audio id={this.props.id}>                        
                        </Audio>
                    </Media>
                    <div className="poster-container">
                        <Poster id={this.props.id}/>
                        <Title id={this.props.id}/>
                    </div>
                </Gui>             
            </Player>
        );
    }
}

AudioPlayerTwo.options = {
    id: "audio-player-two,
    paused: true,
    //...etc
};

createPlayers([AudioPlayer, AudioPlayerTwo]);

Code that is going to be an npm package:

export createPlayers (WrappedPlayers) => {
    const store = createStore(combineReducers({players: playerReducer}));

    ReactDOM.render(
    <Provider store={store}>
        <div>
            {WrappedPlayers.map(WrappedPlayer => {
                const ConnectedPlayer = connect(mapStateToProps, mapDispatchToProps)(WrappedPlayer);

                <ConnectedPlayer key={WrappedPlayer.options.id} id={WrappedPlayer.options.id} />
            })}
        </div>
    </Provider>,
    document.getElementById("app"));
}

actions:

export const play = (id, time) => ({
    type: actionTypes.player.PLAY,
    time,
    id
});
...etc

reducer:

const play = (state, action) => {
    if(state.srcSet) {
        return updateObject(state, {
            paused: false,
            newTime: !isNaN(action.time) ? action.time : state.currentTime 
        });
    }
}

export default (state={}, action) => {
    //Only update the correct player
    const currentPlayer = state[action.id];
    let newState = {...state};

    switch (action.type) {; 
        case actionTypes.player.PLAY: 
            newState = play(currentPlayer, action);
            break;
        default:
            return state;
    }

    return updateObject(state, {
        [action.id]: newState
    });
}

All of my components look similar to this: Notice how I access the correct player from the list with the props id.

const mapStateToProps = (state, props) => ({
    paused: state.players[props.id].paused,
    id: props.id
});

const Play = (props) => {
    const onPlayClick = () => props.paused ? props.dispatch(play(props.id)) : props.dispatch(pause(props.id))
    return <a onClick={onPlayClick}>{props.children}</a>
}

export default connect(mapStateToProps)(Play);

Upvotes: 0

Views: 634

Answers (1)

Jim Bolla
Jim Bolla

Reputation: 8295

You can solve this in userland by making your own wrapper on connect + getContext (from recompose) and use that in your app instead of vanilla connect. It'd look something like

import { connect } from 'react-redux'
import { compose, getContext } from 'recompose'
export default function connectWithMyId(...args) {
  return compose(
    getContext({ myId: PropTypes.string }),
    connect(...args)
  )
}

And then myId would be available as a prop in your mapStateToProps.

Upvotes: 4

Related Questions