Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flux/React Complex Reusable Component

I want to do something like this

var App = React.createClass({
    render: function() {
        return (
            <CountryAutoComplete />
        )
    }
});

Different app

var App2 = React.createClass({
    render: function() {
        return (
            <CountryAutoComplete />
        )
    }
});

Here is a simple Autocomplete (Not the entire file)

var AutoComplete = React.createClass({
    componentDidMount: function() {
        $(this.getDOMNode()).typeahead(this.props);
    },
    render: function() {
        return (
            <input type="text" class="typeahead" onChange={this.props.onChange} />
        );
    }
});

The CountryAutoComplete would be something like this to be self contained.

var CountryAutoComplete = React.createClass({
    search: function(country, process) {
        // Make an ajax call and return the data. No other components needed
        $.ajax({
            url: '/country' + '?search=' + country
        }).then(process);
    },
    render: function() {
        return (
            <AutoComplete onChange={this.props.onChange} source={this.search} />
        );
    }
});

Based on the Flux docs, it looks like anything with an API call needs to go through the

actions -> API -> Dispatcher -> stores -> component

That makes the CountryAutoComplete tied to a specific app because the actions, Dispatcher and stores are specific to the App. What is the best way to make this component reusable across apps?

like image 602
Taylor Hakes Avatar asked Nov 07 '14 02:11

Taylor Hakes


1 Answers

You shouldn't be making any ajax calls in the autocomplete component (since you said you want to make it reusable). I usually put all the data request calls/api usage into a separate module that uses promises to prevent the multiple requests

So the idea then is to just have your autocomplete component get the options/data from a parent component. That parent component can get the data from store initially, and listen for any change events in that store, and update its state if needed. Pass that this.state.options (or whatever state you're using for the options) as a prop to AutoComplete. When a user types something, emit an action with the query. That action in turns should call the API and Dispatcher, updates the store, and emits a change event for the store. Your parent component will update its state respectively, and that will flow to the AutoComplete component as prop.

So something like this:

var App = React.createClass({
    getInitialState: function() {
        return {
            // default results/data?
            data : Store.getResults('')
        };
    },
    storeChangeListener: function(newData) {
        this.setState({
            data: newData
        });
    },
    componentDidMount: function() {
        this.listenTo(Store, this.storeChangeListener);
    },
    onChange: function(query) {
        // on search change
        Actions.getResults(query);
    },
    render: function() {
        return (
            <AutoComplete data={this.state.data} onChange={this.onChange} />
        );
    }
});

And in store, something like this:

var countryAPI = require('./countryAPI')
var Store = {
    getResults: function(query) {
        // check cache if any? otherwise make call
        if(this.cache[query]) {
            return this.cache[query];
        } else {
            countryAPI.search(query).then(this.update);
        }
    },
    update: function(data) {
        AppDispatcher.dispatch({
            type: "DATA_FROM_SERVER",
            payload: {id: query, data: result}
        })
    },
    handleDataFromServer: function(action) {
        //store into cache/store
        this.cache[action.payload.id] = action.payload.result;
        this.emit("change"); // re-render app on whoever is listening to this store
    }
}

and your api for example

var countryAPI = {
    search: function(query) {
        // check to make sure this promise isnt called before
        if(!this.allPromises[query]) {
            this.allPromises[query] = $.ajax({
                url: '/country' + '?search=' + country
            })
        }
        return this.allPromises[query];
    }
}

To sum it up, the actual API implementation imo should be separated from flux actions, they only should be concerned with Web-API specific stuff and just let flux actions/stores handle the responses separately as data flows:

Component --> Listens to Store
          --> Calls Load Action --> Show Pending State/Optimistic Updates --> Dispatcher --> Store --> changeEvent (Component will be listening and be updated)
          --> countryAPI.load() 
onLoadSuccess --> Dispatcher --> Store --> changeEvent --> Component
onLoadError   --> Dispatcher --> Store --> changeEvent --> Component
like image 111
trekforever Avatar answered Sep 20 '22 06:09

trekforever