How to dispatch multiple actions - reactjs

I am trying to dispatch functions from reducer but it call only one function.
Reducer looks like this:
import types from "./types";
const initState = {
active: false,
myData: []
};
function toggleActive(state, action) {
return {
...state,
active: action.payload
};
}
function watchInfo(state, action) {
return {
...state,
myData: action.payload
};
}
const watchReducer = (state = initState, action) => {
switch (action.type) {
case types.TOGGLE_ACTIVE:
return toggleActive(state, action);
case types.WATCH_DATA:
return watchInfo(state, action);
default:
return state;
}
};
export default watchReducer;
and action creator is set like this:
import types from "./types";
function toggleActive(bool) {
return {
type: types.TOGGLE_ACTIVE,
payload: bool
};
}
function watchInfo(data) {
return dispatch => {
dispatch({
type: types.WATCH_DATA,
payload: data
});
};
}
export { toggleActive as default, watchInfo };
and in component in which I am importing connect and corresponding action creator, i am trying to use it like this:
const mapStateToProps = state => {
const mapDispatchToProps = dispatch => ({
watchInfo: () => dispatch(watchInfo())
});
export default connect
mapDispatchToProps
)(MyComponent);
So when I inspect in redux console it only calls toggleActive, never calls watch info.
I am not sure what I am doing wrong.

change this action creator
function watchInfo(data) {
return dispatch => {
dispatch({
type: types.WATCH_DATA,
payload: data
});
};
}
to:
function watchInfo(data) {
return {
type: types.WATCH_DATA,
payload: data
}
}
action creator is a function that return an object that representing an action. we use action creators for better code maintenance and prevent some Spelling error but this code:
dispatch(watchInfo(someData))
is equivalent to this:
dispatch({
type: types.WATCH_DATA,
payload: someData
})

Related

Redux saga enters in infinite loop state of calling api endpoint

I am using Redux for state management and saga as a middleware. For some reason my app is in some infinite loop state of calling API endpoint.
This is my actions:
export const GET_USERS = "GET_USERS";
export const getUsers = () => ({
type: GET_USERS,
});
export const GET_USERS_SUCCESS = `${GET_USERS}_SUCCESS`;
export const getUsersSuccess = (data) => ({
type: GET_USERS_SUCCESS,
payload: data,
});
export const GET_USERS_FAIL = `${GET_USERS}_FAIL`;
export const getUsersFail = (error) => ({
type: GET_USERS_FAIL,
payload: error,
});
This is saga:
export function* getUsers$() {
try {
const users = yield getUsersAPI();
yield put(actions.getUsersSuccess(users.data));
} catch (error) {
yield put(actions.getUsersFail(error));
}
}
export default function* () {
yield all([takeLatest(actions.getUsers, getUsers$)]);
}
This is a reducer:
export default (state = initialState(), action) => {
const { type, payload } = action;
switch (type) {
case actions.GET_USERS:
return {
...state,
users: {
...state.users,
inProgress: true,
},
};
case actions.GET_USERS_SUCCESS:
return {
...state,
users: {
inProgress: false,
data: payload,
},
};
case actions.GET_USERS_FAIL:
return {
...state,
users: {
...state.users,
inProgress: false,
error: payload,
},
};
default:
return state;
}
};
And this is a component connected with redux:
const Home = (props) => {
useEffect(() => {
props.getUsers();
console.log('props', props.data);
}, []);
return(
<h1>Title</h1>
);
}
const mapStateToProps = ({
users: {
users: {
data
}
}
}) => ({data})
export default connect(mapStateToProps, {getUsers})(Home);
Why is this happening?
This is due to the fact that you misused the sagas in your example. As with any other effect creator as the first parameter must pass a pattern, which can be read in more detail in the documentation. The first parameter can also be passed a function, but in a slightly different way. View documentation (block take(pattern)).
In your case, you are passing a function there that will return an object
{
type: 'SOME_TYPE',
payload: 'some_payload',
}
Because of this, your worker will react to ALL events that you dispatch.
As a result, you receive data from the server, dispatch a new action to save data from the store. And besides the reducer, your getUsers saga will be called for this action too. And so on ad infinitum.
Solution
To solve this problem, just use the string constant actions.GET_USERS that you defined in your actions.
And your sagas will look like this:
export function* getUsers$() {
try {
const users = yield getUsersAPI();
yield put(actions.getUsersSuccess(users.data));
} catch (error) {
yield put(actions.getUsersFail(error));
}
}
export default function* () {
yield all([takeLatest(actions.GET_USERS, getUsers$)]);
}
This should fix your problem.

Manage state of global variable react redux

How do I get the state of this? I only need to put it as false and true the time I want at my components, but i`m doing something wrong, i know how do it when calling an API, but not like this.
I have this actions:
import { HIDE_MENU, ESTADO_MENU } from "./types";
export const hideMenu = dispatch => {
return dispatch({
type: HIDE_MENU
});
};
export const estadoDoMenu = open => dispatch => {
dispatch({
type: ESTADO_MENU
});
};
and this reducer:
import { HIDE_MENU, ESTADO_MENU } from "../actions/types";
const initialState = {
open: true
};
export default function(state = initialState, action) {
switch (action.type) {
case HIDE_MENU:
return {
...state,
open: false
};
case ESTADO_MENU:
console.log("chega aqui");
return {
...state
};
default:
return state;
}
}
but calling it like this:
componentDidMount() {
console.log("Estado do Menu: ", this.props.estadoDoMenu());
}
I get undefined at the console, what is wrong?

State is undefined in mapStateToProps

I've been trying to retrieve the new state from my vitaminReducer() reducer function, and connect it through mapStateToProps. But when I console.log the state, I get back "the state is {vitamin: undefined}".
This is the Vitamins component where I'm calling mapStateToProps()
(Vitamins.js)
componentDidMount() {
this.props.fetchVitamins();
}
function mapStateToProps(state) {
return {
vitamin: state,
}
};
console.log('the state is', mapStateToProps());
export default connect(mapStateToProps, { fetchVitamins })(Vitamins);
(reducers.js)
function vitaminReducer(state = [], action) {
switch(action.type) {
case FETCH_VITAMINS_SUCCESS:
return [
...state,
action.payload.vitamins
];
default:
return state;
}
}
const reducers = combineReducers({
vitamin: vitaminReducer,
});
I have the data coming through an Express server. I've console logged "vitamins" here and I get the data back, so I know that's not the issue.
(actions.js)
export function fetchVitamins() {
return dispatch => {
return fetch("/users")
.then(handleErrors)
.then(res => res.json())
.then(micros => {
dispatch(fetchVitaminsSuccess(micros));
const vitamins = micros.vitamins;
}
)};
};
export const FETCH_VITAMINS_SUCCESS = 'FETCH_VITAMINS_SUCCESS';
export const fetchVitaminsSuccess = vitamins => ({
type: FETCH_VITAMINS_SUCCESS,
payload: vitamins
});
If I do: "return { vitamin: state.vitamin, }" instead of "return { vitamin: state, }", I get back "TypeError: Cannot read property 'vitamin' of undefined". But that's what I called vitaminReducer in my combineReducers() function at the bottom of reducers.js, so I thought that was the right way to do it.
Thank you everyone for your input! I was able to get it working.
I ditched the mapStateToProps() and instead did this
(Vitamins.js)
componentDidMount() {
this.props.fetchVitamins();
}
renderData() {
const { vitamins } = this.props.vitamins;
return vitamins.map((micro, index) => {
return (
<option value={micro.value} key={index}>{micro.name}</option>
)
})
}
export default connect(
state => ({
vitamins: state.vitamins
}),
{
fetchVitamins
},
)(Vitamins);
I set the dispatch action inside of the fetchVitamins() function
(actions.js)
export function fetchVitamins() {
return dispatch => {
return fetch("/users")
.then(handleErrors)
.then(res => res.json())
.then(micros => {
dispatch({
type: "RECEIVE_VITAMINS",
payload: micros.vitamins
});
}
)};
};
export const RECEIVE_VITAMINS = 'RECEIVE_VITAMINS';
In reducers I set the initialState to the vitamins array, and passed the new state of micros.vitamins from my RECEIVE_VITAMINS action
(reducers.js)
const initialState = {
vitamins: [],
}
function vitaminReducer(state = initialState, action) {
switch(action.type) {
case RECEIVE_VITAMINS:
return {
...state,
vitamins: action.payload
};
default:
return state;
}
}
const reducers = combineReducers({
vitamins: vitaminReducer,
});
Thanks everyone for your help! Let me know if you have any other suggestions :D

Component structure to handle Async Action with Redux-thunk ?

After a bit of trial and error I finally manage to get my action creator working properly and passing the data I wanted into my redux store. Until now I've been dispatching it "manually" like this store.dispatch(fetchTest()); but It would be great if could use these data into a component.
So here is my action creator :
export const fetchTest = () => (dispatch) => {
dispatch({
type: 'FETCH_DATA_REQUEST',
isFetching:true,
error:null
});
return axios.get('http://localhost:3000/authors')
.then(data => {
dispatch({
type: 'FETCH_DATA_SUCCESS',
isFetching:false,
data: data
});
})
.catch(err => {
dispatch({
ype: 'FETCH_DATA_FAILURE',
isFetching:false,
error:err
});
console.error("Failure: ", err);
});
};
Here is my reducer :
const initialState = {data:null,isFetching: false,error:null};
export const ThunkData = (state = initialState, action)=>{
switch (action.type) {
case 'FETCH_DATA_REQUEST':
case 'FETCH_DATA_FAILURE':
return { ...state, isFetching: action.isFetching, error: action.error };
case 'FETCH_DATA_SUCCESS':
return Object.assign({}, state, {data: action.data, isFetching: action.isFetching,
error: null });
default:return state;
}
};
So far everything is working properly when using store.dispatch(fetchTest());.
Based on this example I tried to build the following component :
class asyncL extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchTest(this.props.thunkData)
// got an error here : "fetchTest is not a function"
}
render() {
if (this.props.isFetching) {
return console.log("fetching!")
}else if (this.props.error) {
return <div>ERROR {this.props.error}</div>
}else {
return <p>{ this.props.data }</p>
}
}
}
const mapStateToProps = (state) => {
return {
isFetching: state.ThunkData.isFetching,
data: state.ThunkData.data.data,
error: state.ThunkData.error,
};
};
const AsyncList = connect(mapStateToProps)(asyncL);
export default AsyncList
It doesn't work, I have an error on the componentWillMount() and probably somewhere else.
Also my data structure is kind of weird. To actually get to the data array I have to do state.ThunkData.data.data. The first data object is full of useless stuff like request, headers, etc...
So how should I write this component so I can at least passed the Async data into a console.log.
Thanks.
You need to mapDispatchToProps as well.
import { fetchTest } from './myFetchActionFileHere';
import { bindActionCreators } from 'redux';
function mapDispatchToProps(dispatch) {
return {
fetchTest: bindActionCreators(fetchTest, dispatch)
};
}
const AsyncList = connect(mapStateToProps, mapDispatchToProps)(asyncL);
export default AsyncList
documentation link: http://redux.js.org/docs/api/bindActionCreators.html

Redux: Distinguish objects in reducers

I'm quite new to Redux and from what I understand, a reducer should be created for each type of object. E.g. for user interaction a user reducer should be created. My question is: How do you handle cases where you require the object for different purposes?
Scenario: Imagine having a user reducer which returns the current user. This user would be required in the entire application and needed for general controls on every page.
Now what happens when you need to load another user which is used for different purposes. E.g. profile page: loading a user to display information.
In this case there would be a conflict if the user reducer would be used. What would be the correct way to handle this in Redux? In case a different reducer would have to be created, what would be the naming convention for the new reducer?
First, you've mentioned:
a user reducer which loads the current user
I don't know if I got you correctly, but if this means you want to fetch (from an API, for example) the current user inside the reducer, this is a wrong approach.
Reducers are intended to be pure functions. You can call them with the same arguments multiple times and they will always return the same expected state.
Side effects like that should be handled by action creators, for example:
actions/user.js
export const FETCH_ME = 'FETCH_ME'
export const FETCH_ME_SUCCESS = 'FETCH_ME_SUCCESS'
// it's using redux-thunk (withExtraArgument: api) module to make an async action creator
export const fetchMe = () => (dispatch, getState, api) => {
dispatch({ type: FETCH_ME })
return api.get('/users/me').then(({ data }) => {
dispatch({ type: FETCH_ME_SUCCESS, data })
return data
})
}
Inside your reducer you can simple get the data and set a new state (note that if you send the action with the same data multiple times, the state will always be the same).
reducers/user.js
import { FETCH_ME, FETCH_ME_SUCCESS } from '../actions/user'
const initialState = {
item: null,
loading: false
}
export const userReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_ME:
return {
...state,
loading: true
}
case FETCH_ME_SUCCESS:
return {
...state,
loading: false,
item: action.data
}
default:
return state
}
}
Now, for your scenario:
Now what happens when you need to load another user which is used for different purposes. E.g. profile page: loading a user to display information.
You will just write another action creator for that:
actions/user.js
export const FETCH_ME = 'FETCH_ME'
export const FETCH_ME_SUCCESS = 'FETCH_ME_SUCCESS'
export const FETCH_USER = 'FETCH_USER'
export const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS'
export const fetchMe = () => (dispatch, getState, api) => {
dispatch({ type: FETCH_ME })
return api.get('/users/me').then(({ data }) => {
dispatch({ type: FETCH_ME_SUCCESS, data })
return data
})
}
export const fetchUser = (id) => (dispatch, getState, api) => {
dispatch({ type: FETCH_USER })
return api.get(`/users/${id}`).then(({ data }) => {
dispatch({ type: FETCH_USER_SUCCESS, data })
return data
})
}
Then you adapt your reducer to manage more sets:
reducers/user.js
import { combineReducers } from 'redux'
import { FETCH_ME, FETCH_ME_SUCCESS, FETCH_USER, FETCH_USER_SUCCESS } from '../actions/user'
const initialState = {
item: null,
loading: false
}
const meReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_ME:
case FETCH_ME_SUCCESS:
return userReducer(state, action)
default:
return state
}
}
const activeReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_USER:
case FETCH_USER_SUCCESS:
return userReducer(state, action)
default:
return state
}
}
const userReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_USER:
case FETCH_ME:
return {
...state,
loading: true
}
case FETCH_USER_SUCCESS:
case FETCH_ME_SUCCESS:
return {
...state,
loading: false,
item: action.data
}
default:
return state
}
}
export default combineReducers({
activeUser: activeReducer,
me: meReducer
})
Your final user state should be something like:
{
me: {
item: null,
loading: false
},
active: {
item: null,
loading: false
}
}

Resources