How to control a boolean in redux? - javascript

It seems like I can't dispatch boolean values. Can someone tell me what I am doing wrong?
This is my state:
const initState = {
allSongs: songData(),
currentSong: { ...songData()[0] },
isPlaying: false,
};
How can I control isPlaying with the dispatch function? When I dispatch it like this, it doesn't work:
dispatch(playAndPause(true));
With this being my action creator:
export const playAndPause = (isPlayingBool) => (dispatch) => {
return {
type: PLAY_AND_PAUSE,
payload: {
isPlaying: isPlayingBool,
},
};
};
Reducer:
case PLAY_AND_PAUSE:
return {
...state,
isPlaying: action.payload.isPlaying,
};

What you have there playAndPause is no normal action creator, but a thunk action creator.
That innermost function would be called and allowed you to do asynchronous actions before actually dispatching. If you wanted to go that route, the correct way would be
export const playAndPause = (isPlayingBool) => (dispatch) => {
// do some stuff here
dispatch({
type: PLAY_AND_PAUSE,
payload: {
isPlaying: isPlayingBool,
},
});
};
But really, you probably want a normal action creator:
export const playAndPause = (isPlayingBool) => ({
type: PLAY_AND_PAUSE,
payload: {
isPlaying: isPlayingBool,
},
});
Also, please note that you are writing a pretty old style of redux here. If you are learning redux right now, please stop whatever tutorial you are following and follow the the official tutorials instead.
In the end, you will write maybe 1/4 of the code you will be writing with the current style you are using.
Modern redux does not require you to write any action creators or action type constants for example. It also has no switch...case reducers or immutable logic in reducers.

Related

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.

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: wait for async function to dispatch

I'm creating a React Native application and using redux and redux-thunk to implement my API requests. I would like to know how I can wait for my action to be dispatched and make sure that my state has been updated in an async thunk logic. If I understand correctly, await will wait for the end of the thunk but the action is not dispatched yet. Although, as you can see in my usage, I need the state to be modified to proceed the rest of the code accordingly.
actions/user.js
export const tryLogin = (
email: string,
password: string,
sessionToken: string = ''
): Function => async (dispatch: Function) => {
const logUser = () => ({ type: LOG_USER })
const logUserSuccess = (data: any, infos: any) => ({
type: LOG_USER_SUCCESS,
data,
infos,
})
const logUserError = (signinErrorMsg: string) => ({
type: LOG_USER_ERROR,
signinErrorMsg,
})
dispatch(logUser())
try {
{ /* Some API requests via axios */ }
dispatch(logUserSuccess(responseJson, infos))
return true
} catch (error) {
{ /* Error handling code */ }
dispatch(logUserError(error.response.data.error))
return false
}
reducers/user.js
case LOG_USER:
return {
...state,
isLoggingIn: true,
}
case LOG_USER_SUCCESS:
return {
...state,
isLoggingIn: false,
data: action.data,
infos: action.infos,
error: false,
signinErrorMsg: '',
}
case LOG_USER_ERROR:
return {
...state,
isLoggingIn: false,
error: true,
signinErrorMsg: action.signinErrorMsg,
}
RegisterScreen.js
if (await trySignup(
emailValue,
firstNameValue,
lastNameValue,
passwordValue,
birthdateValue,
genderValue
)
) {
if (userReducer.data) {
navigation.navigate('Secured')
}
In Redux,
When an Action is dispatched to the store, it will update the state of the UI automatically with new props.
Instead of watching the dispatched action, You can add a flag in the reducer signUpSuccess similar to isLoggingIn flag and listen to the changes in componentDidUpdate lifecycle method.
trySignup can be called separately (like on an event, formSubmit, button click, etc.)
RegisterScreen.js
class RegisterScreen extends React.Component{
...
componentDidUpdate(prevProps) {
if (prevProps.signUpSuccess !== this.props.signUpSuccess){
if (this.props.signUpSuccess) {
navigation.navigate('Secured')
}
}
}
...
}
const mapStateToProps = (state) => ({
signUpSuccess: state.userReducer.signUpSuccess,
});
export default connect(mapStateToProps)(RegisterScreen);
If I understand correctly, await will wait for the end of the thunk
but the action is not dispatched yet.
Render can be update if any changes happens in props, so extract props in your render method and update UX as per change in props.
I would suggest use React native debugger to check your actions
and current saved state.

Schedule dispatch in reducer

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.

Accessing a part of reducer state from one reducer within another reducer

I do not know how to access a boolean isLoading flag from reducerForm.js reducer in reducerRegister.js. I have used combineReducers() and I use isLoading to disable a button during form submit.
It's initial state is false, after clicking submit, it changes to true. After the form submission is successful, isLoading is reset to false again. Below is the relevant code for this issue:
actionRegister.js
let _registerUserFailure = (payload) => {
return {
type: types.SAVE_USER_FAILURE,
payload
};
};
let _registerUserSuccess = (payload) => {
return {
type: types.SAVE_USER_SUCCESS,
payload,
is_Active: 0,
isLoading:true
};
};
let _hideNotification = (payload) => {
return {
type: types.HIDE_NOTIFICATION,
payload: ''
};
};
// asynchronous helpers
export function registerUser({ // use redux-thunk for asynchronous dispatch
timezone,
password,
passwordConfirmation,
email,
name
}) {
return dispatch => {
axios.all([axios.post('/auth/signup', {
timezone,
password,
passwordConfirmation,
email,
name,
is_Active: 0
})
// axios.post('/send', {email})
])
.then(axios.spread(res => {
dispatch(_registerUserSuccess(res.data.message));
dispatch(formReset());
setTimeout(() => {
dispatch(_hideNotification(res.data.message));
}, 10000);
}))
.catch(res => {
// BE validation and passport error message
dispatch(_registerUserFailure(res.data.message));
setTimeout(() => {
dispatch(_hideNotification(res.data.message));
}, 10000);
});
};
}
actionForm.js
export function formUpdate(name, value) {
return {
type: types.FORM_UPDATE_VALUE,
name, //shorthand from name:name introduced in ES2016
value
};
}
export function formReset() {
return {
type: types.FORM_RESET
};
}
reducerRegister.js
const INITIAL_STATE = {
error:{},
is_Active:false,
isLoading:false
};
const reducerSignup = (state = INITIAL_STATE , action) => {
switch(action.type) {
case types.SAVE_USER_SUCCESS:
return { ...state, is_Active:false, isLoading: true, error: { register: action.payload }};
case types.SAVE_USER_FAILURE:
return { ...state, error: { register: action.payload }};
case types.HIDE_NOTIFICATION:
return { ...state , error:{} };
}
return state;
};
export default reducerSignup;
reducerForm.js
const INITIAL_STATE = {
values: {}
};
const reducerUpdate = (state = INITIAL_STATE, action) => {
switch (action.type) {
case types.FORM_UPDATE_VALUE:
return Object.assign({}, state, {
values: Object.assign({}, state.values, {
[action.name]: action.value,
})
});
case types.FORM_RESET:
return INITIAL_STATE;
// here I need isLoading value from reducerRegister.js
}
return state;
};
export default reducerUpdate;
reducerCombined.js
import { combineReducers } from 'redux';
import reducerRegister from './reducerRegister';
import reducerLogin from './reducerLogin';
import reducerForm from './reducerForm';
const rootReducer = combineReducers({
signup:reducerRegister,
signin: reducerLogin,
form: reducerForm
});
export default rootReducer;
This is where I use isLoading:
let isLoading = this.props.isLoading;
<FormGroup>
<Col smOffset={4} sm={8}>
<Button type="submit" disabled={isLoading}
onClick={!isLoading ? isLoading : null}
>
{ isLoading ? 'Creating...' : 'Create New Account'}
</Button>
</Col>
</FormGroup>
Mapping state to props within the same component
function mapStateToProps(state) {
return {
errorMessage: state.signup.error,
isLoading: state.signup.isLoading,
values: state.form.values
};
}
This is covered in the Redux FAQ at https://redux.js.org/faq/reducers#how-do-i-share-state-between-two-reducers-do-i-have-to-use-combinereducers:
Many users later want to try to share data between two reducers, but find that combineReducers does not allow them to do so. There are several approaches that can be used:
If a reducer needs to know data from another slice of state, the state tree shape may need to be reorganized so that a single reducer is handling more of the data.
You may need to write some custom functions for handling some of these actions. This may require replacing combineReducers with your own top-level reducer function. You can also use a utility such as reduce-reducers to run combineReducers to handle most actions, but also run a more specialized reducer for specific actions that cross state slices.
Async action creators such as redux-thunk have access to the entire state through getState(). An action creator can retrieve additional data from the state and put it in an action, so that each reducer has enough information to update its own state slice.
A reducer cannot access another reducer's state, but if you're using redux-thunk you can do so from within an action creator. As an example, you can define an action creator like this:
export const someAction = () =>
(dispatch, getState) => {
const someVal = getState().someReducer.someVal;
dispatch({ type: types.SOME_ACTION, valFromOtherReducer: someVal });
};
React Redux works on unidirectional data flow.
Action ---> Reducer /store ---> Reducer
Reducer works on small subset of store, you can not access store inside reducer which is not part of Reducer. you can either need to fire new action from the component based on reducer state return.

Categories