Leif John Leif John - 7 months ago 244
Javascript Question

How to properly initialize React app with redux in Cordova - mapStateToProps not called

I have a game pilot implemented with React + Redux, and it works just fine in a regular browser (web version).

When running with Cordova (which is the end goal), it stops right after the reducers have processed data (a token from the server). The state change is not being passed on to the mapStateToProps method in the connected container. In the web version it works like a charm.

It seems like the "connect" has not been fulfilled... somehow. The mapStateToProps is not called after the reducers have finished their work, but this only happens when running in Cordova!

I suspect the problem starts all the way in the startup of the app. I have studied and searched for variants of the mandatory wait for the 'deviceready' event. This is my index.jsx code:

import React from "react";
import ReactDOM from "react-dom";
import {Provider} from "react-redux";
import {Router, Route, IndexRoute, useRouterHistory, hashHistory} from "react-router";
import {createHashHistory} from "history";
import App from "./components/app";
import Game from "./components/game";
import Frontpage from "./components/frontpage";
import {store} from "./store";

function startApp() {
ReactDOM.render(
<Provider store={store}>
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute component={Frontpage}/>
<Route path="game" component={Game}/>
</Route>
</Router>
</Provider>
, document.querySelector('.my-container')
);
}

const url = document.URL;
const isSmart = (url.indexOf("http://") === -1 && url.indexOf("https://") === -1);
const isRipple = (url.indexOf(":3000") !== -1);

if (isSmart || isRipple) {
document.addEventListener('deviceready', startApp, false);
} else {
startApp();

}


The "troublesome" container looks like this (frontpage.js):

import React, {Component} from "react";
import {connect} from "react-redux";
import * as actions from "../actions";
import {hashHistory} from "react-router";

class Frontpage extends Component {

componentWillMount() {
if (this.props.token) {
hashHistory.push("/game");
} else {
this.props.fetchToken(this.props.handleMessageFromChannel);
}
}

componentWillUpdate(nextProps) {
if (nextProps.token) {
hashHistory.push("/game");
}
}

render() {
return <div className="frontpage"></div>
}
}

function mapStateToProps(state) {
return { token: state.token };
}

export default connect(mapStateToProps, actions)(Frontpage);


And the store is defined in store.js:

import reducers from "./reducers/index";
import ReduxPromise from "redux-promise";
import {createStore, applyMiddleware} from "redux";

export const store = applyMiddleware(ReduxPromise)(createStore)(reducers);


The token reducer in file token_reducer.js:

import {FETCH_TOKEN} from "../actions/types";

export default function (state = null, action) {
switch (action.type) {
case FETCH_TOKEN:
return action.payload.data.token;
}

return state;
}


From Google and SO searches, I couldn't find anybody with the same problem.
I will gladly post more files if it is relevant...?

Answer

The error was somewhere else...

One of the other reducers got stuck - or failed - and caused the processing of the action to stop. There were no warnings or errors in the console, so it was tricky to pinpoint the problem.

Why the different behaviour on Cordova? The other reducer creates an EventSource object (for receiving Server Sent Events) and per "school book patterns" I created it so it would use the original protocol, be it https or http:

const eventSource = new EventSource("//192.168.1.2:8080/api/channel...);
eventSource.onmessage = function(message) {
  store.dispatch(handleMessageFromChannel(message.data))
};

But this will not play well on Cordova, since the protocol of the loaded JavaScript is file://, and it is obviously wrong to establish the EventSource towards the device's file system.

The correct code should be:

const eventSource = new EventSource("http://192.168.1.2:8080/api/channel...);
eventSource.onmessage = function(message) {
  store.dispatch(handleMessageFromChannel(message.data))
};

...which for production will be a proper URL and preferrably https.

Comments