redux-connect icon indicating copy to clipboard operation
redux-connect copied to clipboard

Wait to render before promise is resolved.

Open masureshho opened this issue 6 years ago • 13 comments

@asyncConnect does not keep the components render stalled before the promise is resolved. Is this because I am not doing the server-side rendering? P.S I do not want server-side rendering. Does this work only for a client? Please help.

masureshho avatar Jun 12 '18 11:06 masureshho

Same issue here. Async connect renders components before returned promise is resolved.

The app is initialized like that:

import { ReduxAsyncConnect } from 'redux-connect';
import { BrowserRouter } from 'react-router-dom';
import ReactDOM from 'react-dom';
import { hot } from 'react-hot-loader';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import 'babel-polyfill';

import routes from './routes';
import store, { history } from './store';

const root = <ReduxAsyncConnect routes={routes} />;

hot(module)(root);

ReactDOM.render((
  <Provider store={store}>
    <BrowserRouter>
      <ConnectedRouter history={history}>
        {root}
      </ConnectedRouter>
    </BrowserRouter>
  </Provider>
), window.document.getElementById('react-view'));

I tried to remove ConnectedRouter and hot(module) parts but it isn't helped.

The reducer:

import { reducer as reduxAsyncConnect } from 'redux-connect';
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';

import reducers from '../redux/reducers';

export default function createReducer() {
  return combineReducers({
    form: formReducer,
    reduxAsyncConnect,
    ...reducers,
  });
}

Store configuration:

const middlewares = [
    thunkMiddleware,
    sideEffectsMiddleware(),
    apiMiddleware(),
    routerMiddleware(history),
  ];

  const enhancers = [
    applyMiddleware(...middlewares),
    window.devToolsExtension && __DEVTOOLS__ ? window.devToolsExtension() : f => f,
  ];

  const store = createStore(
    connectRouter(history)(createReducer()),
    initialState,
    compose(...enhancers),
  );

The redux-connect enhancer creation:

const reduxAsyncConnect = asyncConnect([{
  key: 'hey',
  promise: async ({ store: { dispatch, getState } }) => {
    await new Promise(r => setTimeout(r, 5000))
  },
}]);

So everything looks set up as described.

There is the part of action log:

[{
	"type": "@@router/LOCATION_CHANGE",
	"payload": {
		"location": {
			"pathname": "/login",
			"hash": "",
			"search": "?redirect=%2F",
			"key": "q0k114"
		},
		"action": "REPLACE"
	}
}, {
	"type": "@redux-conn/LOAD",
	"payload": {
		"key": "hey"
	}
}, {
	"type": "@redux-conn/BEGIN_GLOBAL_LOAD"
}, {
	"type": "@redux-conn/BEGIN_GLOBAL_LOAD"
}, {
	"type": "@redux-conn/END_GLOBAL_LOAD"
}, {
	"type": "@redux-conn/LOAD_SUCCESS",
	"payload": {
		"key": "hey"
	}
}, {
	"type": "@redux-conn/END_GLOBAL_LOAD"
},
...

@@router/LOCATION_CHANGE action call is made inside the enhanced component (via redux-auth-wrapper) but it called before any @redux-conn/XXX call. If I remove the redux-auth-wrapper enhancer, then another child component is rendered before promise is finalized (so this is not about this library).

finom avatar Jun 22 '18 17:06 finom

Almost everything is looking just fine: actions are fired; store is updated with data returned by promise function. One problem: all the redux-connect actions are fired with delay, which makes enhanced component render before END_GLOBAL_LOAD. I noticed that it happens on first route load. If the route is reached second time, children components are rendered normally: after promise is finalized. Any thoughts?

finom avatar Jun 22 '18 17:06 finom

@aleksxor @AVVS any ideas why that could happen?

finom avatar Jun 22 '18 17:06 finom

@finom @masureshho did you find a solution for this? We use server-side rendering on the initial load and redux-connect properly defers loading, but client-side we aren't seeing the same global loading events being properly fired using asyncConnect

kc-beard avatar Aug 21 '18 13:08 kc-beard

@kc-beard I've written my own super-tricky solution (the company I work for doesn't do open-source, so I can't share code with you, unfortunately). The issue with redux-connect isn't solved so I just removed it from the project.

finom avatar Aug 21 '18 13:08 finom

thanks @finom!

For others who happen to land on this issue, see #116 for related discussion. In short, asyncConnect won't work for routes that are bundle-split.

kc-beard avatar Aug 21 '18 16:08 kc-beard

I've the same issue as @finom mentioned, i.e. component wrapped with @asyncConnect is getting rendered before @asyncConnect promise is resolved on the initial render (i.e. on page reload). However everything works correctly on subsequent transitions.

Packages versions:

"react-redux": "^7.1.1",
"react-router-config": "^5.1.1",
"react-router-dom": "^5.1.2",
"redux-connect": "^10.0.0"

@AVVS @aleksxor guys can you please take a look ?

ekscentrysytet avatar Oct 18 '19 15:10 ekscentrysytet

make sure that you have resolved a list of routes - the module works by walking through the tree of components to be rendered, gathering statics (meaning that a component must be returned as one of . the top-level components from react-router-config) and then it waits for promises to be resolved. seeing you are using async/await it could be a transpilation issue of sorts

AVVS avatar Oct 18 '19 15:10 AVVS

@AVVS We don't do code-splitting yet and I just removed all async/await usage from my code and it's still fails. Here's the example of my setup:

routes.js:

[{
  component: App,
    path: '/',
    routes: [
      {
        path: '/page-path',
        component: Page,
      },
      ...
    ],
    ...
}]

App.js

const reduxAsyncConnect = asyncConnect([{
  promise: ({ store: { dispatch, getState }, route }) => {
    console.log('App - asyncConnect');
    return dispatch(auth());
  }
}]);

const reduxConnect = connect((state, { params }) => {
  console.log('App - reduxConnect');
});

export default compose(
  reduxAsyncConnect,
  reduxConnect,
  ...
);

Page.js

const reduxAsyncConnect = asyncConnect([{
  promise: ({ store: { dispatch, getState }, route }) => {
    console.log('Page - asyncConnect');
    return dispatch(getPageData());
  }
}]);

const reduxConnect = connect((state, { params }) => {
  console.log('Page - reduxConnect');
});

export default compose(
  reduxAsyncConnect,
  reduxConnect,
  ...
);

When I realod page on /page-path I see following in console:

App - asyncConnect
App - reduxConnect
Page - reduxConnect
Page - asyncConnect

And when I go to some other page and then navigate back to /page-path I see following:

App - asyncConnect
App - reduxConnect
Page - asyncConnect
Page - reduxConnect

which is expected - i.e. reduxConnect should be only triggered after asyncConnect promise resolved

ekscentrysytet avatar Oct 18 '19 16:10 ekscentrysytet

well, make sure that

    return dispatch(auth());

returns a promise. if its not a promise - it wont wait until this is finished

AVVS avatar Oct 18 '19 16:10 AVVS

@AVVS yes, it returns a promise. It worth nothing to say that I've faced this problem only after I've upgraded to RR v4 and redux-connect@9. For RR v3 it was working flawlessly

ekscentrysytet avatar Oct 18 '19 16:10 ekscentrysytet

Any alternatives to this library?

finom avatar Aug 10 '20 20:08 finom

@finom we are really waiting Suspense for data fetching from React team to go live and RR v6 is already experimental release which features Suspense ready navigation

ekscentrysytet avatar Aug 12 '20 08:08 ekscentrysytet