Hot to fix ngrx auto overrite all another stores when dispach any - ngrx

I need help whit Angular ngRx store. I spend couple days for this, but I can't figure out((
I have 2 store in my app and when i dispatch one of them (store.dispatch(new LoadProperties() )) my previous value from another store overrated
this my effects, requcers and app.module
recipes.effects.ts
#Effect() loadRecipes$ = this.dataPersistence.fetch(fromRecipes.LOAD_RECIPES, {
run: (action: LoadRecipes, state: RecipesState) => {
return this.recipesService.getRecipes()
.pipe(map((res: Recipe[]) => new LoadRecipesSuccess(res)));
},
onError: (action: LoadRecipes, error) => {
this.toaster.errorSnackBar(error.statusText, 'Cant fetch categories');
return of(new LoadRecipesError(error));
}
});
properties.effects.ts
#Effect() loadProperties$ = this.dataPersistence.fetch(fromProperties.LOAD_PROPERTIES, {
run: (action: LoadProperties, state: PropertiesState) => {
return this.propertiesService.getProperties()
.pipe(map((res: Property[]) => new LoadPropertiesSuccess(res)));
},
onError: (action: LoadProperties, error) => {
this.toaster.errorSnackBar(error.statusText, 'Cant fetch properties');
return of(new LoadPropertiesError(error));
}
});
export interface AppState { recipes: fromRecipes.RecipesState; properties: fromProperties.PropertiesState;
}
imports: [
SharedModule,
BrowserModule.withServerTransition({appId: 'my-app'}),
HttpClientModule,
ToastrModule.forRoot(),
BrowserAnimationsModule,
StoreModule.forRoot(fromApp.appReducer),
EffectsModule.forRoot(fromApp.appEffects),
StoreDevtoolsModule.instrument({ maxAge: 10 }),
NxModule.forRoot()
...]
upd
this is recipe reducer
Hey there! recipes reducers
witch (action.type) {
case RecipesActions.LOAD_RECIPES:
return {
...state, // the incoming state
loading: true // turn loading indicator on
};
case RecipesActions.LOAD_RECIPES_SUCCESS:
return {
...state, // the incoming state
recipes: action.payload,
loaded: true, // recipes were loaded
loading: false, // turn loading indicator off
};
case RecipesActions.LOAD_RECIPES_ERROR:
return {
...state, // the incoming state
loaded: false, // the recipes were loaded
loading: false, // turn loading indicator off
};
this is properties.reducer
switch (action.type) {
case PropertiesActions.LOAD_PROPERTIES:
return {
...state, // the incoming state
loading: true // turn loading indicator on
};
case PropertiesActions.LOAD_PROPERTIES_SUCCESS:
return {
...state, // the incoming state
properties: action.payload,
loaded: true, // properties were loaded
loading: false, // turn loading indicator off
};
case PropertiesActions.LOAD_PROPERTIES_ERROR:
return {
...state, // the incoming state
loaded: false, // the properties were loaded
loading: false, // turn loading indicator off
};

Seems like the reducers don't have a default case, which it should have otherwise the state will be undefined.
switch (action.type) {
... other cases ...
default:
return state;
}

Related

how to fix issue with action type reducer

I've created react app with react-reducer.
I've declared types for actions and in the main Reduce I face an issue:
Can't read property type of undefined
import {ADD_USERS, DELETE_USER, GET_USERS} from '../types'
const initialState = {
users: [
{
id: 1,
name: 'Oksana'
},
{
id: 2,
name: 'Serge'
},
],
loading: true
}
export default function(state = initialState, action){
switch(action.type){
case GET_USERS:
return {
...state,
users: action.payload,
loading: false
}
case ADD_USERS:
const newId = state.users[state.users.length-1] + 1
return {
...state,
users: {
[newId] : {
id: newId,
name: action.name
}
},
loading: false
}
case DELETE_USER :
return {
...state,
users: state.users.filter(i => i.id !== action.id)
}
default: return state
}
}
so here I implement simple get/delete/add methods.
export function getUsers (name) {
return {
type: GET_USERS,
payload: name
}
}
....
and this is actions file
I export all the actions, idk where could I make a mistake
the rest of actions I've not mentioned.
Your switch statement uses action.type, however action is undefined in at least one of the actions you pass to the reducer. Either add a default value to action or add a guard before the switch-statement like so:
export default function(state = initialState, action){
if (!action) {
return state
}
switch(action.type){
case GET_USERS:
return {
...state,
users: action.payload,
loading: false
}
...
As you have a default condition already, adding a default value to action, e.g. action = {} might be the cleaner solution.
If you use Typescript you could also just do the following:
export default function(state = initialState, action){
switch(action?.type){
case GET_USERS:
return {
...state,
users: action.payload,
loading: false
}
...

Problem With Redux's Store.dispatch Doesn't Get Updated

Upgrading meteor (from 1.4 to 1.7) and react (from 15.3.2 to 16.8.6).
"react-redux": "^4.4.10"
"redux": "3.5.2"
I found my codes were unable to update/store using Store.dispatch(), the Store just not updated.
My ACTIONS file as below:
actions/config.js
...
export default {
load({Meteor, Store}) {
return new Promise((resolve, reject) => {
Meteor.call('variables.load', null, (err, data) => {
if (err) {
reject({_error: err.reason });
return;
}
console.log("************ Store (A) = "+JSON.stringify(Store.getState()))
Store.dispatch({
type: LOAD_CONFIG,
data
});
resolve();
console.log("************ Store (B) = "+JSON.stringify(Store.getState()))
});
});
},
...
Both the console.log() were having the following:
Store (A) = {"router":{"locationBeforeTransitions":{"pathname":"/settings/config","search":"","hash":"","action":"PUSH","key":"zif4ls","basename":"/crm","query":{}}},"form":{"config":{"syncErrors":{"reportLimit":"Required"}}},"loadingBar":{}}
Store (B) = {"router":{"locationBeforeTransitions":{"pathname":"/settings/config","search":"","hash":"","action":"PUSH","key":"zif4ls","basename":"/crm","query":{}}},"form":{"config":{"syncErrors":{"reportLimit":"Required"}}},"loadingBar":{}}
Which I do expect it will have something like "reportLimit":6 , which was confirmed to have loaded into the data variable. Instead, I was getting the following error in browser console:
Uncaught TypeError: Cannot read property 'data' of undefined
Is there anything wrong/breaking changes you could think of? As these codes had been working before the upgrade.
EDIT:
I've further narrowed down the problem. It may seems to be my Routes is not calling the Reducer.
I've since change the code in my Routes to remove the need to use require.ensure .
routes.jsx (prior)
{
path: 'config',
getComponent(nextState, cb) {
require.ensure([], (require) => {
Store.injectReducer('config', require('./reducers').config)
cb(null, require('./containers/config.js'))
}, 'config')
}
},
routes.jsx (latest, to get rid of require.ensure)
{
path: 'config',
getComponent(nextState, cb) {
import('./containers/config.js')
.then(mod => {Store.injectReducer('config', require('./reducers').config);
cb(null, mod);});
}
},
Then I notice that in the reducer:
reducer/config.js
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[LOAD_CONFIG]: (state, action) => ({
...state,
data: action.data
})
};
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {
data: null
};
export default function configReducer(state = initialState, action) {
console.log("************ Reducer")
const handler = ACTION_HANDLERS[action.type];
return handler ? handler(state, action) : state;
}
As per logged, function configReducer doesn't seem to have been called elsewhere.
After much trial-and-error, confirmed the problem is with the routing part, final changes:
routes.jsx (final)
{
path: 'config',
getComponent(nextState, cb) {
import('./containers/config').then(mod => {
Store.injectReducer('config', require('./reducers/config').default);
cb(null, mod.default);
});
}
}
Key points:
1) This is how the to migrate from require.ensure (used by webpack) to without relying on webpack (which was my case as am fully using Meteor Atmosphere to run)
2) mod and require(...).xxx had changed to mod.default and require(...).default if reducer function is exported as export default, otherwise said reducer will not be called.
Really took me weeks to figure this out!
Try this:
Store.dispatch({
type: LOAD_CONFIG,
data: data
});

What's the Redux pattern for updating a single property

From all the reading i've done, all I can see for updating single properties in a reducer is to create an action for updating that property. Is the accepted pattern really to make an action for every property?
For example, let's say I had a todo reducer with a default state with three different properties. Is the expected pattern to create 3 different update actions, one for each property, like this?
const todo = ({
text: '',
complete: false,
assignedUser: null
}, action) => {
switch(action.type) {
case 'UPDATE_TEXT':
return {...state, text: action.payload}
case 'UPDATE_COMPLETE':
return {...state, complete: action.payload}
case 'UPDATE_ASSIGNEDUSER':
return {...state, assignedUser: action.payload}
default:
return state
}
}
If not a separate update action for each property, then what are people doing as the pattern for updating each property?
It is another way and maybe can help you:
const todo = ({
text: '',
complete: false,
assignedUser: null
}, action) => {
switch(action.type) {
case 'UPDATE':
return {...state, ...action.payload}
default:
return state
}
}
Now, everything you pass by action's payload, will enter into the state, for example:
const updateTodo = payload => ({type: "UPDATE", payload})
dispatch(updateTodo({
complete: true,
text: "Hello world",
assignedUser: {}
}))

Redux Thunk - State Undefined After Dispatching Multiple Actions

When I dispatch an action after dispatching another action, I noticed that my state isFetching becomes undefined. I think it's probably some asynchronous issue with the actions dispatching after the other. How would I fix this so that I can get both actions to dispatch correctly?
My redux module:
const FETCHING = 'FETCHING'
const FETCHING_KEYWORD = 'FETCHING_KEYWORD'
function fetching() {
return {
type: FETCHING,
}
}
function settingKeyWord(keyWord) {
return {
type: FETCHING_KEYWORD,
keyWord,
}
}
export function fetchKeyWord (keyWord) {
return (dispatch, getState) => {
const { isFetching } = getState()
const { keyWord } = getState()
// After I dispatch the two actions here, isFetching becomes undefined
dispatch(fetching())
dispatch(settingKeyWord(keyWord))
}
}
const initialState = {
isFetching: false,
keyWord: '',
}
export default function results(state = initialState, action) {
switch (action.type) {
case FETCHING:
return {
isFetching: true,
}
case FETCHING_KEYWORD:
return {
keyWord: action.keyWord,
}
default:
return state
}
}
The reducer cases need to return the entire state, not just the updated part, so the problem should also occur when dispatching either action normally. You can fix it by using Object.assign or object-spread syntax in the reducer cases. For example, for Fetching:
case FETCHING:
return Object.assign((), state, {
isFetching: true,
})
or
case FETCHING:
return {...state,
isFetching: true,
})

Handling loading state of multiple async calls in an action/reducer based application

I donĀ“t think this issue is bound to a specific framework or library, but applies to all store based application following the action - reducer pattern.
For clarity, I am using Angular and #ngrx.
In the application I am working on we need to track the loading state of individual resources.
The way we handle other async requests is by this, hopefully familiar, pattern:
Actions
GET_RESOURCE
GET_RESOURCE_SUCCESS
GET_RESOURCE_FAILURE
Reducer
switch(action.type)
case GET_RESOURCE:
return {
...state,
isLoading = true
};
case GET_RESOURCE_SUCCESS:
case GET_RESOURCE_FAILURE:
return {
...state,
isLoading = false
};
...
This works well for async calls where we want to indicate the loading state globally in our application.
In our application we fetch some data, say BOOKS, that contains a list of references to other resources, say CHAPTERS.
If the user wants to view a CHAPTER he/she clicks the CHAPTER reference that trigger an async call. To indicate to the user that this specific CHAPTER is loading, we need something more than just a global isLoading flag in our state.
The way we have solved this is by creating a wrapping object like this:
interface AsyncObject<T> {
id: string;
status: AsyncStatus;
payload: T;
}
where AsyncStatus is an enum like this:
enum AsyncStatus {
InFlight,
Success,
Error
}
In our state we store the CHAPTERS like so:
{
chapters: {[id: string]: AsyncObject<Chapter> }
}
However, I feel like this 'clutter' the state in a way and wonder if someone has a better solution / different approach to this problem.
Questions
Are there any best practices for how to handle this scenario?
Is there a better way of handling this?
I have faced several times this kind of situation but the solution differs according to the use case.
One of the solution would be to have nested reducers. It is not an antipattern but not advised because it is hard to maintain but it depends on the usecase.
The other one would be the one I detail below.
Based on what you described, your fetched data should look like this:
[
{
id: 1,
title: 'Robinson Crusoe',
author: 'Daniel Defoe',
references: ['chp1_robincrusoe', 'chp2_robincrusoe'],
},
{
id: 2,
title: 'Gullivers Travels',
author: 'Jonathan Swift',
references: ['chp1_gulliverstravels', 'chp2_gulliverstravels', 'chp3_gulliverstravels'],
},
]
So according to your data, your reducers should look like this:
{
books: {
isFetching: false,
isInvalidated: false,
selectedBook: null,
data: {
1: { id: 1, title: 'Robinson Crusoe', author: 'Daniel Defoe' },
2: { id: 2, title: 'Gullivers Travels', author: 'Jonathan Swift' },
}
},
chapters: {
isFetching: false,
isInvalidated: true,
selectedChapter: null,
data: {
'chp1_robincrusoe': { isFetching: false, isInvalidated: true, id: 'chp1_robincrusoe', bookId: 1, data: null },
'chp2_robincrusoe': { isFetching: false, isInvalidated: true, id: 'chp2_robincrusoe', bookId: 1, data: null },
'chp1_gulliverstravels': { isFetching: false, isInvalidated: true, id: 'chp1_gulliverstravels', bookId: 2, data: null },
'chp2_gulliverstravels': { isFetching: false, isInvalidated: true, id: 'chp2_gulliverstravels', bookId: 2, data: null },
'chp3_gulliverstravels': { isFetching: false, isInvalidated: true, id: 'chp3_gulliverstravels', bookId: 2, data: null },
},
}
}
With this structure you won't need isFetching and isInvalidated in your chapter reducers as every chapter is a separated logic.
Note: I could give you a bonus details later on on how we can leverage the isFetching and isInvalidated in a different way.
Below the detailed code:
Components
BookList
import React from 'react';
import map from 'lodash/map';
class BookList extends React.Component {
componentDidMount() {
if (this.props.isInvalidated && !this.props.isFetching) {
this.props.actions.readBooks();
}
}
render() {
const {
isFetching,
isInvalidated,
data,
} = this.props;
if (isFetching || (isInvalidated && !isFetching)) return <Loading />;
return <div>{map(data, entry => <Book id={entry.id} />)}</div>;
}
}
Book
import React from 'react';
import filter from 'lodash/filter';
import { createSelector } from 'reselect';
import map from 'lodash/map';
import find from 'lodash/find';
class Book extends React.Component {
render() {
const {
dispatch,
book,
chapters,
} = this.props;
return (
<div>
<h3>{book.title} by {book.author}</h3>
<ChapterList bookId={book.id} />
</div>
);
}
}
const foundBook = createSelector(
state => state.books,
(books, { id }) => find(books, { id }),
);
const mapStateToProps = (reducers, props) => {
return {
book: foundBook(reducers, props),
};
};
export default connect(mapStateToProps)(Book);
ChapterList
import React from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import map from 'lodash/map';
import find from 'lodash/find';
class ChapterList extends React.Component {
render() {
const { dispatch, chapters } = this.props;
return (
<div>
{map(chapters, entry => (
<Chapter
id={entry.id}
onClick={() => dispatch(actions.readChapter(entry.id))} />
))}
</div>
);
}
}
const bookChapters = createSelector(
state => state.chapters,
(chapters, bookId) => find(chapters, { bookId }),
);
const mapStateToProps = (reducers, props) => {
return {
chapters: bookChapters(reducers, props),
};
};
export default connect(mapStateToProps)(ChapterList);
Chapter
import React from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import map from 'lodash/map';
import find from 'lodash/find';
class Chapter extends React.Component {
render() {
const { chapter, onClick } = this.props;
if (chapter.isFetching || (chapter.isInvalidated && !chapter.isFetching)) return <div>{chapter.id}</div>;
return (
<div>
<h4>{chapter.id}<h4>
<div>{chapter.data.details}</div>
</div>
);
}
}
const foundChapter = createSelector(
state => state.chapters,
(chapters, { id }) => find(chapters, { id }),
);
const mapStateToProps = (reducers, props) => {
return {
chapter: foundChapter(reducers, props),
};
};
export default connect(mapStateToProps)(Chapter);
Book Actions
export function readBooks() {
return (dispatch, getState, api) => {
dispatch({ type: 'readBooks' });
return fetch({}) // Your fetch here
.then(result => dispatch(setBooks(result)))
.catch(error => dispatch(addBookError(error)));
};
}
export function setBooks(data) {
return {
type: 'setBooks',
data,
};
}
export function addBookError(error) {
return {
type: 'addBookError',
error,
};
}
Chapter Actions
export function readChapter(id) {
return (dispatch, getState, api) => {
dispatch({ type: 'readChapter' });
return fetch({}) // Your fetch here - place the chapter id
.then(result => dispatch(setChapter(result)))
.catch(error => dispatch(addChapterError(error)));
};
}
export function setChapter(data) {
return {
type: 'setChapter',
data,
};
}
export function addChapterError(error) {
return {
type: 'addChapterError',
error,
};
}
Book Reducers
import reduce from 'lodash/reduce';
import { combineReducers } from 'redux';
export default combineReducers({
isInvalidated,
isFetching,
items,
errors,
});
function isInvalidated(state = true, action) {
switch (action.type) {
case 'invalidateBooks':
return true;
case 'setBooks':
return false;
default:
return state;
}
}
function isFetching(state = false, action) {
switch (action.type) {
case 'readBooks':
return true;
case 'setBooks':
return false;
default:
return state;
}
}
function items(state = {}, action) {
switch (action.type) {
case 'readBook': {
if (action.id && !state[action.id]) {
return {
...state,
[action.id]: book(undefined, action),
};
}
return state;
}
case 'setBooks':
return {
...state,
...reduce(action.data, (result, value, key) => ({
...result,
[key]: books(value, action),
}), {});
},
default:
return state;
}
}
function book(state = {
isFetching: false,
isInvalidated: true,
id: null,
errors: [],
}, action) {
switch (action.type) {
case 'readBooks':
return { ...state, isFetching: true };
case 'setBooks':
return {
...state,
isInvalidated: false,
isFetching: false,
errors: [],
};
default:
return state;
}
}
function errors(state = [], action) {
switch (action.type) {
case 'addBooksError':
return [
...state,
action.error,
];
case 'setBooks':
case 'setBooks':
return state.length > 0 ? [] : state;
default:
return state;
}
}
Chapter Reducers
Pay extra attention on setBooks which will init the chapters in your reducers.
import reduce from 'lodash/reduce';
import { combineReducers } from 'redux';
const defaultState = {
isFetching: false,
isInvalidated: true,
id: null,
errors: [],
};
export default combineReducers({
isInvalidated,
isFetching,
items,
errors,
});
function isInvalidated(state = true, action) {
switch (action.type) {
case 'invalidateChapters':
return true;
case 'setChapters':
return false;
default:
return state;
}
}
function isFetching(state = false, action) {
switch (action.type) {
case 'readChapters':
return true;
case 'setChapters':
return false;
default:
return state;
}
}
function items(state = {}, action) {
switch (action.type) {
case 'setBooks':
return {
...state,
...reduce(action.data, (result, value, key) => ({
...result,
...reduce(value.references, (res, chapterKey) => ({
...res,
[chapterKey]: chapter({ ...defaultState, id: chapterKey, bookId: value.id }, action),
}), {}),
}), {});
};
case 'readChapter': {
if (action.id && !state[action.id]) {
return {
...state,
[action.id]: book(undefined, action),
};
}
return state;
}
case 'setChapters':
return {
...state,
...reduce(action.data, (result, value, key) => ({
...result,
[key]: chapter(value, action),
}), {});
},
default:
return state;
}
}
function chapter(state = { ...defaultState }, action) {
switch (action.type) {
case 'readChapters':
return { ...state, isFetching: true };
case 'setChapters':
return {
...state,
isInvalidated: false,
isFetching: false,
errors: [],
};
default:
return state;
}
}
function errors(state = [], action) {
switch (action.type) {
case 'addChaptersError':
return [
...state,
action.error,
];
case 'setChapters':
case 'setChapters':
return state.length > 0 ? [] : state;
default:
return state;
}
}
Hope it helps.

Resources