rt2zz / redux-persist

persist and rehydrate a redux store
MIT License
12.93k stars 865 forks source link

Issues with actions being fired before rehydrate is completed #226

Open traverse opened 7 years ago

traverse commented 7 years ago

Hello,

I'm having some issues with actions being fired before the rehydrate is done. I've also tried using an action buffer which shows the actions being fired after the rehydrate but they're fired using the initial state and not the rehydrated state.

My code currently looks as follows for the middlewares, store and persistStore:

const middlewares = applyMiddleware(
  thunk,
  routerMiddleware(browserHistory),
  createActionBuffer(REHYDRATE)
)

export const store = createStore(
  rootReducer,
  compose(
    autoRehydrate(),
    middlewares,
  )
)

persistStore(store, {
  keyPrefix: 'key',
  blacklist: ['items'],
  debounce: 500
})

I'm probably overlooking something simple but I've been stuck on this for a few days now so any help or insights are greatly appreciated!

vovkasm commented 7 years ago

May be you need something as this: https://github.com/rt2zz/redux-persist/blob/master/docs/recipes.md#delay-render-until-rehydration-complete

I successfully use this method to show kind of "splash screen" until initial loading done.

traverse commented 7 years ago

Thanks for your reply. The problem I'm having however isn't the rendering part, basically actions are being fired that are related to updating data and or redirecting based on that data which is going awry because they're being fired with the initial state instead of the rehydrated state.

vovkasm commented 7 years ago

But you can use the same method to:

  1. Do not do any things, that can generate "actions" until rehydration process end (In react this most often leads to not instantiate some components).
  2. If "actions" come from "external world" (network or other sources), you can postpone subscriptions to this "actions" until state fully loaded or create some sort of "buffer" to collect this events and send "actions" later.
traverse commented 7 years ago

That's what I thought the action buffer would do, save up the actions and fire them after rehydration is completed or am I mistaken? Because using the devtools it shows the actions being fired after rehydration is complete but just using console logs I can see that it's using the wrong state.

If this isn't the way the action buffer is supposed to be used I'll try out the example you linked earlier 👍

Thanks a lot!

rt2zz commented 7 years ago

@Traverse can you explain what you mean by, "the actions are fired with the initial state". Are the actions in question thunks? If so this is true, the thunk will run before rehydrate and getState will return initial state.

I can see three resolutions here:

Let me know if that helps, I will gladly accept a pr to the action buffer for thunk support since that seems like a common enough use case, although we should probably hide it behind a deferThunks option so as not to break the other valid use case which is firing the think asap and only deferring the result.

traverse commented 7 years ago

The actions in question were automatically being fired by react-router-redux but I was checking if certain conditions were met using a middleware. In the middleware getState would return the initial state even if it showed the actions being fired after the rehydrate.

After what you mentioned about thunks I went ahead and had a look at react-router-redux's code and the actions fired by it is a thunk so that is probably why it wouldn't work as I expected it to.

Updating action buffer to support thunks seems like the ideal solution but I sadly do not have time for it at the moment but I might do in the future however.

rt2zz commented 7 years ago

@Traverse understood, in this case you are better going with what I think is the more stable solution and what I personally do which is defer loading the router until after persistence has completed. In most cases this is a trivial amount of time.

hyperh commented 7 years ago

Thanks this worked for me https://github.com/rt2zz/redux-persist/blob/master/docs/recipes.md#delay-render-until-rehydration-complete

damianobarbati commented 7 years ago

@Traverse did you find solution to this? When using react-router-redux "@@router/LOCATION_CHANGE" is automatically fired to sync history with current opened link at store/app bootstrap. Using the "delay method" wrapping the app doesn't work because the action is fired non-caring about rendering. So I get: redux-persist/autoRehydrate: 2 actions were fired before rehydration completed. This can be a symptom of a race condition where the rehydrate action may overwrite the previously affected state. Consider running these actions after rehydration:

What could I do? Anybody else using react-router-redux?

traverse commented 7 years ago

@damianobarbati By "delay method" you mean waiting with loading the router till hydration is complete? Because that should work.

damianobarbati commented 7 years ago

@Traverse how do you wait to load the router? I don't know how :(

Store.js

const reducers = {
    routing: routerReducer,
    auth: authReducer,
    home: homeReducer,
    item: itemReducer,
    list: listReducer,
};
const finalReducer = combineReducers(reducers);

const loggerMiddleware = createLogger();
const finalMiddleware = applyMiddleware(routerMiddleware(browserHistory), thunkMiddleware, promiseMiddleware, loggerMiddleware);

const store = createStore(finalReducer, undefined, composeWithDevTools(
    finalMiddleware,
    autoRehydrate({ log: true }),
));

App.js

export default class App extends React.Component {
    constructor() {
        super();
        this.state = { rehydrated: false }
    }

    componentWillMount() {
        persistStore(store, {}, () => {
            this.setState({ rehydrated: true })
        });
    }

    render() {
        if(!this.state.rehydrated)
            return <div>loading...</div>;

        return (
            <Provider store={store}>
                <Router history={history}>
                    <Route path="/admin" component={Layout}>
                        <IndexRedirect to="/admin/home" />
                        <Route path="/admin/home" component={Home} />
                        <Route path="/admin/auth" component={Auth} />
                        <Route path="/admin/list/:model/:filter" component={List} />
                        <Route path="/admin/list/:model" component={List} />
                        <Route path="/admin/item/:model/:id" component={Item} />
                        <Route path="/admin/item/:model" component={Item} />
                        <Route path="/admin/*" component={NotFound} />
                    </Route>
                </Router>
            </Provider>
        );
    }
}
traverse commented 7 years ago

There is an example in the docs that shows it which you can find here, I hope this helps!

Edit: I just noticed you're already applying it, it's weird that it's not working for you then... You could always use an action buffer depending on what exactly you need.

damianobarbati commented 7 years ago

@Traverse that's what I'm doing: react-router-redux fires the action before the rehydrate anyway

traverse commented 7 years ago

@damianobarbati the only thing I can suggest then is what @rt2zz suggested me to do and that's updating the action buffer to support thunks, unless someone else has any other insights.

damianobarbati commented 7 years ago

I opened an issue on that as well: https://github.com/rt2zz/redux-action-buffer/issues/7 My only action firing before the HYDRATE is @@INIT but it complains anyway!

rt2zz commented 7 years ago

I just fixed the bad logic in the warning to ignore the @@INIT action 👍

As for firing actions I still think it is best to delay rendering the router until rehydration is complete, but if this does not work for you, as @Traverse mentioned you can use redux-action-buffer + thunks to achieve more complicated logic. Just be careful not to introduce any accidental race conditions!

hugows commented 6 years ago

For the record, the recipe for "delayed render" is now at the frontpage (https://github.com/rt2zz/redux-persist) and it magically works ;)