I'm building a mobile app with react-native and redux, I'm organizing my project structure by features in this way:
Component1/
---Component1Actions.js
---Component1Reducer.js
---...
Component2/
---Component2Actions.js
---Component2Reducer.js
---...
In my opinion this kind of project structure is amazing for many reasons, first of all for its great scalability.
Only problem I have come across so far is when 2 different components have to dispatch the same actions (such as a simple text change in a textbox).
It wouldn't make sense to rewrite the exact same action in 2 different files and I also know that importing an action from one component to another component is really a bad practice.
I thought about keeping these kind of "shareable" actions in a global module and then import them in the various components but I'm not sure if it is a good practice.
I would like to know the best way to handle this kind of situations.
Thanks in advance.
You can handle the same "ACTION_TYPE" in multiple reducers.
... actions are by design global. They are not meant to be tied to a particular reducer (Dan Abramov)
You could handle an "LOGOUT" action in all your reducers, which would just return the initial state.. and set the application to defaults
for example..
const postReducer = (state = initial, action) => {
swtich(action.type) {
...
case "LOGOUT":
return initial
default:
return state
}
}
Define it in common reducers.js, actions.js.
In compose pass it to REACT component connect(mapStateToProps, {commonAction, ...actions})
Related
I have a very large and complex React application. It is designed to behave like a desktop application. The interface is a document style interface with tabs, each tab can be one of many different type of editor component (there are currently 14 different editor screens). It is possible to have a very large number of tabs open at once (20-30 tabs). The application was originally written all with React class components, but with newer components (and where significant refactors have been required) I've moved to functional components using hooks. I prefer the concise syntax of functions and that seems to be the recommended direction to take in general, but I've encountered a pattern from the classes that I don't know how to replicate with functions.
Basically, each screen (tab) on the app is an editor of some sort (think Microsoft office, but where you can have a spreadsheet, text document, vector image, Visio diagram, etc all in tabs within the same application... Because each screen is so distinct they manage their own internal state. I don't think Redux or anything like that is a good solution here because the amount of individually owned bits of state are so complex. Each screen needs to be able to save it's current working document to the database, and typically provides a save option. Following standard object oriented design the 'save' function is implemented as a method on the top level component for each editor. However I need to perform a 'save-all' function where I iterate through all of the open tabs and call the save method (using a reference) on each of the tabs. Something like:
openTabs.forEach((tabRef) => tabRef.current.save());
So, If I make this a functional component then I have my save method as a function assigned to a constant inside the function:
const save = () => {...}
But how can I call that from a parent? I think the save for each component should live within that component, not at a higher level. Aside from the fact that would make it very difficult to find and maintain, it also would break my modular loading which only loads the component when needed as the save would have to be at a level above the code-splitting.
The only solution to this problem that I can think of is to have a save prop on the component and a useEffect() to call the save when that save prop is changed - then I'd just need to write a dummy value of anything to that save prop to trigger a save... This seems like a very counter-intuitive and overly complex way to do it.... Or do I simply continue to stick with classes for these components?
Thankyou,
Troy
But how can I call that from a parent? I think the save for each component should live within that component, not at a higher level.
You should ask yourself if the component should be smart vs dumb (https://www.digitalocean.com/community/tutorials/react-smart-dumb-components).
Consider the following:
const Page1 = ({ onSave }) => (...);
const Page2 = ({ onSave }) => (...);
const App = () => {
const handleSavePage1 = (...) => { ... };
const handleSavePage2 = (...) => { ... };
const handleSaveAll = (...) => {
handleSavePage1();
handleSavePage2();
};
return (
<Page1 onSave={handleSavePage1} />
<Page2 onSave={handleSavePage2} />
<Button onClick={handleSaveAll}>Save all</button>
);
};
You've then separated the layout from the functionality, and can compose the application as needed.
I don't think Redux or anything like that is a good solution here because the amount of individually owned bits of state are so complex.
I don't know if for some reason Redux is totally out of the picture or not, but I think it's one of the best options in a project like this.
Where you have a separated reducer for each module, managing the module's state, also each reducer having a "saveTabX" action, all of them available to be dispatched in the Root component.
I'm slowly adding Redux to React app. Not to all state, but to state that is consumed by many different components in the application.
There was function called saveOrRemoveNewItemOrBlankItem() that could be triggered by multiple events. I'd like to make this a single Redux action, or, at least I don't want to have to repeat the same logic everywhere the actions are dispatched, for example:
if(item.isBlank){
store.dispatch(removeItem())
} else {
store.dispatch(saveItem())
}
It would be better to just call one action (albeit one with a long name):
store.dispatch(saveOrRemoveNewItemOrBlankItem())
I know it's possible (using thunk middleware) for one Redux action creator to dispatch other Redux actions, and am already doing this in places. However it isn't possible (or at least I don't know if it's possible) if I want to save time on boilerplate by using ReduxJS/Toolkit CreateSlice. I can accept more boilerplate, but I want to know... is it terribly 'un-Redux' for actions to call other actions (in this case for saveOrRemoveNewItemOrBlankItem to call either saveItem or removeItem conditionally) ? What's the proper design route to accomplish this? Surely not writing the same conditional logic in multiple places... Is there another layer that can offload this responsibility from the react components themselves? New(ish) to React and new to Redux. Less interested in solving this particular example than getting a good grasp of best practices. Thoughts, opinions, facts, expertise, brilliance appreciated... What am I missing?
It is fine to combine actions in another action, you are right to not want this logic in your component. You don't need thunk to do this when using createSlice:
const slice = createSlice(/** stuff that creates slice */);
const sliceWithCombinedAction = {
...slice,
actions: {
...slice.actions,
commitItem(item) {
return item.isBlanc
? slice.actions.removeItem()
: slice.actions.saveItem();
},
},
};
export default sliceWithCombinedAction;
In this Redux: Colocating Selectors with Reducers Egghead tutorial, Dan Abramov suggests using selectors that accept the full state tree, rather than slices of state, to encapsulate knowledge of the state away from components. He argues this makes it easier to change the state structure as components have no knowledge of it, which I completely agree with.
However, the approach he suggests is that for each selector corresponding to a particular slice of state, we define it again alongside the root reducer so it can accept the full state. Surely this implementation overhead undermines what he is trying to achieve... simplifying the process of changing the state structure in the future.
In a large application with many reducers, each with many selectors, won't we inevitably run into naming collisions if we're defining all our selectors in the root reducer file? What's wrong with importing a selector directly from its related reducer and passing in global state instead of the corresponding slice of state? e.g.
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, todo(undefined, action)];
case 'TOGGLE_TODO':
return state.map(t => todo(t, action));
default:
return state;
}
};
export default todos;
export const getVisibleTodos = (globalState, filter) => {
switch (filter) {
case 'all':
return globalState.todos;
case 'completed':
return globalState.todos.filter(t => t.completed);
case 'active':
return globalState.todos.filter(t => !t.completed);
default:
throw new Error(`Unknown filter: ${filter}.`);
}
};
Is there any disadvantage to doing it this way?
Having made this mistake myself (not with Redux, but with a similar in-house Flux framework), the problem is that your suggested approach couples the selectors to the location of the associated reducer's state in the overall state tree. This causes a problem in a few cases:
You want to have the reducer in multiple locations in the state tree (e.g. because the related component appears in multiple parts of the screen, or is used by multiple independent screens of your application).
You want to reuse the reducer in another application, and the state structure of this application is different from your original application.
It also adds an implicit dependency on your root reducer to each module's selectors (since they have to know what key they are under, which is really the responsibility of the root reducer).
If a selector needs state from multiple different reducers, the problem can be magnified. Ideally, the module should just export a pure function that transforms the state slice to the required value, and it's up to the application's root module files to wire it up.
One good trick is to have a file that only exports selectors, all taking the state slice. That way they can be handled in a batch:
// in file rootselectors.js
import * as todoSelectors from 'todos/selectors';
//...
// something like this:
export const todo = shiftSelectors(state => state.todos, todoSelectors);
(shiftSelectors has a simple implementation - I suspect the reselect library already has a suitable function).
This also gives you name-spacing - the todo selectors are all available under the 'todo' export. Now, if you have two todo lists, you can easily export todo1 and todo2, and even provide access to dynamic ones by exporting a memoized function to create them for a particular index or id, say. (e.g. if you can display an arbitrary set of todo lists at a time). E.g.
export const todo = memoize(id => shiftSelectors(state => state.todos[id], todoSelectors));
// but be careful if there are lot of ids!
Sometimes selectors need state from multiple parts of the application. Again, avoid wiring up except in the root. In your module, you'll have:
export function selectSomeState(todos, user) {...}
and then your root selectors file can import that, and re-export the version that wires up 'todos' and 'user' to the appropriate parts of the state tree.
So, for a small, throwaway application, it's probably not very useful and just adds boilerplate (particularly in JavaScript, which isn't the most concise functional language). For a large application suite using many shared components, it's going enable a lot of reuse, and it keeps responsibilities clear. It also keeps the module-level selectors simpler, since they don't have to get down to the appropriate level first. Also, if you add FlowType or TypeScript, you avoid the really bad problem of all your sub-modules having to depend on your root state type (basically, the implicit dependency I mentioned becomes explicit).
Redux documentations says I should make actions and action creators, like this:
function addTodo(filter) {
return {
type: SET_VISIBILITY_FILTER,
filter
}
}
Then write reducers, like this:
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
});
}
}
I then invoke the action using dispatch:
store.dispatch(addTodo("Ask question on stackoverflow"));
It seems there's a one-one correspondence between actions and reducers; the sole purpose of the action is to select a reducer and provide input data for that reducer.
Why don't we skip the middle man and identify actions with reducers and action creators with functions producing reducers? Then dispatch would take a single argument, a reducer/action of type State => State:
// Action/reducer. (Parametrised state transformer, really.)
const addTodo = text => state => {
return Object.assign({}, state, {
visibilityFilter: action.filter
});
}
// Dispatch takes as a argument the action/reducer
store.dispatch(addTodo("Ask question on stackoverflow"));
You'd lose the ability to serialise actions, but otherwise, it seems you'd get rid of boilerplate action creators and express more clearly the connection between actions and reducers. If you're in Typescript, you also get typechecking of the data in actions, which is difficult to express otherwise.
So what reasons for having actions as data am I missing?
The main purpose of action in Redux is to reduce the state.
Reduce method will be called on array of actions (thats why it called a reducer). Example:
import reducer from './reducer';
const actions = [
{type: 'INIT'},
{type: 'SOME_ACTION', params: {...}},
{type: 'RECEIVE_DATA', data: [...]},
{type: 'SOME_ANOTHER_ACTION', params: {...}},
{type: 'RECEIVE_DATA', data: [...]},
...
];
const finalState = actions.reduce(reducer, undefined);
Action creators is a function that can create actions. It is not necessary that action creator will create only one action.
Actually, if your reducer is able to receive functions instead of objects - your actions will be functions and it will do the main purpose, but you can loose some benefits of Redux functional abilities.
In that case the reducer will be implemented like this:
function reducer(state, action) {
return action(state);
}
The reasons why you may create actions as {type: 'ACTION_NAME'} format:
Redux DevTools expects this format.
You need to store sequence of actions.
Reducer makes state transformations on worker.
Everybody in Redux ecosystem use this format. It's a kind of convention.
Hot reloading abilities (your stored functions will not be reloaded).
You need to send actions as is on server.
Debugging benefits - to see the stack of actions with actions names.
Writing unit tests for reducer: assert.equal(finalState, expectedState).
More declarative code - action name and parameters are about "what to do" and not about "how to do" (but addTodo('Ask question') is declarative too).
Note about coupling between action creators and state changes
Just compare two notations:
First:
function someActionCreator() {
return {
type: "ADD_TODO",
text: "Ask question on stackoverflow"
}; // returns object
}
Second:
function someActionCreator() {
return addTodo("Ask question on stackoverflow"); // returns function
}
"In both cases we see that code is declarative and action creator is decoupled from state change. You can still reuse addTodo or dispatch two addTodo's or use middleware or dispatch compose(addTodo('One'), addTodo('Two')). The main difference is that we created Object and Function and place in code where state changes.
There is NOT a one-to-one mapping between actions and reducers. Per Dan Abramov's comments at https://github.com/Pitzcarraldo/reduxible/issues/8 :
It reinforces a very common misconception about Redux: namely that action creators and reducers are one-to-one mapping.
This is only true in trivial examples, but it is extremely limiting in real applications. Beginners exposed to this pattern couple reducers and action creators, and fail to realize they're meant to be many-to-many and decoupled.
Many reducers may handle one action. One reducer may handle many actions. Putting them together negates many benefits of how Flux and Redux application scale. This leads to code bloat and unnecessary coupling. You lose the flexibility of reacting to the same action from different places, and your action creators start to act like “setters”, coupled to a specific state shape, thus coupling the components to it as well.
As for actions and the "type" parameter, the other answers are right. That's deliberately how Redux was designed, and that was intended to give the benefits of serialization for debugging purposes.
Good question.
Separating actions from state changes is really a Flux pattern, rather than a specifically Redux thing. (Though I will answer the question with reference to Redux.) It's an example of loose coupling.
In a simple app, tight coupling between actions and state changes might be fine. But in a larger app, this could be a headache. For instance, your addTodo action might trigger changes in several parts of the state. Splitting actions from state changes - the latter performed in reducers - allows you to write smaller functions, which are easier to reason about and more testable.
Additionally, decoupling your actions and state changes allows your reducer logic to be more reusable. e.g. Action X might trigger state changes A and B, whilst action Y only triggers state change A.
Furthermore, this decoupling gives rise to a Redux feature called middleware. Middleware listens to action dispatches. It doesn't change the state of the app, but it can read the current state and the next state, as well as the action information. Middleware is useful for functionality extraneous from the core application logic, e.g. logging and tracking (dev tools were mentioned in a previous answer).
[UPDATE] But why have actions as objects?
If it were simply a matter of functions calling other functions, that decoupling would be much less explicit. Indeed it may even get lost; since most actions only enact one state change, developers might tire of a single function calling a single function and do away with the separation entirely.
The other thing is the Flux data flow model. One-way data flow is very important to the Flux/React paradigm. A typical Redux/React goes something like this: Store state -> Higher order React components -> Lower order React components -> DOM. The action is the aberration in the model; it's the backwards arrow transmitting data from the view to the store. It makes sense to make the action something as loud and emphatic as possible. Not a mere function call, but a dispatched object. It's as if your app were announcing Hey! Something important happened here!
How do people typically approach having "global" data in a React application?
For example, say I have the following data for a user once they're logged into my app.
user: {
email: 'test#user.com',
name: 'John Doe'
}
This is data that almost any component in my app might like to know about - so it could either render in a logged in or logged out state, or perhaps display the users email address if logged in.
From my understanding, the React way of accessing this data in a child component is for a top level component to own the data, and pass it to child components using properties, for example:
<App>
<Page1/>
<Page2>
<Widget1/>
<Widget2 user={user}/>
</Page2>
</App>
But this seems unwieldy to me, as that would mean I'd have to pass the data through each composite, just to get it to the child that needed it.
Is there a React way of managing this type of data?
Note: This example is very simplified - I like to wrap intents up as composites so implementation details of entire UI features can be drastically changed as I see fit.
EDIT: I'm aware that by default, calling setState on my top level component would cause all child components to be re-rendered, and that in each child component I can render using whatever data I like (e.g. global data, not just state or props). But how are people choosing to notify only certain child components that they should be rendered?
Since I originally answered this question, it's become apparent to me that React itself doesn't support "global" data in any sense - it is truly meant to manage the UI and that's it. The data of your app needs to live somewhere else. Having said that, it does now support accessing global context data as detailed in this other answer on this page. Here's a good article by Kent Dodds on how the context api has evolved, and is now officially supported in React.
The context approach should only be used for truly global data. If your data falls into any other category, then you should do as follows:
Facebook themselves solve this problem using their own Flux library.
Mobx and Redux are similar to Flux, but seem to have more popular appeal. They do the same thing, but in a cleaner, more intuitive way.
I'm leaving my original edits to this answer below, for some history.
OLD ANSWER:
The best answer I've found for this so far are these 2 React mixins, which I haven't had a chance to try, but they sound like they'll address this problem:
https://github.com/dustingetz/react-cursor
and this similar library:
https://github.com/mquan/cortex
MAJOR NOTE: I think this is a job for Facebook's Flux, or something similar (which the above are). When the data flow gets too complex, another mechanism is required to communicate between components other than callbacks, and Flux and it's clones seem to be it....
Use the React Context Property This is specifically for passing global data sets down the chain without explicitly forwarding them. It does complicate your Component lifecycle functions though, and note the cautions offered on the page I've linked.
You can use the React Context API for passing global data down to deeply nested child components. Kent C. Dodds wrote an extensive article on it on Medium React’s ⚛️ new Context API. It'll help in getting a better understanding of how to use the API.
I think React.createContext() is perfect solution for your purpose.
React will re-render only components, that listen context changes with useContext hook.
Here is a simple snippet for your code:
export const CurrentUser = React.createContext({})
const App = () =>
{
const User = getUser() // any authorisation method
return <>
<CurrentUser.Provider value={User}>
<App>
<Page1/>
<Page2>
<Widget1/>
<Widget2/>
</Page2>
</App>
</CurrentUser.Provider>
</>
}
const Widget2 = () =>
{
const User = useContext(CurrentUser)
return <>{User?.name}</>
}
In case if you want to control re-renders directly, you can use React.memo in nested components. For example, if you need re-render component only after specific attribute change.
Also, with nesting context values, you can reach good flexibility of your app. You can pass different context values for different part of your application.
export const CurrentUser = React.createContext({})
const App = () =>
{
const User = getUser() // any authorisation method
const AnotherUser = getAnotherUser() // any authorisation method
return <>
<CurrentUser.Provider value={User}>
<App>
<Page1/>
<CurrentUser.Provider value={AnotherUser}>
<Page2>
<Widget1/>
<Widget2/>
</Page2>
</CurrentUser.Provider>
</App>
</CurrentUser.Provider>
</>
}
const Widget2 = () =>
{
const User = useContext(CurrentUser)
return <>{User?.name}</>
}
What's wrong with just passing data all the way down the component chain via rendering all children with {...restOfProps}?
render(){
const {propIKnowAbout1, propIKnowAbout2, ...restOfProps} = this.props;
return <ChildComponent foo={propIKnowAbout1} bar={propIKnowAbout2} {...restOfProps}/>
}
There is Reactn https://www.npmjs.com/package/reactn
You use this.global and this.setGlobal to get and set the global state same as you do with the local state.
To be able to do so you only need to
import React from 'reactn';