mpen
mpen

Reputation: 282915

How to force an update from inside mapStateToProps?

I'm talking about the mapStateToProps argument of connect.

One of my components has a property called rules which is an array of functions. In mapStateToProps I invoke those rules, passing in the current state. The rules either return an error message or true if there is no error. I put these error messages into a new prop so that the final component receives an array of error messages instead of an array of functions.

So far this has been working great, but now I want to add support for asynchronous rules. My plan right now is that whenever a rule returns a Promise instead of an error string, I will push a message like "Checking..." into the array of error messages instead, but once the promise is settled I need to "refresh" this array and insert the resolved message.

I figure if I can force an update then redux will have to call mapStateToProps again. In such a case, all the rules would be re-evaluated, but if I'm not careful I would receive a new Promise which would put right back to square one. However, if I memoize these rules then I can get back the same Promise instance as before, and then check if it's settled and then put that value into my error array instead.

So now the only problem is figuring out how to get a reference to the component instance within the context of mapStateToProps which only has access to the current state and props.

Is there any way I can force a re-render once my Promise has resolved?

Alternatively, if I could dispatch an action when it's resolved, I could work with that as well.

Upvotes: 3

Views: 2019

Answers (2)

forrert
forrert

Reputation: 4219

How about creating a stateful component? If you keep the results from your promises in the component's state, it will automatically re-render the component once the promises resolve.

import React, {Component} from 'react';
import {connect} from 'react-redux;
import Promise from 'promise';

class MyStatefulComponent extends Component {
    constructor(props) {
        this.state = {
            messages: [] // initialize empty, or with the synchronous rules
        };
    }

    componentWillMount() {
        const setState = this.setState;
        const rules = this.props.rules; // rules contains a list of either evaluated rules or promises
        Promise.all(rules).then(results => {
            // this is executed once all the promises in rules have resolved
            // setState will trigger a re-render of this component
            setState({
                messages: [] // construct your array of messages here from the results
            });
        })
    }

    render() {
        // render your list of messages
        return <ul>
            {this.state.messages.map(message => <li>{message}</li>)}
        </ul>;
    }
}

export default connect(
    (state, ownProps) => ({
        rules: ownProps.rules.map(rule => rule(state))
    })
)(MyStatefulComponent);

Upvotes: 1

markerikson
markerikson

Reputation: 67489

You can't do that in mapState. The mapState function is intended to be totally synchronous. If you need to do something async, trigger that outside of mapState, store some values in state, and update them with the results to force mapState to re-run.

Upvotes: 2

Related Questions