I would like to refactor my Next.js webapp to have different pages handle different screens. Currently, I have this component holding several states to know in which screen I'm in. In the jsx section, I'm using {value && ... } to render the right component.
But I feel this is not good design, and won't be maintainable when adding more and more screens.
I would also like to avoid Redux as it is overkill for my project.
I was thinking about persisting data in cookies so I can retrieve them with getInitialProps in every component when rendering a new page, but is there a more elegant way?
I've read about tweaking the _app.js but I'm not sure to understand the consequences of doing so, and how it could help me..
Any suggestion?
When multiple of your pages need to make use of same data, you can make use of Context to store the result. It a good way to make a centralized storage without using complex and more self sufficient libraries like redux
You can implement context inside of _app.js file which must reside inside your root folder. This way next.js treats it as a root wrapper and you would just need to use 1 instance of Context
contexts/appContext
import React from 'react';
const AppContext = React.createContext();
export const AppProvider = AppContext.Provider;
export const AppConsumer = AppContext.Consumer;
export default AppContext;
_app.js
import React from 'react'
import App from 'next/app'
import AppProvider from '../contexts/appContext';
class MyApp extends App {
state={
data:[]
}
render() {
const { Component, pageProps } = this.props;
// You can implement logic in this component to fetch data and update state
return (
<div>
<AppProvider value={this.state.data}> // pass on value to context
<Component {...pageProps} />
</AppProvider>
</div>
)
}
}
export default MyApp
Now further each component can make use of context value by using AppConsumer or using useContext if you use hooks
Please read more about how to use Context here
I'm completely new with sagas.
Recently I was experimenting with an infrastructure for my future projects, including react-router v4 and sagas.
Well following the Beginner tutorial of sagas and some investigation on the matter i create an approximation, the idea is to make every connected component to redux to be it's own "micro universe" and have it own reducer, and, of course it own sagas file.
Ok, go with the code, here is the base structure of a component folder and the src folder:
src/
+components/
+foo-component/
-actions.js
-constants.js
-defaultState.js
-index.js
-reducer.js
-sagas.js
-App.js
-index.js
-rootReducer.js
-rootSagas.js
-store.js
Now, this is my sagas.js file fir foo-component, im creating the worker saga and the watcher saga, then i export them as an array to use in the rootSagas.js:
import { put, takeEvery } from 'redux-saga/effects'
import { COUNT } from './actions'
// Worker count
function* count() {
yield put({ type: COUNT })
}
// Watcher helloSaga
function* watchCount() {
yield takeEvery(COUNT, count)
}
const CounterSagas = [
count(),
watchCount()
]
export default CounterSagas
In the rootSagas.js file i get this sagas and export a single entry point which yield's all sagas at once:
import { all } from 'redux-saga/effects'
// sagas
import CounterSagas from './components/counter/sagas'
// run all
export default function* rootSagas() {
yield all([
// decompose
...CounterSagas,
])
}
Finally, in the store config the sagas are initiated:
import { compose, applyMiddleware, createStore } from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootReducer from './rootReducer'
import rootSagas from './rootSagas'
let store
const sagaMiddleware = createSagaMiddleware()
if (process.env.NODE_ENV === 'production') {
store = createStore(
rootReducer,
applyMiddleware(sagaMiddleware)
)
} else {
store = createStore(
rootReducer,
compose(
applyMiddleware(sagaMiddleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
)
}
sagaMiddleware.run(rootSagas)
export default store
export const action = (type, payload) => store.dispatch({type, payload})
Ok, with this i have two problems:
The counter saga is fired one time automatically (not when the event is fired by a button).
When i fire the action COUNT, the browser run into a infinite loop and get blocked:
Error: An error was thrown inside one of your components, but React doesn't know what it was. This is likely due to browser flakiness. React does its best to preserve the "Pause on exceptions" behavior of the DevTools, which requires some DEV-mode only tricks. It's possible that these don't work in your browser. Try triggering the error in production mode, or switching to a modern browser. If you suspect that this is actually an issue with React, please file an issue.
I know, this is my inexperience talking out, so can anyone give me some guidance?
Thanks
Solved! the problem was in the middleware.run(sagas), removing it all works perfectly.
By removing middleware.run(sagas) the saga will actually be disabled. In fact the issue is that when you dispatch the action it will go to saga middleware and in the middleware we use put method(after watch) instead of dispatch and that will cause infinitely dispatch the same action. The problem will be solved by rename the action type or saga action type in takeEvery method first argument, they shouldn't be same!! like this:
// Watcher helloSaga
function* watchCount() {
yield takeEvery(A_COUNT, count)
}
I am learning react-native and redux from this article,
https://github.com/alinz/example-react-native-redux/tree/master/Counter, and I want to understand why inside folder reducers, there is an index.js with content as below:
import counter from './counter';
export {
counter
};
I dont understand why we need this, since in the same folder reducers, there is counter.js with content as follow
export default function counter(state = initialState, action = {}) {
...
}
it already export default counter, why does index.js do it again
If your application grows with lots of reducers, you can 'import nameHere from reducers'. (it is just a convenience). Also, your code is easier to 'refactor' ussually, since you don't need to change the actual import, but you can for instance import multiple from this same file.
// this is preferred
import { ScalesReducer, BoxReducer } from './reducers';
// does the same, takes more space (more distraction in your code)
import ScalesReducer from './reducers/ScalesReducer';
import BoxReducer from './reducers/BoxReducer';
I am using react-redux actions for app level state changes.
but not all components should be rendered when certain actions are triggered.
how can I check which action was triggered from any rendering React component?
Use redux-logger to log all the actions triggered in your dev tools console.
https://www.npmjs.com/package/redux-logger
It's a middle-ware so you 'll have to add it like so in your store:
/* ...import main reducer from wherever */
import createLogger from 'redux-logger';
import { createStore, applyMiddleware } from 'redux';
const logger = createLogger();
const store = createStore(
reducer,
applyMiddleware(/*..all the other middleares,*/ logger)
);
.
Bonus tip: You could integrate redux-devtools.
It adds a side dock to your site that tracks all the actions. See screen shot below:
I'm going migrate to Redux.
My application consists of a lot of parts (pages, components) so I want to create many reducers. Redux examples show that I should use combineReducers() to generate one reducer.
Also as I understand Redux application should have one store and it is created once the application starts. When the store is being created I should pass my combined reducer. This makes sense if the application is not too big.
But what if I build more than one JavaScript bundle? For example, each page of application has own bundle. I think in this case the one combined reducer is not good. I looked through the sources of Redux and I have found replaceReducer() function. It seems to be what I want.
I could create combined reducer for each part my application and use replaceReducer() when I move between parts of application.
Is this a good approach?
Update: see also how Twitter does it.
This is not a full answer but should help you get started. Note that I'm not throwing away old reducers—I'm just adding new ones to the combination list. I see no reason to throw away the old reducers—even in the largest app you're unlikely to have thousands of dynamic modules, which is the point where you might want to disconnect some reducers in your application.
reducers.js
import { combineReducers } from 'redux';
import users from './reducers/users';
import posts from './reducers/posts';
export default function createReducer(asyncReducers) {
return combineReducers({
users,
posts,
...asyncReducers
});
}
store.js
import { createStore } from 'redux';
import createReducer from './reducers';
export default function configureStore(initialState) {
const store = createStore(createReducer(), initialState);
store.asyncReducers = {};
return store;
}
export function injectAsyncReducer(store, name, asyncReducer) {
store.asyncReducers[name] = asyncReducer;
store.replaceReducer(createReducer(store.asyncReducers));
}
routes.js
import { injectAsyncReducer } from './store';
// Assuming React Router here but the principle is the same
// regardless of the library: make sure store is available
// when you want to require.ensure() your reducer so you can call
// injectAsyncReducer(store, name, reducer).
function createRoutes(store) {
// ...
const CommentsRoute = {
// ...
getComponents(location, callback) {
require.ensure([
'./pages/Comments',
'./reducers/comments'
], function (require) {
const Comments = require('./pages/Comments').default;
const commentsReducer = require('./reducers/comments').default;
injectAsyncReducer(store, 'comments', commentsReducer);
callback(null, Comments);
})
}
};
// ...
}
There may be neater way of expressing this—I'm just showing the idea.
This is how I implemented it in a current app (based on code by Dan from a GitHub issue!)
// Based on https://github.com/rackt/redux/issues/37#issue-85098222
class ReducerRegistry {
constructor(initialReducers = {}) {
this._reducers = {...initialReducers}
this._emitChange = null
}
register(newReducers) {
this._reducers = {...this._reducers, ...newReducers}
if (this._emitChange != null) {
this._emitChange(this.getReducers())
}
}
getReducers() {
return {...this._reducers}
}
setChangeListener(listener) {
if (this._emitChange != null) {
throw new Error('Can only set the listener for a ReducerRegistry once.')
}
this._emitChange = listener
}
}
Create a registry instance when bootstrapping your app, passing in reducers which will be included in the entry bundle:
// coreReducers is a {name: function} Object
var coreReducers = require('./reducers/core')
var reducerRegistry = new ReducerRegistry(coreReducers)
Then when configuring the store and routes, use a function which you can give the reducer registry to:
var routes = createRoutes(reducerRegistry)
var store = createStore(reducerRegistry)
Where these functions look something like:
function createRoutes(reducerRegistry) {
return <Route path="/" component={App}>
<Route path="core" component={Core}/>
<Route path="async" getComponent={(location, cb) => {
require.ensure([], require => {
reducerRegistry.register({async: require('./reducers/async')})
cb(null, require('./screens/Async'))
})
}}/>
</Route>
}
function createStore(reducerRegistry) {
var rootReducer = createReducer(reducerRegistry.getReducers())
var store = createStore(rootReducer)
reducerRegistry.setChangeListener((reducers) => {
store.replaceReducer(createReducer(reducers))
})
return store
}
Here's a basic live example which was created with this setup, and its source:
Example
Source
It also covers the necessary configuration to enable hot reloading for all your reducers.
There is now a module that adds injecting reducers into the redux store. It is called Redux Injector.
Here is how to use it:
Do not combine reducers. Instead put them in a (nested) object of functions as you would normally but without combining them.
Use createInjectStore from redux-injector instead of createStore from redux.
Inject new reducers with injectReducer.
Here is an example:
import { createInjectStore, injectReducer } from 'redux-injector';
const reducersObject = {
router: routerReducerFunction,
data: {
user: userReducerFunction,
auth: {
loggedIn: loggedInReducerFunction,
loggedOut: loggedOutReducerFunction
},
info: infoReducerFunction
}
};
const initialState = {};
let store = createInjectStore(
reducersObject,
initialState
);
// Now you can inject reducers anywhere in the tree.
injectReducer('data.form', formReducerFunction);
Full Disclosure: I am the creator of the module.
As of October 2017:
Reedux
implements what Dan suggested and nothing more, without touching your store, your project or your habits
There are other libraries too but they might have too many dependencies, less examples, complicated usage, are incompatible with some middlewares or require you to rewrite your state management. Copied from Reedux's intro page:
redux-modules
redux-module-builder
redux-stack
paradux
redux-dynamic-reducer
redux-injector
redux-dynamix
We released a new library that helps modulating a Redux app and allows dynamically adding/removing Reducers and middlewares.
Please take a look at
https://github.com/Microsoft/redux-dynamic-modules
Modules provide the following benefits:
Modules can be easily re-used across the application, or between multiple similar applications.
Components declare the modules needed by them and redux-dynamic-modules ensures that the module is loaded for the component.
Modules can be added/removed from the store dynamically, ex. when a component mounts or when a user performs an action
Features
Group together reducers, middleware, and state into a single, re-usable module.
Add and remove modules from a Redux store at any time.
Use the included component to automatically add a module when a component is rendered
Extensions provide integration with popular libraries, including redux-saga and redux-observable
Example Scenarios
You don't want to load the code for all your reducers up front. Define a module for some reducers and use DynamicModuleLoader and a library like react-loadable to download and add your module at runtime.
You have some common reducers/middleware that need to be re-used in different areas of your application. Define a module and easily include it in those areas.
You have a mono-repo that contains multiple applications which share similar state. Create a package containing some modules and re-use them across your applications
Here is another example with code splitting and redux stores, pretty simple & elegant in my opinion. I think it may be quite useful for those who are looking for a working solution.
This store is a bit simplified it doesn't force you to have a namespace (reducer.name) in your state object, of course there may be a collision with names but you can control this by creating a naming convention for your reducers and it should be fine.
Following is the approach that I have followed to implement this.
We have our store file where we will have static reducers who will always be present in the reducer, and dynamic reducers will be added when required component is mounted.
reducer file
The staticReducers that will always be present in the application
const staticReducers = combineReducers({
entities1: entities1,
});
const createReducer = (asyncReducers) => {
return combineReducers({
staticReducers,
...asyncReducers,
});
};
export default createReducer;
store file
Here we can have our custom middleware, loggers etc, those we can pass in the middlewares array.and use it as follows.
import { createStore, applyMiddleware, compose } from "redux";
import createReducer from "./reducers";
import api from "./middlewares/api";
const middlewares = [ api, thunkMiddleware]
const middlewareEnhancer = applyMiddleware(...middlewares)
const enhancers = [middlewareEnhancer]
const composedEnhancers = composeWithDevTools(compose(...enhancers))
const store = createStore(createReducer(), composedEnhancers)
export default function configureStore() {
// Add a dictionary to keep track of the registered async reducers
store.asyncReducers = {};
// Create an inject reducer function
// This function adds the async reducer, and creates a new combined
// reducer
store.injectReducer = (key, asyncReducer) => {
store.asyncReducers[key] = asyncReducer;
store.replaceReducer(createReducer(store.asyncReducers));
};
// Return the modified store
return store;
}
export function getStore() {
return store;
}
Now suppose we have a component that we want to load dynamically and that component might have its own slice(reducer), then we can call inject reducer to dynamically add its to the existing reducer.
const Counter2 = React.lazy(() =>
import("../counter2/counter2").then(async (module) => {
const entities2 = await
import("../../../store/entities2").then((todosModule) =>
todosModule.default);
store.injectReducer("entities2", entities2);
return module;
})
)
<React.Suspense fallback={<div>loading...</div>}>
<Counter2 />
</React.Suspense>
After mounting this component we will find entities2 injected to our store.