-
-
Notifications
You must be signed in to change notification settings - Fork 15.3k
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
Best async serverside loading technique? #99
Comments
Hey, thanks!
Redux enforces that Stores are completely synchronous. What you describe should happen in the action creator instead. I think it may even be possible with the default thunk middleware. Your action creator would look like: export function doSomethingAsync() {
return (dispatch) => {
dispatch({ type: SOMETHING_STARTED });
return requestSomething().then(
(result) => dispatch({ type: SOMETHING_COMPLETED, result }),
(error) => dispatch({ type: SOMETHING_FAILED, error })
);
};
} and handling the actual (granular) actions in the Store. It's also possible to write a custom middleware to remove the boilerplate. |
Genius! I figured I was overlooking something obvious. I like that separation of doing and storing. I look forward to watching this library grow, although it's pretty doggone complete already. Cheers, @gaearon! |
You can also write a custom middleware like this export default function promiseMiddleware() {
return (next) => (action) => {
const { promise, ...rest } = action;
if (!promise) {
return next(action);
}
next({ ...rest, readyState: 'request' );
return promise.then(
(result) => next({ ...rest, result, readyState: 'success' }),
(error) => next({ ...rest, error, readyState: 'failure' })
);
};
} and use it instead of the default one. This will let you write async action creators like
and have them turn into
|
Ooh, that's nice as well, and more of what I had in mind when I asked the original question. I can't tell if I like the tradeoff in reducing the number of action constants in exchange for adding Thanks, though. |
Yeah that's totally up to your taste. You could have a similar version that turns |
// Middleware
export default function promiseMiddleware() {
return (next) => (action) => {
const { promise, types, ...rest } = action;
if (!promise) {
return next(action);
}
const [REQUEST, SUCCESS, FAILURE] = types;
next({ ...rest, type: REQUEST });
return promise.then(
(result) => next({ ...rest, result, type: SUCCESS }),
(error) => next({ ...rest, error, type: FAILURE })
);
};
}
// Usage
function doSomethingAsync(userId) {
return {
types: [SOMETHING_REQUEST, SOMETHING_SUCCESS, SOMETHING_FAILURE],
promise: requestSomething(userId),
userId
};
} |
Oh man, I love that solution. So much nicer than having the |
Let me know how (and whether ;-) it works! |
You left out a |
👍 |
@erikras I'm curios how you implemented waiting for the promises to resolve on the server? |
This is just pseudocode, so don't go pasting this anywhere, but I'm using react-router (whose api is changing as fast as redux's) something like this: app.get('/my-app', (req, res) => {
Router.run(routes, req.path, (error, initialState) => {
Promise.all(initialState.components
.filter(component => component.fetchData) // only components with a static fetchData()
.map(component => {
// have each component dispatch load actions that return promises
return component.fetchData(redux.dispatch);
})) // Promise.all combines all the promises into one
.then(() => {
// now fetchData() has been run on every component in my route, and the
// promises resolved, so we know the redux state is populated
res.send(generatePage(redux));
});
});
}); Does that clear anything up? |
Quoting your problem in Slack:
I think the problem here is you're not actually returning anything from Either remove the braces: export function fooBar() {
return dispatch =>
doAsync().then(() => dispatch({type: FOO_BAR}));
} or add an explicit export function fooBar() {
return dispatch => {
return doAsync().then(() => dispatch({type: FOO_BAR}));
};
} Either way, it might be easier to use a custom promise middleware as suggested above. |
@erikras Regarding your last comment where you are calling the fetchData method on what you have as here's an example of what I'm talking about import React from 'react';
import Router from 'react-router';
import {Route, RouteHandler, DefaultRoute} from 'react-router';
//imagine Bar needs some data
const Bar = React.createClass({
render(){
return(
<div>bar</div>);
}
});
const Foo = React.createClass({
render(){
return (
<div>
foo
<Bar/>
</div>);
}
});
const App = React.createClass({
render(){
return (
<div>
<RouteHandler />
</div>
);
}
});
const routes = (
<Route path="/" handler={App} name="App">
<DefaultRoute handler={Foo} name="Foo"/>
</Route>
);
Router.run(routes,'/',function(Root,state){
console.log(state);
}); output: { path: '/',
action: null,
pathname: '/',
routes:
[ { name: 'App',
path: '/',
paramNames: [],
ignoreScrollBehavior: false,
isDefault: false,
isNotFound: false,
onEnter: undefined,
onLeave: undefined,
handler: [Object],
defaultRoute: [Object],
childRoutes: [Object] },
{ name: 'Foo',
path: '/',
paramNames: [],
ignoreScrollBehavior: false,
isDefault: true,
isNotFound: false,
onEnter: undefined,
onLeave: undefined,
handler: [Object] } ],
params: {},
query: {} } You won't have access to Bar in Routes |
@erikras Fantastic! That's exactly the kind of route I want to go down. Thanks for sharing. |
@iest I hope that pun was intentional, "go down a route" by iterating through the matching routes. :-) @mattybow That is true. If you really need a component that is not in your routes to load something, then the only option is to run |
@erikras |
@transedward I wish I did, but my stuff so far using the method I detailed here is still very immature. Sorry. |
+1 on the advanced isomorphic example |
@transedward Here's a sample project with all the bleeding edge tech I've cobbled together. https://github.com/erikras/react-redux-universal-hot-example/ |
@erikras This is awesome! Can you please submit PR to add it to this README and React Hot Loader's docs' "starter kits" section? |
Thanks! PRs submitted. |
@erikras Great - Thank you! |
Just a note that—based off some of the ides in this conversation—I made a middleware to handle promises: https://github.com/pburtchaell/redux-promise-middleware. |
@pburtchaell There is this library by @acdlite as well. https://github.com/acdlite/redux-promise |
Promise based HTTP client for the browser and node.js https://github.com/mzabriskie/axios promise middleware reduxjs/redux#99 (comment) https://medium.com/@bananaoomarang/handcrafting-an-isomorphic-redux-appl ication-with-love-40ada4468af4
@gaearon this code you wrote #99 (comment), Is this something that is included the redux library or is it something I have to create manually? Sorry if this is a newb question, just getting into React / Flux (Redux). Just started this tutorial https://github.com/happypoulp/redux-tutorial |
It's not included. It's just there to give you an idea of what you can do—but you're free to do it differently. |
Thanks for this useful info. I wanted to pick your brains on reseting a store -
How did you guys solve that/have any ideas how to? Just a new store for each user would work instead? |
Are you talking about server rendering? Create a new store on every request. We have a guide for server rendering in the docs. |
Thanks I'll do that |
After trying to understand…
Is this too naive? (no one else seems to do this—i think) // server.js
app.use(function (req, res) {
match({…}, function (error, redirectLocation, renderProps) {
…
if (renderProps) {
const store = configureStore();
const promises = renderProps.components.map(function (component, index) {
if (typeof component.fetchData !== 'function') {
return false;
}
return component.fetchData(store.dispatch);
});
Promise.all(promises).then(function () {
res.status(200).send(getMarkup(store, renderProps));
});
}
})
}); // home.js
export class Home extends Component {
static fetchData() {
return Promise.all([
dispatch(asyncAction);
]);
},
componentDidMount() {
const { dispatch } = this.props;
Home.fetchData(dispatch);
}
}
export default connect()(Home); // action.js
export function asyncAction() {
return (dispatch, getState) => {
dispatch(request);
return fetch(…)
.then(response => response.json())
.then(data => dispatch(requestSuccess(data)))
;
}
} I was also trying to figure out a solution for @mattybow's question #99 (comment) (nested components managing data fetching), but no such success (wasn't sure on how to collect promises from |
@chemoish I'm also trying to get my head around server-side rendering with react and redux. The example in the documentation does not handle this use case very well. I do not want to specify and couple every API request on the server with my components again. The component only should specify how to get the data needed (which the server then should pick up). Your solutions looks quite good to accomplish that. Did this work out for you? Thanks Edit: I'm correct that "componentDidMount" does not get triggered again on the client when it is rendered on the server? |
@ms88privat I haven't gotten much feedback on the solution yet, nor have tested its limits. However, the solution posed above requires each page to know the data for all of its children components. I haven't dug deeper into have nested components worry about data management themselves (due to collecting nested promises). It seems to be doing what you would expect, so its good enough for me for now.
I get around this by preventing the Examine https://github.com/reactjs/redux/blob/master/examples/async/actions/index.js#L47 for an idea of what I am talking about. |
Okey, I got that. Thanks.
Maybe I misread your solution, but isn't this a necessary requirement regardless of the server-side rendering? (e.g. it should render the same state, if I refresh on the current route, even if it is a SPA) |
It would, but you may want a nested component manage its own data fetching, for whatever reason. For instance, a component that is repeated on many pages, but each page doesn't have much data fetching needs. |
@chemoish I'm not sure if we are on the same page. Let me try to explain what my point of view is. For example I got three nested components:
Each of them have there own "componentDidMount" methods, with there own dataFetching declarations (dispatching actions via its static dataFetching method). If I do not have sever-side rendering and I refresh the current URL, my components will mount and trigger all the actions needed to load all the required data afterwards. With server-side rendering, your Do you have a reference to your |
@ms88privat |
@dominictobias thx, do you have a solution to this problem? Is there a possibility to get all the nested components? |
Probably this can help? https://github.com/gaearon/react-side-effect |
Sorry for bumping this discussion again, but I have recently run into the same problem of prefilling state with async action. I can see that @erikras moved his boilerplate project to redux-async-connect. I wonder, if somebody found another solution? |
@vtambourine I have been looking at https://github.com/markdalgleish/redial which is quite helpful |
Yes, I have looked through it. But I didn't get, how to make sure data
|
Also curious if anyone has found a stable solution for this challenge. I love @erikras's boilerplate, but as @vtambourine mentioned, it has moved to redux-async-connect which seems like it may not be a stable long-term solution: #81 Is redux-async-connect dead?. |
@vtambourine there is a fork thats available at https://github.com/makeomatic/redux-connect and is well-maintained. It has similar API with a few changes to it, check it out if you are interested |
for those interested in a redux solution with middleware as mentioned by @gaearon I have an example project which implements this technique and allows the components themselves request the data they need server-side https://github.com/peter-mouland/react-lego-2016#redux-with-promise-middleware |
How to unit test action creator with this approach? |
First of all, I love this library and the patterns you guys are using. 👍👍
I'm trying to use redux to build an isomorphic app. It's working beautifully so far, except that I need to figure out how to wait until my stores have loaded (on the server) before returning the initial page load. Ideally, the loading should take place in the stores themselves, but when I call
dispatch(userActions.load())
, the store has to return the new state (i.e.return { ...state, loading: true };
), so it can't return a promise to wait for.dispatch()
returns the action passed to it for some reason. I'd really like something like...dispatch(someAsyncAction, successAction, failureAction) => Promise
...where the promise doesn't resolve until one of the other two actions is dispatched.
Is that the sort of thing that could be enabled with the middleware pattern?
Am I totally off base and there's a simple way to do this already?
Thanks.
The text was updated successfully, but these errors were encountered: