I am working on a react-with-redux application I am working with the library redux-undo and as the capability of the library goes, it listens to a list of actions and reverts to the previous state when the undo happens.
Scenario: I have a page where a list item will be created/deleted and a API call is made whenever these actions happen. The user can undo both the create and delete operations.
I would like to know if there is any way to know the latest action that has been dispatched.
For example: If the user creates a list item and clicks undo, I would like to know that the latest action that was dispatched was create, so that I can revert the create(delete the list item by making an API call).
Like wise, If the user deleted an list item I would like to know that the latest action that was dispatched was delete, so that I can revert the delete(create the list item again by making an API call, fetching the details from past shape of state and sending the details of the deleted list item)
Please let m know if there is any way to achieve this?
You can use store.subscribe, as stated here:
The easiest way is to have a reducer that remembers just the last action:
function lastAction(state = null, action) {
return action;
}
Then you can use store.getState().lastAction, assuming you did
something like
import { combineReducers, createStore } from 'redux';
const rootReducer = combineReducers({
someReducer,
someOtherReducer,
lastAction // <-- use it!
});
const store = createStore(rootReducer);
store.subscribe(() => {
console.log(store.getState().lastAction);
});
You would need to find an alternative storage space to store your latest action that is unaffected by redux-undo but also global so you can access it anywhere you need.
I recommend a local storage solution.
In your reducer, you can add a statement to set which was the latest dispatched action into browser storage:
...
case CREATE: {
localStorage.setItem("latestAction", "CREATE");
return someNewState;
}
case DELETE: {
localStorage.setItem("latestAction", "DELETE");
return someNewState;
}
...
Then when you want to get that value from anywhere in code:
localStorage.getItem("latestAction");
Related
Basically I want to implement an architecture of the type
UserManager.getUser(22)
getUser() -> ReduxStore -> (Does not contain an user with ID 22) -> Goes to User Provider -> User Provider goes to API and returns User object.
Redux Store then saves for subsequent requests and returns User object.
Redux has unidirectional data flow, so the writing and the reading of data are decoupled.
Components read Redux data by subscribing to the store via connect or useSelector, and they write data via disptaching actions in the store.
A selector takes in state and returns a subset of the state, but it does not change the state. A dispatched action can change the state, but it does not return any state.
CQRS (Command Query Responsibility Segregation) is one of the motivations behind Redux. The idea of CQRS is basically to:
use a different model to update information than the model you use to read information
In Redux the update-model is the actions and the read-model is the selectors. To combine them into a single "provider" would be to defeat the purpose of Redux's design.
But if you absolutely had to conflate the two concerns, it might be possible to somehow combine a selector and action-dispatch with a thunk. Again, though, it would not be idiomatic Redux.
Yes we call them action creators. Let's say you're using redux thunk for side effects so getUser will be an action creator that'll first query redux store to see if there is data available if not it'll fetch it from server and store in the redux store like this:
function getUser(id) {
// getState is a function that gives us access to entire redux store
return (dispatch, getState) => {
try {
let user = getState().users.find(x => x.id === id) || null;
if (user) {
dispatch({ type: 'GET_USER', payload: user })
return;
}
user = fetchUserFromServer(id);
dispatch({ type: 'GET_USER', payload: user })
} catch(error) {
// handle error here
}
}
}
Now when next time getUser is called there will be data for that user in the redux store and a call to server will be avoided.
Hope it helps :)
I am learning react-redux at the moment, already grasped the basics (I believe), I am now trying to create a notification array on my state that can be populated by several actions.
But since is a notification, I only want to add a notification after each of those actions is completed (the state is changed).
I thought about using a middleware class but that would add the notification into array before the action is computed on the reduce.
Quick example:
User logins -> dispatch action -> affects state property related to login -> adds a notification on the notification array
User adds item -> dispatch action -> affects state property related to items -> adds a notification on the notification array
These are 2 different actions, I could in the if logic of each action (on reducer) update the notification array, but seems like repeated code and I would be creating notification object on a reducer, is that okay?
So in this case where should I dispatch the action of adding a notification to the notification array?
I am looking for best practices
The best practice is to handle such scenario's in reducer. Process the data one-time and update state accordingly. In your scenario, you want to update the notifications array once the action is completed. You have to do that repeatedly because it is to be done. Here is what you can do:
case LOGIN:
//checks if user is valid
return {
...state
loggedIn: true,
user,
notifications: state.notification.push({ id: 1, message: "Logged In!" })
}
And if you want to remove a notification you can do that:
case REMOVE_NOTIFICATION:
// You can get the notification ID you want to remove from action
let notifications = state.notification.filter(notify => notify.id !== action.id);
return {
...state
notifications
}
Hope this will help you.
I'm trying to figure out what the proper way to go about this is.
Lets say we have a store of items. These items can be edited, deleted and created. When editing or adding an item the route changes to /item/add or /item/edit/{id}.
After an item has been added or edited successfully by saga we want to send them back to the base route. What's the proper way to go about this?
I've seen two ways, one where you inject a history object into a and then include the history object in the saga's as well. Another to keep a "status" ("", "failed", "success") in the item store using in the components and resetting that status when the component unmounts since add and edit both need to use the status.
Which is the proper way to go about this problem though?
In the past, I've used react-router v3 with react-router-redux integration to dispatch actions that change the route.
React router 4 has a react-router-redux package, which is still in alpha stage. Although it's alpha, this functionality works fine for me, but you should check it for yourself.
In your saga, you can use the put effect to dispatch the action:
import { push } from 'react-router-redux';
yield put(push('/route'));
There's no proper way, just whatever works with you. I prefer the minimal:
//history.js
...
export const history = createHistory();
//Root.js
import { history } from './history';
<Root history={history}>
//mySaga.js
import { history } from './history';
function *mySaga() {
yield call(history.push, '/myRoute');
}
Check this doc: https://reacttraining.com/react-router/core/guides/redux-integration
According to this doc, the best solution is to include the history object (provided to all route components) in the payload of the action, and your async handler can use this to navigate when appropriate.
If you have history object from the payload to the action, you can use
history.push('requiredRoute');
to requiredRoute.
I have a simple React App. It allows a user to edit a form to update data in a database. On Submit the page generates an action with the form data. A Redux Saga yields to the action and asynchronously updates the database. This all works.
However in one case the update is slightly more involved. Not only must new data be added but any data deleted on the form must be deleted from the database in a series of API calls. To do this I need to know what has changed (e.g. what has been deleted) rather than just the new state.
How can my saga have access to this information? I could calculate it in the reducer because that has access to the previous state but it is commonly held to be an anti-pattern for the reducer to then dispatch a new action.
Sagas have a select effect available, which just runs a selector function and returns the extracted state. You can use this to retrieve the old and new items from the Redux store, and deal with the changes from there:
function* handleFormUpdates() {
const oldItem = yield select(selectOldItem);
const newItem = yield select(selectNewItem);
const changes = diffTheItems(oldItem, newItem);
// make API calls to deal with changes appropriately
}
Overall, this is a good reason to keep the "temporary" or "draft" state in Redux, so that you can make use of both the "current" and "draft" values in your logic.
I discussed some related concepts in my blog posts Practical Redux, Part 7: Form Change Handling, Data Editing, and Feature Reducers and Practical Redux, Part 8: Form Draft Data Management
...any data deleted on the form must
be deleted from the database in a series of API calls. To do this I
need to know what has changed (e.g. what has been deleted) rather than
just the new state.
If I understand correctly you have form state saved in a redux store and you need to know when and what has changed. You could create your own watcher saga:
function* watchFormUpdates(){
while (true) {
const oldFormState = yield select(selectors.selectFormState);
const action = yield take (actionTypes.FORM_UPDATE); // wait until action is dispatched
const newFormState = yield select(selectors.selectFormState); // at this point store has been updated
// compare oldFormState with newFormState...
if(oldFormState.hasSubscription && !newFormState.hasSubscription) {
yield fork(deleteSubscriptionSaga); // start non-blocking worker task
// or just dispatch action - yield put(actionCreators.deleteSubscription());
}
}
}
I'm new to React and Redux and I'm trying to write a simple application where a person can submit a URL for an image and it will show up on the page. Note that there is no backend to the application as of yet.
export const addImage = (url) => {
return {
type: ADD_IMAGE,
key: Guid.create().toString(),
payload: url
}
}
Adding an image creates an action of type ADD_IMAGE and my reducer updates the state consequently. However I also check if the URL is already in the list.
switch (action.type) {
case ADD_IMAGE:
if (state.find(image => image.url === action.payload)) {
return state;
} else {
return(
[
...state,
{key: action.key, url: action.payload}
]
);
}
break;
default:
}
The problem is that when I deny a post because the URL is already in the state I also want to convey that message to the user by showing it in a div next to the form. From what I've read I think I'm not supposed to try to access React state from reducers (if that is even possible) and... well.. I'm just stuck. I've been trying to find a simple guide on how to do this but I find nothing I can quite understand. After adding a database I guess I will have to do this as part of the async process but as I have it now I guess there should be some kind of simple solution.
You are starting to introduce logic into your reducer and this will inevitably lead to situation where you need to process some state outside of the reducer's scope.
The solution is to transfer your reducer logic into a thunk using a middleware package such redux-thunk (or similar package). This allows you to treat special kinds of actions as functions which means you can extend a plain action with specific action-related logic. The example you give of needing to dispatch an error action under certain conditions is an excellent use-case for redux-thunk.
Below is a example of how you might pull the logic out of your reducer into a thunk. You should note that, unlike reducers, thunks explicitly support fetching state and dispatching subsequent actions via the getState and dispatch functions.
Thunk example
export const addImage = (url) => {
return (dispatch, getState) => {
const key = Guid.create().toString()
dispatch({
type: ADD_IMAGE,
key,
payload: url
})
const state = getState()
// you would want to use a `selector` here to locate the existing image
// within the state tree
const exists = selectors.images.exists(state, url)
if (exists) {
dispatch(actions.ERROR_IMAGE_EXISTS({key, url}))
}
}
}
A note on selectors
You will see that I am using a selector to determine if the image exists. In the same way that thunks are the place to put your dispatch logic, a selector is the place to put your state-traversal logic. They are used to return portions of the state-tree or provide simple state-utilities such as the exists function shown above. Packages are available to help, for example reselect.
Follow on questions from comments
Are selectors not a built-in thing in Redux?
No they are not. Selectors are an idea that builds on top of redux and the concept exists as a place to put your state searching, caching, reading logic. This extracts the sometimes complex state traversal logic out of your thunks and components and into a nice tidy, structured collection of selectors.
Why use a selector instead of state.images.find(i => i.url === url)?
If you use a selector package then you get far more benefit than just a good separation of concerns, you get a big performance improvement (see usage example below).
Here are the headlines from the popular reselect package:
Selectors can compute derived data, allowing Redux to store the minimal possible state.
Selectors are efficient. A selector is not recomputed unless one of its arguments change.
Selectors are composable. They can be used as input to other selectors.
Why doesn't actions.ERROR_IMAGE_EXISTS(url) work for me
Because I just made that up for the example. The point is that you can dispatch actions from within the thunk, how you declare or get access to the action is up to you. I tend to centralise all my shared actions into an actions object that I import.
Selector usage example
Here is an example from my real-life code that shows how I use selectors to passing portions of the state as props to a react component:
const mapStateToProps = (state) => ({
model: services.model.selector.getSelected(state),
build: services.build.selector.getLastBuild(state),
recommendations: services.recommend.selector.getRecommendations(state)
})
Each of these selectors is finding the correct portion of the state tree and delivering it back ready for use. Nice and tidy, and if you use reselector, very efficient.