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!
Related
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;
I have a login popup which maps a 'isLoggingIn' boolean to the redux store. When a login request action is dispatched a saga intercepts the action and sends another action that the login is processing, the reducer will take that in and set the 'isLoggingIn' boolean to true.
My store:
export interface AppState {
playerToken:string,
loginOpen: boolean,
loginProcessing: boolean
}
The login saga:
function* loginUser(action: any) {
yield put({ type: (LOGIN + PROCESSING) });
try {
const response = yield call(apiCall, 'api/token', 'POST', { username: action.payload.username, password: action.payload.password });
if (response)
{
yield put({ type: (LOGIN + SUCCESS), payload: response.data });
}
catch ({ statusCode }) {
if (statusCode === 401) {
yield put({ type: (LOGIN + FAIL), payload: { error: "Invalid username or password" } })
}
console.log(statusCode);
}
}
Once the saga is done with the login if there's an error it dispatches an action which the reducer sets to a 'loginError' string in the store and sets the isLoggingIn to false, otherwise isLoggingIn is set to false and the user login id is set which prompts the popup to hide itself (i.e. isVisible={this.props.playerToken == undefined).
This seems insanely complicated but I'm not sure how to break this down using Redux principles. I feel strongly the isProcessingLogin should be part of the components state, but the component has no real idea what's going on after it sends the login attempt event and there's no way for it to ever know unless it's listening on for something in the props.
It gets much worse with the various crud operations which need to happen and the various 'isCreatingXModel' booleans which have to be set to true/false in the store and mapped correctly in components.
Is this how redux is supposed to work or am I over using it in places it doesn't belong?
If this is how redux is supposed to be used what are its benefits exactly? I've read online a lot about things which make sense like having a single point of truth, but they can all be done without the crazy redux bloat, I've read people say not to use redux until you need it but that means I'm going to be doing api calls in two conceptually separate areas of code when redux is integrated whenever I 'need it', finally one of the biggest advantages I see purported by advocates is its ability to rewind and move forward in time, which is great but it won't work in any live application which connects to a database in the backend it manipulates unless as part of rewinding there's an undo last api call action.
Keep in mind that these are all entirely my opinions.
1. You might not need sagas (or thunk or other 'async' redux plugin)
Remember that redux is state management only. The API calls can be written in vanilla javascript with or without redux. For example: here's a basic replication of your flow without sagas:
e.g.
import { setLoadingStatus } from './actions'
import { store } from './reducers' // this is what is returned by a createStore call
export function myApiCall(myUrl, fetchOptions) {
store.dispatch(setLoadingStatus('loading'))
return fetch(myUrl, fetchOptions)
.then((response) => {
store.dispatch(setLoadingStatus('succeeded', data))
// do stuff with response data (maybe dispatch a different action to use it?)
})
.catch((error) => {
store.dispatch(setLoadingStatus('failed', error))
// do stuff
})
}
Note the use of store.dispatch. There's an interesting notion in React-Redux that you can only dispatch actions with mapDispatchToProps, but fortunately, that's not true.
I replaced your multiple actions with one that takes a state and optional data. This'll reduce the number of actions and reducers you need to write, but your action history will be harder to read. (Instead of three distinct actions, you'll only have one.)
2. You might not need redux.
The example function above could look basically identical if you weren't using redux -- imagine replacing the store.dispatch calls with this.setState calls. In the future, when you added it, you'd still have to write all the reducer, action, action creator boilerplate, but it would be only slightly more painful than doing it from the start.
As I said above, I usually go with Redux when working with React the built-in state management is has a bad mental map with any sort of large app.
There are two opposing rules of thumb:
use redux for state that needs to be shared between components
use redux for all state and get a single source of truth
I tend to lean to the second one. I hate hunting down errant pieces of state in the leaves of a large React component tree. This is definitely a question with no "correct" answer.
My question is "Is there a way to avoid sending callbacks deep in the tree when I want to access/react upon child components data".
I have a component Application that contains a child component Person which in turn contains a child component TraitCollection which in turn contains a child component Trait.
When Trait is changed I want to send data back to Application to be forwarded to another child in Application named PersonList.
My current solution is to send callbacks as props to each child. Each callback function constructs and appends the data which finally reaches Application where I pass it down as a prop to PersonList.
I can imagine that "adding more levels", or splitting components up in more smaller parts will further complicate this callback chain.
Is there any other way to do this or is this the best way to handle passing data from children to parents in React?
The way you are handling it is the recommended way and the most React-like. There is nothing wrong with that approach by itself other than the problem you have found. It looks like Redux could help you solve that problem. Redux lets you unify the state of your React application with the usage of a common store and a good design pattern based on action creators, actions and reducers.
You can use React Context directly https://facebook.github.io/react/docs/context.html, but better option will to use React-Redux, it will allow you to avoid passing callbacks and you will be able to change state of any component from any component (connected to the store) with dispatch function.
The docs answer this directly:
In large component trees, an alternative we recommend is to pass down a dispatch function from useReducer via context
From: https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down
(Archive)
To avoid Deep Callbacks, according to React documentation, useReducer via context as below:
How to avoid passing callbacks down?
We’ve found that most people don’t enjoy manually passing callbacks through every level of a component tree. Even though it is more explicit, it can feel like a lot of “plumbing”.
In large component trees, an alternative we recommend is to pass down a dispatch function from useReducer via context:
const TodosDispatch = React.createContext(null);
function TodosApp() {
// Note: `dispatch` won't change between re-renders
const [todos, dispatch] = useReducer(todosReducer);
return (
<TodosDispatch.Provider value={dispatch}>
<DeepTree todos={todos} />
</TodosDispatch.Provider>
);
}
Any child in the tree inside TodosApp can use the dispatch function to pass actions up to TodosApp:
function DeepChild(props) {
// If we want to perform an action, we can get dispatch from context.
const dispatch = useContext(TodosDispatch);
function handleClick() {
dispatch({ type: 'add', text: 'hello' });
}
return (
<button onClick={handleClick}>Add todo</button>
);
}
This is both more convenient from the maintenance perspective (no need to keep forwarding callbacks), and avoids the callback problem altogether. Passing dispatch down like this is the recommended pattern for deep updates.
I am trying my hands on ngrx library for managing the state of my application. I have gone through many ngrx documents and git pages. I understand that there are three important concept:
Store
Reducer and
Action
Store is the single source of data for our application. So any modification or retrieval of data is done through Actions. My question here is what exactly happens when an action is dispatched to the store? How does it know which reducers is to be invoked? Does it parses all the reducers registered to the store? There can be multiple actions with the same name in that case what happens?
Thanks in advance.
A picture is worth a thousand words...
Source: Building a Redux Application with Angular2
Example Code: ngrx-todo-app
Demo: Todo App using #ngrx/store and #ngrx/effects
My question here is what exactly happens when an action is dispatched to the store?
All of the registered reducers get a chance to handle the action
How does it know which reducers is to be invoked?
All of the registered reducers get invoked. Try putting console.logs into all the reducers and you can see for yourself.
Does it parses all the reducers registered to the store?
Yes
There can be multiple actions with the same name in that case what happens?
If you have multiple actions with the same name they would be treated the same. For example if I dispatched type "ADD" with payload 3 and then dispatched a different action called type "ADD" with payload 3 it would be the same thing.
Ngrx isn't that smart. Let's say we have the following reducers:
const reducers = {
blog: BlogReducer,
post: PostReducer,
comment: CommentReducer
}
Say I dispatch 'ADD_COMMENT'. Basically BlogReducer will first try to handle it, followed by PostReducer, and finally by CommentReducer. The ordering is determined by how you specified the reducers object above. So if I did this:
const reducers = {
comment: CommentReducer,
blog: BlogReducer,
post: PostReducer
}
CommentReducer would be the first one to try and handle the 'ADD_COMMENT'.
The details of my problem involve me integrating a D3 force graph with my redux state. Each tick update dispatches an action to update a position collection in my redux state. These positions are then merged with node data and use to update my visualization. React handles the DOM and D3 is used basically just to calculate forces, collision detection, etc.
I'm finding it difficult/impossible to maintain a smooth experience for the user using this design pattern. Locally on my laptop, I'm getting ~117ms between actions, well below 60fps (16ms between actions).
I've tried to simplify and streamline my middleware as much as possible to reduce latency.
What other strategies can I employ to get better update time using redux? Or am I trying to do something Redux was never meant to do?
On each tick means requestAnimationFrame right? If not, use it. :)
If you are only dispatching one action, meaning one Redux update -> one change callback -> one React re-render, there's not much you can do, your calculations take too much or your React component tree is too big and non efficient (no shouldComponentUpdate and so on).
In case you're dispatching more than one action on each frame, you may find useful one technique I've used in the past, which is wrapping my reducer so it can handle an array of actions at once. That way you can avoid re-renders until the last one. The code is surprisingly simple, as everything with Redux is:
function withBatching(originalReducer){
return function(state, action){
if(action.type === 'BATCH' && Array.isArray(action.payload)){
return action.payload.reduce(state, originalReducer)
} else {
return originalReducer(state, action)
}
}
}
//now wrap any (or all) of your reducers
const batchedAppReducer = withBatching(myCombinedAppReducer)
In your action creator, instead of dispatching N actions, dispatch one batch of them:
{
type: 'BATCH'
payload: [
{ type: 'MY_ACTION_TYPE', payload: 'xxxx' },
{ type: 'ANOTHER_THING', payload: 'xxxx' }
]
}
If you want to batch actions handled by different reducers, I would add batching on your combined reducer, just prior to createStore. Otherwise you can simply enable batching for your specific need.
I've used this technique when prototyping some games with great success: in my case I had to update multiple entities at once but didn't want to render until the "world" state was updated, so I batched all that updates.
Hope it helps, if you provide some details on your "update" cycle logic I'll try to help you further.
If you are using react-redux, there is also a batch API: https://react-redux.js.org/api/batch
From the doc:
import { batch } from 'react-redux'
function myThunk() {
return (dispatch, getState) => {
// should only result in one combined re-render, not two
batch(() => {
dispatch(increment())
dispatch(increment())
})
}
}