React toolkit and redux-first-router - reactjs

I am digging into React with Redux for a rewrite of our product.
A lot of fog around Redux was cleared by using Redux-Toolkit https://redux-toolkit.js.org/.
Then I found that React-Router made state management messy and found a solution in redux-first-router https://github.com/faceyspacey/redux-first-router.
Now I want to combine these excellent libraries. But I think I'm doing something wrong in the configuration.
Here is the code. Starting with a sandbox example at https://codesandbox.io/s/m76zjj924j, I changed the configureStore.js file into (for simplicity I have omitted code for the user reducer)
import { connectRoutes } from 'redux-first-router';
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit'
import { routePaths } from '../routes';
const { reducer: location } = connectRoutes(routePaths);
const {
middleware: routerMiddleware,
enhancer: routerEnhancer,
initialDispatch
} = connectRoutes(routePaths, { initialDispatch: false });
export default function configureRouteStore() {
const store = configureStore({
reducer: {
location: location
},
middleware: [...getDefaultMiddleware(), routerMiddleware],
enhancers: (defaultEnhancers) => [routerEnhancer, ...defaultEnhancers]
})
initialDispatch();
return store;
}
But now each time a change in route = Redux store is updated, I get an exception in the browser:
index.js:1 A non-serializable value was detected in the state, in the path: `location.routesMap.PROFILE.thunk`. Value: dispatch => {
dispatch(USER_DATA_LOADED({
avatar: null
}));
const avatar = `https://api.adorable.io/avatars/${Math.random()}`;
setTimeout(() => {
// fake async call
dispatch(USER_…
Take a look at the reducer(s) handling this action type: HOME.
I can see that this stems from the routes definitions if the route has a 'thunk' property defined as this: PROFILE: { path: "/profile/:username", thunk: fetchUserData },
If I change the thunk property to a serializable value (or remove it) the error is gone.
Somehow now the thunk is added to the payload of the action to update paths. What...?
What to do? OK, I can get it work with the traditional Redux setup but as I am a big fan the redux toolkit it would be sweet for me and maybe a few more people out there to make it work with the toolbox.

I'm a Redux maintainer and creator of Redux Toolkit.
Based on that error message and reading the Redux-First-Router source code, it looks like the library is indeed attempting to store thunk functions in the Redux store. This is a problem, because we specifically instruct users to never put non-serializable values like functions in state or actions.
By default, Redux Toolkit adds a "serializable state invariant middleware" that warns you if non-serializable values are detected in state or actions, to help you avoid accidentally making this mistake.
It is possible to pass some options to getDefaultMiddleware() to customize the behavior of these middlewares. There is currently an ignoredActions option, but I don't think we have an option to ignore specific sections of the state tree. The included redux-immutable-state-invariant middleware does have an ignore option for portions of the state, so perhaps we could add that approach.
I've added https://github.com/reduxjs/redux-toolkit/issues/319 to see if we can add an option like that.
In the meantime, you could potentially turn off the middleware by calling getDefaultMiddleware({serializableCheck: false}).
update
I've just published Redux Toolkit v1.2.3, which adds an ignoredPaths option to the serializability check middleware to allow ignoring specific keypaths within the state.
Again, please note that this is purely an escape hatch to work around misbehaving libraries, and should not be used as a regular approach.

Related

How to use StoreApi in zustand

From the docs of zustand I found that together with set and get parameters they provide an additional parameter at StateCreator, called api.
Example bellow
import create, { StateCreator } from 'zustand'
import type { Store } from './store.types'
const globalStateCreator: StateCreator<Store> = (set, get, api) => ({
...mySlice(set, get, api),
})
What does it stands for? What are the best practices for using it?
If you're using React, you're probably using hooks to do something like this:
const color: PaletteColor = usePaintbrush((s) => s.color);
The hooks make zustand seem more self-contained than it really is. You have a lot of control over the state.
Hooks are limited to components, but you can use the api methods bound to the store from anywhere, imperatively.
// .ts
const color: PaletteColor = usePaintbrush.getState().color[shade];
// .tsx
const color = useRef<PaletteColor>(usePaintbrush.getState().color[shade]);
useEffect(() => usePaintbrush.subscribe((s) => (color.current = s.color[shade])), [shade])
Store actions are not required to be in the store either!
// at module level
const setColor(color: PaletteColor) => usePaintbrush.setState({ color })
You're unlikely to touch the api parameter unless you're creating a middleware.
Docs cover specific example usage with the persist middleware
The persist api enables you to do numbers of interactions with the persist middleware from inside or outside a React component.
references (updated #1033):
store without actions
persist middleware api
middleware that changes store type

Redux batch doesn't work properly on React Native app

Need to call multiple dispatches. Trying to use batch from react-redux, but it triggers only the first dispatch inside the function. Tried to exchange dispatches, the same, triggers only the first one, checked in dev tools. Thunk is connected
In app:
dispatch(
setData({
user,
userLang,
userToken,
dateMargin,
}), )
Action:
export const setData = ({user, userLang, userToken, dateMargin}) => {
return dispatch => {
batch(() => {
dispatch(setToken(userToken))
dispatch(setLang(userLang))
dispatch(setUser(user))
dispatch(setDateMargin(dateMargin))
})
}
}
Yes, batch is a re-export from react-dom. React-native does not have a similar API, so that is not possible there.
Generally, it is recommended to Model Actions as Events, Not Setters, Allow Many Reducers to Respond to the Same Action and to Avoid Dispatching Many Actions Sequentially (see the links for more explanation), so what you are doing here is pretty much an anti-pattern:
You have the logic in your component, although the big strentgh of Redux is that it moves the state logic out of your component, into your Store.
Going by the recommendations from the official Redux Style guide, you'd rather
dispatch(userLoggedIn({
userToken,
userLang,
user,
dateMargin
})
and have potentially multiple reducers acting on that event-type action.

i was writing test cases for actions but having received anonymous function while using dispach using jest and enzyme

I was receiving:
Expected: {"data": true, "type": "USER"}
Received: [Function anonymous]
export function userDispatch(data) {
return dispatch => {
//update the screens
dispatch(user(data));
}
}
export function user(data) {
return {
type: USER,
data
}
}
it("testing userDispatch", () => {
const data = true
const expectedAction = {
type: USER,
data
}
expect(actions.userDispatch(data)).toEqual(expectedAction)
})
You are writing a thunk action creator named userDispatch here.
Also, you have a normal action creator called user here.
A thunk is a function that needs to be dispatched to do anything - so in the current state, calling
dispatch(userDispatch(data))
does exactly the same as calling
dispatch(user(data))
userDispatch is just completely useless and you can replace it with user everywhere you use it - including your test.
If it makes sense to test an action creator at all is highly debatable. The Redux documentation does not really encourage such things:
(from https://redux.js.org/usage/writing-tests)
The return value of action creators is considered an implementation detail within your application, and when following an integration testing style, do not need explicit tests.
Please be aware, that generally this is a very outdated style of Redux and modern Redux will auto-generate action creators for you. Also, in modern Redux you will not write switch case reducers or use connect. You might have been following outdated (and in case of this thunk action creator, simply misguided) tutorials.
Best follow the official Redux tutorial at https://redux.js.org/tutorials/essentials/part-1-overview-concepts

Is it possible to create a React Hook factory function?

I have a React app that relies heavily on redux, react-redux, and redux-saga. I'm starting to experiment with React Hooks, and the useSelector and useDispatch hooks in react-redux, and I've run into an issue with how to essentially write a factory function to generate hooks for each of my redux nodes.
In general, I have a redux node for each API endpoint my app consumes. There are about 100 unique endpoints in this app, and so there are about 100 redux nodes. Each of those nodes then corresponds to one state-[node].js file, like state-users.js, etc. Those state files each encapsulate the endpoint they should call, trigger sagas to handle the HTTP lifecycle (start, fail, success, etc), and so on.
Over time, I've written code that abstracts much of the boilerplate away into utility functions, including functions that auto generate action creators, selectors, reducers, and the connect function. It's a bunch of code, and somewhat convoluted, but the gist looks something like this. First, I set up an array of objects describing actions that are possible for this redux node. A simplified version looks like this:
const messages = [
{ action: 'GET', creator: 'get', connect: true },
{ action: 'POST', creator: 'post', connect: true },
{ action: 'CLEAR', creator: 'clear', connect: true },
];
This describes that there will be three actions, get , post, and clear, and that they should be exposed in the connector. I have a set of common reducers (e.g. most get reducers are identical across nodes), so those are assumed here based on name.
Then I set up a list of selectors, like this:
const selectorKeys = ['data','pending','errors'];
...and then I have a factory function that I feed these arrays into, which looks something like this:
const { connector } = stateGenerators({
keyword: 'users', //redux node keyword
messages: messages,
selectorKeys: selectorKeys
})
This is a simplified version of how everything really works, but it's the crux of it. Again, all of the above code is abstracted into a state file, like state-users.js.
Then, in my class component, I just need to import the connector from state-users.js, like this:
import { connector } from 'state-users';
class Users extends Component {
componentDidMount() {
this.props.get();
}
componentWillUnmount() {
this.props.clear();
}
render () {
const { data } = this.props;
return (
<div>
{data.map()}
</div>
)
}
}
export connector()(Users)
This model does get clunky at times, but the nice part is that nearly all of the redux boilerplate is abstracted into common files, so my individual state files are, for the most part, really simple.
Now, to the question: is it possible to do something like this "factory function" approach with Hooks? So far in my experimenting I have not figured out how to do this. There are two fundamental issues:
First, you can't put hooks in loops, so I can't do this:
const selectors = {}
const reduxNodeKeyword = 'users';
['data','pending','errors'].map((selectorKey) => {
selectors[selectorKey] = useSelector((state) => state[keyword].selectorKey);
})
That code results in this error:
React hook "useSelector" cannot be called inside of a callback.
In practice, that means I can't just pass in an array of selector keys I'd like and then have it spit back my selectors.
Second, you can't put hooks inside conditionals. So since the first idea failed, I tried a different approach in my factory function, which looks something like this:
if (_.includes(stateSelectors, 'data')) {
result['data'] = useSelector((state) => state[keyword].data);
}
That results in this error:
React hook "useSelector" is called conditionally. React Hooks must be called in the exact same order in every component render
So that's also a bummer. But I think what I'm left with is that for each of my 100 Redux nodes, I would have to write a custom, and verbose, hook to more or less replicate connect.
I know, I know, hooks should encourage me to think differently, but I still need to fetch data from the server and provide it to components, and I need to follow that same basic pattern 100 times. Even if hooks makes my components more elegant (as I expect it would), the thought of writing 100 or so hooks out by hand, each with a fair amount of repeated data, rather than somehow auto-creating them using some sort of factory approach, gives me hives.
Help?
Thanks!
Not sure if this will be useful for others, but I found an approach that works for me. I can't put hooks in iterators, nor in if statements, but I do have common patterns all over the place. So the answer was to abstract those common patterns into generic hooks. For example, I have a hook called useCommonReduxListSelectors, which looks like this:
export const useReduxListCommonSelectors = (keyword: string) => {
return {
data: useSelector((state: any) => state[keyword].data),
errorMessage: useSelector((state: any) => state[keyword].errorMessage),
filters: useSelector((state: any) => state[keyword].filters),
pending: useSelector((state: any) => state[keyword].pending),
totalCount: useSelector((state: any) => state[keyword].totalCount)
};
};
I have lots of Redux state nodes that are responsible for handling lists returned from an API, and the data shape of most of those list endpoints is what you see above. So then the hook that a component would invoke uses useReduxListCommonSelectors, like this:
export const useReduxState = ({ id, defaultValues }) => {
const selectors = useReduxListCommonSelectors({
keyword:'users'
});
return {
...selectors,
};
};
And then obviously useReduxState can have any other data in there (actionCreators, custom selectors, etc) that is required by that node. So it allows me to abstract the most common patterns into reusable hooks, and also have the flexibility to add custom code to each useReduxState instance as needed.

React redux - how to handle events?

I am learning the concepts of Redux.
From what I understand, reducers are not supposed to mutate state, they return a new state with mutations applied. They are also not supposed to (or maybe even can't?) dispatch other actions.
Given those limitations, that pretty much leaves you with the action creators, middleware, or your React components (if using Redux with React) to call your business logic. So, what I'm trying to do is use Firebase, the wall I've hit, and the questions I have are the following:
Where should you be creating an instance (i.e. initialising Firebase)?
Similarly, where should be uninitialising Firebase? Usually I would expect these to be in a service of some kind that you'd call, but I'm not really sure where you would make the call to that service.
Where should you listen for changes? Firebase provides .on on references to allow you to react to changes (i.e. child_added, child_removed) - where should these .on handlers go? I was thinking that surely when one of these events comes in, an action needs to be dispatched with the child in question to update the state, but I can't do this in the reducers, I don't want to do this on a component, and the action creator just seems like an odd place for it to (and when I think about it, how would that even work?)!
Solution
Finally I ended up following the #ampersand suggestion. I made a module with all the listeners of Firebase application. I import in the Firebase module a dispatcher.js file, that is composed by dispatcher function like these:
import store from '../store';
import * as actions from './index';
export function addUserMessage(text) {
store.dispatch(actions.addUserMessage(text));
}
export function addResponseMessage(messageObj) {
store.dispatch(actions.addResponseMessage(messageObj));
}
What I was not understanding was how to dispatch action from an external module. Now I have the concepts clear.
I'm not familiar with Firebase, but I think middleware is most likely the answer here.
You can write a simple middleware that has access to your Firebase instance and will dispatch these message to the Redux store.
// firebase-middleware.js
export default function createFireBaseMiddleware (firebaseInstance) {
return store => dispatch => {
fireBaseinstance.on(message => {
dispatch({
type: 'FIREBASE_MESSAGE',
payload: message,
})
})
return action => {
if (action.type === 'TALK_BACK_TO_FIREBASE' {
firebaseInstance.postMessage(action)
}
// Let the action continue down the path of middleware to the store
return dispatch(action)
}
}
}
// createStore.js
export default createStore(
reducer,
applyMiddleware(createFireBaseMiddleware(myfirebaseInstance)
)
To tack onto #chautelly's response, generally services in react/redux are just modules—create your firebase instance inside a standalone module, export the appropriate object from it, and import where needed.
database.js
import firebase from 'firebase';
firebase.initializeApp({
apiKey: 'api key',
authDomain: 'db_name.firebaseio.com',
databaseURL: 'https://db_name.firebaseio.com/'
});
const database = firebase.database();
export default database;

Resources