ReplaceReducer causing unexpected key error - javascript

I have a React app that dynamically loads a module, including the module's reducer function, and then calls Redux's replaceReducer to, well, replace the reducer. Unfortunately I'm getting an error of
Unexpected key "bookEntry" found in initialState argument passed to createStore. Expected to find one of the known reducer keys instead: "bookList", "root". Unexpected keys will be ignored.
where bookEntry was a key on the older reducer that's getting replaced. And starting with the bookEntry module and switching to bookList causes this inverse error
Unexpected key "bookList" found in initialState argument passed to createStore. Expected to find one of the known reducer keys instead: "bookEntry", "root". Unexpected keys will be ignored.
The code is below - un-commenting the commented code does in fact fix this, but I'm guessing it shouldn't be needed.
Am I doing something else wrong with Redux that's making this code necessary?
function getNewReducer(reducerObj){
if (!reducerObj) return Redux.combineReducers({ root: rootReducer });
//store.replaceReducer(function(){
// return {
// root: rootReducer()
// }
//});
store.replaceReducer(Redux.combineReducers({
[reducerObj.name]: reducerObj.reducer,
root: rootReducer
}));
}

In general we don’t suggest you to “clean up” the data when changing routes or loading new modules. This makes the application a little less predictable. If we’re talking about hundreds of thousands of records, then sure. Is this the volume of the data you plan to load?
If there’s just a couple of thousands of items on every page, there is no benefit to unloading them, and there are downsides associated with the complexity you add to the application. So make sure you’re solving a real problem, and not optimizing prematurely.
Now, to the warning message. The check is defined inside combineReducers(). It means that unexpected state keys will be discarded. After you removed the bookEntry reducer that managed state.bookEntry, that part of the state was no longer recognized by the new root reducer, and combineReducers() logged a warning that it’s going to be discarded. Note that this is a warning, and not an error. Your code runs just fine. We use console.error() to make warning prominent, but it didn’t actually throw, so you can safely ignore it.
We don’t really want to make the warning configurable because you’re essentially implicitly deleting part of the application state. Usually people do this by mistake, and not intentionally. So we want to warn about that. If you want to get around the warning, your best bet is to write the root reducer (currently generated by combineReducers()) by hand. It would look like this:
// I renamed what you called "root" reducer
// to "main" reducer because the root reducer
// is the combined one.
let mainReducer = (state, action) => ...
// This is like your own combineReducers() with custom behavior
function getRootReducer(dynamicReducer) {
// Creates a reducer from the main and a dynamic reducer
return function (state, action) {
// Calculate main state
let nextState = {
main: mainReducer(state.main, action)
};
// If specified, calculate dynamic reducer state
if (dynamicReducer) {
nextState[dynamicReducer.name] = dynamicReducer.reducer(
nextState[dynamicReducer.name],
action
);
}
return nextState;
};
}
// Create the store without a dynamic reducer
export function createStoreWithoutDynamicReducer() {
return Redux.createStore(getRootReducer());
}
// Later call this to replace the dynamic reducer on a store instance
export function setDynamicReducer(store, dynamicReducer) {
store.replaceReducer(getRootReducer(dynamicReducer));
}
However the pattern we recommend is to keep the old reducers around.

Related

Redux: Is it common to do deep-compare at reducer and return original state if no change?

Action: fetch an array of data from the server.
Reducer: save an array of data to store.
My reducer saves the array of data in the expected immutable fashion:
return {
...state,
arrayOfData: fetchedArrayOfData,
};
The fetch action happens periodically, but the content of the array remains the same most of the time. However, since a new reference is saved each time, the selector for this data will be considered "changed", and doesn't memoize well if used as an input for creatorSelector.
The only solution I can think of is to perform a deep-compare at the reducer, and if the contents are the same, simply return the original state:
return state;
If this common practice/pattern?
Note: I tried to look around, and most projects are doing the same thing that I was doing (i.e. return new state object), which will do not memoize well and causes selector transformations to run.
There are some solutions like using immutableJS as mentioned in the comments but you can return your state conditionally by comparing your fetchedArrayOfData with the last one (which is stored in your state).
Assume there is a comparison function that gives two arrays and compares them.
In the reducer:
const previousFetchedData = state.fetchedArrayOfData;
const newFetchedData = action.payload.fetchedArrayOfData;
const resultOfComparision = isSameArray(previousFetchedData, newFetchedData) // true or false
if (resultOfComparision) { // case of same arrays
return state
} else { // case of different arrays
...state,
arrayOfData: fetchedArrayOfData,
};
Note 1: you can create your own comparison function but there are many nice ones in this post of StackOverflow which you can use them.
Note 2: using immutableJs and conditional returning data from reducer is common(in such scenario) and don't worry about using them.
Note 3: You can also compare your data at the component level by using the traditional way with shouldComponentUpdate.
Node 4: using middlewares like redux-saga will be useful, you can also implement the isSameArray function in the saga and then dispatch the proper action. read more on saga documentation.
Note 5: The best solution (in my thought) is to handle this case on the backend services with 304 status which means Not modified. then you can easily determine the right action according to the response status. more info on MDN documentation.

How can I use an object as initializer for custom hooks without adding complexity/state or inviting future problems?

I just started using hooks in react and am creating a prototype custom hook for a framework.
The hook should take an object as an argument for initialization and cleanup (setting up/removing callbacks for example).
Here is my simplified Code so far:
export function useManager(InitObj) {
const [manager] = useState(() => new Manager());
useEffect(() => {
manager.addRefs(InitObj)
return () => manager.removeRefs(InitObj)
}, [manager]);
return manager;
}
to be used like this:
useManager({ cb1: setData1, cb2: setData2... })
In future Iterations the Manager might be a shared instance, so I need to be able to be specific about what I remove upon cleanup.
I put console.log all over the place to see If i correctly understand which code will be run during a render call. From what I can tell this code does 100% what I expeted it to do!
Unfortunately (and understandably) I get a warning because I did not include InitObj in the effects dependencies. But since I get an object literal simply putting it in there will cause the effect to be cleaned up/rerun on every render call since {} != {} which would be completely unnecessary.
My research so far only revealed blog posts like this one, but here only primitive data is used that is easily classified as "the same" (1 == 1)
So far I have found 3 possible solutions that I am not completely happy with:
using useMemo to memoize the object literal outside the hook
useManager(useMemo(() => { cb: setData }, []))
This adds more responsibility on the developer using my code => not desirable!
using useState inside the hook
const [iniOBj] = useState(InitObj);
A lot better already, but it adds state that does not feel like state. And it costs (minimal) execution time and memory, I would like to avoid that if possible.
using // eslint-disable-next-line react-hooks/exhaustive-deps
Works for sure, but there might still be other dependencies that might be missed if I simply deactivate the warning.
So my question is:
How can I use an object as initializer for custom hooks without adding complexity/state or inviting future problems?
I half expect that the useState option will be my best choice, but since I am new to hooks there might still be something that eluded my understanding so far.

simplify redux with generic action & reducer

In React-Redux project, people usually create multiple actions & reducers for each connected component. However, this creates a lot of code for simple data updates.
Is it a good practice to use a single generic action & reducer to encapsulate all data changes, in order to simplify and fasten app development.
What would be the disadvantages or performance loss using this method. Because I see no significant tradeoff, and it makes development much easier, and we can put all of them in a single file! Example of such architecture:
// Say we're in user.js, User page
// state
var initialState = {};
// generic action --> we only need to write ONE DISPATCHER
function setState(obj){
Store.dispatch({ type: 'SET_USER', data: obj });
}
// generic reducer --> we only need to write ONE ACTION REDUCER
function userReducer = function(state = initialState, action){
switch (action.type) {
case 'SET_USER': return { ...state, ...action.data };
default: return state;
}
};
// define component
var User = React.createClass({
render: function(){
// Here's the magic...
// We can just call the generic setState() to update any data.
// No need to create separate dispatchers and reducers,
// thus greatly simplifying and fasten app development.
return [
<div onClick={() => setState({ someField: 1 })}/>,
<div onClick={() => setState({ someOtherField: 2, randomField: 3 })}/>,
<div onClick={() => setState({ orJustAnything: [1,2,3] })}/>
]
}
});
// register component for data update
function mapStateToProps(state){
return { ...state.user };
}
export default connect(mapStateToProps)(User);
Edit
So the typical Redux architecture suggests creating:
Centralized files with all the actions
Centralized files with all the reducers
Question is, why a 2-step process? Here's another architectural suggestion:
Create 1 set of files containing all the setXField() that handle all the data changes. And other components simply use them to trigger changes. Easy. Example:
/** UserAPI.js
* Containing all methods for User.
* Other components can just call them.
*/
// state
var initialState = {};
// generic action
function setState(obj){
Store.dispatch({ type: 'SET_USER', data: obj });
}
// generic reducer
function userReducer = function(state = initialState, action){
switch (action.type) {
case 'SET_USER': return { ...state, ...action.data };
default: return state;
}
};
// API that we export
let UserAPI = {};
// set user name
UserAPI.setName = function(name){
$.post('/user/name', { name }, function({ ajaxSuccess }){
if (ajaxSuccess) setState({ name });
});
};
// set user picture URL
UserAPI.setPicture = function(url){
$.post('/user/picture', { url }, function({ ajaxSuccess }){
if (ajaxSuccess) setState({ url });
});
};
// logout, clear user
UserAPI.logout = function(){
$.post('/logout', {}, function(){
setState(initialState);
});
};
// Etc, you got the idea...
// Moreover, you can add a bunch of other User related methods,
// like some helper methods unrelated to Redux, or Ajax getters.
// Now you have everything related to User available in a single file!
// It becomes much easier to read through and understand.
// Finally, you can export a single UserAPI object, so other
// components only need to import it once.
export default UserAPI
Please read through the comments in the code section above.
Now instead of having a bunch of actions/dispatchers/reducers. You have 1 file encapsulating everything needed for the User concept. Why is it a bad practice? IMO, it makes programmer's life much easier, and other programmers can just read through the file from top to bottom to understand the business logic, they don't need to switch back and forth between action/reducer files. Heck, even redux-thunk isn't needed! And you can even test the functions one by one as well. So testability is not lost.
Firstly, instead of calling store.dispatch in your action creator, it should return an object (action) instead, which simplifies testing and enables server rendering.
const setState = (obj) => ({
type: 'SET_USER',
data: obj
})
onClick={() => this.props.setState(...)}
// bind the action creator to the dispatcher
connect(mapStateToProps, { setState })(User)
You should also use ES6 class instead of React.createClass.
Back to the topic, a more specialised action creator would be something like:
const setSomeField = value => ({
type: 'SET_SOME_FIELD',
value,
});
...
case 'SET_SOME_FIELD':
return { ...state, someField: action.value };
Advantages of this approach over your generic one
1. Higher reusability
If someField is set in multiple places, it's cleaner to call setSomeField(someValue) than setState({ someField: someValue })}.
2. Higher testability
You can easily test setSomeField to make sure it's correctly altering only the related state.
With the generic setState, you could test for setState({ someField: someValue })} too, but there's no direct guarantee that all your code will call it correctly.
Eg. someone in your team might make a typo and call setState({ someFeild: someValue })} instead.
Conclusion
The disadvantages are not exactly significant, so it's perfectly fine to use the generic action creator to reduce the number of specialised action creators if you believe it's worth the trade-off for your project.
EDIT
Regarding your suggestion to put reducers and actions in the same file: generally it's preferred to keep them in separate files for modularity; this is a general principle that is not unique to React.
You can however put related reducer and action files in the same folder, which might be better/worse depending on your project requirements. See this and this for some background.
You would also need to export userReducer for your root reducer, unless you are using multiple stores which is generally not recommended.
I mostly use redux to cache API responses mostly, here are few cases where i thought it is limited.
1) What if i'm calling different API's which has the same KEY but goes to a different Object?
2) How can I take care if the data is a stream from a socket ? Do i need to iterate the object to get the type(as the type will be in the header and response in the payload) or ask my backend resource to send it with a certain schema.
3) This also fails for api's if we are using some third party vendor where we have no control of the output we get.
It's always good to have control on what data going where.In apps which are very big something like a network monitoring application we might end up overwriting the data if we have same KEY and JavaScript being loosed typed may end this to a lot weird way this only works for few cases where we have complete control on the data which is very few some thing like this application.
Okay i'm just gonna write my own answer:
when using redux ask yourself these two questions:
Do I need access to the data across multiple components?
Are those components on a different node tree? What I mean is it isn't a child component.
If your answer is yes then use redux for these data as you can easily pass those data to your components via connect() API which in term makes them containers.
At times if you find yourself the need to pass data to a parent component, then you need to reconsider where your state lives. There is a thing called Lifting the State Up.
If your data only matters to your component, then you should only use setState to keep your scope tight. Example:
class MyComponent extends Component {
constructor() {
super()
this.state={ name: 'anonymous' }
}
render() {
const { name } = this.state
return (<div>
My name is { name }.
<button onClick={()=>this.setState({ name: 'John Doe' })}>show name</button>
</div>)
}
}
Also remember to maintain unidirectional data flow of data. Don't just connect a component to redux store if in the first place the data is already accessible by its parent component like this:
<ChildComponent yourdata={yourdata} />
If you need to change a parent's state from a child just pass the context of a function to the logic of your child component. Example:
In parent component
updateName(name) {
this.setState({ name })
}
render() {
return(<div><ChildComponent onChange={::this.updateName} /></div>)
}
In child component
<button onClick={()=>this.props.onChange('John Doe')}
Here is a good article about this.
Just practice and everything will start to make sense once you know how to properly abstract your app to separate concerns. On these matter composition vs ihhertitance and thinking in react are a very good read.
I started writing a package to make it easier and more generic. Also to improve performance. It's still in its early stages (38% coverage). Here's a little snippet (if you can use new ES6 features) however there is also alternatives.
import { create_store } from 'redux';
import { create_reducer, redup } from 'redux-decorator';
class State {
#redup("Todos", "AddTodo", [])
addTodo(state, action) {
return [...state, { id: 2 }];
}
#redup("Todos", "RemoveTodo", [])
removeTodo(state, action) {
console.log("running remove todo");
const copy = [...state];
copy.splice(action.index, 1);
return copy;
}
}
const store = createStore(create_reducer(new State()));
You can also even nest your state:
class Note{
#redup("Notes","AddNote",[])
addNote(state,action){
//Code to add a note
}
}
class State{
aConstant = 1
#redup("Todos","AddTodo",[])
addTodo(state,action){
//Code to add a todo
}
note = new Note();
}
// create store...
//Adds a note
store.dispatch({
type:'AddNote'
})
//Log notes
console.log(store.getState().note.Notes)
Lots of documentation available on NPM. As always, feel free to contribute!
A key decision to be made when designing React/Redux programs is where to put business logic (it has to go somewhere!).
It could go in the React components, in the action creators, in the reducers, or a combination of those. Whether the generic action/reducer combination is sensible depends on where the business logic goes.
If the React components do the majority of the business logic, then the action creators and reducers can be very lightweight, and could be put into a single file as you suggest, without any problems, except making the React components more complex.
The reason that most React/Redux projects seem to have a lot of files for action creators and reducers because some of the business logic is put in there, and so would result in a very bloated file, if the generic method was used.
Personally, I prefer to have very simple reducers and simple components, and have a large number of actions to abstract away complexity like requesting data from a web service into the action creators, but the "right" way depends on the project at hand.
A quick note: As mentioned in https://stackoverflow.com/a/50646935, the object should be returned from setState. This is because some asynchronous processing may need to happen before store.dispatch is called.
An example of reducing boilerplate is below. Here, a generic reducer is used, which reduces code needed, but is only possible the logic is handled elsewhere so that actions are made as simple as possible.
import ActionType from "../actionsEnum.jsx";
const reducer = (state = {
// Initial state ...
}, action) => {
var actionsAllowed = Object.keys(ActionType).map(key => {
return ActionType[key];
});
if (actionsAllowed.includes(action.type) && action.type !== ActionType.NOP) {
return makeNewState(state, action.state);
} else {
return state;
}
}
const makeNewState = (oldState, partialState) => {
var newState = Object.assign({}, oldState);
const values = Object.values(partialState);
Object.keys(partialState).forEach((key, ind) => {
newState[key] = values[ind];
});
return newState;
};
export default reducer;
tldr It is a design decision to be made early on in development because it affects how a large portion of the program is structured.
Performance wise not much. But from a design perspective quite a few. By having multiple reducers you can have separation of concerns - each module only concerned with themselves. By having action creators you add a layer of indirection -allowing you to make changes more easily. In the end it still depends, if you don't need these features a generic solution helps reduce code.
First of all, some terminology:
action: a message that we want to dispatch to all reducers. It can be anything. Usually it's a simple Javascript object like const someAction = {type: 'SOME_ACTION', payload: [1, 2, 3]}
action type: a constant used by the action creators to build an action, and by the reducers to understand which action they have just received. You use them to avoid typing 'SOME_ACTION' both in the action creators and in the reducers. You define an action type like const SOME_ACTION = 'SOME_ACTION' so you can import it in the action creators and in the reducers.
action creator: a function that creates an action and dispatches it to the reducers.
reducer: a function that receives all actions dispatched to the store, and it's responsible for updating the state for that redux store (you might have multiple stores if your application is complex).
Now, to the question.
I think that a generic action creator is not a great idea.
Your application might need to use the following action creators:
fetchData()
fetchUser(id)
fetchCity(lat, lon)
Implementing the logic of dealing with a different number of arguments in a single action creator doesn't sound right to me.
I think it's much better to have many small functions because they have different responsibilities. For instance, fetchUser should not have anything to do with fetchCity.
I start out by creating a module for all of my action types and action creators. If my application grows, I might separate the action creators into different modules (e.g. actions/user.js, actions/cities.js), but I think that having separate module/s for action types is a bit overkill.
As for the reducers, I think that a single reducer is a viable option if you don't have to deal with too many actions.
A reducer receives all the actions dispatched by the action creators. Then, by looking at the action.type, it creates a new state of the store. Since you have to deal with all the incoming actions anyway, I find it nice to have all the logic in one place. This of course starts to be difficult if your application grows (e.g. a switch/case to handle 20 different actions is not very maintainable).
You can start with a single reducer, the move to several reducers and combine them in a root reducer with the combineReducer function.

React/Redux/Immutable.js - Immutable actions (Error: Actions must be plain objects)

I'm using React with Redux and Immutable.js - My state is composed of immutable objects and that works fine.
Now I'm trying to make my actions immutable objects instead of plain Javascript objects.
My reducers and action creators work and all my unit tests pass, but when I try to use the immutable action object in a React component I get an error because my Action object is an immutable Map (instead of a plain javascript object).
Here is my action:
export const cancel = () => {
return Immutable.Map({
type: ACTION_TYPES.get('CANCEL')
})
}
My reducer is like this (works when unit tested but React never calls it due to the error):
export const site = (state = initialState, action) => {
const actionType = null
try {
action.get('type')
} catch (e) {
return state
}
switch (actionType) {
... so on and so forth ...
Here is the error when testing a component:
FAIL src/App.test.js
● Test suite failed to run
Actions must be plain objects. Use custom middleware for async actions.
What middleware do I need to add so that my Immutable.js objects work as actions? I cannot find it in the docs...
What middleware do I need to add so that my Immutable.js objects work as actions? I cannot find it in the docs...
If you want to build custom behavior, you'll need to build the middleware yourself. Something like this:
import { isImmutable } from 'immutable';
// Allows immutable objects to be passed in as actions. Unwraps them, then
// forwards them on to the reducer (or the next middleware).
const immutableMiddleware = store => next => action => {
if (isImmutable(action)) {
next(action.toJS())
} else {
next(action);
}
};
That said, i don't really see what benefit you get from doing actions as immutable objects. Actions are usually created right when they're needed, and never accessed again, so protecting it from mutation is almost never a concern. And you typically won't be cloning them, so the performance benefits that immutablejs provides when creating a new immutable object from an old one will be unrealized.

redux state selectors in top level reducer

After watching the new egghead course by Dan Abramov, I have question regarding the selectors that was mentioned.
The purpose of the selectors is to hide the details of the state tree from the components, so that it is easy to manage code later if tree changes.
If I understand it correctly, that means, the selectors called inside mapStateToProps should only be the ones that live in the top-level reducer. Because the state that is passed to mapStateToProps is the whole application state tree. If this is true, as the application grows, I can imagine it would become very difficult to manage the top level selectors.
Have I miss understood the concept here? or is this a valid concern?
Edit: trying to make my question clearer.
Say my whole state start with
{ byIds, listByFilter } and I have
export const getIsFetching = (state, filter) =>
fromList.getIsFetching(state.listByFilter[filter]);
in my top level reducer reducers/index.js, and components would simply use getIsFetching passing the whole state to is, which is totally fine because it is the top level.
However, later on, I decided my whole app is going to contain a todo app and an counter app. So it make sense to put the current top level reducers into reducers/todo.js, and create a new top level reducers reducers/index.js like this:
combineReducers({
todo: todoReducer,
counter: counterReducer
})
at the point my state would be like
{
todo: {
byIds,
listByFilter
},
counter: {
// counter stuff
}
}
components can no longer use the getIsFetching from reducers/todo.js, because the state in getIsFetching is now actually dealing with state.todo. So i have to in the top level reducer reducers/index.js export another selector like this:
export const getIsFetching = (state, filter) =>
fromTodo.getIsFetching(state.todo);
only at this point, the component is able to use getIsFetching without worring about the state shape.
However, this raises my concern which is all the selectors directly used by components must live in the top-level reducer.
Update 2: essentially we are exporting selectors from the deepest level all the way up to the top-level reducers, while all the exports in the intermediate reducers are not using them, but they are there because the reducer knows the shape of the state at that level.
It is very much like passing props from parent all the way down to children, while the intermediate component aren't using props. We avoided this by context, or connect.
apologize for the poor English.
So while mapStateToProps does take the entire state tree, it's up to you to return what you'd like from that state in order to render your component.
For instance, we can see he calls getVisibleTodos and passes in state (and params from the router), and gets back a list of filtered todos:
components/VisibleTodoList.js
const mapStateToProps = (state, { params }) => ({
todos: getVisibleTodos(state, params.filter || 'all'),
});
And by following the call, we can see that the store is utilizing combineReducers (albeit with a single reducer), as such, this necessitates that he pass the applicable portion of the state tree to the todos reducer, which is, of course, state.todos.
reducer/index.js
import { combineReducers } from 'redux';
import todos, * as fromTodos from './todos';
const todoApp = combineReducers({
todos,
});
export default todoApp;
export const getVisibleTodos = (state, filter) =>
fromTodos.getVisibleTodos(state.todos, filter);
And while getVisibleTodos returns a list of todos, which by is a direct subset of the top-level state.todos (and equally named as such), I believe that's just for simplicity of the demonstration:
We could easily write another perhaps another component where there's a mapStateToProps similar to:
components/NotTopLevel.js
const mapStateToProps = (state, { params }) => ({
todoText: getSingleTodoText(state, params.todoId),
});
In this case, the getSingleTodoText still accepts full state (and an id from params), however it would only return the text of todo, not even the full object, or a list of top-level todos. So again, it's really up to you to decide what you want to pull out of the store and stuff into your components when rendering.
I also came across this issue (and also had a hard time explaining it...). My solution for compartmentalization this follows from how redux-forms handles it.
Essentially the problem boils down to one issue - where is the reducer bound to? In redux-forms they assume you set it at form (though you can change this) in the global state.
Because you've assumed this, you can now write your module's selectors to accept the globalState and return a selector as follows: (globalState) => globalState.form.someInnerAttribute or whatever you want.
To make it even more extensible you can create an internal variable to track where the state is bound to in the global state tree and also an internal function that's like getStateFromGlobalState = (globalState) => globalState[boundLocation] and uses that to get the inner state tree. Then you can change this variable programatically if you decide to bind your state to a different spot in the global state tree.
This way when you export your module's selectors and use them in mapStateToProps, they can accept the global state. If you make any changes to where the where the reducer is bound, then you only have to change that one internal function.
IMO, this is better than rewriting every nested selector in the top level. That is hard to scale/maintain and requires a lot of boilerplate code. This keeps the reducer/selector module contained to itself. The only thing it needs to know is where the reducer is bound to.
By the way - you can do this for some deeply nested states where you wouldn't necessarily be referring about this from globalState but rather some upper level node on the state tree. Though if you have a super nested state it may make more sense to write the selector from a upper state's POV.

Categories

Resources