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

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.

Related

Is it possible to dispatch multiple actions?

I'm trying to clear fields from a form (clearing state from the store) when the user changes their country so I was wondering if it was possible to dispatch two actions under one event... -- tho my action also doesn't clear the fields so not sure where I'm going wrong
in index.jsx
export default function Form() {
const {
apartmentNumber,
birthDay,
birthMonth,
birthYear,
buildingNumber,
countryCode
} = state;
const [formData, setFormData] = useState({
apartmentNumber,
birthDay,
birthMonth,
birthYear,
buildingNumber
});
const onInputChange = (attribute, value) => {
setFormData({
...formData,
[attribute] : value
});
};
const onCountryChange = (value) => {
dispatch(updateCountry(value));
dispatch(clearForm(formData));
};
in reducer.js --
export const initialState = {
apartmentNumber : '',
birthDay : '',
birthMonth : '',
birthYear : '',
buildingNumber : ''
};
export default (state, action) => {
const { payload, type } = action;
switch (type) {
case UPDATE_COUNTRY:
return {
...state,
countryCode : payload
};
case UPDATE_FIELDS: {
return {
apartmentNumber : initialState.apartmentNumber,
birthDay : initialState.birthDay,
birthMonth : initialState.birthMonth,
birthYear : initialState.birthYear,
buildingNumber : initialState.buildingNumber
};
}
default:
return state;
}
};
You can reset the values be passing in initialState. Ideally, you should have an action for when UPDATE_COUNTRY is successful. Then you can reset to initialState once the country has been successfully updated.
case UPDATE_COUNTRY_SUCCESS:
return initialState;
or if you don't want to add a success action, you can just do
case UPDATE_COUNTRY:
return {
...initialState,
countryCode: payload
};
As for dispatching multiple actions. You can use redux or if your reducer does not do a side effect you can change your reducer to handle these at one go.
See:
Sending multiple actions with useReducers dispatch function?
If your output is not side-effecty you can do similar to:
https://codezup.com/how-to-combine-multiple-reducers-in-react-hooks-usereducer/
I don't think you need to use context api for that just pass the reducer and state to the components you want to call dispatch from.
If they have side effects you can achieve chain the effects by setting a reducer which returns the next effect to be ran from useEffect this is useful if you need the ui to change in each disptach. For multiple effects you can combine them and make then all run in one useEffect.
Other than that libs like redux handle these basically out of the box. But I have never used redux.
You can do this using useReducer React hook. Take note of the createReducer function and how it can be composed to handle arrays of actions.
const createReducer = (actions, state) {
return (state, action) => {
if(Array.isArray(action)) {
actions.forEach(action => {
state = actions[action[i].type](state, action[i])
})
return state
} else if(actions[action.type]) {
return actions[action.type](state, action)
} else {
return state
}
}
}
const actions = {
INCREMENT: (state, action) => {
state.counter++
return state
}
}
const initState = () => ({
counter: 0
})
const reducer = createReducer(actions)
const App = () => {
const [state, setState] = React.useReducer(reducer, initState())
return <div>Count: {state.count}
<button onClick={e => setState([
{type: 'INCREMENT'},
{type: 'INCREMENT'}
])}>+</button>
</div>
}
I suspect your issue is that state is propagating through the DOM tree with every actions dispatched, which can lead to broken or weird DOM states. With this architecture, you apply each of the actions in the array before the state is returned, meaning propagation only occurs after all actions have been applied to the state.

Redux action not firing - no errors

I'm trying to call a simple action on click which fires a dispatch action. I can't seem to get a result or even indiciation that it's firing.
I'm trying to dispatch on click in a component. I've also tried putting a console.log in the action to see if it even gets fired but it doesn't. Redux dev tools also doesn't suggest it even gets fired on click.
onClick={() => {
setAQIType(name);
}}
Action:
import { SET_AQITYPE } from "./types";
export const setAQIType = (AQIType) => dispatch => {
dispatch({
type: SET_AQITYPE,
payload: { AQIType }
});
};
Reducer:
import { SET_AQITYPE } from '../actions/types';
const initialState = {
aqiType: 'DEFAULT',
loading: false,
};
export default function(state = initialState, action){
const { type, payload } = action;
switch(type){
case SET_AQITYPE:
return [...state, payload];
default:
return state;
}
}
Types:
export const SET_AQITYPE = 'SET_AQITYPE';
Three errors,
In reducer: Your state is an object and not a list.
In reducer: Assign payload to aqiType key
In dispatch: Payload is a string and not an object.
To fix:
export const setAQIType = (AQIType) => dispatch => {
dispatch({
type: SET_AQITYPE,
payload: AQIType // (3) pass as string
});
};
// In reducer
case SET_AQITYPE:
return { // (1) object
...state,
aqiType: payload // (2) specify aqiType key
};
This assumes that you've checked the basic example with connect() and mapDispatchToProps.
Most likely you missed to connect the component with the redux store, which means there is no dispatch function passed to your action.
https://react-redux.js.org/using-react-redux/connect-mapdispatch
Cheers
Try to return inside action function as below:
import { SET_AQITYPE } from "./types";
export const setAQIType = (AQIType) => dispatch => {
return dispatch({
type: SET_AQITYPE,
payload: { AQIType }
});
};

React redux reducer getting data from state?

Trying to mess around with react and redux to fetch a list of files from an API.
When looking in the react dev tools I can see my data there but it is not being rendered.
actions.js
export const requestFiles = ({
type: REQUEST_FILES,
});
export const receiveFiles = (json) => ({
type: RECEIVE_FILES,
files: json,
receivedAt: Date.now()
});
export const fetchFiles = (dispatch) => {
dispatch(requestFiles);
return fetch('/api/files')
.then(response => response.json())
.then(json => dispatch(receiveFiles(json)))
};
The action gets data from JSON
reducer.js
const files = (state = {
isFetching: false,
items: []
}, action) => {
switch (action.type) {
case REQUEST_FILES:
return {
...state,
isFetching: true,
};
case RECEIVE_FILES:
return {
...state,
isFetching: false,
items: action.files,
lastUpdated: action.receivedAt
};
default:
return state
}
};
const filesUploaded = (state = { }, action) => {
switch (action.type) {
case RECEIVE_FILES:
case REQUEST_FILES:
return {
...state,
items: files(state[action], action)
};
default:
return state
}
};
const rootReducer = combineReducers({
filesUploaded
});
export default rootReducer
App.js
class App extends Component {
static propTypes = {
files: PropTypes.array.isRequired,
isFetching: PropTypes.bool.isRequired,
dispatch: PropTypes.func.isRequired
};
componentDidMount() {
const {dispatch} = this.props;
dispatch(fetchFiles);
}
handleChange = nextSubreddit => {
};
render() {
const {files, isFetching} = this.props;
const isEmpty = files.length === 0;
console.log(`Files is empty ${isEmpty}`);
return (
<div>
<h1>Uploadr</h1>
{isEmpty
? (isFetching ? <h2>Loading...</h2> : <h2>No files.</h2>)
: <div style={{opacity: isFetching ? 0.5 : 1}}>
<Files files={files}/>
</div>
}
</div>
)
}
}
const mapStateToProps = state => {
const {uploadedFiles} = state;
const {isFetching, items: files} = uploadedFiles
|| {isFetching: true, items: []};
console.log(files);
return {
files,
isFetching,
}
};
The data being received in the action but I am not sure if it is getting stored or if the problem is accessing it from the redux store.
The files property is still zero on the App component as shown in the screenshot above.
Any ideas?
Delete your filesUploaded reducer. You don't need it. Instead, just use the files reducer:
const rootReducer = combineReducers({
files,
});
Please note, the slice of state you are interested in will be called files. Change your mapStateToProps function to this:
const mapStateToProps = state => {
const {isFetching, items: files} = state.files
console.log(files);
return {
files,
isFetching,
}
};
You can see here, we grab the files slice of state and pass that into your component.
Your filesUploaded reducer does not make any sense. I'm not sure what filesUploaded is even supposed to be doing. Your files reducer looks like a normal reducer. It seems like you could just delete filesUploaded and everything would be fine.
In particular, filesUploaded is calling files(state[action], action). action is an object. What is state[SOME_OBJECT] supposed to be? Because it's being parsed as state['[object Object]'] which is surely undefined and never would become defined.
Your files reducer also has an items parameter that is just never used. A reducer should only have two parameters: state and action. Drop the items parameter.
Your mapStateToProps is looking for state.uploadedFiles, but your reducer is called filesUploaded. It should be state.filesUploaded (or if you replace it with the files reducer, just state.files).
mapStateToProps will not need || {isFetching: true, items: []} since you have an initial state on your files reducer.

Prevent reducer property from nesting in state

I want my component to have a boolean property called waiting. I trigger changes on this property via the setWaiting action creator. The only problem is that inside my component this property arrives nested inside a property of the same name:
In my component I want to be able to do:
{this.props.waiting}
... but right now what I have to is:
{this.props.waiting.waiting}
Action creator:
export const WAITING = "waiting";
export function setWaiting(isWaiting) {
return {
type: WAITING,
payload: isWaiting
};
}
In my component I call this action like this:
this.props.setWaiting(true); // or false depending the case
Reducers setup:
import { combineReducers } from "redux";
import { reducer as formReducer } from "redux-form";
import waitingReducer from "./reducer-waiting"
const rootReducer = combineReducers({
form: formReducer,
waiting: waitingReducer
});
export default rootReducer;
Reducer:
import _ from "lodash";
import { WAITING } from "../actions";
export default function waitinReducer(state = {}, action) {
if (_.isUndefined(state.waiting)) {
return Object.assign({}, state, { waiting: false });
}
switch (action.type) {
case WAITING:
// This is irrelevant here because the problem happens before this
// ever gets called. Just left it here for completeness sake.
return Object.assign({}, state, { waiting: action.payload });
default:
return state;
}
}
Map state to props (which already comes with a "bad" state):
let mapStateToProps = state => {
// This prints { waiting: { waiting: false } }
// but I just want { waiting: false }
console.log(state)
return {
waiting: state.waiting
}
}
Some extra context:
react: 16.4.1
react-dom: 16.4.1
redux: 3.7.2
react-redux: 4.4.9
Basically the change you want should happen in the reducer. You're using a object as state and setting waiting property inside the object. If you want only that boolean value, use that boolean as the state object, like below.
export default function waitinReducer(state = false, action) {
// default initialisation is false
// if (_.isUndefined(state.waiting)) {
// return Object.assign({}, state, { waiting: false });
// }
switch (action.type) {
case WAITING:
// just return the payload
return action.payload;
default:
return state;
}
}
Try this and let me know whether this works for you. One concern I have is, it won't update the props, since it's boolean is primitive type and value is stored not the reference.
Easiest change is #SamVK answer
You could try mapping just that state:
let mapStateToProps = state => ({
waiting: state.waiting.waiting
})
That'll keep each reducer as an object but at least not give you that awkward repetitiveness in the props themselves.

Can not see updated state

I have the following action:
export function loginUserRequest() {
console.log('ACTION CALLED');
return {
type: LOGIN_USER_REQUEST,
};
}
and this is the reducer:
export default function loginReducer(state = initialState, action) {
switch (action.type) {
case LOGIN_USER_REQUEST:
console.log('REDUCER CALLED');
return Object.assign({}, state, {
isAuthenticated: true,
isAuthenticating: true,
statusText: null,
});
default:
return initialState;
}
}
Then, my component:
class Login extends React.Component {
goHome = () => {
browserHistory.push('/');
}
handleSubmit = (values) => {
console.log(this.props.isAuthenticating);
this.props.actions.loginUserRequest();
console.log(this.props.isAuthenticating);
}
render() {
return (
<LoginForm onSubmit={this.handleSubmit} />
);
}
}
Login.propTypes = {
actions: PropTypes.objectOf(PropTypes.func).isRequired,
isAuthenticating: PropTypes.bool.isRequired,
};
const mapStateToProps = state => ({
token: state.login.token,
isAuthenticated: state.login.isAuthenticated,
isAuthenticating: state.login.isAuthenticating,
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(actionCreators, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
LoginForm is a redux-form component.
So, the expeted ouput from the handleSubmit function is:
false
ACTION CALLED
REDUCER CALLED
true
but it is giving me:
false
ACTION CALLED
REDUCER CALLED
false
But in the redux dev tool I can see the diff in LOGIN_USER_REQUEST:
Why I don't see it inside the handleSubmit function? Is it something related to redux-form library?
Extra info:
Added shouldComponentUpdate and logger
shouldComponentUpdate = (nextProps, nextState) => {
console.log('Should component update called');
if (this.props.isAuthenticating !== nextProps.isAuthenticating) {
console.log('distntict');
return true;
}
console.log('false');
return false;
}
You are getting such a result because of async nature of Javascript. So in your code
handleSubmit = (values) => {
console.log(this.props.isAuthenticating);
this.props.actions.loginUserRequest();
console.log(this.props.isAuthenticating);
}
First, you are printing the value of prop, and then the action gets called but before the action returns a response with the updated state, your third statement gets called to log the value and since the state is not yet updated you see the same result.
One approach will be have callbacks but that doesn't seem to be a requirement for your case. If your want to log the state then you can do so in componentWillReceiveProps function
like
componentWillReceiveProps(nextProps) {
if(this.props.isAuthenicating != nextProps.isAuthenticating) {
console.log(nextProps.isAuthenticating);
}
}
I hope it helps

Categories