I've been using redux for a few weeks now, and I've used flux before too, but I want to make sure I use redux the right way. All my research so far has pointed toward having a single store. This is fine, but most explanations and examples discuss a single page application. What happens when an application grows to more than 1 'single-page app'?
For example, a collection of single page applications, each organized as a module folder, each having nothing to do with each other. Is this a case where multiple stores are an option? So each module would have its own store, reducers, etc... Or is it still considered the redux way to have 1 store shared between all the modules?
BTW - by 'single-page app' I don't literally mean 1 page, but a module, which could consist of 1-5 actual pages that share data and other features
having a single store
This is exactly how Redux works. You have a single store and many actions. Each action, when dispatched, updates the store via reducer.
When you create store, you create a single store:
import { createStore } from 'redux';
import { render } from 'react-dom';
export default render(
<Provider store={createStore(...)}>
...
</Provider>
, document.getElementById('...'));
The createStore call returns a single store that will then be used by Provider component to pass data from the store to connected components (aka containers).
a collection of single page applications, each organized as a module folder, each having nothing to do with each other.
Each individual part can use its own part of the store. You can divide them using combineReducers function:
import { createStore } from 'redux';
import { render } from 'react-dom';
import moduleA from './reducers/moduleA';
import moduleB from './reducers/moduleB';
import moduleC from './reducers/moduleC';
const store = createStore(combineReducers({
moduleA,
moduleB,
moduleC
});
export default render(
<Provider store={store}>
...
</Provider>
, document.getElementById('...'));
and have store as object that has as many properties as there are reducers. Each reducer is now responsible for its own part of the state, therefore module. None of reducers, when done correctly, can mutate the part of the state different from the one this reducer is responsible for.
You should take a look at any boilerplate, there are many. For example, erikras/react-redux-universal-hot-example.
Related
I have some data that is only defined in my index.tsx file, right before I render my <App/>. Once that data is defined, it does not change anymore. I need my <App/> to be aware of that data.
Other needs:
Sometimes that data is needed inside a component (i.e: would be accessible via context and state)
Sometimes that data is needed inside a redux thunk (i.e: would be accessible only via state, or parameter passed to the thunk)
For example:
index.tsx
const COUNTRY = getCountry(window.location);
ssrApp.tsx (on server)
const COUNTRY = getCountry(req.hostname);
My dilema is that I cannot export that data from the index.tsx or the ssrApp.tsx files, because they are 2 different files and one is for client and one is for server rendering. I also need to provide that data to my <App/>.
The easier route I guess would be to pass to the app via context. Like:
render(
<SomeContext.Provider value={COUNTRY}>
<App/>
</SomeContext.Provider>
);
I started doing this, and it worked nicely for the most parts, but it broke when I started needing that data inside a thunk. AFAIK, thunks don't get ACCESS to the context. But they can access state (redux state).
So I created a redux state that I call PRELOADED, and I add that data to the store as a preloaded state value.
Something like:
configureStore({
reducer: rootReducer,
preloadedState: {
PRELOADED: {
COUNTRY: getCountry(window.location)
}
}
});
It solves the "accessible to thunks" problem, but it creates other problems.
Because #reduxjs/toolkit demands you to initialize every state slice, even though you know for sure that that state will ALWAYS be preloaded.
const PRELOADED_SLICE = createSlice({
name: "PRELOADED",
initialState: getInitialState(),
reducers: {
SOME_ACTION(state, action: SOME_ACTION) {
// DO SOMETHING
}
}
});
So you end up having to create a "dummy" initialState, because the real state will only be defined in run-time.
Anyway, the PRELOADED state approach seems to do the work so far. Is there a best practice to this problem?
This is static data, right, so why not include it in a source file and import it to both index.tsc and ssrApp.tsx if they both need it.
Alternatively using your PRELOADED slice, why not just populate the getInitialState() with the data you need in the first place and have no Actions and a noop reducer in that slice?
I have a feeling your description I have some data that is only defined in my index.tsx file, right before I render my <App/>. actually means something other than static data.
I've a component that uses Redux, Redux-Sage and I want to convert it to a sharable library.
How can I structure my code and what to export to make it easier to share?
Ideally we should create stateless component for shareable library. But your components are appearing lots of dependency or state management constraint like Redux, Redux-saga etc. My suggestion, you should avoid this.
If you really want to do this then please create some initialization library function and enforce the calling of this function. The function should check for all pre-requiste before showing your component. But that will be challenging in term of coding.
Usually I would just export the different constituents of the "library" like this, and offer a guide how to integrate them into the projects redux... Long story short it would be... messy. But I'd do it like this:
const defaultState = {};
export libReducer = ( /* ... */ ) {
//...
}
export libSaga = ( /* ... */ ) {
//...
}
export LibComponent = ()=> {
// ...
}
The problematic parts are redux and redux-saga though.
Because it will be required to integrate the reducer using combineReducers, and the saga inside the applyMiddleware part during redux store integration.
import {
createStore,
applyMiddleware,
combineReducers
} from 'redux';
import createSagaMiddleware from 'redux-saga';
// explain that this needs to be imported
import { libSaga, libReducer } from 'your-lib';
const sagaMiddleware = createSagaMiddleware()
// explain how to:
// create a rootSaga to use multiple sagas
// the equivalent of a combineReducers
// but only IF the target project uses
// saga at applyMiddleware. Else it would
// be sufficient to:
// sagaMiddleware.run(libSaga)
// later...
function* rootSaga () {
yield [
fork(libSaga),
fork(otherSagaThatProjectUses)
];
}
// explain howto use combineReducers
// and applyMiddleware to add your reducer
// and enable saga... again depending upon
// the project using redux/saga or not...
const store = createStore(
combineReducers{
// ...
fixedNamespaceForYourLib: libReducer
},
applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(rootSaga);
The best practice is encapsulating your redux provider to use in all project (even non-redux based projects):
<Provider store={store}>
<YourLibrary />
</Provider >
Frankly speaking, It is a really wide-open question. It'll significantly help to answer if you provide a code example or narrow down the context.
In general, it is preferable to avoid any other than React dependencies in a component as it would unlock it for projects without redux + redux saga stack. Often, state handling can be moved from Redux to the local component state, side-effects can be abstracted out. So it's boiling down the pure "view" component which gives enough flexibility for users to bind to custom business logic.
Here are a few general questions to consider regarding creating reusable lib:
What is the component actually supposed to do?
Is the component interface intuitive and unambiguous?
Can it be broken down into smaller and simpler components under the hood?
Does it have a good test coverage? Getting to a good test coverage will encourage having a well-structured and written set of components in a library.
I have simple Redux reducer, but for some actions I need to show notifications, in order to do so I need to trigger my custom notification function within the reducer so:
case REDUCER_ACTION_NAME:
notificationDisplay.success("Message", {
additionalStuff: extraOptions
});
What's even worse I'm using react-intl for translations and I need the "Message"o be translate-ready so I'm adding this to the mix:
case REDUCER_ACTION_NAME:
notificationDisplay.success(<FormattedMessage id="message" defaultMessage="Message" />, {
additionalStuff: extraOptions
});
It creates a translation in span and requires react so my reducer starts with these imports:
import React from 'react';
import notificationDisplay from 'my-notifications';
import { FormattedMessage } from 'react-intl';
import {
// all the actions
} from './actions.jsx';
// reducer
Is it okay? I feel something here is off - like importing React in reducers is an anti-pattern, because all reducer examples I could find are so clean and sleek and there are no external libs there whatsoever.
Am I right or am I wrong and my code is perfectly fine?
You should not do any kind of computations in reducer. It should change the state and nothing else. The way you are using it is a complete anti-pattern. Because it is doing some UI actions. And Redux is nothing to do with the UI. It should be used as the store and only the store.
But you can use Actions which is way better than doing it in reducer.
Best way to achieve your goal is to use your reducer to just push the messages into an array in the redux store. And create a Container that uses that messages array to show success or error messages. And create a timer that removes the message from the array after some time.
Just look at the https://github.com/diegoddox/react-redux-toastr repo they are doing it very well.
Thanks
Akhil P
I have a folder called actions with a file called index.js, and I have been putting all my actions into it so far. It's getting quite messy now and I was wondering how to best split things. Is there any good strategy? Do you keep them in different files? Grouped by...? Or do you just keep them in different files as partials and then include them in the index file, and then always only ever import the index file?
Putting everything in one action.js will very quickly grow out of control, the same is the case for the reducers.
It really comes down to personal taste, but the trends i've seen, seems to be to seperate the app into features, where each feature is isolated with its own actions, and a reducer for each feature as well.
Each level of the application tree will then combine reducers and your store will end up looking like the folder-structure, making it easier to find things. A feature will typically contain actions.js, reducer,js, index.jsx and maybe also style.scss/css for that feature. The pros of doing it that way, is that it is extremely easy to remove the feature at some point, without having to dig for dead code all over the place.
You don't mention how you bundle your code. This approach is nice when building with ex Webpack.
You can use bindActionCreators (http://redux.js.org/docs/api/bindActionCreators.html).
Small example:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as filterActions from '../actions/filterActions';
import * as writeActions from '../actions/writeActions';
class TestComponent extends Component {
render(){
return <div>Test</div>;
}
}
function mapDispatchToProps(dispatch){
return {
filterActions: bindActionCreators(filterActions, dispatch),
writeActions: bindActionCreators(writeActions, dispatch)
};
}
export default connect(null, mapDispatchToProps)(TestComponent);
This will add the actions to the props of your component (at the key that you used in the return Object in mapDispatchToProps). So if you want to use an action from filterActions you can use this.props.filterActions.actionName().
I made calendar part of app, and also tried to make it can be reusable.
Then I also made another part using calendar.
So I used combineReducers function.
import calendar from '../../common/calendar/Reducer';
import insatester from './Reducer';
const reducerCombiner = combineReducers({
calendar,
insatester
});
export default reducerCombiner
Then, I used the combined reducer to make a store.
It seems to work fine first.
But the store and data was separated.
example)
store.calendar.data1 <- from calendar
store.insatester.data2 <- from insatester
Then I tried to get calendar event DB data from insatester, because I think reusable calendar doesn't do that.
But the problem is using stored db data from calendar app also need to access store.insatester, it doesn't make sense.
Question1. How can I combine reducers to make the store having same level?
example)
store.data1 <- from calendar
store.data2 <- from insatester
Question2. Is my structure having problems? How can I change it?
You need to write a reducer that can access the full state. As you've seen, combineReducers limits you to a single slice of the state. You can put your custom reducer in front to handle the actions that need data from both slices, and let the rest fall into the combineReducers part.
This page of the Redux docs explains: http://redux.js.org/docs/recipes/reducers/BeyondCombineReducers.html#sharing-data-between-slice-reducers