在MapStateToProp内容上使用redux和immutable.js错误进行服务器端渲染(Server-side rendering with redux and immutable.js errors on MapStateToProp contents)

我目前正在使用react v0.14 , redux v3.0 , immutable v3.7.6进行服务器渲染,但是我遇到了一些问题。 每当我转到包含mapStateToProps应用程序页面时,我会在控制台中收到错误,具体取决于显示类似Uncaught TypeError: e.dashboard.shoppingCart.get is not a function的状态Uncaught TypeError: e.dashboard.shoppingCart.get is not a function 。

dashboard.shoppingCart.get是我在mapStateToProps设置的状态的值,而.get()是指Map上的immutable.js方法。 我不确定是什么导致这个错误,它会杀死应用程序中的任何javascript,没有任何效果。

服务器

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}`)

客户

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);

仪表板减速机

    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;
      }
    }

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;
      }
    }

                

最满意答案

我能够找出问题的解决方案。 我的应用程序的服务器实现自动强制immutable数据结构,导致get()无法被识别,并引发错误,暂停应用程序中的所有Javascript。

要解决这个问题,您必须确保在服务器和客户端上调用getState()之后将getState()转换为不可变数据结构,然后再将其传递给configureStore() 。 为此,只需将以下行添加到服务器和客户端文件中:

var state = I.fromJS(initialState)并将其传递到存储configureStore(state) 。

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).

更多推荐