How to load data in componentWillMount when using redux - reactjs

I'm have trouble figuring out how to structure my code with redux. Here's the high level flow that I'm looking at:
An action execute successfully, and triggers history.push('/something')
That leads a new component Something to be loaded
In Something.componentWillMount(), I want to fetch some data for the component, so I call this.props.loadSomething(), which is another action
Step (3) is the problem. Since the history.push() call is in an action, redux doesn't let me call this.props.loadSomething(), since that would be calling an action within an action.
What is the proper way to handle this? I feel like this must be a very common problem, so there should be a standard approach for it.

What you're looking at is indeed a common scenario. The easy way around the problem is the use of a thunk.
It is basically built so that you can dispatch multiple actions simultaneously and they can even be asynchronous.
You can find relevant documentation here.
Sample usage in an app, an action creator:
export function doSomething(someParam, someOtherParam) {
return dispatch => {
dispatch(someOtherActionCreator(someParam));
dispatch(loadMyDataActionCreator(someParam, someOtherParam));
};
}
You could possibly only redirect once you've loaded the data. You can also do that with thunks.
export function fetchMeStuffNRedirect(params) {
return dispatch => {
return dispatch(loadSomeStuffThatReturnsPromise())
.then(result => dispatch(doSomethingWithResult(result))
.catch(error => dispatch(awSnapActionCreator(error));
};
}

Related

redux - Best way to handle action calls out of component while on 'dirty' state?

I'm using redux-logic to handle the business logic of my react-redux app.
I have component A with some fields. outside of this component, I have other components, that a user can click on them, and call actions.
while do some things in component A, the user can click on those other components.
What is the best way to 'catch' those calls, but first show some popup that says 'hey, are you sure?' and if the user click 'ok' - fire those calls?
so far i just managed to do something like this:
user call an action from component B for example (while still have unfinished business in component A)
the the logic that listen to this call, check if we are in dirty mode. If it is, we reject the call.
two things that not feels right for me :
I need to implement this checking in EVERY call
it does not tells me to show the pop up.
If you were using redux-thunk, then something like this would be as easy as:
function goingAwayAction(){
return (dispatch, getState) => {
const state = getState();
if (Selectors.formIsDirty(state)) {
confirm("Are you sure?", () => { dispatch(GoAway()) })
} else {
dispatch(GoAway())
}
}
}
I don't know redux-logic in particular, but your general solution is checking for form dirtiness as part of your async action. There are good libraries for confirmations which let you just specify a callback which happens when they say yes, that'll simplify some logic for you. I've used react-confirm-alert before and had success with it!
Sounds like you need to use a middleware. Redux-saga should be the answer.
Checkout: https://redux-saga.js.org/docs/basics/DispatchingActions.html

Await value returned from a redux-thunk dispatch rather than using state

I am using react, redux, and redux-thunk have a set of queries I have to run in the "logic layer". The results of each "action" call determine the path I take, so I am actually awaiting the promises returned from the dispatch call to the redux-thunks.
const handleClick = async (args) => {
let foo = await dispatch(fetchFoo(args));
if(foo) { do something... }
else {
let bar = await dispatch(fetchBar(args));
if(bar) { do another thing... }
else { or do another thing... }
}
};
Example thunk:
export const fetchFoo = args => async (dispatch) => {
let foo = await api.fetchFoo(args);
dispatch(fetchSuccess(foo));
// !!!!!!!!!!
return foo;
// !!!!!!!!!!
}
If I don't do this, it's pretty awkward to wait until a re-render (maybe) puts "foo" in the redux state prop, then wait again until a re-render (maybe) puts "bar" in the redux state, etc...
I've never really seen this pattern before although I have seen awaiting void promises return from thunks.
Is it acceptable to return the value from the redux-thunk action and use it rather than getting the values from a redux state selector? This seems to break the rules of the "single source of truth." If not, what do I do?
Using this approach it would be much harder to refactor state and change flow how data is loaded. Say in case requirements are changed and fetchBar() should be called by interval or need to be loaded by parent and passed through props. Or you want to throttle loading. Or cache data for some time.
I used this pattern with Promise returned and found it makes things rather complicated. The only advantage I found: I didn't need having isLoading or loadingError flags in redux storage to know if data is already loaded or not.
But also it means I have to call some methods redundantly just to retrieve brand new Promise await for - since promises can be resolved only once - in order to wait until some data loaded. And once some action triggers few API calls in sequence I ended with awaiting for data I did not even need.
In opposite if we rely only on data in redux it would be as easy as const isStillLoading = props.isData1Loading && props.isData2Loading
Consider moving condition for loading data into action itself. If you find API calls sequence becomes too complex you may switch to redux-saga or redux-loop which both provide better control over execution flow.

Is it wrong to call component methods passed as arguments inside of redux actions?

I'm struggling with one task I've been appointed to and the only workaround I found is to call the action argument callback inside the action. Is this a bad idea from a design point of view, because the code itself works and passes numerous tests? The general purpose of this solution is to somehow trigger the component function when a certain logic is being followed.
export function myAction(componentClb: () => void): any {
return (dispatch: Dispatch<AppStore>): void => {
someRESTAPIcall()
.then((condition) => {
condition
? dispatch(anotherActionThatTakesCallbackAsArgument(componentClb))
: componentClb();
})
.catch((error: Error) => {
dispatch(myErrorAction());
});
};
}
The biggest mistake about this is to take React components as functional classes. Components really are just to handle the WHAT and HOW of page rendering. Anything outside of rendering logics should be removed from it.
Instead of working as callback, this anotherActionThatTakesCallbackAsArgument should update the redux store.
The container that componentClb belongs to should be connected to redux store and selects the fields from the stores, and pass these fields to componentClb as props, so that componentClb can handle the rendering based on the response of myAction
With the info you provided, it's hard to give a concrete code example. Maybe describe the scenario you are trying to solve and people are able to give you more direct feedbacks.

Sending error message from reducer to user

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.

Redux avoid stale data?

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.

Resources