Schedule dispatch in reducer - javascript

I know solutions like redux thunk exist when you want to dispatch action asynchronously. However, lately I had following situation:
import {store} from "./store";
const initialState = {
todos: []
}
​
function todoApp(state = initialState, action) {
​
if(action.type == "ACTION_A"){
// 1. do smth with state
// 2. do smth with state, and then... schedule a dispatch say using setTimeout:
setTimeout(()=>store.dispatch({type:"ACTION_B", payload:1}), 2000);
return state;
}// check other actions e.g. ACTION_B etc.
return state;
}
You can see ACTION_B isn't an action I would like to dispatch from somewhere else say as an async action (so that I could use redux thunk say), rather it is part of the logic in ACTION_A.
My question is: how are such situations handled in redux?
PS. This answer, says it is fine to schedule a dispatch in reducer (my situation above) and even gives some solution using middleware. However, I followed that solution to a blog post (see comments on that answer) and saw comments by Mark Erikson (maintainer of Redux) on blog, that that is still not the right way to do it. He seems to suggest redux-loop for such situation.
My question is what are the right ways to handle such situations in redux?
Are there other solutions also apart from redux-loop?
Or can we still solve this situation using redux thunk?

This is a good case for a thunk:
const actionA = () = ({ dispatch, getState }) => {
dispatch(actionA1) // dispatch another action that will change the state
setTimeout(()=> {
const { data } = getState();
dispatch({type:"ACTION_B", payload: data });
}, 2000);
}
or a custom middleware, that will schedule the timeout, but will allow actionA to continue to the reducer, and change the state (this will happen before the timeout, because it's synchronous):
const middleware = ({ dispatch, getState }) = next => action => {
if(action.type == "ACTION_A"){ //
setTimeout(()=> {
const { data } = getState();
dispatch({type:"ACTION_B", payload: data });
}, 2000);
}
next(action);
}
In general reducers should be pure functions, ie no side effects like scheduling or dispatching actions. If an action needs to do something other than changing the state, it should use a middleware (thunk for example) to do so.

Related

Understand React-Redux Toolkit Syntax?

I'm new to react, redux and tyring to understand the redux-toolkit tutorial i follow. I have the slice as follows.
const initialState = {
count: 0,
};
export const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.count += 1;
},
},
});
export const { increment } = counterSlice.actions;
export default counterSlice.reducer;
export const incrementTest = () => (dispatch) => {
dispatch(increment());
};
Then I use that incrementTest action as follows
<button onClick={() => dispatch(incrementTest())}> + </button>
I want to understand following.
In following fuction
export const incrementTest = () => (dispatch) => {
dispatch(increment());
};
we return a function which takes argument as dispatch then call that provided dispatch function with argement of another function increment which is defined above and exported.
However when we call this function we use dispatch(incrementTest()) providing incrementTest as a param to dispatch. I don't understand this concept . Which concept in javascript should i further study to learn this ?
Also increment reducer take state as parameter ( and action also in some cases ). Who provide this (state,action) to this function as we call it as dispatch(incrementTest())
So this:
export const incrementTest = () => (dispatch) => {
dispatch(increment());
};
is an example for a thunk, a function that gets called (by a redux middleware) with two arguments, dispatch and getState. It is typically used to coordinate async work since you can await stuff inside the function or deal with promises. Look up the thunk middleware if you want to know more. I'm not sure why they made this action a thunk, there's no need for it, maybe testing.
To your second question, the library does. All you do is call dispatch() with an action, the library calls the reducer function with the current state and your action. Think of it as emitting an event. Your job is to create the event, the library takes care of updating the global state accordingly. The reducer is written declaratively, sort of. As in "how would the global state need to change if that specific event happened?".

Call API before dispatching action - Redux Toolkit

I am a bit conflicted about this and seem to find no answer.
I have a slice with some actions. What I want to do is to fire a request without awaiting it's result (it's a firebase call that should be handled optimistically).
How should I then structure my code around this case? Using createAsyncThunk shouldn't be considered because I am not awaiting anything and dont need to update any local requestStatus variables.
This is what I have right now:
// Component.js (where I do the dispatch)
useEffect(() => dispatch(updateCheckIn(payload)), [])
// CheckInActions.js
import { updateCheckInInDb } from "../../api/checkin"
import { updateCheckInAction } from "../reducers"
export const updateCheckIn = (update) => {
updateCheckInInDb(update) // make API call
return updateCheckInAction(update) // return actual reducer action
}
// CheckInReducer.js
const checkInSlice = createSlice({
name: "checkIn",
initialState: INITIAL_STATE,
reducers: {
updateCheckInAction(state, action) {
return updateStateViaUpdateApi(state.data, action)
},
},
})
export const { updateCheckInAction } = checkInSlice.actions
export default checkInSlice
This works but I feel it is a bit awkward (specially the naming). I would need to call one a updateCheckInAction and the other updateCheckIn. I am a bit new to redux toolkit. Isn't there a more elegant way of doing this?
The most idiomatic answer here would be hand-write a thunk that kicks off the async request and then dispatches the plain action:
export const updateCheckIn = (update) => {
return (dispatch) => {
// make API call
updateCheckInInDb(update)
// dispatch actual action to update state
dispatch(updateCheckInAction(update))
}
}
// used as
dispatch(updateCheckIn(123))
See https://redux.js.org/usage/writing-logic-thunks for details on what thunks are overall and how to write them.

how to use redux useSelector to check state and do a fetch to a database

I am using redux in a project and I want to make a useSelector that would check to see if the values in the redux state are the default values if not it will do a request to the database and update the the state I feel like it is quite complicated though and I am having a hard time getting my head around how I need to do this.
I need to do this because sometimes the correct state is not loaded in the state I am considering just doing a check every time I use useSelector to check if the values are the default values then fetch from the database but I would much prefer to write it a way that would allow to be handled within the redux selector but I can't really grasp I how I need to do it.
const info = useSelector(getInfo)
Ideally I would like the info to be handled when I fetch here
import { SET_USER_DETAILS } from "../constants/userConstants";
const intialState = {
user: { },
};
const userReducer = (state = intialState, action: any) => {
switch (action.type) {
case SET_USER_DETAILS:
return { ...state, user: action.payload };
default:
return state;
}
};
here is what my current reducer looks like what would be the best way to do this as I am finding it a little bit difficult to follow the documentation on the redux website.
You can use redux-thunk. https://redux.js.org/usage/writing-logic-thunks
then your thunk could look something like that:
const thunkFunction = (dispatch, getState) => {
// logic here that can dispatch actions or read state
const currentState = getState() as typeof initialState;
// check if state is default state
if (JSON.stringify(initialState) === JSON.stringify(currentState)) {
fetch(url).then(data => {
dispatch({type: SET_USER_DETAILS, payload: data})
})
}
}
You need first to fetch data in react component:
const MyComponent = () => {
// while fetch is fetching, data will be default state,
// and when fetch is done, that component will automatically
// rerender with new data
const data = useSelector(getInfo);
const dispatch = useDispatch();
useEffect(() => {
dispatch(thunkFunction)
},[])
return <code>{JSON.stringify(data, null, 2)}</code>
}
I did not test it so may require some changes
but in general concept is like this

Where is a good to call actions for fail and success in react-redux

I have a question about redux.
Now, I'm building my mobile app following redux advanced tutorial.
The tutorial says that you have to create 3 actions each 1 function so I have created 3 actions for sign-in function like below:
requestSignIn
requestSignInSuccess
requestSignInFailure
However, I don't understand where the app should call them from.
Now, in my app, the app calls requestSignInSuccess and requestSignInFailure in requestSignIn.
This is my action code:
export const REQUEST_SIGN_IN = 'REQUEST_SIGN_IN';
export const REQUEST_SIGN_IN_FAILURE = 'REQUEST_SIGN_IN_FAILURE';
export const REQUEST_SIGN_IN_SUCCESS = 'REQUEST_SIGN_IN_SUCCESS';
import firebase from 'react-native-firebase';
export function requestSignIn() {
// start sign in function
firebase.auth().signInWithEmailAndPassword(EMAIL, PASSWORD)
.then(response => {
// success, so call requestSignInSuccess() to change state
requestSignInSuccess(response.user);
})
.catch(error => {
// fail, so call requestSignInFailure() to change state
requestSignInFailure(error);
})
}
function requestSignInSuccess(user) {
// save user info into state in reducer
return {
type: 'REQUEST_SIGN_IN_SUCCESS'
payload: user
}
}
function requestSignInFailure(error) {
// save error message into state in reducer
return {
type: 'REQUEST_SIGN_IN_FAILURE'
payload: error
}
}
[Questions]
Am I following the redux tutorial correctly? (The app calls requestSignInFailure and requestSignInSuccess in requestSignIn function, is it good?)
If I want the app to have isLoading flag into state, which action should change the flag?
Let me try to answer one by one your questions.
Am I following the redux tutorial correctly?
Yes, you are on the right track, just few steps missing. The below explanation is for class based components.
Technically if you created actions - just like above in your question - then what you need to do is dispatching them in the component in order to use.
Firstly need to dispatch the actions in mapDispatchToProps.
Then need to connect the component - to call requestSignIn action - with the Redux store.
Pass the created mapDispatchToProps to connect as the second parameter.
Please find the following example below:
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
// ... other imports
class YourComponent extends React.Component {
constructor (props) {
super(props);
}
// ... component code
// ... obviously this is just an example component for representation
render() {
return (
<>
<a onClick={props.requestSignIn()}>Request Sign In</a>
</>
)
}
}
const mapDispatchToProps = dispatch => {
return bindActionCreators({ requestSignIn }, dispatch);
};
export default connect(null, mapDispatchToProps)(YourComponent);
If I want the app to have isLoading flag into state, which action should change the flag?
I would create in the reducer a new property called isLoading just like below:
const initialState = {
isLoading: false,
};
export default (state=initialState, action) => {
switch(action.type) {
case 'ENABLE_LOADING':
return {
...state,
isLoading: true,
};
case 'REQUEST_SIGN_IN_SUCCESS':
return {
...state,
isLoading: false,
};
case 'REQUEST_SIGN_IN_FAILURE':
return {
...state,
isLoading: false,
};
// ... other actions
}
}
In your requestSignIn action need to trigger ENABLE_LOADING once you start fetching the data one line before firebase.auth().signInWithEmailAndPassword(EMAIL, PASSWORD) then it will hopefully work for you. Just like how you did with REQUEST_SIGN_IN_SUCCESS and REQUEST_SIGN_IN_FAILURE.
To access the reducer's properties you need to use mapStateToProps further.
In functional component case you need to use useDispatch to call the created actions:
This hook returns a reference to the dispatch function from the Redux store. You may use it to dispatch actions as needed.
And to access the data from the store there is a hook called useSelector:
Allows you to extract data from the Redux store state, using a selector function.
Quick summary:
If you are looking for a fully working example with useSelector and useDispatch in a functional component then take a look at this git repository:
https://github.com/norbitrial/react-redux-loading-data-example
In the repository you will find a nice representation of a fake API call which loads data into a table with a loader indicator, just like what you need from your question.
In case of further interest in more details please find the below links which are pretty useful:
Connect: Dispatching Actions with mapDispatchToProps
Connect: Extracting Data with mapStateToProps
Dispatching actions with useDispatch() for functional component
Extract data with useSelector() for functional component
I hope this helps, let me know if you need further clarification.
Try using redux-thunk middleware to handle promises in the actions. You will get a fullfilled, pending (your loading) and failure actions for every promises.
You are doing right but to keep code clean and readable i would advise you to take all type variables out of component, create action folder inside src -> create file types.js (or any name you want) and move all variables to it. you can do same with requestSignInFailure and requestSignInSuccess (keep action related stuff in your actions folder. (UNLESS you are not using react hooks because you must use useDispatch in React component funcion, not in pure function)). and to dispatch data to store you must use dispatch (I assume you use connect method while exporting component) and if you pass your action to connect as second argument function will get second function inside with argument dispatch like this
function requestSignInFailure(error) {
// save error message into state in reducer
function(dispatch){
dispatch({
type: 'REQUEST_SIGN_IN_FAILURE'
payload: error
})
}
}
it is same if you pass function directly in connect like so:
connect(mapStateToProps, {
(dispatch) => {
dispatch({
type: 'REQUEST_SIGN_IN_FAILURE'
payload: error
})
},
(dispatch) => {
dispatch({
type: 'REQUEST_SIGN_IN_SUCCESS'
payload: success
})
}
})(Component);
and to change isLoading value you can use componentDidMount, and dispatch action inside componentDidMount inside it to change isLoading , it will dispatch action while component renders (mounts) like so:
function changeLoading(){
function(dispatch){
dispatch({
isLoading: false
})
}
}
componentDidMount(){
this.props.changeLoading();
}
export default connect(null, changeLoading)(Component)
react docs about lifecycle methods.
i had made some changes to above code. requestSignInFailure and requestSignInSuccess in requestSignIn fine you can use it, or you have the choice to change thunk request to RSAA request but connecting it to firebase may be difficult.
import firebase from 'react-native-firebase';
export function requestSignIn() {
// start sign in function
startedRequestForSignIn()
firebase.auth().signInWithEmailAndPassword(EMAIL, PASSWORD)
.then(response => {
// success, so call requestSignInSuccess() to change state
requestSignInSuccess(response.user);
})
.then(()=>{
endedRequestForSignIn()
})
.catch(error => {
// fail, so call requestSignInFailure() to change state
requestSignInFailure(error);
})
}
function requestSignInSuccess(user) {
// save user info into state in reducer
return {
type: 'REQUEST_SIGN_IN_SUCCESS'
payload: user
}
}
function requestSignInFailure(error) {
// save error message into state in reducer
return {
type: 'REQUEST_SIGN_IN_FAILURE'
payload: error
}
}
function startedRequestForSignIn{
return {
type: 'IS_LOADING'
payload: true
}
}
function endedRequestForSignIn{
return {
type: 'IS_LOADING'
payload: false
}

Redux-Thunk getStore() doesn't retain state. Returning 'undefined'

This might be a question of best practices but I'd appreciate an explanation on why this doesn't work. I'm using Typescript + Redux + Thunk and trying to call actions like this:
export const requestUserDashboards = createAction<DashboardModel>(Type.REQUEST_USER_DASHBOARDS);
Dispatch in the fetch:
export const fetchDashboards = () => {
return async (dispatch: Dispatch, getState: any) => {
try {
dispatch(requestUserDashboards({
currentDashboard: getState.currentDashboard,
dashboards: getState.dashboards,
hasDashboards: false,
error: getState.error
}))
...
}
})
}
Here's the corresponding reducer:
export const dashboardReducer = handleActions<RootState.DashboardState, DashboardModel>(
{
[DashboardActions.Type.REQUEST_USER_DASHBOARDS]: (state = initialState, action): RootState.DashboardState => ({
currentDashboard: action.payload!.currentDashboard,
dashboards: action.payload!.dashboards,
hasDashboards: action.payload!.hasDashboards,
error: action.payload!.error
})
},
initialState
);
dispatch is working, however, getState doesn't correctly collect the current store state. I'm testing this by doing the following in the component receiving the updated store:
componentWillReceiveProps(nextProps: Login.Props) {
console.log(nextProps.defaultAccounts.defaultAccount);
}
Calling this in the component using:
this.props.defaultAccountActions.fetchUserDefaultAccount();
The action is working as the values from the fetch are being captured.
However, where I am using the getState.xxxx, these values are returning as undefined:
index.tsx:84 Uncaught TypeError: Cannot read property 'defaultAccount' of undefined
The initialState from my reducer is working. I can see this from doing the console.log(this.props.defaultAccounts.defaultAccount) from the componentWillMount() function.
I'm not sure what else I can provide. I think I'm actually just fundamentally misunderstanding how actions/reducers manage the store.
Questions
I am trying to get the current store values by using the getState.xxxx in the dispatch. Is this the correct way to do this?
isn't getState a function in that place? So you would need to do something
const state = getState();
and then use state inside dispatch
found in documentation, yeah it is a function at that place so you should firstly invoke a function to get state and then use it (e.g. from documentation below)
function incrementIfOdd() {
return (dispatch, getState) => {
const { counter } = getState();
if (counter % 2 === 0) {
return;
}
dispatch(increment());
};
}
If you are using mapstatetoprops in your component you can use that to get the values from store. mapStateToProps first argument is actually the Redux state. It is practically an abstracted getState().
const mapStateToProps = function(state, ownProps) {
// state is equivalent to store.getState()
// you then get the slice of data you need from the redux store
// and pass it as props to your component
return {
someData: state.someData
}
}

Categories