redux sagas are blocking the browser - javascript

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)
}

Related

What difference between using `compose` for middleware or list them in `applyMiddleware`

What the difference between these configureStore functions and where has gone initialState argument?
import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
export default function configureStore(initialState){
const store = createStore(
rootReducer,
initialState,
applyMiddleware(thunk, logger) //list of middlewares in arguments
);
return store;
}
export default function configureStore() {
const store = compose( //composed middlewares
applyMiddleware(thunk),
applyMiddleware(logger)
)(createStore)(rootReducer);
return store;
}
From the documentation:
All compose does is let you write deeply nested function transformations without the rightward drift of the code. Don't give it too much credit!
and it's usage:
This is a functional programming utility, and is included in Redux as a convenience.
You might want to use it to apply several store enhancers in a row.
So in essence, it is a helpful utility if you want to apply a bunch of middleware:
...
compose(
applyMiddleware(thunk),
DevTools.instrument(),
// ... more enhancements, that may or may not be middleware
)
...
applyMiddleware is specifically for wrapping the store's dispatch function, whereas compose will allow you to add additional enhancers that may not have anything to do with dispatch, such as Redux Dev Tools.
Middleware only wraps the store's dispatch function. Technically, anything a middleware can do, you can do manually by wrapping every dispatch call, but it's easier to manage this in a single place and define action transformations on the scale of the whole project.

React-Native - Redux - access to store

I got a big problem for my project and cannot continue without that.
I have 2 screens : for example one for main page and another for second page.
So, there are 2 sections in my Navigation Drawer.
In main page, I save state into my store and get this:
object: {
fruits: ['apple', 'orange', 'banana'],
drinks: ['mojito', 'colar']
}
I want to access to the store of mainPage so in my second page I did :
store.getState().
And I got
object: {
fruits: [],
drinks: []
}
So I got the same state but it's empty. I don't understand why. If you have any ideas. I can maybe later show you more my code if necessary.
My store :
import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'remote-redux-devtools';
import reducer from '../reducers';
const middleware = applyMiddleware(thunk, logger);
export default function configureStore(initialState) {
const store = createStore(
reducer,
initialState,
middleware
);
return store;
};
For each component, there are a container? Or only one container for all components? I don't really understand that. Sorry I'm new to React-Native, Redux and navigation drawer. It's hard for me to make them works all together..
Maybe the problem is in connect() but I connected them together normally
Sorry it's a little late and hard for me to say anything without seeing your reducers. Within your mainPage, you need to dispatch actions which will trigger a concat to the fruits and drinks array. You can also move your store declaration outside the function declaration and export it. Importing and calling it in other screens will allow you to have access to it, but this isn't the recommended way. The standard way is to call mapStateToProps method and connect that to your component.

You must pass a component to the function returned by connect. Instead received undefined

The code below gives
Uncaught Error: You must pass a component to the function returned by connect. Instead received undefined
List.js
import React from 'react';
import { connect, bindActionCreators } from 'react-redux';
import PostList from '../components/PostList'; // Component I wish to wrap with actions and state
import postList from '../Actions/PostList' //Action Creator defined by me
const mapStateToProps = (state, ownProps) => {
return state.postsList
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({"postsList":postList},dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(PostList);
PostList.js
import React from 'react'
export const PostList = (props) => {
return <div>List</div>
}
Please help me with a solution?
You are doing import PostList from '../components/PostList'; so you need to use export default in your PostList.js file.
Otherwise you need to do import { PostList } from '../components/PostList';.
To whoever is interested, here is a nice article about es6 import/export syntax: http://www.2ality.com/2014/09/es6-modules-final.html
Not related to the asker specifically, but if you're facing this error, it's worth to check if you have the connect() syntax right:
const PreloadConnect = connect(mapStateToProps, {})(Preload);
export default PreloadConnect;
Note that Preload, is passed as a IIFE parameter.
More details can be found here.
There might be three reasons, that are summarized as follows:
Circular dependencies between components
Wrong usage of export and export default then imported the wrong way
Used the connect function wrongly, passed the wrong parameters
In my case is was Circular dependencies, and the circular-dependency-plugin helped me fix it.
In my case it was Expo server that sometimes doesn't catch filesaves on Windows (probably) and it was seening old version of the component I've tried to connect (I had no export there yet probably). Re-saving my component without really touching anything fixed the issue.
Restarting Expo server with cleaned cache would probably help as well.
In my case, it was because of the usage of enums (TypeScript).
Try without enums in your code.
Reason : Enums can go undefined during runtime.
Link to Related Question
Hope it solves your problem :)

React-native Redux counter example: explain about reducer

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';

How to dynamically load reducers for code splitting in a Redux application?

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.

Categories

Resources