-
-
Notifications
You must be signed in to change notification settings - Fork 15.2k
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
State properties without reducers. #1457
Comments
Can you show some code, both for client and server side? I have a hard time imagining how you’re using |
I plan to build isomorphic app. I use components represents pages in my app. It will be not a SPA in normal sense. import React from 'react';
import { connect } from 'react-redux';
import Head from 'components/Head';
import Meta from 'components/Meta';
import Body from 'components/Body';
import UniversalAnalytics from 'components/UniversalAnalytics';
import PageInsights from 'components/PageInsights';
import JavaScript from 'components/JavaScript';
import StyleScheet from 'components/StyleScheet';
class MainPage extends React.Component {
render() {
const { meta, styles, scripts, insights } = this.props;
const { analytics: { google, facebook } } = this.props;
return (
<html>
<Head>
<Meta meta={meta} />
<StyleScheet styles={styles} />
<PageInsights insights={insights} />
</Head>
<Body>
<span> Main page there</span>
<UniversalAnalytics trackingCode={google.trackingCode} />
<JavaScript scripts={scripts} />
</Body>
</html>
);
}
}
function select(state) {
return state;
}
/**
* Redux connected main page
*/
export default connect(select)(MainPage); This will render almost full html, I dont need reducers for analytics, styles, meta etc... import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import buildStore from './buildStore';
export default class ReactRenderer {
constructor() {
this.type = 'react';
}
render(Page, storeFunction, reducer) {
const promise = buildStore(
storeFunction()
);
return promise
.then((initState) => {
const pageStore = createStore(reducer, initState);
const page = ReactDOMServer.renderToString(
<Provider store={pageStore}>
<Page/>
</Provider>
);
return `<!doctype html> ${page}`;
});
}
} This will render a page based on initalStore, Page composite component like MainPage and server side reducers. First It will use storeFunction that return object with mixed values and promises. buildStore will resolve all promises. Create store will take reducers and initalState and connect this to page component. import { combineReducers } from 'redux';
import analytics from 'redux/reducers/analytics';
import posts from 'redux/reducers/posts';
import auth from 'redux/reducers/auth';
const reducers = {
analytics,
posts,
auth,
};
export default combineReducers(reducers); I will have reducers for server side only for a things that can affect my render, but I dont need to have reducers for user interactions. |
In my cases initialState will be different depends on what route in server side user will hit. In my case I will need to create custom implementation of combineStores or add empty reducers for all possible state properties. |
const reducer = (state = {}, action) {
return Object.assign({}, state, {
posts: posts(state.posts, action),
// other client side reducers
})
} which would not drop the keys and would have no warnings. |
To piggyback off this issue, I too experienced some frustration with the sometimes over stringent verifications in Another minor annoyance is using parameter destructuring with flux standard actions. So I would attempt to write something like:
but that will also fail because of I forced to do
instead. |
Please help me understand what you are doing better.
How do you “add” this read-only state? |
Maybe I threw you off with the phrase "read-only". Its just node in the state tree that does not respond to any action (but it may in future versions of the application), therefore its not modified throughout the applications life-cycle. Its added as part of the initial state when creating a store. |
Please show how you create the store. |
I'm not quite clear as to what you mean, we create the store in what I thought was the standard way.
where Would you suggest that those type of "read-only" properties do not belong in a redux store? |
I’d say so. If these are constants, just make a module that exports these constants and lets you inject their values once from the server payload. An alternative I suggest is to express this initial data as actions. For example your server may emit something like window.INITIAL_ACTIONS = [
{ type: "AUTH_SUCCESS", userId: 3 },
{ type: "PRELOAD_DATA", { ... } },
// could have more actions
] In your window.INITIAL_ACTIONS.forEach(store.dispatch) Now you can write reducers that don’t care whether those actions are local or not. function auth(state = {}, action) {
switch (action.type) {
case AUTH_USER:
return { ...state, userId: action.userId }
default:
return state
}
} The benefit of this approach is that it’s easier to later perform these actions on the client as well, if there is ever a need for this, and that it is very easy to preload the data (e.g. pre-fetched responses for entities that are likely to be requested) in a uniform way without coupling the server code to the state shape of the client code. Everything goes as an action. I hope this helps! |
Thanks @gaearon , that helps. |
Hi here, I came here because I'm having a discussion with another dev about keeping never-changing constants inside redux store (api access token, or some settings). So the guy is advocating the approach of Single Source of Truth, and thus keep the constants inside the store too. Similar to this StackOverflow answer: https://stackoverflow.com/a/45227593
While I think of redux store as a container for changing data. If something is never changed during the page JS life (it's still different for every user), I prefer to pass it as a prop into the app component from outside (a PHP template injecting props values fetched from database). I feel like storing constants is abusing the store purpose. I really hope to hear @gaearon thoughts on this. Thanks. |
I have another use case that causes the same problem. I've got dynamic chunks per route that add dynamic reducers. These dynamic chunks are created via Webpack and are automatically and asynchronously loaded on route change (or page load). Asynchronously means that, even on page load, the dynamic chunk will always be fetched after the initial chunk (the one that creates the store) is executed. The server is pre-hydrating the store for that dynamic reducer, yet, the reducer wasn't loaded at the time the store and initialState were created. I.e. in chronological order
Seems like a valid flow, but that warning is annoying me. I don't want to add dummy reducers just to silent the warning as my entry chunk should be completely agnostic of other modules. |
It is possible to have reducers that do not have defined actions for some state properties. This will be the case for single reducer working on root state. combineReducers need define a reducer for each property on state otherwise it will drop keys on initalState.
Since normal reducer do not need to address changes in all state properties, why combineReducers and createStore are such strongly opinionated??
In many cases, state will have properties that will be read once and reducer is not really needed. For example server side rendering where component will be rendered only once and props data will be taken from connected store.
I would like to avoid defining "empty" reducers like state => state just to avoiding keys in my state object being dropped. I still want to have some reducers because sometimes I would like to two components to talk to each other during render.
The text was updated successfully, but these errors were encountered: