Managing a collection of different views with redux toolkit - reactjs

I'm trying to manage the state of an app that uses react-mosaic with redux.
The app can have many views of different types. Each view does it's own thing, but has some "layout" specific operations, like "remove", "copy" etc.
So in addition to something like copyLayout:PayloadAction<{layout}> i ended up with a bunch of actions that look like someAction:PayloadAction<{modulePath,foo,bar}>, which i dispatch as such:
const dispatch = useAppDispatch()
const {modulePath} = useModuleContext()
...
dispatch(someModuleAction({modulePath,foo,bar})
I would like to avoid having to be explicit about a "path" where this state lives and have something like:
const {dispatchWithPath} = useModuleContext()
...
dispatchWithPath(someAction({foo,bar})
Right now im trying to strip my action creators i get from the slice with PayloadAction<{modulePath, ...}> :
const createActionModuleWrapper = <
T extends ActionCreatorWithPayload<$IntentionalAny>
>(
action: T
) => (modulePath: ModulePath) => {
type PayloadWithPath = Parameters<T>[0] & { modulePath: ModulePath }
type PayloadWithoutPath = Omit<PayloadWithPath, 'modulePath'>
const wrapped = (payload: PayloadWithoutPath) => {
const paramsWithPath: PayloadWithPath = { ...payload, modulePath }
action(paramsWithPath)
}
return wrapped
}
With the idea to stick an actions object in this context where i would be explicit:
const moduleActions = {
someAction:createActionModuleWrapper(slice.actions.someAction),
...
}
const useSomeContext = ()=>{
const {modulePath} = useModuleContext()
const actions = {
someAction: moduleActions.someAction(modulePath)
}
}
But it feels like a hefty amount of boiler plate. I would also have to repeat these universal actions such as "copy", "remove", "duplicate" wherever i mention a "do something specific in THIS module".
I transform these creators in the slice file, so maybe just a universal useModuleDispatch without these specific actions per context would do the trick.
Does any of this feel right? Is there a better way to manage collections such as this? I cant figure out a way to setup a slice with toolkit and break this up. Each someModuleAction:
someModuleAction:(state, action:ModuleAction)=>{
if(getModule(state, action.payload.modulePath).type !==SomeModuleType) return
//SomeModule domain
}
I saw a mention of a different pattern where each action has a metadata field. Would a universal dispatch method work better with this? Now that i think of it, if i used a better PayloadAction (i use ModuleAction<T> to `T & {modulePath:ModulePath}, but only on some actions) i could just ignore the known, maybe empty field if i'm in a reducer that doesn't care about it.
Roughly the pattern that i'm replacing:
const {changeModule} = useModuleContext(SomeType)
const setFooInline = ()=>
changeModule((oldModule)=({...oldModulue, foo})
const setBarInline = ()=>
changeModule((oldModule)=({...oldModulue, bar})
So it's super powerful, since i get the one and only right setter, but then i have a million of these inline setters/action creators, and a ton of prop drilling to get them into places, since they all operate on this one generic update state tree root, while having access to the entire state of that tree, which is what i'm trying to avoid.

Related

Redux toolkit: Exporting a custom hook from slice file to access actions instead of exporting all actions and then calling it again with dispatch?

Inside a slice file we export all the the actions from that slice. For example:
export const {signoutUser, updateProfile, authenticateUser, clearUserState} = sliceName.actions;
And then we import useDispatch and particular actions from the slice or action file based on your folder structure. For example
import {clearUserState} from './slice';
import { useDispatch } from 'react-redux';
export const Component () {
const dispatch = useDispatch(clearUserState());
//rest component body
}
Now instead I am exporting a custom hook from the slice file like mentioned below:
export const useUserDispatch = () => {
const dispatch = useDispatch();
const userDispatch = {
signoutUser: (data) => dispatch(signoutUser(data)),
updateProfile: (data) => dispatch(updateProfile(data)),
authenticateUser: (data) => dispatch(authenticateUser(data)),
clearUserState: () => dispatch(clearUserState())
};
return {userDispatch}
};
And then i can just import that hook and use like
const {userDispatch}=useUserDispatch();
//inside component
<button onClick={userDispatch.clearUserState()}>Dispatch Button</button>
I just wanted to know if it's something that's not recommended in terms of redux way of writing code or am I doing anything wrong, it works perfectly fine though.
There is nothing wrong with your code. and the question can not be answered to pros and cons based on my experience, redux and all other open-source packages consider base common cases which people are using in the everyday app. There might be some suggestions for improvement but not best-case explanations for every app. you can just consider following and decide yourself
You can not use them as you mentioned useUserDispatch().clearUserState()
e.g.
<button onCleack={useDispatcher().clearUserState}>clear</button>
Hooks can be called conditionally so calling them as this level in the UI part might be conditionally canceled which is a really common case
every time this hook is called a hole new object is created for dispatchers
Also, useDispatch doesn't receive any argument and returns the nearest redux provider store.dispatch. see source code
Note: Redux suggests having one state for all of your apps and doesn't wrap part of your code with multiple providers.
Remember if you need this one of dispatcher (e.g. updateProfile) from some other part of the code, you may need to use this hook which is a waste of resources, or use it directly which shows a is a little bit of uncertainty and be not consistent from the other version (just a little is not a big case).
There are other options to handle these cases.
Remember what redux suggests for one provider, if you accept that you can also write your own dispatcher.
const storeDispatch = store.dispatch
// from slice file export dispatcher instead of action creator
const updateProfile = sliceName.actions.updateProfile;
export const updateProfileDispatcher = (data) => storeDispatch(updateProfile(data))
This can not only be used in your component but also can be used outside of react component inside the logic
Note: using a dispatcher outside the react is not the standard or recommended pattern and you might not want to use it.
You can also pass dispatch as the argument, something like thunk dispatcher
const updateProfile = dispatch => (data) => dispatch(updateProfile(data))
// or alongside of payload data
const updateProfile = (dispatch, data) => dispatch(updateProfile(data))
// also can set the default value of dispatch to store.dis
const updateProfile = (data, dispatch=storeDispatch) => dispatch(updateProfile(data))
// component
const dispatch = useDispatch()
<ProfileComponent onUpdate={updateProfile(dispatch)} />

Is it bad practice to select an entire slice in redux?

I want to select an entire slice in redux like the following:
interface AuthState {
user?: User;
shouldShowLogin: boolean;
}
export const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
setUser: ...,
showLogin: ...,
hideLogin: ...,
},
});
export const useAuth = () => useSelector((state) => state.auth);
So I could do something like const { user, shouldShowLogin } = useAuth(); within components. This is handier in most cases but I'm not sure if it's bad practice/inefficient.
I want to do something similar for dispatch (i.e. abstract away useDispatch()). Maybe something like const setUserWithDispatch = (info) => useDispatch()(info); so I can do simply setUserWithDispatch(userInfo) in my components, without having to do useDispatch() and then dispatch(setUser(userInfo)).
Ideally, I'd like to do const { user, shouldShowLogin, setUser, showLogin, hideLogin } = useAuth(); but I'm not sure how to go about it. Again, is this bad practice/inefficient? Why doesn't redux configure this automatically?
The component will rerender when the value selected from useSelector changes. Consequently, you generally want to select the minimum amount of information that you need in order to minimize rerenders.
If there are multiple properties in state.auth and you only care about one then the downside to this destructuring approach is that it will cause the component to rerender when other properties of the object change. In your example you seem to be using all of the properties so it shouldn’t matter.
I think you will have a hard time doing something custom with dispatch without violating the rules of hooks. Hooks cannot be called conditionally and must be called at the top level of the component. You cannot call useDispatch (or a custom hook that uses it) inside of an event handler callback, inside a useEffect function, etc.
The current implementation of the useDispatch hook means that you call useDispatch once per component to get access to the dispatch function for the current store based on the react-redux context. The returned dispatch function is a pure function (not a hook) and can be used anywhere.
You could use redux’s bindActionCreators to bind actions to the dispatch for a particular store instance. This sounds like what you are trying to do, but it is advised against. The downside is that you create an unnecessary coupling between the actions and a particular store instance. You would lose the ability to use a different store instance for testing.

What is the meaning of actions and action types in React-redux?

Now if i want to change value in store i should do following steps:
Go to constants/actionTypes file, create a line with action type
Go to actions and create action function
In each component where i use it i should create a function for mapDispatchToProps
In reducer i should write a logic of changing
Whats the point of such complexity?
Will it be wrong if i will do just one file with actions which will change the state? For example:
// actions.js
export const setCategories = (payload, setState, currentState) => setState({ categories: payload })
export const addCategory = (payload, setState, currentState) => setState({ categories: [...currentState.category, payload] })
To make it work i can create just couple of universal functions for all projects
1) getActions, which authomaticly collects all exports from actions.js and provide them to mapDispatchToProps, so in all components we could just write
const mapDispatchToProps = getActions
code of it can be something like
// actionsDispatcher.js
import * as actions from 'actions'
const getActions = (dispatch, ownProps) => {
Object.keys(actions).forEach(actionName => {
const fn = actions[actionName]
actions[actionName] = payload => dispatch({ action: fn, payload, type: _.toSnakeCase(actionName) })
}
return actions
}
which means we pass to dispatch the function of action from actions.js
2) setState which will work similary to react function, but for redux state
then in reducer function we just right
function rootReducer(state = initialState, action) {
if (action.action) {
action.action(action.payload, setState, state)
}
// here we make it extandable for ordinary way if required
if (action.type === '...') {
// ...
}
}
and nothing else...
So the question is whats wrong in such approach that will require for coder just write a function in one file 'actions.js' and call it from any component as props.someActionName(someParams) instead of changing 4 differents files?
Thank you
Redux is supposed to make complex requirements easier to implement but if you have simple requirements then it makes implementing these requirements more complicated.
The motivation mentions CQRS(Command Query Responsibility Segregation) that separates how you read from store (in redux with selectors and I'm a big fan of reselect) with how you write to it (with action and reducers).
The actions and reducers are the command (write) part of CQRS and is event sourcing, redux is sometimes referred to as an event store. This enables you to add or remove handlers (reducers or middle ware) for your events (actions) that can update the store, dispatch other events (=actions), do asynchronous stuff, write to local storage.
If you need to do all these things in one function (async fetch, write to local storage, call other functions (dispatch other actions),...) then that function becomes unmanageable.
Even if the function only calls other functions then it still needs to know the entire process of certain action. But if (for example) you had a local storage middleware that would write to storage on certain actions then no other code needs to know how or when it's called. So when logic of writing to local storage changes it is limited to the local storage middle ware.
This is the advantage of handlers (reducers, middleware) listening to events (actions), the handler only needs to know about a small portion of the process, not the entire process.
With event resourcing we also know why the state has a certain value instead of only knowing what the state is, the article states:
However there are times when we don't just want to see where we are, we also want to know how we got there.
Another big advantage of an event store is that you can re create the data by playing back the events. All this is excellently done with redux def tools.
Here is a great book on React with Redux.
Conventional-redux is a:
Library for small and medium react applications, it wraps the react-redux and provides API based on convention over configuration pattern without breaking redux compatibility.
You simply define an interactor:
class CounterInteractor {
// initial state
defaultState() {
return 0;
}
// actions:
doubleAsync() {
setTimeout(() => { this.dispatch('counter:double') }, 500)
}
// reduce methods:
onIncrement() {
return this.state + 1;
}
onDouble() {
return this.state * 2;
}
}
And dispatch actions to that interactor from your connected component component. That's it!

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.

Can I manipulate values within mapStateToProps

I'm learning React and Redux and found following problem that I can't get my head around, I have a config object in store that may looks like this:
{
general:{duration:100,strength:100},
gameA:{strength:50},
gameB:{duration:50}
}
Where general object will always be there and will have all properties, and it could have one or more game objects with all or part of overriding properties.
Now, games GameA and GameB are using duration and strength props so I can do:
const mapStateToProps = (state) => {
return {
duration: state.general.duration,
strength: state.general.strength
}
export default connect(mapStateToProps)(GameA);
but as my store example shows I could have different setup per game type which I want then override general setup. Can I then do this manipulation in the mapStateToProps function?
const mapStateToProps = (state) => {
let {duration, strength} = state.general;
if(state.gameA && state.gameA.duration) duration = state.gameA.duration;
if(state.gameA && state.gameA.strength) strength= state.gameA.strength;
return {
duration: duration,
strength: strength
}
Another pattern is to use reselect to compute derived values from state:
https://redux.js.org/docs/recipes/ComputingDerivedData.html#composing-selectors
Selectors offer the benefit of memoizing derived values which proves extremely useful given the sensitivity of react lifecycle methods (why make this calculation more than once if you don't have to?).
I find they are super useful for abstracting presentation logic from a component.
Here is a short and very simple example:
const mapStateToProps = state => ({
price: getPriceWithDollarSign(state),
})
// selectors/price.js
const getProduct = (state) => state.product // returns { price: 7.00 }
// i find it useful to immediately identify selectors with `get` so I know its a selector
export const getPriceWithDollarSign = createSelector(getProduct, (item) => {
// this will only be evaluated once for this specific item, future calls to this method
// will return the value cached by re-select (until the methods input changes)
return `$${item.price}` // item is value returned by getProduct
})
In your component, you'd end up with this.props.price -> '$7.00'
The beauty of reselect is in its ability to compose multiple selectors, making it easy to share and use other selectors within one another.
Check out https://github.com/reactjs/reselect for more information.
While usage of reselect is geared towards deriving values from redux state, you can use the library with any data structure / library / framework you please.

Resources