From the code snippets below, the AppState contains 2 lists (teachers and classrooms lists). The store is assigned to a single middleware createClassroomMiddleware, and I want to have another middleware for createTeacherMiddleware which is using for CRUD operations on the data. How can I have more than one middleware attached to the store?
I'm currently using flutter redux library, and following this example
My appState is
class AppState {
final List<ClassRoom> classrooms;
final List<Teacher> teachers; //etc..
AppState(this.classrooms, this.teachers);
}
My store:
Store<AppState> {
initialState: new AppState(const [], const [])
appReducer,
middleware: createClassroomMiddleware()
}
My middleware:
List<Middleware<AppState>> createClassroomMiddleware() {
return combineTypedMiddleware([
new MiddlewareBinding<AppState, ClassroomLoadedAction>(_loadClassrooms(),
new MiddlewareBinding<AppState, ClassroomDeletedAction>(_deleteClassroom()
]);
}
I think the solution might be easier than you expect!
You can combine these Lists of Middleware together using the List class' addAll method.
Store<AppState> {
initialState: new AppState(const [], const [])
appReducer,
middleware: createClassroomMiddleware()..addAll(createTeacherMiddleware())
}
How does this work? Under the hood, the middleware parameter allows you to add as many Middleware to your Store as you want, since it's just a List<Middleware<AppState>>. Since both of your createMiddleware functions return a list, you need to combine those two lists together into one big list.
You do that with the List class' addAll method, as seen above!
The middleware property in the store is a List<Middleware<State>>.
If you want to add different middleware in the same store, you need just to concatenate them, and an easy way to concatenate list in dart is:
[]..addAll(list1)..addAll(list2)..addAll(listn)
In your example:
class ReduxApp extends StatelessWidget {
final store = Store<AppState>(
appReducer,
initialState: new AppState(const [], const []),
middleware: []
..addAll(createClassroomMiddleware())
..addAll(createTeacherMiddleware()),
);
}
Instead of calling createClassroomMiddleware, name as createAllMiddleware and put all middlewares. Will that solve?
This will work as you expected:
List<Middleware<AppState>> listMiddleWare = new List<Middleware<AppState>>();
class ReduxApp extends StatelessWidget {
final store = Store<AppState>(
appReducer,
initialState: AppState.loading(),
middleware: listMiddleWare
..addAll(createStoreOne())
..addAll(createStoreOrdersMiddleware()),
);
}
Related
Issue (tl;dr)
How can we create a custom redux-orm reducer with redux-toolkit's createSlice?
Is there a simpler, recommended, more elegant or just other solution than the attempt provided in this question?
Details
The example of a custom redux-orm reducer looks as follows (simplified):
function ormReducer(dbState, action) {
const session = orm.session(dbState);
const { Book } = session;
switch (action.type) {
case 'CREATE_BOOK':
Book.create(action.payload);
break;
case 'REMOVE_AUTHOR_FROM_BOOK':
Book.withId(action.payload.bookId).authors.remove(action.payload.authorId);
break;
case 'ASSIGN_PUBLISHER':
Book.withId(action.payload.bookId).publisherId = action.payload.publisherId;
break;
}
return session.state;
}
It's possible to simplify reducers with the createSlice function of redux-toolkit (based on the redux-toolkit usage-guide):
const ormSlice = createSlice({
name: 'orm',
initialState: [],
reducers: {
createBook(state, action) {},
removeAuthorFromBook(state, action) {},
assignPublisher(state, action) {}
}
})
const { actions, reducer } = ormSlice
export const { createBook, removeAuthorsFromBook, assignPublisher } = actions
export default reducer
However, at the beginning of redux-orm reducer we need to create a session
const session = orm.session(dbState);
then we do our redux-orm reducer magic, and at the end we need to return the state
return session.state;
So we miss something like beforeEachReducer and afterEachReducer methods in the createSlice to add this functionality.
Solution (attempt)
We created a withSession higher-order function that creates the session and returns the new state.
const withSession = reducer => (state, action) => {
const session = orm.session(state);
reducer(session, action);
return session.state;
}
We need to wrap every reducer logic in this withSession.
import { createSlice } from '#reduxjs/toolkit';
import orm from './models/orm'; // defined elsewhere
// also define or import withSession here
const ormSlice = createSlice({
name: 'orm',
initialState: orm.session().state, // we need to provide the initial state
reducers: {
createBook: withSession((session, action) => {
session.Book.create(action.payload);
}),
removeAuthorFromBook: withSession((session, action) => {
session.Book.withId(action.payload.bookId).authors.remove(action.payload.authorId);
}),
assignPublisher: withSession((session, action) => {
session.Book.withId(action.payload.bookId).publisherId = action.payload.publisherId;
}),
}
})
const { actions, reducer } = ormSlice
export const { createBook, removeAuthorsFromBook, assignPublisher } = actions
export default reducer
This is a fascinating question for me, because I created Redux Toolkit, and I wrote extensively about using Redux-ORM in my "Practical Redux" tutorial series.
Off the top of my head, I'd have to say your withSession() wrapper looks like the best approach for now.
At the same time, I'm not sure that using Redux-ORM and createSlice() together really gets you a lot of benefit. You're not making use of Immer's immutable update capabilities inside, since Redux-ORM is handling updates within the models. The only real benefit in this case is generating the action creators and action types.
You might be better off just calling createAction() separately, and using the original reducer form with the generated action types in the switch statement:
export const createBook = createAction("books/create");
export const removeAuthorFromBook = createAction("books/removeAuthor");
export const assignPublisher = createAction("books/assignPublisher");
export default function ormReducer(dbState, action) {
const session = orm.session(dbState);
const { Book } = session;
switch (action.type) {
case createBook.type:
Book.create(action.payload);
break;
case removeAuthorFromBook.type:
Book.withId(action.payload.bookId).authors.remove(action.payload.authorId);
break;
case assignPublisher.type:
Book.withId(action.payload.bookId).publisherId = action.payload.publisherId;
break;
}
return session.state;
}
I see what you're saying about adding some kind of "before/after" handlers, but that would add too much complexity. RTK is intended to handle the 80% use case, and the TS types for createSlice are already incredibly complicated. Adding any more complexity here would be bad.
I came across this question looking to combine the benefits of redux-toolkit
and redux-orm. I was able to come up with a solution I've been pretty happy
with so far. Here is what my redux-orm model looks like:
class Book extends Model {
static modelName = 'Book';
// Declare your related fields.
static fields = {
id: attr(), // non-relational field for any value; optional but highly recommended
name: attr(),
// foreign key field
publisherId: fk({
to: 'Publisher',
as: 'publisher',
relatedName: 'books',
}),
authors: many('Author', 'books'),
};
static slice = createSlice({
name: 'BookSlice',
// The "state" (Book) is coming from the redux-orm reducer, and so will
// never be undefined; therefore, `initialState` is not needed.
initialState: undefined,
reducers: {
createBook(Book, action) {
Book.create(action.payload);
},
removeAuthorFromBook(Book, action) {
Book.withId(action.payload.bookId).authors.remove(action.payload.authorId);
},
assignPublisher(Book, action) {
Book.withId(action.payload.bookId).publisherId = action.payload.publisherId;
}
}
});
toString() {
return `Book: ${this.name}`;
}
// Declare any static or instance methods you need.
}
export default Book;
export const { createBook, removeAuthorFromBook, assignPublisher } = Book.slice.actions;
The redux-toolkit slice is created as a static property on the class, and then
the model and its actions are exported in a manner similar to Ducks
(ORMDucks??).
The only other modification to make is to define a custom updater for
redux-orm's reducer:
const ormReducer = createReducer(orm, function (session, action) {
session.sessionBoundModels.forEach(modelClass => {
if (typeof modelClass.slice.reducer === 'function') {
modelClass.slice.reducer(modelClass, action, session);
}
});
});
See a more complete example here:
https://gist.github.com/JoshuaCWebDeveloper/25a302ec891acb6c4992fe137736160f
Some Notes
#markerikson makes a good point about some of the features of redux-toolkit
not being used since redux-orm is managing the state. For me, the two
greatest benefits of using this method are not having to wrangle a whole
bunch of action creators and not having to contend with awful switch
statements :D.
I am using the stage 3 class fields and static class features proposals. (See
https://babeljs.io/docs/en/babel-plugin-proposal-class-properties). To make
this ES6 compatible, you can easily refactor the model class to define its
static props using the current syntax (i.e. Book.modelName = 'Book';).
If you decide to mix models like the one above with models that don't define
a slice, then you'll need to tweak the logic in the createReducer updater
slightly.
For a real world example, see how I use the model in my project here:
https://github.com/vallerance/react-orcus/blob/70a389000b6cb4a00793b723a25cac52f6da519b/src/redux/models/OrcusApp.js.
This project is still in the early stages. The largest question in my mind is
how well this method will scale; however, I am optimistic that it will continue
to provide numerous benefits as my project matures.
Try using normalized-reducer. It's a higher-order-reducer that takes a schema describing the relationships, and returns a reducer, action, and selectors that write/read according to the relationships.
It also integrates easily with Normalizr and Redux Toolkit.
Basic Problem
I've got a Redux store with the following data:
foo: {
currentId: 1,
things: [{id: 1}, {id: 2}),
}
I'd like to make a utility method somewhere (eg. on a Foo singleton object) such that any module in my code can do:
import Foo from 'foo';
foo.getCurrentFoo(); // returns foo.thins[foo.currentId];
but I'm having trouble figuring out where to put it.
Failed Attempt
My initial attempt was to create a Foo component singleton:
// Foo.js
class FooBase extends React.Component {
getCurrentFoo() {
return this.state.foo.things[this.state.foo.currentId];
}
}
const Foor = connect((state) => state.foo)(FooBase);
export default new FooWrapper();
But that doesn't work. Redux complaieds about the property store not existing (when I did new FooWrapper()). That makes sense, because my component isn't inside a <Provider />. However, I just want a stand-alone utility class/object, not something actually in the DOM, which rules out <Provider/>.
How can I make a method like the one described above, that actually works, without involving <Provider /> ... and where do I put it?
The nice thing about the react-redux helpers is that they allow you to use connect() and <Provider /> to automatically pass the store to child components via React's context. However, that doesn't necessarily mean that you have to use these helpers, especially in areas of a codebase that don't use React.
So here lies the problem: connect() and <Provider /> help us by giving our React components access to a singleton instance of a store, but how can we access this store somewhere where connect() and <Provider /> can't be used?
I think the easiest solution here is to create a singleton class that holds on to the store, so any non-React module can still use the store.
So let's say you're creating your store like this:
init.js
import {createStore} from 'redux';
const initialState = {
currentId: 1,
things: ['foo', 'bar']
};
const reducer = (state = initialState, action) => {
if (action.type === 'SET_CURRENT_ID') {
return Object.assign({}, state, {
currentId: action.id
});
}
return state;
};
const store = createStore(reducer);
This store takes an action of type SET_CURRENT_ID which simply returns a new state with the currentId property changed to whatever was handed to it. You could then get the current "thing" by doing something like store.getState().things[store.getState().currentId]. So let's create a Singleton class that can hold on to the store and provide a wrapper around this functionality.
store.js
class Store {
constructor() {
this._store = undefined;
}
setStore(store) {
this._store = store;
}
getCurrentThing() {
if (this._store) {
const {things, currentId} = this._store.getState();
return things[currentId];
}
}
setCurrentThing(id) {
if (this._store) {
const action = {
type: 'SET_CURRENT_ID',
id
};
this._store.dispatch(action);
}
}
}
export let singletonStore = new Store();
This class creates an instance the first time it is used, and uses that instance every subsequent time. So when you originally create your store, simply import this class and call setStore().
init.js
import {singletonStore} from './store';
singletonStore.setStore(store);
Then, every subsequent file where singletonStore is used will have the same state.
test.js
import {singletonStore} from './store';
console.log(singletonStore.getCurrentThing()); // 'bar'
singletonStore.setCurrentThing(0);
console.log(singletonStore.getCurrentThing()); // 'foo'
This should work just fine for your need to use your store in modules that don't have the benefit of being passed the store magically with connect() and <Provider />.
Is this possible?
I'm using redux store in an IoC environment and want to add middleware to the store after it is created.
e.g.:
class MyApp {
store = createStore(...);
}
let app = new MyApp();
// later on
import thunk from 'redux-thunk';
app.store.addEnhancer(thunk);
I have created a function to do this. If redux think this is valuable, I can do a PR.
This is code that tailored to my module. The actual one add to PR will look a bit different.
addMiddleware(middleware: Middleware) {
const middlewareAPI: MiddlewareAPI<any> = {
getState: this.getState,
dispatch: (action) => this.dispatch(action)
};
this.dispatch = compose(middleware(middlewareAPI))(this.dispatch);
}
I was able to use the code from above from unional but I needed to change it slightly to get it working with 2018 redux.
constructor(myMiddleware: myMiddleware) {
this.addMiddleware(myMiddleware)
}
addMiddleware(middleware: Middleware) {
this.redux.dispatch = compose(applyMiddleware(middleware))(this.redux.dispatch);
}
All:
I am pretty new to Redux, when I follow its Reddit API example, there is one code snippet confuse me so much:
In AsyncApp.js, there is:
componentDidMount() {
const { dispatch, selectedSubreddit } = this.props
dispatch(fetchPostsIfNeeded(selectedSubreddit))
}
I wonder where the dispatch and selectedSubreddit get bind to this.props?
Thanks
That example is using the connect() function from react-redux to inject certain parts of the Redux state and the store's dispatch() function as props in that component. See the 'Usage With React' part of the Redux docs for more information.
For example:
App.js:
export class App extends Component {
//...
}
function mapStateToProps(state) {
const { selectedReddit, postsByReddit } = state
const {
isFetching,
lastUpdated,
items: posts
} = postsByReddit[selectedReddit] || {
isFetching: true,
items: []
}
return {
selectedReddit,
posts,
isFetching,
lastUpdated
}
}
export default connect(mapStateToProps)(App)
The connect() function here is taking the mapStateToProps() function above to inject the appropriate parts of the Redux state as props in the <App /> component. The keys of the object returned by mapStateToProps() correspond to the names of the props injected, and the corresponding values are the values of those injected props.
connect() can also take a second argument, matchDispatchToProps(), which can be used to inject specific action dispatch functions as props in your component. Whether or not you supply any arguments to connect(), it will inject your Redux store's dispatch() function as a prop called dispatch.
These connected container components receive state updates from the store, so when your Redux state changes, the connected container components will receive new props accordingly.
I'm trying to build keyboard shortcut support into my React/Redux app in an idiomatic React/Redux way. The way I am planning to do this is to have the following action creator and associated action:
registerShortcut(keyCode, actionCreatorFuncReference)
The reducer would then update a registeredShortcuts object in the redux store with a mapping of keyCodes to actionCreatorFuncReferences. Then my root component would listen for keyup and see if there is an associated keyCode registered and if so, then dispatch the mapped action via the action creator function reference.
However, this would be the first time I am storing function references in my Redux store. To date, I've only had objects with keys with vanilla values (strings, ints, etc).
The Redux docs says:
You should do your best to keep the state serializable. Don’t put anything inside it that you can’t easily turn into JSON.
Does this suggest it's a bad idea to store such function references in my Redux store? If so, what is a better way to accomplish what I'm trying to do in React/Redux?
An alternative approach is just to store the mapping of keyCodes and function references in the root react component itself, but that didn't feel very Redux-like since now the application state is not in the Redux store.
No, you should not store function references in the redux store. They are not serializable, and as you mentioned state should be serializable at all times. The most redux-friendly approach I can think of is just to keep the map of hotkeys to their actionCreatorFuncNames.
TL;DR: You don't. The store state must be serializable at all times (as Nathan answered).
The Redux way is via enhancers, or the Redux-Observable way via dependencies.
NL;PR: Based on the Redux docs example, what you want is to pass the reference in your action(1), ignore it your reducer(2) and use it in your enhancer(3):
//... in your action:
const data={val:1}, ref=()=>{};
const action = {type:'ACTION_WITH_REF', data, ref}; //(1)
//... in your reducer:
case 'ACTION_WITH_REF':
return {...state, data: action.data}; //(2)
//... and in your enhancer:
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
export const myRefStore= {};
function refHandler({ getState }) {
return next => action => {
switch(action.type){
// this can be done more elegantly with a redux-observable
case 'ACTION_WITH_REF':
myRefStore.aRef = action.ref; // (3)
break;
}
// be sure to maintain the chain of the store
const returnValue = next(action);
// otherwise, your midddeware will break the store
return returnValue;
};
}
const store = createStore(
reducers,
initialState,
applyMiddleware(refHandler)
);
Note: As far as there are no side-effects in your enhancers, you are good to go. Be aware that you could have obtained the refs directly in the reducers, but such an approach keeps the reference at the reducer-level and misses the point of combineReducers(). With an enhancer, you keep them all in one place(myRefStore).
One final observation is that a redux store is not an any-data store but a state store, thus why we need to handle functions and other non-state related stuff in enhancers. You can leverage the enhancer backbone to Redux-Observable and inject myRefStore via dependencies.
I'm new to redux, but the way I see it, you could pass the key code and an action type.
Then a reducer could be listening for that action type and make changes accordingly.
Here is an example using the library Mousetrap:
// On your Container
function registerShortcut(element, dispatch, keyCode, actionType) {
Mousetrap(element).bind(keyCode, function(e) {
dispatch({
type: actionType,
payload: {
keyCode: keyCode,
event: e
}
});
});
});
mapDispatchToProps = function(dispatch) {
return {
onMount: function(element) {
registerShortcut(element, dispatch, ['command+f', 'ctrl+f'], 'OPEN_SEARCH');
},
onUnmount: function(element) {
Mousetrap(element).unbind(['command+f', 'ctrl+f']);
}
};
};
// On your Component
componentDidMount() {
onMount(ReactDOM.findDOMNode(this));
};
componentWillUnmount() {
onUnmount(ReactDOM.findDOMNode(this));
};
// On your reducer
function reducer(oldState, action) {
if (action.type == 'OPEN_SEARCH') {
//... make changes ...//
return newState;
}
return oldState;
};
This way, keyboard shortcuts will dispatch an action. The reducer will make the changes necessary to the state. And finally, the application can re-render.