Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example of implementation #4

Open
dougsmith1000 opened this issue Jun 8, 2017 · 8 comments
Open

Example of implementation #4

dougsmith1000 opened this issue Jun 8, 2017 · 8 comments

Comments

@dougsmith1000
Copy link

dougsmith1000 commented Jun 8, 2017

Hi Jeremy, could you post a version of this with a working example? The project I'm building is using React Router v4, which doesn't seem have syncHistory as a working function. I think I almost have this hooked up, but am getting caught up on the middleware when creating my store. I've posted an example below that forces the use of your taxi middleware regardless of environment.

I'm also getting an empty value when I instantiate ReduxTaxi(). Because I handle a universal js implementation a little differently, I'm trying to pass the reduxTaxi object around to different files without completely understanding what I am passing. These errors are in both my development and production environments. Please take a look if you get a chance. Thank you!

Here's the code - used from several boilerplates that I've modified to do what I want:

ssr.js:

// Node Modules
import fs from 'fs';
import {basename, join} from 'path';

// Libraries
import React from 'react';
import {renderToString} from 'react-dom/server';

// Redux
import createStore from 'universal/redux/createStore.js';
import createHistory from 'history/createMemoryHistory'

import { ReduxTaxi } from 'redux-taxi';

// Components
import Html from './Html.js';

function renderApp(url, res, store, assets, reduxTaxi) {
  const context = {};

  const html = renderToString(
    <Html
      title='Test Site'
      store={store}
      url={url}
      context={context}
      assets={assets}
      reduxTaxi={reduxTaxi} />
  );

  res.send('<!DOCTYPE html>'+html);
}

export const renderPage = function (req, res) {
  const reduxTaxi = ReduxTaxi();
  const history = createHistory( );
  const store  = createStore(history, reduxTaxi);

  const assets = require('../../build/assets.json');

  assets.manifest.text = fs.readFileSync(
    join(__dirname, '..', '..', 'build', basename(assets.manifest.js)),
    'utf-8'
  );

  renderApp(req.url, res, store, assets, {reduxTaxi});
};

export const renderDevPage = function (req, res) {
  const reduxTaxi = ReduxTaxi();
  const history =  createHistory( );
  const store   = createStore(history, reduxTaxi);
  renderApp(req.url, res, store, {reduxTaxi});
};

export default renderPage;

Html.js:

// Libraries
import React, { Component } from 'react';
import {StaticRouter} from 'react-router';
import {renderToString} from 'react-dom/server';
import PropTypes from 'prop-types';

// Redux
import { Provider } from 'react-redux';
import {ReduxTaxi, ReduxTaxiProvider} from 'redux-taxi';

import createStore from '../universal/redux/createStore'; // Your store configurator

import promise from 'es6-promise';
promise.polyfill();

import { I18nextProvider } from 'react-i18next';
import i18n from '../i18n.server';


class Html extends Component {
  static propTypes = {
    url: PropTypes.string.isRequired,
    store: PropTypes.object.isRequired,
    title: PropTypes.string.isRequired,
    assets: PropTypes.object
  }

  render () {
    const PROD = process.env.NODE_ENV === 'production';

    const {
      title,
      store,
      assets,
      url,
      context,
      reduxTaxi
    } = this.props;

    const {
      manifest,
      app,
      vendor
    } = assets || {};

    let state = store.getState();
    const initialState = `window.__INITIAL_STATE__ = ${JSON.stringify(state)}`;
    const Layout =  PROD ? require( '../../build/prerender.js') : () => {};

  const initialComponent = (
      <I18nextProvider i18n={ i18n }>
        <Provider store={store}>
          <ReduxTaxiProvider reduxTaxi={reduxTaxi}>
            <StaticRouter location={url} context={context}>
              <Layout />
            </StaticRouter>
          </ReduxTaxiProvider>
        </Provider>
      </I18nextProvider>
    );

    const root = PROD && renderToString(initialComponent);

//Does this have to be a different instantiation from what is passed in the props?
    const allPromises = reduxTaxi.getAllPromises();

    if (allPromises.length) {
        // If we have some promises, we need to delay server rendering
        promise
            .all(allPromises)
            .then(() => {
              return (
               <html>
                 <head>
                   <meta charSet="utf-8"/>
                   <title>{title}</title>
                   {PROD && <link rel="stylesheet" href="/static/prerender.css" type="text/css" />}
                 </head>
                 <body>
                   <script dangerouslySetInnerHTML={{__html: initialState}} />
                   {PROD ? <div id="root" dangerouslySetInnerHTML={{__html: root}}></div> : <div id="root"></div>}
                    {PROD && <script dangerouslySetInnerHTML={{__html: manifest.text}}/>}
                    {PROD && <script src={vendor.js}/>}
                   <script src={PROD ? app.js : '/static/app.js'} />
                 </body>
               </html>
              );
            })
            .catch(() => {
                return {"error": "An error occurred"}
            })
    } else {
        // otherwise, we can just respond with our rendered app
        return (
           <html>
             <head>
               <meta charSet="utf-8"/>
               <title>{title}</title>
               {PROD && <link rel="stylesheet" href="/static/prerender.css" type="text/css" />}
             </head>
             <body>
               <script dangerouslySetInnerHTML={{__html: initialState}} />
               {PROD ? <div id="root" dangerouslySetInnerHTML={{__html: root}}></div> : <div id="root"></div>}
                {PROD && <script dangerouslySetInnerHTML={{__html: manifest.text}}/>}
                {PROD && <script src={vendor.js}/>}
               <script src={PROD ? app.js : '/static/app.js'} />
             </body>
           </html>
          );
    }
}

export default Html;

createstore.js:

import {
  createStore,
  combineReducers,
  applyMiddleware,
  compose,
} from 'redux';

import {
  ConnectedRouter,
  routerReducer,
  routerMiddleware,
  syncHistory
} from 'react-router-redux';

import {
  ReduxTaxiMiddleware,
  PromiseMiddleware
} from 'redux-taxi';

import thunk from 'redux-thunk';

import {ReduxTaxi} from 'redux-taxi';


import * as Reducers from './reducers/index.js';

export default (history, instance) => {
  const middleware = [
    routerMiddleware(history),
    ReduxTaxiMiddleware(instance.reduxTaxi),
    PromiseMiddleware,
    thunk
  ];

  const store = compose(createStore(combineReducers({
    ...Reducers,
    router: routerReducer
  }), applyMiddleware(...middleware)));


  if (module.hot) {
    // Enable Webpack hot module replacement for reducers
     module.hot.accept('./reducers', () => {
       const nextReducers = require('./reducers/index.js');
       const rootReducer = combineReducers({
         ...nextReducers,
         router: routerReducer
       });

       store.replaceReducer(rootReducer);
     });
   }


  return store;
}
@dougsmith1000
Copy link
Author

I'm going to work on the md showing up and I'll repost. Sorry for the ugliness.

@tizmagik
Copy link
Contributor

@dougsmith1000 I haven't tested this in RRv4 but happy to look at an example repo or something to see how it might work. Maybe you can push up a repo that I can pull down and try it out on?

@jaredpalmer
Copy link

@tizmagik It works in RR4. Was using this for a while on another project

@tizmagik
Copy link
Contributor

Ah great, thanks @jaredpalmer !

Curious how you're finding the lib so far?

@jaredpalmer
Copy link

jaredpalmer commented Jul 24, 2017

This is a great lib, especially when paired with recompose. I no longer use Redux, but when I did this was my preferred way to go about SSR data.

@dougsmith1000
Copy link
Author

dougsmith1000 commented Jul 26, 2017

Hi @tizmagik, sorry for the silence. I was working on somethiing else for awhile and never got back to this. I cleaned up the markup above and am still curious if you can help me out on it. The error I get is around syncHistory, hence my question about it being absent from RRv4 and taxi's compatibility. I'm glad to hear that jaredpalmer has it working though. This library looks exactly like what I'm looking for for my project. Thanks again!

PS, today or later this week, I'll create a sample repo of my project structure if you're able to look into it.

@dougsmith1000 dougsmith1000 reopened this Jul 26, 2017
@tizmagik
Copy link
Contributor

Sounds good @dougsmith1000 -- maybe @jaredpalmer could lend a hand as well 😁

@jaredpalmer
Copy link

Happy to review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants