New to react/redux so bear with me :).
When selecting a regionlevel from a dropdown, I want to store the selected RegionLevelId, and make an API call to fetch the regions that belong to this RegionLevel.
At first I was storing this regionLevelId in the regionLevelReducer, which made it so that I only triggered one state change.
However, I'm saving more selected options in my state so I figured it might be cleaner to make a "selectOption" reducer that stores the selectedIds.
The point is now however, that when I select a regionLevel from the dropdown, I have to make two action calls. One to store the regionLevelId, and one to fetch data from the API. This renders the page twice, which is unnecessary.
I was wondering if it's possible to call two actions, while rendering AFTER these two actions have been completed. Now it renders in between these two actions.
I'm not sure if this is defying of how redux should be used. But I'm interested in how people would solve this problem.
The action I'm using atm:
export function selectRegionLevel(regionLevelId) {
return function(dispatch) {
dispatch({
type: "SELECT_REGION_LEVEL",
payload: regionLevelId
});
dispatch({
type: "FETCH_REGIONS",
payload: {
request: {
url: `regionLevels/${regionLevelId}/regions`
}
}
});
};
}
Thanks.
From your requirement, i think what you are looking for is Batched Action. Take a look at react-batched-actions which aims to solve this problem and is recommended by redux team in such scenerios.
Related
I am new to redux so apologies if it's a stupid question. I have a react application and I am adding redux store to it. I am using thunk middleware to execute async code to get/save data from the backend.
I have this below in index.js:
store.dispatch(loadInitialState());
loadInitialState() calls backend service and loads the initial data and dispatches an action to load initial state:
dispatch({
type: LOAD_INITIAL_STATE,
payload: initData,
});
Now, in case things didn't go well I dispatch a set error action like this:
dispatch({ type: SET_ERROR_MESSAGE, payload: errorMessage });
and the reducer has got this:
case actions.SET_ERROR_MESSAGE:
return {
...state,
error: action.payload,
};
case actions.RESET_ERROR_MESSAGE:
return {
...state,
error: null,
};
I have a component called <DisplayError /> that I have included in App.js at the top where on every page of the application this component will be first one to render and it uses useSelector(state => state.error) to find if there and an error to display it.
The problem: If I have an error on a route/page and if I navigate away from that route the error message stays there because that component it common at the top of all pages. The only way I see to solve this is to do dispatch({ type: RESET_ERROR_MESSAGE, payload: null }); everywhere in the application to make sure that error message doesn't appear where it's not supposed to.
What is the way to reset error that set in redux state?
A few things...
The redux store has no relation to the routing. That's the whole point of the Redux storage, so you have data everywhere. If you have a component watching for the error message to be present in the store in all pages, either that component has to handle the route where to show which errors OR indeed you will have to call reset after the error message is displayed (or when navigating away).
So what's the right way to do it?
Well, the "right" way in my opinion is with callbacks, when talking about queries. This: react-query-v3.tanstack.com/guides/queries sets a pretty good standard IMO. Think about it this way: Errors are local instances of messages you need to show to the user. So you should show them, but not necessarily store them in your data store. So you use a callback, do the alerting in the way you want to and that's it.
"Ok, so the good practice is to keep all API calls within the component and then dispatch relevant redux actions?"
In my opinion, yes. Isolation and reusability are always in my mind at least.
"One case that I need to understand is there is a component that shows data from the db on the home page and in App.js I dispatch a thu..."
Short answer is: you should not be doing that in App.js but rather in a component specific to that data, where you would manage it's life-cycle, including errors.
This applies even if it's something that will be visible throughout the app.
Good hygiene to keep things isolated IMO.
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 have read multiple articles on the need to use Redux and have built two fully-functioning React+Redux applications. I have even posted the question on Quora I still cannot have a final answer to my question:
Do I have to save every component state property to the Redux store?
The first project, I have built by following a tutorial where he basically saves everything to the store.
Here's the Github link.
Since I was learning React and Redux, I did not question this approach and went on with it. But, it does seem somewhat unnecessary to save everything to the store
For example, there's an action that saves the comment data to the store:
postActions.js
// Add Comment
export const addComment = (postId, commentData) => dispatch => {
dispatch(clearErrors());
axios
.post(`/api/posts/comment/${postId}`, commentData)
.then(res =>
dispatch({
type: GET_POST,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
};
And it is called like this:
CommentForm.js
onSubmit(e) {
e.preventDefault();
const { user } = this.props.auth;
const { postId } = this.props;
const newComment = {
text: this.state.text,
name: user.name,
avatar: user.avatar
};
this.props.addComment(postId, newComment);
this.setState({ text: '' });
}
If I were working on my own project, I would've kept the message data stored locally at the component level:
The second project was a personal project, where the only data I saved in the store, is the user account information because I would need it in different components throughout the app to send it in some backend API requests.
All the other components are basically independent or the flow between them does not go beyond two or three components. So I really could not see why I would make myself code all the actions, reducers...etc for all of the components. So I simply pass the props and functions between components in the plain old react way of doing things.
Most of the answers that I found do not go into this specific detail mentioned in my question. All of them talk from a high-level perspective.
Before going ahead and working on other projects, I would like to :
A clear answer to my question
Whether the approach I used for my personal project is okay. In other words, can I use Redux simply for the user account information and for the rest of the components not use it?
I just want to clear this confusion so that when I am using Redux, I am 100% sure, I am using it because I actually need it.
Do I have to save every component state property to the Redux store?
Short answer: No you don't.
Longer answer: To quote Dan Abramov on a similar question:
Use React for ephemeral state that doesn't matter to the app globally and doesn't mutate in complex ways. For example, a toggle in some UI element, a form input state.
Use Redux for state that matters globally or is mutated in complex ways. For example, cached users, or a post draft.
There is nothing wrong with the approach taken in your personal project. Redux is great for storing/sharing global application state, such as the user info you describe.
Before putting state into Redux I'd ask:
Will this state be consumed by other components independent to this one?
If the answer to #1 is yes: then ask how often?
If the answer to #2 is frequently: then ask is a single source of truth (the Redux store) the best way to share this particular piece of state? Would
other techniques (hooks / render props / higher order components) be more appropriate?
Another quote from Dan in the same linked thread is:
If it gets tedious and frustrating don’t be afraid to put state into the components. My point is that use single state tree unless it is awkward, and only do this when it simplifies things for you rather than complicates them. That’s the only guideline.
The mantra Yagni (You Aren't Gonna Need It) springs to mind.
If you're unsure whether state should be abstracted from a component into Redux, then the chances are it's too early todo so. This helps avoid making design decisions too early, whilst keeping your Redux state lean and intentional (i.e: not convoluted with unnecessary single use concerns).
Ultimately the cost of putting state into Redux needs to pay off.
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.
I'm trying to implement, let's say Twitter. I'm doing something like
initialState = {
tweets: {id => tweet}
}
then, a user goes to his timeline, now the action fetchTweets fetches all his tweets. However, then he can post a tweet, tweet T. But if I don't manually insert the posted T into state.tweets, he will not see this tweet in his timeline.
So here comes the question, when a user did some actions on his page, is that a good point to refresh the data? How does redux avoid data stale in this kind of case?
Thanks!
It's a bit of a paradigm shift when using Redux, but you don't want any functions to be your state. So, that initial state should actually just be either null or an empty object (depending on the design of the components that receive those props). But to answer your question a bit more directly, if you want to make sure that data stays "fresh" you need to make it happen. There is no magic in Redux, which is a GOOD THING.
However, if you design your code properly, the user shouldn't experience something resembling a full page refresh. At a high level, here is how I might design what you are describing.
Write actions for requesting and receiving tweets, i.e.:
export function requestTweets() => {
return {
type: REQUEST_TWEETS
}
}
export function receiveTweets(tweets) => {
return {
type: RECEIVE_TWEETS,
payload: tweets
}
}
Then wrap that in a "public" function that can be reused wherever:
export const fetchTweets = () => {
return (dispatch, getState) => {
dispatch(requestTweets())
//network code here is pseudocode
fetch('https://tweeter/api/v1/tweets')
.then(data => dispatch(receiveTweets(data))
.catch(err => displayErrorMsg(err))
}
}
Then in your action handlers just reduce the tweets into the next state.
Now with fetchTweets, you can call that on the first load, after posting or on an interval. The nice thing is React will handle the diffing well for you and not re-render the entire page. It's just up to you to design it well so the user notices when and where new tweets come in.
Hope that helps!
Redux manages your state for you. It tells anyone who wants to hear about any changes in the state, such as React-Redux.
It does not do anything to help you with getting data, such as tweets. Nor does it help you decide when to get this data.
You'll have to decide for yourself when to do this and you can use setTimeout or anything else you feel like.
It's probably best if you manually insert the tweet into the state rather than refetch when you post. It's so much more responsive.