How redux-thunk works? - reactjs

So I am currently new with redux and stuck in the middleware part. I need to know how these two codes interact with each other.
My action creator:
import jsonPlaceHolder from '../APis/jsonPlaceHolder';
export const fetchPosts= ()=>{
return async (dispatch)=>{
const response = await jsonPlaceHolder.get('/posts');
dispatch({type:'FETCH_POSTS',payload: response});
};
};
redux-thunk code:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;

Plain redux only works with sync actions. Redux thunk gives you the ability to work with async actions (to dispatch multiple actions from a single action creator, for async actions that is usually the REQUEST/RESPONSE/ERROR action). Middleware is something that stands between you dispatching the action and reducer updating the store. Since redux only works with plain objects, to use a action creator (like fetchPosts) you need something (redux-thunk here). It simply injects the dispatch parameter (and getState that gives you the ability to get the current state if your action creator depends on it).
The next(action) inside middleware is the method that propagates your action object to the next middleware(or if it is the last one to your reducer). Redux-thunk checks if the thing you dispatched is a function(since we said that redux can only work with plain objects), and if it is a function it just injects the above mentioned parameters.
So it is basically:
dispatch(fetchPosts()) -> redux-thunk-middleware -> it is function so
lt's call it with injected dispatch/getState (this will not be propagated to the reducer) ->
dispatch({type:'FETCH_POSTS',payload: response}) ->
redux-thunk-middleware -> not a function, let it through -> reducer ->
update state
Hope this helps.

Related

How does dispatching a action creator function work?

Say I have a action creator function like the one below:
import {v4 as uuidv4} from uuid;
export const doSomething = (task) => (dispatch) => {
dispatch({
const id = uuidv4();
dispatch({
type: "SET_TASK",
payload: {id, task}
})
})
}
What is the logic behind having to wrap it in a dispatch method when I am calling it to update the state of a store in another action creator function?
i.e.:
import {setAlert} from "./doSomething"
// another action creator
export const anotherActionCreator = () => dispatch => {
...
dispatch(doSomething("Laundry"));
...
}
When I remove the dispatch method wrapping, it would not call the reducer and update the state in the redux store. I am thinking the action is somehow not connected to the store, but I don't understand how. I thought when you call doSomething("Laundry"), the dispatch inside it will already update the store -- but somehow it didn't -- why is that?
By default, the Redux store only understands how to accept plain action objects passed to dispatch, like:
store.dispatch({type: "todos/todoAdded", payload: "Buy milk"};
If you pass a function to dispatch(), the store will throw an error.
However, middleware wrap up the dispatch function, and can intercept whatever's been passed in to dispatch(). This allows middleware to "teach the store how to accept non-action values", such as passing a function to dispatch(someFunction).
This is how the redux-thunk middleware works. It looks for anything that is actually a function instead of an action object, intercepts that function, and calls it.

About Redux-thunk, if a handler dispatches a function to be called, why not call it directly?

As I understand redux-thunk, it can be
<button onClick={fn1}>Click Me</button>
and in fn1, it dispatches not an action object, but dispatches a function fn2, so that when the redux-thunk middleware calls it ( meaning fn2), invoke a fetch or Ajax call, and let the then fulfillment handler or the callback of Ajax dispatch an action object, like
{ type: "DATA_RECEIVED", data: data }
but why doesn't fn1 directly call fn2 to do the task, but let the middleware call it instead?
Redux Thunk is a very thin middleware (like 14 lines thin) that introduces the convention that Redux should know how to deal with asynchronous processes for you.
You said
the thing is you do want fn2 to fire immediately. Such as: user clicks
a button to fetch something, you do want the function that does the
fetch or Ajax to fire immediately. I think with redux-thunk, if you
dispatch a function, it is fired immediately
You're right, you do want it to fire immediately, but do you want to be the one that manually wires up calling the function and passing the dispatch or do you want Redux Thunk to do it for you?
No matter what, Redux is going to need to dispatch an action at some later time. Redux Thunk does the "later" for you.
Consider these differences:
const store = {
dispatch() {
console.log("Dispatch");
}
};
// Action creator that retuns an action
const inc = () => ({
type: "INCREMENT"
});
// Impure action creator that you shouldn't use
const badAsyncInc = () => {
setTimeout(() => {
store.dispatch({
type: "INCREMENT"
});
}, 1000);
};
// Action creator that returns a function that takes dispatch as arg
const aInc = () => dis => setTimeout(() => dis({
type: "INCREMENT"
}), 1000);
/*
* The following will do everything including waiting to dispatch
* and dispatching to the store. That's good, but not good for
* when we want to use a different store.
*/
badAsyncInc()();
/*
* The following is the "manual" way of wiring up the delayed
* dispatch that you have to do without Redux Thunk.
*/
aInc()(store.dispatch);
/*
* With Redux Thunk, this will call the "fn2" immediately but also
* pass the dispatch function to "fn2" for you so that you don't
* have to wire that up yourself.
*/
store.dispatch(aInc);
In this instance, you could just call badAsyncInc and be done since it will call store.dispatch for you. But what if you have multiple stores? What if you want to use a testing harness' store? What if you want to mock the store completely? Your badAsyncInc is now coupled with the store which is bad for action creators.
Alternatively, you can just call aInc yourself and pass it a dispatch function. However, why do that when all your other action creators are probably being written as store.dispatch(actionCreator()). Redux Thunk helps you keep that pattern, even with async actions.
Redux Thunk tries to help you write action creators that are loosely coupled and testable.
in fn1, it dispatches not an action object, but dispatches a function fn2
The motivation behind dispatching a function is that the function includes a dispatch argument (which Redux Thunk provides) for you to use.
Here is an example of how powerful making dispatch available can be:
const action = (url) => async (dispatch) => {
try {
const response = await fetch(url);
dispatch(success(response));
} catch (error) {
dispatch(failure(error));
}
}
Dispatch enables you to dispatch a success or failure action for your reducers to handle.
More details:
https://redux.js.org/advanced/async-actions/#async-action-creators
if a handler dispatches a function to be called, why not call it directly?
If it were called immediately, you couldn't cleanly do the above: dispatching subsequent actions after resolving a fetch or any asynchronous call.

How are getState and dispatch imported in redux-thunk action creator?

import _ from 'lodash';
import jsonPlaceholder from '../apis/jsonPlaceholder';
export const fetchPostsAndUsers = () => async (dispatch, getState) => {
await dispatch(fetchPosts());
_.chain(getState().posts)
.map('userId')
.uniq()
.forEach(id => dispatch(fetchUser(id)))
.value();
};
export const fetchPosts = () => async dispatch => {
const response = await jsonPlaceholder.get('/posts');
dispatch({ type: 'FETCH_POSTS', payload: response.data });
};
In the above code getState and dispatch functions are passed as arguments to the action creator function, what i m puzzled about is why are these functions not imported from anywhere or does react/redux somehow import them for us?
ok I will try to clear your confusion,
As you know action creators returns plain javascript object, but thunk is a middleware which allows you to return function instead of plain javascript object from the action creators, so when you use thunk if you return plain javascript object from action creator its handled in normal way, but when you return a function from action creator than thunk handle it and call this function with dispatch and getState, so you can dispatch an action asynchronously, you are not passing these arguments, see it this way that you are returning a callback from action creator and thunk call this callback with these arguments.
Hope it helps.
When you connect a react component with redux using a connect function provided by redux you will pass in to functions: mapStateToProps and mapDispatchToProps. Those will be the parameters your looking for (dispatch and getState).
Thunk is a function which optionally takes some parameters and returns another function, it takes dispatch and getState functions, and both of these are supplied by Redux Thunk middleware

How to prevent UI freeze when calling API with axios

I am trying to load data when my component loads using componentDidMount. However calling the Redux action, making the call with axios seems to freeze the UI. When I have a form with 12 inputs and one makes an API call I would assume I can type in the other inputs and not have them freeze up on me.
I've tried reading some other posts on the subject but they are all a little different and everything I have tried doesn't seem to resolve the issue.
I am working on linux using React 16.8 (when using RN I use 55.4)
I have tried making my componentDidMount async as well as the redux-thunk action. It didn't seem to help anything, so I must be doing something wrong.
I tried doing the following with no success. Just using short form for what I tried. Actual code listed below.
async componentDidMount() {
await getTasks().then();
}
And I tried this
export const getTasks = () => (async (dispatch, getState) => {
return await axios.get(`${URL}`, AJAX_CONFIG).then();
}
Current Code:
Component.js
componentDidMount() {
const { userIntegrationSettings, getTasks } = this.props;
// Sync our list of external API tasks
if (!isEmpty(userIntegrationSettings)) {
getTasks(userIntegrationSettings.token)
// After we fetch our data from the API create a mapping we can use
.then((tasks) => {
Object.entries(tasks).forEach(([key, value]) => {
Object.assign(taskIdMapping, { [value.taskIdHuman]: key });
});
});
}
}
Action.js
export const getTasks = () => ((dispatch, getState) => {
const state = getState();
const { token } = state.integrations;
const URL = `${BASE_URL}/issues?fields=id,idReadable,summary,description`;
const AJAX_CONFIG = getAjaxHeaders(token);
dispatch(setIsFetchingTasks(true));
return axios.get(`${URL}`, AJAX_CONFIG)
.then((response) => {
if (!isEmpty(response.data)) {
response.data.forEach((task) => {
dispatch(addTask(task));
});
return response.data;
} else {
dispatch(setIsFetchingTasks(false));
}
})
.catch((error) => {
dispatch(setIsFetchingTasks(false));
errorConsoleDump(error);
errorHandler(error);
});
});
reducer.js
export default (state = defaultState, action) => {
switch (action.type) {
case ADD_TASK:
case UPDATE_TASK:
return update(state, {
byTaskId: { $merge: action.task },
isFetching: { $set: false }
});
default:
return state;
}
};
So in my answer what are you going to learn?
General data loading with Redux
Setting up a component lifecycle method such as componentDidMount()
Calling an action creator from componentDidMount()
Action creators run code to make an API request
API responding with data
Action creator returns an action with the fetched data on the payload property
Okay, so we know there are two ways to initialize state in a Reactjs application, we can either invoke a constructor(props) function or we can invoke component lifecycle methods. In this case, we are doing component lifecycle methods in what we can assume is a class-based function.
So instead of this:
async componentDidMount() {
await getTasks().then();
}
try this:
componentDidMount() {
this.props.fetchTasks();
}
So the action creators (fetchTasks()) state value becomes the components this.props.fetchTasks(). So we do call action creators from componentDidMount(), but not typically the way you were doing it.
The asynchronous operation is taking place inside of your action creator, not your componentDidMount() lifecycle method. The purpose of your componentDidMount() lifecycle method is to kick that action creator into action upon booting up the application.
So typically, components are generally responsible for fetching data via calling the action creator, but it's the action creator that makes the API request, so there is where you are having an asynchronous JavaScript operation taking place and it's there where you are going to be implementing ES7 async/await syntax.
So in other words it's not the component lifecycle method initiating the data fetching process, that is up to the action creator. The component lifecycle method is just calling the action creator that is initiating the data fetching process a.k.a. the asynchronous request.
To be clear, you are able to call this.props.fetchTasks() from your componentDidMount() lifecycle method after you have imported the action creator to your component like and you have imported the connect function like so:
import React from "react";
import { connect } from "react-redux";
import { fetchTasks } from "../actions";
You never provided the name of the component you are doing all this in, but at the bottom of that file you would need to do export default connect(null, { fetchTasks })(ComponentName)
I left the first argument as null because you have to pass mapStateToProps, but since I don't know if you have any, you can just pass null for now.
Instead of this:
export const getTasks = () => (async (dispatch, getState) => {
return await axios.get(`${URL}`, AJAX_CONFIG).then();
}
try this:
export const fetchTasks = () => async dispatch => {
const response = await axios.get(`${URL}`, AJAX_CONFIG);
dispatch({ type: "FETCH_TASKS", payload: response.data });
};
There is no need to define getState in your action creator if you are not going to be making use of it. You were also missing the dispatch() method which you need when developing asynchronous action creators. The dispatch() method is going to dispatch that action and send it off to all the different reducers inside your app.
This is also where middleware such as Redux-Thunk comes into play since action creators are unable to process asynchronous requests out of the box.
You did not show how you wired up your redux-thunk, but it typically goes in your your root index.js file and it looks like this:
import React from "react";
import ReactDOM from "react-dom";
import "./index.scss";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import App from "./components/App";
import reducers from "./reducers";
const store = createStore(reducers, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector("#root")
Remember that connect function I said you needed to implement? That came into being as a result of implementing or you should have implemented the Provider tag. With the Provider tag, your components can all have access to the Redux store, but in order to hook up the data to your components you will need to import the connect function.
The connect function is what reaches back up to the Provider and tells it that it wants to get access to that data inside whatever component you have that lifecycle method in.
Redux-Thunk is most definitely what you needed to implement if you have corrected everything as I have suggested above.
Why is Redux-Thunk necessary?
It does not have anything intrinsically built into it, it's just an all-purpose middleware. One thing that it does is allow us to handle action creators which is what you need it to be doing for you.
Typically an action creator returns an action object, but with redux-thunk, the action creator can return an action object or a function.
If you return an action object it must still have a type property as you saw in my code example above and it can optionally have a payload property as well.
Redux-Thunk allows you to return either an action or function within your action creator.
But why is this important? Who cares if it returns an action object or a function? What does it matter?
That's getting back to the topic of Asynchronous JavaScript and how middlewares in Redux solves the fact that Redux is unable to process asynchronous JavaScript out of the box.
So a synchronous action creator instantly returns an action with data ready to go. However, when we are working with asynchronous action creators such as in this case, it takes some amount of time for it to get its data ready to go.
So any action creator that makes an network request qualifies as an asynchronous action creator.
Network requests with JavaScript are asynchronous in nature.
So Redux-Thunk, being a middleware which is a JavaScript function that is going to be called with every single action that you dispatch. The middleware can stop the action from proceeding to your reducers, modify the action and so on.
You setup dispatch(setIsFetchingTasks(true)) but when axios returns you never set it to false. Did you miss to add dispatch(setIsFetchingTasks(false)) before return response.data;?
This could be the reason if your UI waits for the fetchingTasks to finish

How to make API calls using React, Redux in TypeScript

I'm using typescript-fsa and typescript-fsa-reducers packages to simply create actions and reducers in TypeScript React application.
const actionCreator = actionCreatorFactory();
export function signInHandler(state: UserState, action: Action): UserState {
// ????
return { ...state };
}
export const signIn = actionCreator.async<SignInRequest, RequestResponse<SignInResponse>>("USER_SIGNIN");
export const UserReducer = reducerWithInitialState({ signedIn: false } as UserState)
.casesWithAction([signIn.started, signIn.done], signInHandler)
.build();
Usage in component:
export default connect<StateProps, DispatchProps>(
(state: RootState) => ({} as StateProps),
(dispatch: Dispatch<RootState>) => {
return {
signIn: (userName: string, password: string) => dispatch(signIn.started(new SignInRequest(userName, password)))
};
}
)(SignIn);
And now I'm stuck. I don't know how to make HTTP calls to my API so I can send request when component dispatches action on dispatch next action when response from API arrives. I would like to use promises.
How to solve that?
In React without the typescript-fsa abstraction, you'd make async API callsat the action creator level, since actions are just dispatched POJOs and reducers are supposed to not have any side effects.
There are two projects that make it easy to do this, redux-thunk and redux-saga. I prefer redux-thunk because it is easier to wrap your head around. Basically your action creators get passed the dispatch function, and then they can be responsible for dispatching more than one thing... like so:
function asyncActionCreator(dispatch) {
dispatch(startAsyncAction());
doSomethingAsync()
.then(result => dispatch(completeAsyncAction(result))
.catch(err => dispatch(errorAsyncAction(err));
}
In your typescript-fsa world, there are some companion packages for both of these: typescript-fsa-redux-thunk and typescript-fsa-redux-saga.
It appears that typescript-fsa-redux-thunk takes a similar approach to the above example, using the concept of an "action worker", which coordinates the dispatching of actions via typescript-fsa. There is a really good example of doing this on the typescript-fsa-redux-thunk repo.

Resources