Ad

Server-side Rendering With Redux And Immutable.js Errors On MapStateToProp Contents

- 1 answer

I am currently working on server rendering with react v0.14, redux v3.0, immutable v3.7.6 but I've run into a few issues in making that happen. Whenever I go to a page of my app that contains mapStateToProps I receive an error in the console depending on the piece of the state that says something like Uncaught TypeError: e.dashboard.shoppingCart.get is not a function.

The dashboard.shoppingCart.get is the value for the state that I'm setting in the mapStateToProps and the .get() refers to the immutable.js method on Map. I'm not sure what seems to be causing this error and it kills any javascript in the app, making nothing work.

Server

import http from 'http';
import React from 'react';
import {renderToString} from 'react-dom/server';
import { match, RoutingContext } from 'react-router';
import {Provider} from 'react-redux';
import configureStore from './../common/store/store.js';

import fs from 'fs';
import { createPage, write, writeError, writeNotFound, redirect } from './server-utils.js';
import routes from './../common/routes/rootRoutes.js';

const PORT = process.env.PORT || 4000;



function renderApp(props, res) {
  var store = configureStore();
  var markup = renderToString(
    <Provider store={store}>
      <RoutingContext {...props}/>
    </Provider>
  );
  const initialState = store.getState();
  var html = createPage(markup, initialState);
  write(html, 'text/html', res);
}

http.createServer((req, res) => {

  if (req.url === '/favicon.ico') {
    write('haha', 'text/plain', res);
  }

  // serve JavaScript assets
  else if (/__build__/.test(req.url)) {
    fs.readFile(`.${req.url}`, (err, data) => {
      write(data, 'text/javaScript', res);
    })
  }

  // handle all other urls with React Router
  else {
    match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
      if (error)
        writeError('ERROR!', res);
      else if (redirectLocation)
        redirect(redirectLocation, res);
      else if (renderProps)
        renderApp(renderProps, res);
      else
        writeNotFound(res);
    });
  }

}).listen(PORT)
console.log(`listening on port ${PORT}`)

Client

import React from 'react';
import { match, Router } from 'react-router';
import { render } from 'react-dom';
import { createHistory } from 'history';
import routes from './../common/routes/rootRoutes.js';
import {Provider} from 'react-redux';
import configureStore from './../common/store/store.js';
import './../common/styles/main.scss';


const { pathname, search, hash } = window.location;
const location = `${pathname}${search}${hash}`;

const initialState = window.__INITIAL_STATE__;
const store = configureStore(initialState);



// calling `match` is simply for side effects of
// loading route/component code for the initial location
match({ routes, location }, () => {
  render(
    <Provider store={store}>
      <Router routes={routes} history={createHistory()} />
    </Provider>,
    document.getElementById('app')
  );
});

DashCart

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import DashCartItem from './DashCartItem.jsx';
import * as DashCartActions from './../../actionCreators/Dashboard/DashShoppingCart.js';

function DashCart (props) {

  var DashCartItemsList = props.cartSneakers.map((sneaker, key ) => {
    return <DashCartItem sneaker={sneaker} key={key} remove={props.actions.removeSneakerFromCart}></DashCartItem>;
  });

  var price = () => {
    var prices = [];
    props.cartSneakers.map((sneaker) => prices.push(sneaker.get('price')));
    var result = prices.reduce((sneakerOne, sneakerTwo) => sneakerOne + sneakerTwo, 0);
    return (result !== 0) ? 'Estimated Total: $' + result : 'Cart is Empty!';
  }

  var totalList = props.cartSneakers.map((sneaker, key) => {
    return <div key={key}><h4 className="sneaker">{sneaker.get('sneakerName')}</h4> <h4 className="price"> ${sneaker.get('price')}</h4></div>
  })

  var checkOut = () => props.actions.checkout(props.cartSneakers);

  return (
    <div className="DashCart">
      <div className="col-sm-9 segment nopadding">
        {DashCartItemsList}
      </div>
      <div className="col-sm-3 nopadding checkOut">
        <div className="panel panel-default">
          <div className="panel-heading">
            <h4 className="panel-title">Cart Estimated Subtotal</h4>
          </div>
            {totalList}
            <hr></hr>
            <h4 className="total">{price()}</h4>
            <h4 className="total"></h4>
          </div>
      </div>
      <button onClick={checkOut} className="checkout btn btn-default">CheckOut</button>
    </div>
  );
}

function mapStateToProps(state) {
  return {
    cartSneakers: state.dashboard.shoppingCart.get('cartSneakers')
  }
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(DashCartActions, dispatch)
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(DashCart);

Dashboard Reducer

    export const dashboardReducer = combineReducers({
      userSneakers: dashSharedReducer,
      shoppingCart: dashShoppingCartReducer,
      trades: dashTradesReducer,
      orderHistory: dashOrderHistoryReducer,
      events: dashEventsReducer,
      sneakerTracking: dashSneakerTrackingReducer,
      shippingInfo: dashShippingReducer,
      billingInfo: dashBillingReducer,
      accountInfo: dashAccountSettingsReducer
    });
    //ShoppingCart Reducer
    export function dashShoppingCartReducer (state = sample, action ) {
      switch (action.type) {

        case CHECKOUT:
          return handleCheckout(state, action.cartPayload);

        case REMOVE_SNEAKER_FROM_CART:
          return handleRemoveSneakerFromCart(state, action.sneakerToRemove);

        case RECEIVE_SNEAKERS_IN_CART:
          return handleReceiveSneakersInCart(state, action.cartSneakers);

        default:
          return state;
      }
    }
Ad

Answer

I was able to figure out the solution to my problem. The Server implementation for my app was automatically coercing the immutable data structures which was causing get() not to be recognized and throwing an error that halted all Javascript in the app.

To solve this you have to make sure that after calling getState() on both the server and client to convert the state to immutable data structures before passing it into configureStore(). To do this simply add the the following line into server and client files:

var state = I.fromJS(initialState) and pass that into the store configureStore(state).

Ad
source: stackoverflow.com
Ad