GithubHelp home page GithubHelp logo

elierotenberg / react-prepare Goto Github PK

View Code? Open in Web Editor NEW
100.0 5.0 16.0 105 KB

Prepare you app state for async server-side rendering and more!

JavaScript 100.00%
react redux universal server-side-rendering async-await

react-prepare's Introduction

react-prepare

react-prepare allows you to have you deeply nested components with asynchronous dependencies, and have everything just work with server-side rendering.

The typical use-case is when a deeply-nested component needs to have a resource fetched from a remote HTTP server, such as GraphQL or REST API. Since renderToString is synchronous, when you call it on your app, this component won't render correctly.

One solution is to have a central router at the root of your application that knows exactly what data needs to be fetched before rendering. But this solution doesn't fit the component-based architecture of a typical React app. You want to declare data dependencies at the component level, much like your declare your props.

This is exactly what react-prepare does: it allows you to declare asynchronous dependencies at the component level, and make them work fine with server-side rendering as well as client-side rendering.

react-prepare is agnostic and can be used vanilla, but it comes with a tiny helper that makes it extremely easy to use along redux and react-redux (see examples below).

Example with react-redux

Let's assume you have defined an async action creator fetchTodoItems(userName) which performs HTTP request to your server to retrieve the todo items for a given user and stores the result in your redux state.

Your TodoList component definition would look like this:

import { dispatched } from 'react-prepare';
import { connect } from 'react-redux';
import { compose } from 'redux';

import { fetchTodoItems } from './actions';

const enhance = compose(
  dispatched(({ userName }, dispatch) => dispatch(fetchTodoItems(userName))),
  connect(({ todoItems }) => ({ items: todoItems }),
);

const TodoList = ({ items }) => <ul>{items.map((item, key) =>
  <li key={key}>{item}</li>
</ul>}</ul>;

export default enhance(TodoList);

And your server-side rendering code would look like this:

import { renderToString } from 'react-dom/server';
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { Provider } from 'react-redux';
import prepare from 'react-prepare';

import reducer from './reducer';

async function serverSideRender(userName) {
  const store = createStore(reducer, applyMiddleware(thunkMiddleware));
  const app = <Provider store={store}>
    <TodoList userName={userName} />
  </Provider>;
  await prepare(app);
  return {
    html: renderToString(app),
    state: store.getState(),
  };
}

Your client could re-use the data fetched during server-side rendering directly, eg. assuming your injected it in window.__APP_STATE__:

const store = createStore(reducer, JSON.parse(window.__APP_STATE__));
render(<Provider store={store}>
  <TodoList userName={userName} />
</Provider>, document.getElementById('app'));

For a complete example of a fully-functional app using react-prepare in conjunction with redux, see the react-prepare-todo repository.

API

dispatched(sideEffect: async(props, dispatch), opts)(Component)

Helper to use prepared more simply if your side effects consists mostly of dispatching redux actions.

In the body of the sideEffect function, you can use the dispatch function to dispatch redux actions, typically requesting data from an asynchronous source (API server, etc.). For example, let's assume you have defined an async action creator fetchTodoItems(userName) that fetches the todo-items from a REST API, and that you are defining a component with a userName prop. To decorate your component, your code would look like:

class TodoItems extends React.PureComponent { ... }

const DispatchedTodoItems = dispatched(
  ({ userName }, dispatch) => dispatch(fetchTodoItems(userName))
)(TodoItems);

The decorated component will have the following behavior:

  • when server-side rendered using prepare, sideEffect will be run and awaited before the component is rendered; if sideEffect throws, prepare will also throw.
  • when client-side rendered, sideEffect will be called on componentDidMount and componentWillReceiveProps.

opts is an optional configuration object passed directly to the underlying prepared decorator (see below).

prepared(sideEffect: async(props, context), opts)(Component)

Decorates Component so that when prepare is called, sideEffect is called (and awaited) before continuing the rendering traversal.

Available opts is an optional configuration object:

  • opts.pure (default: true): the decorated component extends PureComponent instead of Component.
  • opts.componentDidMount (default: true): on the client, sideEffect is called when the component is mounted.
  • opts.componentWillReceiveProps (default: true): on the client, sideEffect is called again whenever the component receive props.

async prepare(Element)

Recursively traverses the element rendering tree and awaits the side effects of components decorated with prepared (or dispatched). It should be used (and await-ed) before calling renderToString on the server. If any of the side effects throws, prepare will also throw.

Notes

react-prepare tries hard to avoid object keys conflicts, but since React isn't very friendly with Symbol, it uses a special key for its internal use. The single polluted key in the components key namespace is @__REACT_PREPARE__@, which shouldn't be an issue.

react-prepare's People

Contributors

edvinerikson avatar elierotenberg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

react-prepare's Issues

Status of the project?

@elierotenberg I was actually going to start writing a similar library myself for my own needs when I stumbled across your library. What is the status of this project?

Dispatched not working

Hey,

I just found this project, and would really like to use it in my private project.
However I have some trouble with the API.

What is the difference between prepared and dispatched.

The documentation says:
Helper to use prepared more simply if your side effects consists mostly of dispatching redux actions.

As far as I understand this just the arguments of the callback should be different.
However only the prepared decorator works for me.

It seems that the nextPrepare function doesn't get the second parameter:
https://github.com/elierotenberg/react-prepare/blob/master/src/dispatched.js#L5

Somehow in the prepared module the function prepare is only called with one argument.
https://github.com/elierotenberg/react-prepare/blob/master/src/prepared.js#L17
https://github.com/elierotenberg/react-prepare/blob/master/src/prepared.js#L23

To give you some feedback.
I think the props in the prepared decorator are enough, because for me they include the
dispatch function.
Also an option to only call the callback function once would be very nice.
I ran into the problem to dispatch an action which updated my store and reran the callback function
which resulted in a loop.

isReactCompositeComponent checking for render function

Hi @elierotenberg,
I am using your library. It's working great.
Recently I've integrated react-formsy and my app is crashing. I narrowed the problem and think that it is in your isReactCompositeComponent function.
Checking if the prototype has a render function is failing.
Do you know whats going on there?
I Would really appreciate some help.

Andres

Syntax error in README example section

Is this part of the example considered typo?

export default dispatched({ userName }, (dispatch) => dispatch(fetchTodoItems(userName)))(
  connect(({ todoItems }) => ({ items: todoItems })(
    TodoList,
  ),
);

I think it should be

export default dispatched(({ userName }, dispatch) => dispatch(fetchTodoItems(userName)))(
  connect(({ todoItems }) => ({ items: todoItems })(
    TodoList,
  ),
);

Also, I suggest making this part clearer by extracting the connect part into a individual variable before wrapping the dispatched function.

const Component = connect(({ todoItems }) => ({ items: todoItems })(TodoList);

export default dispatched(
 ({ userName }, dispatch) => dispatch(fetchTodoItems(userName))
)(Component);

Compatibility with React Router v4

Hi @elierotenberg, thank you for open sourcing this library! I noticed that the library seems quite agnostic to router, but wondering if you've tried or think it would be compatible with React Router v4?

In general, what would the basic requirements from the router side be for this library to be able to perform its functionality?

Universal application for server and client

Hi @elierotenberg,

Thanks for creating this library. The approach so far is very matching with my current requirement. I tried redial, react-async, react-async-connect and other libs, but none of them keeps up with the changing spec from react or react-router team that make it so painful to update the library.

I have a small problem with this one. I try to call prepare on the server side to pre-render the data, and then load it to the store but the dispatched method is called again in the client side.

For example,

I have a todo component

export const Todos = ({ todos, actions }: { todos: TodoType[], actions: Object }) => (
  <div className="container">
    <div className="row">
      <TodosHeader />
      <TodosAdd addTodo={actions.addTodo} />
      <TodosBody
        todos={todos}
        removeTodo={actions.removeTodo}
        completeTodo={actions.completeTodo}
      />
      <TodosFooter />
    </div>
  </div>
);

export const enhance = compose(
  dispatched(
    (props, dispatch) => dispatch(fetchTodos())
  ),
  connect(
    state => ({
      todos: selectors.getTodos(state),
    }),
    dispatch => ({
      actions: bindActionCreators({
        addTodo,
        removeTodo,
        completeTodo,
      }, dispatch),
    })
  ),
  onlyUpdateForKeys(['todos'])
);

export default enhance(Todos);

And I tried to use it like this in the server:

            const currentRoutes = <RouterContext {...renderProps} />;
            const component = (<App store={store} routes={currentRoutes} />);

            prepare(component)
              .then(() => {
                const prerenderComponent = renderToString(component);
                const prerenderData = store.getState();

                ctx.render(template, {
                  ...parameters,
                  prerenderComponent,
                  prerenderData,
                }).then(resolve).catch(reject);
              });

The server is able to call the fetchTodos() and return it to the client, but it triggers another request to server after the client mount again. Is there any way that I could resolve this problem? (I don't want it to be called again in the client if it doesn't need to)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.