<|start_filename|>src/getRoutesProps.js<|end_filename|>
export default function getRoutesProps(routes) {
  return routes.reduce((previous, route) => {
    // eslint-disable-next-line no-unused-vars
    const { childRoutes, indexRoute, ...rest } = route;

    return {
      ...previous,
      ...rest,
    };
  }, {});
}


<|start_filename|>tests/getAllComponents.test.js<|end_filename|>
import expect from 'expect';
import React from 'react';
import { Route, IndexRoute, match } from 'react-router';

import getAllComponents from '../src/getAllComponents';

describe('getAllComponents', () => {
  it('should return the correct length of route components', () => {
    const MockComponent = () => (
      <div></div>
    );
    const MockNamed1Component = () => (
      <div></div>
    );
    const MockNamed2Component = () => (
      <div></div>
    );

    const routes = (
      <Route path="/" component={MockComponent} myprop={'top'}>
        <IndexRoute component={MockComponent} myprop={'index'} />
        <Route
          path="user"
          components={{ name1: MockNamed1Component, named2: MockNamed2Component }}
        />
      </Route>
    );

    match({ routes, location: '/' }, (error, redirectLocation, renderProps) => {
      expect(getAllComponents(renderProps.components).length).toEqual(2);
    });

    match({ routes, location: '/user' }, (error, redirectLocation, renderProps) => {
      expect(getAllComponents(renderProps.components).length).toEqual(3);
    });
  });

  it('should manage a single component correctly', () => {
    const MockComponent = () => (
      <div></div>
    );
    expect(getAllComponents(MockComponent).length).toEqual(1);
  });
});


<|start_filename|>tests/util/getRoutePath.test.js<|end_filename|>
import expect from 'expect';

import getRoutePath from '../../src/util/getRoutePath';

describe('getRoutePath', () => {
  const routes = [
    {
      path: '/',
      component: function App() {},
    },
    {
      path: 'dashboard',
      component: function Dashboard() {},
    },
    {
      path: 'widget/:widgetName',
      component: function Widget() {},
    },
  ];

  it('Throws an Error when the route cannot be found among the matched routes', () => {
    expect(() => getRoutePath({}, routes)).toThrow('`route` not found in `routes`');
  });

  it('Gives gives the path up to the matched route', () => {
    expect(getRoutePath(routes[0], routes)).toBe('/');
    expect(getRoutePath(routes[1], routes)).toBe('//dashboard');
    expect(getRoutePath(routes[2], routes)).toBe('//dashboard/widget/:widgetName');
  });

  it('appends the provided `name` to the path', () => {
    const name = 'testing';
    expect(getRoutePath(routes[1], routes, name)).toBe(`//dashboard>${name}`);
  });
});


<|start_filename|>src/getAllComponents.js<|end_filename|>
export default function getAllComponents(components) {
  const arr = Array.isArray(components) ? components : [components];
  const result = [];
  arr.forEach(component => {
    if (typeof component === 'object') {
      Object.keys(component).forEach(key => result.push(component[key]));
    } else {
      result.push(component);
    }
  });
  return result;
}


<|start_filename|>examples/simple-with-scroll/server.js<|end_filename|>
import koa from 'koa';
import nunjucks from 'nunjucks';

import render from './render/server';
import routes from './routes';

require('./devServer');

nunjucks.configure(__dirname);

const server = koa();
server.use(function *() {
  try {
    const result = yield render(this.url, routes);
    this.body = nunjucks.render('template.html', {
      ...result,
      redialProps: JSON.stringify(result.redialProps),
    });
  } catch (e) {
    if (e) {
      this.status = 500;
      console.error(e);
    } else {
      this.status = 404;
    }
  }
});
server.listen(3000);
console.log('Server started on http://localhost:3000');


<|start_filename|>examples/named-components/components/Footer.js<|end_filename|>
import React, { Component } from 'react';
import { provideHooks } from 'redial';

class Footer extends Component {
  render() {
    return (
      <div>
        <span>Copyright © {this.props.year}</span>
        <span>Made with ❤ by people</span>
        <span>Additional stuff. You know. Because we can.</span>
      </div>
    );
  }
}

const enhance = provideHooks({
  fetch: ({ setProps, getProps, force }) => new Promise((resolve) => {
    const { year } = getProps();
    if (!year || force) {
      setTimeout(() => {
        setProps({
          year: new Date().getFullYear(),
        });
        resolve();
      }, 250);
    } else {
      resolve();
    }
  }),
});

export default enhance(Footer);


<|start_filename|>examples/redux/redux/actions/fetchGithubUser.js<|end_filename|>
import fetch from 'isomorphic-fetch';

export default function fetchGithubUser(userId) {
    return (dispatch) => {
      return fetch(`https://api.github.com/users/${userId}`)
        .then((response) => response.json())
        .then((user) => dispatch({
          type: 'USER_LOADED',
          payload: user,
          meta: {
            id: userId.toLowerCase(),
          }
        }))
    }
}


<|start_filename|>src/createMap.js<|end_filename|>
export default function createMap(possibleKeys, allValues) {
  const keys = [];
  const values = [];

  const set = (index, value) => {
    const i = keys.indexOf(index);
    if (i > -1) {
      values[i] = value;
    } else {
      keys.push(index);
      values.push(value);
    }
  };

  const size = () => keys.length;

  const get = (index) => values[keys.indexOf(index)];

  const dehydrate = (allPossibleKeys) => allPossibleKeys.map((key) => get(key));

  const rehydrate = (allPossibleKeys, state) => {
    state.forEach((value, index) => {
      if (value) {
        set(allPossibleKeys[index], value);
      }
    });
  };

  if (possibleKeys && allValues) {
    rehydrate(possibleKeys, allValues);
  }

  return {
    set,
    get,
    size,
    dehydrate,
    rehydrate,
  };
}


<|start_filename|>src/RedialContext.js<|end_filename|>
/* global __REDIAL_PROPS__ */

import React, { Component } from 'react';
import PropTypes from 'prop-types';

import triggerHooks from './triggerHooks';
import createMap from './createMap';
import createMapKeys from './util/mapKeys';
import getAllComponents from './getAllComponents';

function hydrate(renderProps) {
  if (typeof __REDIAL_PROPS__ !== 'undefined' && Array.isArray(__REDIAL_PROPS__)) {
    const getMapKeyForComponent = createMapKeys(renderProps.routes, renderProps.components);
    const components = getAllComponents(renderProps.components);
    const componentKeys = components.map(getMapKeyForComponent);
    return createMap(componentKeys, __REDIAL_PROPS__);
  }

  return createMap();
}

export default class RedialContext extends Component {
  static displayName = 'RedialContext';

  static propTypes = {
    children: PropTypes.node.isRequired,

    // RouterContext default
    renderProps: PropTypes.object.isRequired,

    // Custom
    locals: PropTypes.object,
    beforeTransition: PropTypes.array,
    afterTransition: PropTypes.array,
    parallel: PropTypes.bool,
    initialLoading: PropTypes.func,
    onError: PropTypes.func,
    onAborted: PropTypes.func,
    onStarted: PropTypes.func,
    onCompleted: PropTypes.func,

    // Server
    redialMap: PropTypes.object,
  };

  static defaultProps = {
    beforeTransition: [],
    afterTransition: [],
    parallel: false,

    onError(err, { beforeTransition }) {
      if (process.env.NODE_ENV !== 'production') {
        const type = beforeTransition ? 'beforeTransition' : 'afterTransition';
        console.error(type, err);
      }
    },

    onAborted(becauseError) {
      if (process.env.NODE_ENV !== 'production') {
        if (becauseError) {
          console.warn('Loading was aborted from an error');
        } else {
          console.warn('Loading was aborted manually');
        }
      }
    },

    onStarted(force) {
      if (process.env.NODE_ENV !== 'production') {
        console.info('Loading started. Force:', force);
      }
    },

    onCompleted(type) {
      if (process.env.NODE_ENV !== 'production') {
        console.info('Loading completed. Type:', type);
      }
    },
  };

  static childContextTypes = {
    redialContext: PropTypes.object,
  };

  constructor(props, context) {
    super(props, context);
    this.state = {
      loading: false,
      afterTransitionLoading: false,
      aborted: () => false,
      abort: () => {},
      prevRenderProps: undefined,
      redialMap: props.redialMap || hydrate(props.renderProps),
      initial: props.beforeTransition.length > 0,
    };
    this.completed = {
      beforeTransition: false,
      afterTransition: false,
      error: null,
    };
  }

  getChildContext() {
    const { loading, afterTransitionLoading, redialMap } = this.state;
    return {
      redialContext: {
        loading,
        afterTransitionLoading,
        redialMap,
        reloadComponent: (component) => {
          this.reloadComponent(component);
        },
        abortLoading: () => {
          this.abort();
        },
      },
    };
  }

  componentDidMount() {
    this.load(this.props.renderProps.components, this.props.renderProps);
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.renderProps.location === this.props.renderProps.location) {
      return;
    }

    this.load(nextProps.renderProps.components, nextProps.renderProps);
  }

  componentWillUnmount() {
    this.unmounted = true;
  }

  reloadComponent(component) {
    this.load(component, this.props.renderProps, true);
  }

  abort(becauseError, abort) {
    // Make sure we only cancel if it is the correct ongoing request
    if (!abort || this.state.abort === abort) {
      // We need to be in a loading state for it to make sense
      // to abort something
      if (this.state.loading || this.state.afterTransitionLoading) {
        this.state.abort();

        this.setState({
          loading: false,
          afterTransitionLoading: false,
        });

        if (this.props.onAborted) {
          this.props.onAborted(becauseError);
        }
      }
    }
  }

  load(components, renderProps, force = false) {
    let isAborted = false;
    const abort = () => {
      isAborted = true;
    };
    const aborted = () => isAborted;

    const bail = () => {
      const currentLocation = this.props.renderProps.location;
      if (aborted()) {
        return 'aborted';
      } else if (currentLocation !== renderProps.location) {
        return 'location-changed';
      }

      return false;
    };

    if (this.props.onStarted) {
      this.props.onStarted(force);
    }

    this.completed.beforeTransition = false;

    this.setState({
      aborted,
      abort,
      loading: true,
      prevRenderProps: this.state.loading || this.state.aborted()
        ? this.state.prevRenderProps
        : this.props.renderProps,
    });

    this.runBeforeTransition(
      this.props.beforeTransition,
      components,
      renderProps,
      force,
      bail
    )
    .catch((error) => {
      let afterTransition = false;
      if (error && error.afterTransition !== undefined) {
        afterTransition = error.afterTransition;
        error = error.error; // eslint-disable-line
      }

      this.props.onError(error, {
        reason: bail() || 'other',
        // If not defined before it's a beforeTransition error
        beforeTransition: !afterTransition,
        router: this.props.renderProps.router,
        abort: () => this.abort(true, abort),
      });
    });

    if (this.props.parallel) {
      this.runAfterTransition(
        this.props.afterTransition,
        components,
        renderProps,
        force,
        bail
      )
      .then(() => {
        if (this.completed.afterTransition) {
          this.completed.afterTransition = false;
          this.props.onCompleted('afterTransition');
        }
      })
      .catch((err) => {
        // We will only propagate this error if beforeTransition have been completed
        // This because the beforeTransition error is more critical
        const error = () => this.props.onError(err, {
          reason: bail() || 'other',
          beforeTransition: false,
          router: this.props.renderProps.router,
          abort: () => this.abort(true, abort),
        });

        if (this.completed.beforeTransition) {
          error();
        } else {
          this.completed.error = error;
        }
      });
    }
  }

  runAfterTransition(hooks, components, renderProps, force = false, bail) {
    // Get afterTransition data, will not block route transitions
    this.completed.afterTransition = false;
    this.completed.error = null;

    this.setState({
      afterTransitionLoading: true,
    });

    return triggerHooks({
      hooks,
      components,
      renderProps,
      redialMap: this.state.redialMap,
      locals: this.props.locals,
      force,
      bail,
    }).then(({ redialMap }) => {
      this.completed.afterTransition = true;
      this.setState({
        afterTransitionLoading: false,
        redialMap,
      });
    });
  }

  runBeforeTransition(hooks, components, renderProps, force = false, bail) {
    const completeRouteTransition = (redialMap) => {
      if (!bail() && !this.unmounted) {
        this.setState({
          loading: false,
          redialMap,
          prevRenderProps: undefined,
          initial: false,
        });

        this.props.onCompleted('beforeTransition');

        this.completed.beforeTransition = true;

        // Start afterTransition if we are not in parallel
        if (!this.props.parallel) {
          return this.runAfterTransition(
            this.props.afterTransition,
            components,
            renderProps,
            force,
            bail
          )
          .then(() => {
            this.props.onCompleted('afterTransition');
          })
          .catch((error) => Promise.reject({ error, afterTransition: true }));
        } else if (this.completed.afterTransition) {
          this.completed.afterTransition = false;
          this.props.onCompleted('afterTransition');
        } else if (this.completed.error) {
          this.completed.error();
        }
      }

      return Promise.resolve();
    };

    return triggerHooks({
      hooks,
      components,
      renderProps,
      redialMap: this.state.redialMap,
      locals: this.props.locals,
      force,
      bail,
    }).then(({ redialMap }) =>
      completeRouteTransition(redialMap)
    );
  }

  render() {
    if (this.props.initialLoading && this.state.initial && this.state.redialMap.size() === 0) {
      return this.props.initialLoading();
    }

    const props = (this.state.loading || this.state.aborted()) && this.state.prevRenderProps;
    if (props) {
      /* eslint-disable no-unused-vars */
      // Omit `createElement`. Otherwise we might skip `renderRouteContext` in `applyMiddleware`.
      const { createElement, ...prevProps } = props;
      /* eslint-enable no-unused-vars */
      return React.cloneElement(
        this.props.children,
        prevProps,
      );
    }

    return this.props.children;
  }
}


<|start_filename|>tests/getRoutesProps.test.js<|end_filename|>
import expect from 'expect';
import React from 'react';
import { Route, IndexRoute, match } from 'react-router';

import getRoutesProps from '../src/getRoutesProps';

describe('getRoutesProps', () => {
  it('should return the parameters from a React Router route object', () => {
    const MockComponent = () => (
      <div></div>
    );
    const routes = (
      <Route path="/" component={MockComponent} myprop={'top'}>
        <IndexRoute component={MockComponent} myprop={'index'} />
        <Route path="user" component={MockComponent} myprop={'user'} otherprop={200} />
      </Route>
    );
    match({ routes, location: '/' }, (error, redirectLocation, renderProps) => {
      expect(getRoutesProps(renderProps.routes)).toEqual({
        component: MockComponent,
        path: '/',
        myprop: 'index',
      });
    });

    match({ routes, location: '/user' }, (error, redirectLocation, renderProps) => {
      expect(getRoutesProps(renderProps.routes)).toEqual({
        component: MockComponent,
        path: 'user',
        myprop: 'user',
        otherprop: 200,
      });
    });
  });
});


<|start_filename|>examples/named-components/components/App.js<|end_filename|>
import React, { Component } from 'react';
import { Link, IndexLink } from 'react-router';

export default class App extends Component {
  render() {
    const { loading } = this.props;
    const style = {
      opacity: loading ? 0.5 : 1,
      transition: loading ? 'opacity 250ms ease 300ms' : 'false',
    };

    return (
      <div style={style}>
        <h1>React Router Redial Example</h1>
        <ul>
          <li>
            <IndexLink to="/">Start</IndexLink>
          </li>
          <li>
            <Link to="/named">Named components</Link>
          </li>
        </ul>
        {this.props.children}
      </div>
    );
  }
}


<|start_filename|>examples/named-components/components/Header.js<|end_filename|>
import React, { Component } from 'react';
import { Link } from 'react-router';
import { provideHooks } from 'redial';

@provideHooks({
  fetch: ({ setProps, getProps, force }) => new Promise((resolve) => {
    const { color } = getProps();
    if (!color || force) {
      setTimeout(() => {
        const getValue = () => Math.round(Math.random() * 255);
        setProps({ color: `rgb(${getValue()}, ${getValue()}, ${getValue()})` });
        resolve();
      }, 1000);
    } else {
      resolve();
    }
  }),
})
export default class Header extends Component {
  render() {
    return (
      <div>
        <h2 style={{ color: this.props.color }}>I am but a simple header, doing my thing.</h2>
        <Link to="/named/with-children">Checkout more stuff!</Link>
      </div>
    );
  }
}


<|start_filename|>src/util/getRoutePath.js<|end_filename|>
export default function getRoutePath(route, routes, name) {
  const matchIndex = routes.indexOf(route);
  if (matchIndex < 0) {
    throw new Error('`route` not found in `routes`');
  }
  const routesUntil = routes.slice(0, matchIndex + 1);
  let routePath = routesUntil
    .reduce((acc, { path }) => acc.concat(path), [])
    .join('/');
  if (name) {
    routePath = `${routePath}>${name}`;
  }
  return routePath;
}


<|start_filename|>examples/named-components/render/server.js<|end_filename|>
import { triggerHooks, useRedial } from '../../../lib/index';

import { renderToString } from 'react-dom/server';
import { createMemoryHistory, match, applyRouterMiddleware } from 'react-router';

// Render the app server-side for a given path:
export default (path, routes) => new Promise((resolve, reject) => {
  // Set up history for router:
  const history = createMemoryHistory(path);

  // Match routes based on history object:
  match({ routes, history }, (error, redirectLocation, renderProps) => {
    if (error || !renderProps) {
      return reject(error);
    }

    // Wait for async data fetching to complete, then render:
    return triggerHooks({
      renderProps,
      hooks: ['fetch', 'done'],
    }).then(({ redialMap, redialProps }) => {
      const html = renderToString(applyRouterMiddleware(useRedial({ redialMap }))(renderProps));
      resolve({ html, redialProps });
    })
    .catch(reject);
  });
});


<|start_filename|>tests/triggerHooks.test.js<|end_filename|>
import expect, { createSpy } from 'expect';
import React from 'react';
import { Route, IndexRoute, match } from 'react-router';
import { provideHooks } from 'redial';

import getRoutesProps from '../src/getRoutesProps';
import triggerHooks from '../src/triggerHooks';

describe('triggerHooks', () => {
  let renderProps;
  const fetchSpy = createSpy();
  const deferSpy = createSpy();
  const doneSpy = createSpy();

  beforeEach(() => {
    const MockComponent = () => <div></div>;
    const DecoratedMockComponent = provideHooks({
      fetch: fetchSpy,
      defer: deferSpy,
      done: doneSpy,
    })(() => <div></div>);
    const routes = (
      <Route path="/" component={MockComponent} myprop={'top'}>
        <IndexRoute component={DecoratedMockComponent} myprop={'index'} />
        <Route path="user" component={DecoratedMockComponent} myprop={'user'} otherprop={200} />
      </Route>
    );
    match({ routes, location: '/' }, (error, redirectLocation, reactRouterRenderProps) => {
      renderProps = reactRouterRenderProps;
    });
  });

  afterEach(() => {
    fetchSpy.reset();
    deferSpy.reset();
    doneSpy.reset();
  });

  it('should only call hooks listed', () => {
    const hooks = ['fetch'];
    return triggerHooks({
      hooks,
      renderProps,
    }).then(() => {
      expect(fetchSpy).toHaveBeenCalled();
      expect(deferSpy).toNotHaveBeenCalled();
      expect(doneSpy).toNotHaveBeenCalled();
    });
  });

  it('call hooks with the correct default locals', () => {
    const hooks = ['fetch'];
    return triggerHooks({
      hooks,
      renderProps,
    }).then(() => {
      const locals = fetchSpy.calls[0].arguments[0];
      expect(locals.location).toEqual(renderProps.location);
      expect(locals.params).toEqual(renderProps.params);
      expect(locals.setProps).toBeA('function');
      expect(locals.getProps).toBeA('function');
      expect(locals.force).toBe(false);
      expect(locals.routeProps).toEqual(getRoutesProps(renderProps.routes));
    });
  });

  it('call hooks with the additional locals', () => {
    const hooks = ['fetch'];
    const extraLocals = {
      a: 1,
      b: 2,
    };
    return triggerHooks({
      hooks,
      renderProps,
      locals: extraLocals,
    }).then(() => {
      const locals = fetchSpy.calls[0].arguments[0];
      expect(locals.a).toBe(1);
      expect(locals.b).toBe(2);
    });
  });

  it('should only call in the correct order', () => {
    fetchSpy.andCall(({ setProps }) => {
      setProps({ a: 1 });
    });
    deferSpy.andCall(({ setProps }) => new Promise((resolve) => {
      setTimeout(() => {
        setProps({ b: 2 });
        resolve();
      }, 10);
    }));
    doneSpy.andCall(({ getProps }) => {
      expect(getProps()).toEqual({ a: 1, b: 2 });
    });

    const hooks = [['fetch', 'defer'], 'done'];
    return triggerHooks({
      hooks,
      renderProps,
    }).then(() => {
      expect(fetchSpy).toHaveBeenCalled();
      expect(deferSpy).toHaveBeenCalled();
      expect(doneSpy).toHaveBeenCalled();
    });
  });

  it('should return empty object when using getProps for the first time', () => {
    fetchSpy.andCall(({ getProps }) => {
      expect(getProps()).toEqual({});
    });

    const hooks = ['fetch'];
    return triggerHooks({
      hooks,
      renderProps,
    });
  });

  it('should merge multiple calls to setProps for the same component', () => {
    fetchSpy.andCall(({ setProps }) => {
      setProps({ a: 1, b: 2 });
    });

    deferSpy.andCall(({ setProps, getProps }) => {
      setProps({ b: 3, c: 4 });
      expect(getProps()).toEqual({ a: 1, b: 3, c: 4 });
    });

    const hooks = ['fetch', 'defer'];
    return triggerHooks({
      hooks,
      renderProps,
    });
  });

  it('should resolve with an initialized RedialMap', () => {
    fetchSpy.andCall(({ setProps }) => {
      setProps({ a: 1, b: 2 });
    });

    deferSpy.andCall(({ setProps }) => {
      setProps({ b: 3, c: 4 });
    });

    const hooks = ['fetch', 'defer'];
    return triggerHooks({
      hooks,
      renderProps,
    }).then(({ redialMap, redialProps }) => {
      expect(redialMap.size()).toBe(1);
      expect(redialProps).toEqual([undefined, { a: 1, b: 3, c: 4 }]);
    });
  });

  it('should throw if setProps does not recive a plain object', () => {
    fetchSpy.andCall(({ setProps }) => {
      setProps(2);
    });
    const hooks = ['fetch'];
    return triggerHooks({
      hooks,
      renderProps,
    }).catch((err) => {
      expect(err).toBeA(Error);
    });
  });

  it('should break the chain if bail is defined and not false', () => {
    const hooks = ['fetch'];
    const bail = () => 'Was aborted for some reason';
    return triggerHooks({
      hooks,
      renderProps,
      bail,
    }).catch((err) => {
      expect(err).toEqual(new Error(bail()));
    });
  });

  it('should break reject the chain if it was aborted after all was completed', () => {
    let aborted = false;
    doneSpy.andCall(() => {
      aborted = true;
    });
    fetchSpy.andCall(() => {});
    const hooks = ['fetch', 'done'];
    const bail = () => aborted && 'Was aborted for some reason';
    return triggerHooks({
      hooks,
      renderProps,
      bail,
    }).catch((err) => {
      expect(fetchSpy.calls.length).toEqual(1);
      expect(doneSpy.calls.length).toEqual(1);
      expect(err).toEqual(new Error(bail()));
    });
  });
});


<|start_filename|>examples/named-components/routes.js<|end_filename|>
import React from 'react';
import { Route, IndexRoute } from 'react-router';

import App from './components/App';
import Footer from './components/Footer';
import Head from './components/Header';
import Index from './components/Index';
import LoremIpsum from './components/LoremIpsum';
import Main from './components/Main';
import NamedContainer from './components/NamedContainer';

export default (
  <Route path="/" component={App}>
    <IndexRoute component={Index} />
    <Route component={NamedContainer}>
      <Route path="named" components={{ header: Head, footer: Footer, main: Main }}>
        <Route path="with-children" component={LoremIpsum} />
      </Route>
    </Route>
  </Route>
);


<|start_filename|>tests/util/findRouteByComponent.test.js<|end_filename|>
import expect from 'expect';

import findRouteByComponent from '../../src/util/findRouteByComponent';

function toMatchedComponents(routes) {
  return routes.map(route => route.component || route.components);
}

describe('findRouteByComponent', () => {
  const component = () => {};
  it('Returns an empty object for empty routes', () => {
    expect(findRouteByComponent(component, [], [])).toEqual({});
  });
  it('Returns an empty object when the component cannot be found among the routes', () => {
    const routes = [
      {
        component: function notThisOne() {},
      },
      {
        component: function andNotThisOnceEither() {},
      },
    ];
    expect(findRouteByComponent(component, routes, toMatchedComponents(routes))).toEqual({});
  });
  it('Returns the first matched route', () => {
    const routes = [
      {
        component: function notThisOne() {},
      },
      {
        component,
      },
    ];
    expect(findRouteByComponent(component, routes, toMatchedComponents(routes))).toEqual({
      route: routes[1],
    });
  });
  it('Handles `components` for named routes', () => {
    const routes = [
      {
        component: function ARouteHandler() {},
      },
      {
        components: {
          a: function AScreen() {},
          b: function BScreen() {},
          c: component,
          d: function DScreen() {},
        },
      },
    ];
    expect(findRouteByComponent(component, routes, toMatchedComponents(routes))).toEqual({
      route: routes[1],
      name: 'c',
    });
  });
});


<|start_filename|>tests/createMap.test.js<|end_filename|>
import expect from 'expect';

import createMap from '../src/createMap';

describe('createMap', () => {
  let map;

  beforeEach(() => {
    map = createMap();
  });

  describe('constructor', () => {
    it('should create Initialized map from constructor', () => {
      const keys = ['a', 'b', 'c', 'd'];
      const values = [4, undefined, 2, 1];
      const newMap = createMap(keys, values);
      expect(newMap.get('a')).toEqual(4);
      expect(newMap.get('b')).toEqual(undefined);
      expect(newMap.get('c')).toEqual(2);
      expect(newMap.get('d')).toEqual(1);
    });
  });

  describe('size', () => {
    it('should correctly report the size of the map', () => {
      expect(map.size()).toBe(0);
      map.set('a', {});
      expect(map.size()).toBe(1);
    });
  });

  describe('get', () => {
    it('should return undefined for empty key', () => {
      expect(map.get('a')).toEqual(undefined);
    });

    it('should return overwrite previous value for the same key', () => {
      map.set('a', { a: 1, b: 2 });
      map.set('a', { a: 0, c: 3 });
      expect(map.get('a')).toEqual({ a: 0, c: 3 });
    });
  });

  describe('dehydrate', () => {
    it('should return array of values in the same orders as the keys', () => {
      map.set('a', 1);
      map.set('b', 2);
      map.set('c', undefined);
      map.set('d', 4);
      expect(map.dehydrate(['d', 'c', 'b', 'a'])).toEqual([4, undefined, 2, 1]);
    });
  });

  describe('rehydrate', () => {
    it('should correctly rehydrate the map', () => {
      const keys = ['a', 'b', 'c', 'd'];
      const values = [4, undefined, 2, 1];
      map.rehydrate(keys, values);
      expect(map.get('a')).toEqual(4);
      expect(map.get('b')).toEqual(undefined);
      expect(map.get('c')).toEqual(2);
      expect(map.get('d')).toEqual(1);
    });
  });
});


<|start_filename|>examples/named-components/render/client.js<|end_filename|>
import { useRedial } from 'react-router-redial';

import React from 'react';
import { render } from 'react-dom';
import { Router, browserHistory, applyRouterMiddleware } from 'react-router';

// Render the app client-side to a given container element:
export default (container, routes) => {
  const forcePageReloadOnError = true;
  const goBackOnError = false;

  // Function that can be used as a setting for useRedial
  function onError(err, { abort, beforeTransition, reason, router }) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(reason, err);
    }

    // We only what to do this if it was a blocking hook that failed
    if (beforeTransition) {
      if (forcePageReloadOnError && reason === 'other') {
          window.location.reload();
        } else if (goBackOnError && reason !== 'location-changed') {
          router.goBack();
        }
      // Abort current loading automatically
      abort();
    }
  }

  const component = (
    <Router
      history={browserHistory}
      routes={routes}
      render={applyRouterMiddleware(useRedial({
        beforeTransition: ['fetch'],
        afterTransition: ['defer', 'done'],
        parallel: true,
        initialLoading: () => <div>Loading…</div>,
        onError,
      }))}
    />
  );

  // Render app to container element:
  render(component, container);
};


<|start_filename|>src/useRedial.js<|end_filename|>
import React from 'react';

import RedialContext from './RedialContext';
import RedialContainer from './RedialContainer';

export default function useRedial(options) {
  return {
    renderRouterContext: (child, props) => (
      <RedialContext renderProps={props} {...options}>
        {child}
      </RedialContext>
    ),
    /* eslint-disable react/prop-types */
    renderRouteComponent: (child, props) => (
      <RedialContainer routerProps={props}>
        {child}
      </RedialContainer>
    ),
    /* eslint-enable react/prop-types */
  };
}


<|start_filename|>examples/simple-with-scroll/components/App.js<|end_filename|>
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Link, IndexLink } from 'react-router';

export default class App extends Component {
  static propTypes = {
    loading: PropTypes.bool,
    children: PropTypes.node.isRequired,
  };

  render() {
    const { loading } = this.props;
    let style = {
      paddingTop: '2em',
    };
    if (loading) {
      style = {
        ...style,
        opacity: 0.5,
        transition: 'opacity 250ms ease 300ms',
      };
    }

    return (
      <div style={style}>
        <ul style={{ position: 'fixed', margin: 0, padding: '1em', top: 0, left: 0, right: 0, display: 'flex', justifyContent: 'space-around', background: 'white', borderBottom: '1px solid #a7a7a7' }}>
          <li>
            <IndexLink to="/">Start</IndexLink>
          </li>
          <li>
            <Link to="/long-one">Long page</Link>
          </li>
          <li>
            <Link to="/another-long">Another long page</Link>
          </li>
        </ul>
        <h1>React Router Redial Example</h1>
        {this.props.children}
      </div>
    );
  }
}


<|start_filename|>examples/redux/webpack.config.js<|end_filename|>
const path = require('path');
const webpack = require('webpack');

module.exports = {

  devtool: 'source-map',

  entry: ['babel-polyfill', path.join(__dirname, 'client.js')],

  output: {
    path: path.join(__dirname, 'build'),
    filename: 'bundle.js',
    publicPath: '/build/'
  },

  resolve: {
    alias: {
      'react-router-redial': path.join(__dirname, '..', '..', 'src', 'index.js')
    }
  },

  module: {
    loaders: [
      { test: /\.js$/, exclude: /node_modules/, loader: require.resolve('babel-loader') }
    ]
  },

};


<|start_filename|>examples/named-components/client.js<|end_filename|>
import render from './render/client';
import routes from './routes';

render(document.getElementById('application'), routes);


<|start_filename|>examples/simple/components/User.js<|end_filename|>
import React, { Component } from 'react';
import { provideHooks } from 'redial';
import fetch from 'isomorphic-fetch';

@provideHooks({
  fetch: ({ params, setProps, getProps }) => {
    if (!getProps().user || getProps().user.login !== params.id) {
      return fetch(`https://api.github.com/users/${params.id}`)
        .then((response) => response.json())
        .then((user) => setProps({ user }))
    }
  }
})
export default class User extends Component {
  render() {
    return (
      <div>
        <h3>{ this.props.user.name } <pre>@{ this.props.user.login }</pre></h3>
        <img src={ this.props.user.avatar_url } />
      </div>
    );
  }
}


<|start_filename|>examples/simple/components/App.js<|end_filename|>
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Link, IndexLink } from 'react-router';

export default class App extends Component {
  static propTypes = {
    loading: PropTypes.bool,
    children: PropTypes.node.isRequired,
  };

  render() {
    const { loading } = this.props;
    let style;
    if (loading) {
      style = {
        opacity: 0.5,
        transition: 'opacity 250ms ease 300ms',
      };
    }

    return (
      <div style={style}>
        <h1>React Router Redial Example</h1>
        <ul>
          <li>
            <IndexLink to="/">Start</IndexLink>
          </li>
          <li>
            <Link to="/github">Github</Link>
          </li>
          <li>
            <Link to="/fetch">Fetch, with client error</Link>
          </li>
          <li>
            <Link to="/defer">Defer, with client error</Link>
          </li>
        </ul>
        {this.props.children}
      </div>
    );
  }
}


<|start_filename|>examples/simple/routes.js<|end_filename|>
import React from 'react';
import { Router, Route, IndexRoute } from 'react-router'

import App from './components/App';
import Index from './components/Index';
import Github from './components/Github';
import User from './components/User';
import Fetch from './components/Fetch';
import Defer from './components/Defer';

export default (
  <Route path="/" component={App}>
    <IndexRoute component={Index}/>
    <Route path="github" component={Github}>
       <Route path="user/:id" component={User} />
    </Route>
    <Route path="fetch" component={Fetch} />
    <Route path="defer" component={Defer} />
  </Route>
)


<|start_filename|>examples/redux/components/Index.js<|end_filename|>
import React, { Component } from 'react';
import { provideHooks } from 'redial';

@provideHooks({
  fetch: ({ setProps, getProps, force, isAborted }) => new Promise((resolve) => {
    const { color } = getProps();
    if(!color || force) {
      setTimeout(() => {
        if (isAborted()) {
          console.log('We should ignore this');
          return resolve();
        } else {
          console.log('Completed fine')
        }
        const getValue = () => Math.round(Math.random() * 255);
        setProps({color: `rgb(${getValue()}, ${getValue()}, ${getValue()})`});
        resolve();
      }, 2000);
    } else {
      resolve();
    }
  }),
  defer: ({ setProps, getProps, force }) => {
    const { data } = getProps();
    if(!data || force) {
      // Will be available as this.props.data on the component
      setProps({ data: 'Client data' })
    }
  },
  deferDone: () => console.log('deferDone'),
  blockingDone: () => console.log('blockingDone'),
})
export default class Index extends Component {
  render() {
    return (
      <div>
        <h1 style={{ color: this.props.color }}>React Router Redial</h1>
        <p>{ this.props.data }</p>
        <button onClick={ () => this.props.reload() }>Reload color</button>
        <pre>{ JSON.stringify(this.props, null, 2) }</pre>
      </div>
    );
  }}


<|start_filename|>src/triggerHooks.js<|end_filename|>
import { trigger } from 'redial';
import isPlainObject from 'lodash.isplainobject';
import createMap from './createMap';
import getRoutesProps from './getRoutesProps';
import getLocals from './getLocals';
import getAllComponents from './getAllComponents';
import createMapKeys from './util/mapKeys';

export default function triggerHooks({
  hooks,
  components,
  locals,
  renderProps,
  force = false,
  bail = () => false,
  redialMap = createMap(),
}) {
  // Set props for specific component
  const setProps = (key) =>
    (props) => {
      if (!isPlainObject(props)) {
        throw new Error('The input to setProps needs to be an object');
      }
      redialMap.set(key, {
        ...redialMap.get(key),
        ...props,
      });
    };

  // Get components for a specific component
  const getProps = (key) =>
    () => redialMap.get(key) || {};

  const getMapKeyForComponent = createMapKeys(renderProps.routes, renderProps.components);

  const completeLocals = (component) => {
    const key = getMapKeyForComponent(component);
    return ({
      location: renderProps.location,
      params: renderProps.params,
      routeProps: getRoutesProps(renderProps.routes),
      setProps: setProps(key),
      getProps: getProps(key),
      isAborted: bail,
      force,
      ...getLocals(component, locals),
    });
  };

  const hookComponents = getAllComponents(components || renderProps.components);

  return hooks.reduce((promise, parallelHooks) =>
    promise.then(() => {
      if (bail()) {
        throw new Error(`Redial was terminated because: ${bail()}`);
      }
      return Promise.all(
        [].concat(parallelHooks)
          .map((hook) => trigger(hook, hookComponents, completeLocals))
      );
    }), Promise.resolve()
  ).then(() => {
    if (bail()) {
      throw new Error(`Redial was terminated because: ${bail()}`);
    }

    return {
      redialMap,
      redialProps: redialMap.dehydrate(hookComponents.map(getMapKeyForComponent)),
    };
  });
}


<|start_filename|>tests/getLocals.test.js<|end_filename|>
import expect, { createSpy } from 'expect';

import getLocals from '../src/getLocals';

describe('getLocals', () => {
  it('should call locals if it is a function with component', () => {
    const locals = createSpy()
      .andReturn(1);
    const component = {};

    expect(getLocals(component, locals)).toBe(1);
    expect(locals).toHaveBeenCalledWith(component);
  });

  it('should return locals if not a function', () => {
    const locals = {};
    const component = {};

    expect(getLocals(component, locals)).toBe(locals);
  });
});


<|start_filename|>examples/simple-with-scroll/render/client.js<|end_filename|>
import { useRedial } from 'react-router-redial';
import { useScroll } from 'react-router-scroll';

import React from 'react';
import { render } from 'react-dom';
import { Router, browserHistory, applyRouterMiddleware } from 'react-router';

// Render the app client-side to a given container element:
export default (container, routes) => {
  const component = (
    <Router
      history={browserHistory}
      routes={routes}
      render={applyRouterMiddleware(
        useScroll(),
        useRedial({
          beforeTransition: ['fetch'],
          afterTransition: ['defer', 'done'],
          parallel: false,
          initialLoading: () => <div>Loading…</div>,
        })
      )}
    />
  );

  // Render app to container element:
  render(component, container);
};


<|start_filename|>examples/redux/render/client.js<|end_filename|>
import { useRedial } from 'react-router-redial';

import React from 'react';
import { render } from 'react-dom';
import { Router, browserHistory, applyRouterMiddleware } from 'react-router';
import { Provider } from 'react-redux';

// Render the app client-side to a given container element:
export default (container, routes, store) => {
  // Define extra locals to be provided to all lifecycle hooks:
  const locals = store ? {
    dispatch: store.dispatch,
    getState: store.getState,
  } : {};

  let component = (
    <Router
      history={browserHistory}
      routes={routes}
      render={applyRouterMiddleware(useRedial({
        locals,
        beforeTransition: ['fetch', 'blockingDone'],
        afterTransition: ['defer', 'deferDone'],
        parallel: false,
      }))}
    />
  );

  if (store) {
    component = (
      <Provider store={store}>
        {component}
      </Provider>
    );
  }

  // Render app to container element:
  render(component, container);
};


<|start_filename|>src/getLocals.js<|end_filename|>
export default function getLocals(component, locals) {
  return typeof locals === 'function' ?
    locals(component) :
    locals;
}


<|start_filename|>examples/redux/components/User.js<|end_filename|>
import React, { Component } from 'react';
import { provideHooks } from 'redial';
import { connect } from 'react-redux';

import fetchGithubUser from '../redux/actions/fetchGithubUser';

function mapStateToProps(state, ownProps) {
  return { user: state.githubUsers[ownProps.params.id.toLowerCase()] }
}

@provideHooks({
  fetch: ({ params: { id }, dispatch, getState }) => {
    if(!getState().githubUsers[id.toLowerCase()]) {
      return dispatch(fetchGithubUser(id));
    }
  }
})
@connect(mapStateToProps)
export default class User extends Component {
  render() {
    return (
      <div>
        <h3>{ this.props.user.name } <pre>@{ this.props.user.login }</pre></h3>
        <img src={ this.props.user.avatar_url } />
      </div>
    );
  }
}


<|start_filename|>src/util/findRouteByComponent.js<|end_filename|>
import isPlainObject from 'lodash.isplainobject';

const isNamedComponents = isPlainObject;

function searchNamedComponents(component, namedComponents, correspondingRoute, result) {
  return Object.keys(namedComponents)
    .some(name => {
      const isMatch = namedComponents[name] === component;
      if (isMatch) {
        /* eslint-disable no-param-reassign */
        result.name = name;
        result.route = correspondingRoute;
        /* eslint-enable no-param-reassign */
      }
      return isMatch;
    });
}

export default function findRouteByComponent(component, matchedRoutes, matchedComponents) {
  const result = {};

  matchedComponents.some((matchedComponent, i) => {
    if (isNamedComponents(matchedComponent)) {
      return searchNamedComponents(component, matchedComponent, matchedRoutes[i], result);
    }

    const isMatch = component === matchedComponent;
    if (isMatch) {
      result.route = matchedRoutes[i];
    }
    return isMatch;
  });

  return result;
}


<|start_filename|>examples/redux/client.js<|end_filename|>
import render from './render/client';
import routes from './routes';
import configureStore from './redux/configureStore';

const store = configureStore(__FLUX_STATE__);
render(document.getElementById('application'), routes, store);


<|start_filename|>examples/redux/devServer.js<|end_filename|>
import koa from 'koa';
import webpack from 'webpack';
import koaWebpackDevMiddleware from 'koa-webpack-dev-middleware';

import webpackConfig from './webpack.config.js';

const devServer = koa();
const compiler = webpack(webpackConfig);

devServer.use(
    koaWebpackDevMiddleware(compiler, {
        publicPath: '/',
        noInfo: false,
        quiet: false
    })
);

const hotMiddleware = require('webpack-hot-middleware')(compiler);
devServer.use(function* (next) {
    yield hotMiddleware.bind(null, this.req, this.res);
    yield next;
});

devServer.listen(3001);

console.log('Dev server started on http://localhost:3001');


<|start_filename|>examples/simple-with-scroll/routes.js<|end_filename|>
import React from 'react';
import { Route, IndexRoute } from 'react-router'

import App from './components/App';
import Index from './components/Index';
import LongPage from './components/LongPage';

export default (
  <Route path="/" component={App}>
    <IndexRoute component={Index}/>
    <Route path="long-one" component={LongPage} />
    <Route path="another-long" component={LongPage} />
  </Route>
);


<|start_filename|>src/util/mapKeys.js<|end_filename|>
import findRouteByComponent from './findRouteByComponent';
import getRoutePath from './getRoutePath';

export default function createGenerateMapKeyByMatchedRoutes(routes, components) {
  return (component) => {
    const { route, name } = findRouteByComponent(component, routes, components);
    if (!route) {
      throw new Error('`component` not found among the matched `routes`');
    }
    return getRoutePath(route, routes, name);
  };
}


<|start_filename|>examples/redux/components/Github.js<|end_filename|>
import React, { Component } from 'react';
import { Link } from 'react-router';

export default class Github extends Component {

  constructor(props) {
    super(props);
    this.state = { value: '' };
  }

  onChange = (e) => {
    this.setState({ value: e.target.value.toLowerCase() });
  };

  render() {
    return (
      <div>
        <h2>Github</h2>
        <div>
          <p>Please select a user by typing and clicking the link</p>
          <input onChange={this.onChange} />&nbsp;
          <Link to={{ pathname: `/github/user/${this.state.value}` }}>Open user!</Link>
        </div>
        {this.props.children}
      </div>
    );
  }
}


<|start_filename|>src/index.js<|end_filename|>
export triggerHooks from './triggerHooks';
export RedialContext from './RedialContext';
export RedialContainer from './RedialContainer';
export useRedial from './useRedial';


<|start_filename|>examples/simple/components/Github.js<|end_filename|>
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router';

class Github extends Component {

  static propTypes = {
    router: PropTypes.shape({
      push: PropTypes.func.isRequired,
    }),
    children: PropTypes.node,
  };

  constructor(props) {
    super(props);
    this.state = { value: '' };
  }

  onChange = (e) => {
    this.setState({ value: e.target.value });
  };

  render() {
    return (
      <div>
        <h2>Github</h2>
        <div>
          <p>Please select a user by typing and clicking the link</p>
          <input onChange={this.onChange} />
          {' '}
          <button
            type="button"
            onClick={() =>
              this.props.router.push({ pathname: `/github/user/${this.state.value}` })
            }
          >
            Open user!
          </button>
        </div>
        {this.props.children}
      </div>
    );
  }
}

export default withRouter(Github);


<|start_filename|>examples/named-components/components/NamedContainer.js<|end_filename|>
import React, { Component } from 'react';

export default class NamedContainer extends Component {
  render() {
    const { header, footer, main } = this.props;
    return (
      <div>
        <div>
          <h1>Header fragment</h1>
          {header}
        </div>
        <div>
          <h1>Main fragment</h1>
          {main}
        </div>
        <div>
          <h1>Footer fragment</h1>
          {footer}
        </div>
      </div>
    )
  }
}


<|start_filename|>examples/redux/render/server.js<|end_filename|>
import { triggerHooks, useRedial } from '../../../lib/index';

import React from 'react';
import { renderToString } from 'react-dom/server';
import { createMemoryHistory, match, applyRouterMiddleware } from 'react-router';
import { Provider } from 'react-redux';

// Render the app server-side for a given path:
export default (path, routes, store) => new Promise((resolve, reject) => {
  // Set up history for router:
  const history = createMemoryHistory(path);

  // Match routes based on history object:
  match({ routes, history }, (error, redirectLocation, renderProps) => {
    if (error || !renderProps) {
      reject(error);
    }
    // Define extra locals to be provided to all lifecycle hooks:
    const locals = store ? {
      dispatch: store.dispatch,
      getState: store.getState,
    } : {};

    // Wait for async data fetching to complete, then render:
    triggerHooks({
      renderProps,
      locals,
      hooks: ['fetch', 'done'],
    }).then(({ redialMap, redialProps }) => {
      const state = store ? store.getState() : null;
      const component = applyRouterMiddleware(useRedial({ redialMap }))(renderProps);
      const html = store ? renderToString(
        <Provider store={store}>
          {component}
        </Provider>
      ) : renderToString(component);

      // Important that the redialProps are sent to the client
      // by serializing it and setting it on window.__REDIAL_PROPS__
      resolve({ html, state, redialProps });
    })
    .catch(reject);
  });
});


<|start_filename|>src/RedialContainer.js<|end_filename|>
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import getRoutePath from './util/getRoutePath';

export default class RedialContainer extends Component {
  static displayName = 'RedialContainer';

  static propTypes = {
    children: PropTypes.element.isRequired,
    routerProps: PropTypes.object.isRequired,
  };

  static contextTypes = {
    redialContext: PropTypes.object.isRequired,
  };

  render() {
    const { routerProps, ...props } = this.props;
    const {
      abortLoading,
      loading,
      afterTransitionLoading,
      reloadComponent,
      redialMap,
    } = this.context.redialContext;
    const mapKey = getRoutePath(routerProps.route, routerProps.routes, routerProps.key);
    const redialProps = redialMap.get(mapKey);
    const reload = () => reloadComponent(routerProps.route.component);
    const abort = () => abortLoading();

    return React.cloneElement(
      this.props.children,
      {
        ...props,
        ...redialProps,
        ...routerProps,
        loading,
        afterTransitionLoading,
        reload,
        abort,
      }
    );
  }
}


<|start_filename|>tests/util/mapKeys.test.js<|end_filename|>
import expect from 'expect';

import createGenerateMapKeyByMatchedRoutes from '../../src/util/mapKeys';

describe('mapKeys', () => {
  const routes = [
    {
      path: '/',
      component: function App() {},
    },
    {
      path: 'dashboard',
      component: function Dashboard() {},
    },
    {
      path: 'widget/:widgetName',
      component: function Widget() {},
    },
  ];
  const namedRoutes = [
    {
      path: '/',
      component: function App() {},
    },
    {
      component: function NamedContainer() {},
    },
    {
      path: 'named',
      components: {
        header: function Header() {},
        main: function Main() {},
      },
    },
    {
      path: 'child',
      component: function Child() {},
    },
  ];

  describe('regular routes', () => {
    const mapKeyByComponent = createGenerateMapKeyByMatchedRoutes(routes, routes.map(route => route.component || route.components));

    it('Throws an Error when the component cannot be found among the matched routes', () => {
      expect(() => mapKeyByComponent(() => {}, routes)).toThrow(
        '`component` not found among the matched `routes`'
      );
    });

    it('Gives the path up to the matched route', () => {
      expect(mapKeyByComponent(routes[0].component, routes)).toBe('/');
      expect(mapKeyByComponent(routes[1].component, routes)).toBe('//dashboard');
      expect(mapKeyByComponent(routes[2].component, routes)).toBe('//dashboard/widget/:widgetName');
    });
  });

  describe('named routes', () => {
    const mapKeyByComponent = createGenerateMapKeyByMatchedRoutes(namedRoutes, namedRoutes.map(route => route.component || route.components));
    it('Gives the path up to the matched route', () => {
      expect(mapKeyByComponent(namedRoutes[0].component, namedRoutes)).toBe('/');
      expect(mapKeyByComponent(namedRoutes[1].component, namedRoutes)).toBe('//');
      expect(
        mapKeyByComponent(namedRoutes[2].components.header, namedRoutes)
      ).toBe('///named>header');
      expect(mapKeyByComponent(namedRoutes[3].component, namedRoutes)).toBe('///named/child');
    });
  });
});


<|start_filename|>examples/simple/components/Defer.js<|end_filename|>
import React, { Component } from 'react';
import { provideHooks } from 'redial';

@provideHooks({
  defer: ({ setProps, getProps }) => new Promise((resolve) => {
    const { color } = getProps();
    if(!color) {
      if (typeof window === 'object') {
        throw new Error('Defer: Demo error on client side only');
      }
      setTimeout(() => {
        const getValue = () => Math.round(Math.random() * 255);
        setProps({color: `rgb(${getValue()}, ${getValue()}, ${getValue()})`});
        resolve();
      }, 1000);
    } else {
      resolve();
    }
  })
})
export default class Fetch extends Component {
  render() {
    return (
      <div>
        <h1 style={{ color: this.props.color }}>Client will get error on blocking</h1>
      </div>
    );
  }
}
