Reusing Redux-react async request action creator / reducer - reactjs

So I have this simple async request action creator using axios for requests and redux-thunk to dispatch actions. I want to re-use the same action creator for different requests - so each request would have its own state. I'll try to show some code and then explain more. Here is my action creator
function responseOK(response){
return {
type: RESPONSE_OK,
payload: response,
}
}
function loadingChangedStatus(isLoading) {
return {
type: IS_LOADING,
isLoading: isLoading,
}
}
function handleError(errorID){
return {
type: HANDLE_ERROR,
errorID: errorID,
}
}
export function makeRequest( URL){
return function(dispatch, getSate){
dispatch( loadingChangedStatus(true) );
axios.get( URL )
.then(function(response){
dispatch(loadingChangedStatus(false));
dispatch(responseOK(response));
})
.catch(function (response) {
dispatch(loadingChangedStatus(false));
dispatch(handleError('connection_error'));
});
}
}
And my reducer reducerRequest:
export default function(state = {isLoading:false, payload:false,errorID:false}, action){
switch (action.type) {
case IS_LOADING:
return Object.assign({}, state, {
isLoading: action.isLoading,
});
break;
case RESPONSE_OK:
return Object.assign({}, state, {
isLoading: state.isLoading,
payload: action.payload,
});
break;
case HANDLE_ERROR:
return Object.assign({}, state, {
isLoading: state.isLoading,
payload: action.payload,
errorID:action.errorID,
});
break;
default:
return state;
}
}
HERE STARTS MY PROBLEM
I combine reducers like so:
import { combineReducers } from 'redux';
import Request from "./reducerRequest";
const rootReducer = combineReducers({
// I WANT EACH OF THESE TO BE SEPARATE INSTANCE BUT USE THE SAME ACTION CREATOR / REDUCER
defaults: Request,
updates: Request,
});
export default rootReducer;
In my component:
function mapStateToProps( {defaults, updates} ){
return {defaults, updates}
}
function mapDispatchToProps( dispatch ){
return bindActionCreators({ makeRequest}, dispatch);
}
PROBLEM: I want to re-use my action creator for different requests. How can I
call makeRequest('www.defaults.com') and it ends up in defaults
call makeRequest('www.updates.com') and it ends up in updates
Now the only way I can image to solve this would be to write for every request its own action creator and own reducer - just lots of copy paste - that doesn't feel right.
How can I reuse my action creator and reducer to create 2 separate instances of defaults and updates in my component?

You can prefix your reducer actions per action:
export default function(namespace)
return function(state = {isLoading:false, payload:false,errorID:false}, action){
switch (action.type) {
case namespace + IS_LOADING:
return Object.assign({}, state, {
isLoading: action.isLoading,
});
break;
case namespace + RESPONSE_OK:
return Object.assign({}, state, {
isLoading: state.isLoading,
payload: action.payload,
});
break;
case namespace + HANDLE_ERROR:
return Object.assign({}, state, {
isLoading: state.isLoading,
payload: action.payload,
errorID:action.errorID,
});
break;
default:
return state;
}
}
}
and then add the namespace
function responseOK(namespace, response){
return {
type: namespace + RESPONSE_OK,
payload: response,
}
}
const rootReducer = combineReducers({
// I WANT EACH OF THESE TO BE SEPARATE INSTANCE BUT USE THE SAME ACTION CREATOR / REDUCER
defaults: Request("DEFAULTS_"),
updates: Request("UPDATES_"),
});
and then use the namespace when you call the make requests
call makeRequest('DEFAULTS_', 'www.defaults.com') and it ends up in defaults
call makeRequest('UPDATES_', 'www.updates.com') and it ends up in updates
HTH

Related

Setting the initial state of redux in reducers

Hey guys i am stuck in a situation in which i have to set the inital state of reducer to some value let me show you the code
First of all i have an action creater like this
export const fetchuser = () => {
return async dispatch => {
const res = await axios.get("/api/currentuser");
dispatch({
type: "fetchuser",
payload: res.data
});
};
};
which just fetches the data from api and dispatches an action to reducer
export default function(state = {}, action) {
switch (action.type) {
case "fetchuser":
return action.payload||false;
default:
return state;
}
}
now in second action creater i have to make a post request and increase the "credits" value in user database
export const handletoken = token => {
return async dispatch => {
const res = await axios.post("/api/stripe", token);
dispatch({ type: "credits", payload: res.data });
};
};
so i get the updated value here then i pass this on to the reducer
export default function(state = {}, action) {
switch (action.type) {
case "credits":
return action.payload
default:
return state;
}
}
and then combine them in reducer/index.js
export default combineReducers({
auth: authreducer,
credits:creditsreducer
});
console log of auth reducer in app.js in mapstatetoprops function gives
auth:
credits: 40
googleid: "109463598810933991924"
__v: 0
_id: "5d7fff2c4cb0604139055ce4"
so in credits reducer as u can see i have defined initial value of state as an empty object but i want to set it as the value of credits key of auth reducer, I could easily set it to array or an object hardcoding it but here i need to set its value as a value which is already in my another reducer so how can i achieve this ?
Assuming you need to wait for "fetchuser" to succeed to set credits in your creditsreducer you can handle the "fetchuser" action in your creditsreducer as well:
export default function(state = {}, action) {
switch (action.type) {
case "fetchuser":
return action.payload ? action.payload.credits : state;
case "credits":
return action.payload
default:
return state;
}
}
Always keep previous reducer state value. Otherwise no use of redux state value. like this
1.export default function(state = {}, action) {
switch (action.type) {
case "fetchuser":
let data = action.payload||false;
return {
...state,
fetchuser: data //any where you can access fetchuser data as well as previous state will not change.
}
default:
return state;
}
}
Change all the reducers like above.

redux rejactjs Use one store for multiple actions

I am new in react and redux and I would like to know if it is possible to use one store for multiple actions.
I am trying this but the first action ges overwritten with the last action, why?
I am calling the action in two separate component and I call those two component in my app component.
reducer.js
const dataReducer = (state = {
fetching: false,
fetched: false,
data: {},
error: null
}, action) => {
switch (action.type) {
case 'FETCH_DATA_PENDING':
return {...state, fetching: true}
break;
case 'FETCH_DATA_FULFILLED':
return {...state, fetching: false, fetched: true, data: action.payload.data }
break;
case 'FETCH_DATA_REJECTED':
return {...state, fetching: false, error: action.payload }
break;
}
return state;
}
module.exports = dataReducer;
action.js
import axios from 'axios';
const apiUrl = 'https://swapi.co/api/';
//fetch categories
export function fetchCategories() {
return {
type: 'FETCH_DATA',
payload: axios.get(apiUrl)
}
}
//fetch films
export function fetchFilms() {
return {
type: 'FETCH_DATA',
payload: axios.get(apiUrl + 'films')
}
}
You should be able to do it this way, but the fact that both of your actions have the same type might be confusing in your reducers. It might be more helpful to have a type FETCH_FILMS and FETCH_CATEGORIES. That way the reducer can do separate things with them, unless of course, you always want every reducer to do the exact same thing with them.

how to call another reducer method with redux react

Reducer 1 code is as below. I want to call another reducer method after successful authetication of user. so its based of response of reducer 1 , I want to call method/action of reducer 2.
const LOGIN = 'redux-example/auth/LOGIN';
const LOGIN_SUCCESS = 'redux-example/auth/LOGIN_SUCCESS';
const LOGIN_FAIL = 'redux-example/auth/LOGIN_FAIL';
import { browserHistory } from 'react-router';
import { apiurl } from '../../Constants';
import {savedata} from '../../redux/modules/new';
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case LOGIN:
return {
...state,
loggingIn: true
};
case LOGIN_SUCCESS:
return {
...state,
loggingIn: false,
user: action.result
};
case LOGIN_FAIL:
return {
...state,
loggingIn: false,
user: null,
loginError: action.error
};
default:
return state;
}
}
export function login(page,email,password) {
var querystring = require('querystring');
if(action == undefined) action = null;
var data = querystring.stringify({
email: email,
password: password
});
return {
types: [LOGIN, LOGIN_SUCCESS, LOGIN_FAIL],
promise: (client) => client.post(apiurl + 'ajax/login', {
data: data
}).then(response => {
//console.log(response);
switch(page){
case 'signin':
if(response.auth == 'true') {
redirectuser(response);
}
break;
default:
break;
}
return response;
})
.catch( error => Promise.reject(error))
};
}
export function redirectuser(response) {
console.log('response is as below');
console.log(response);
if(response.action == 'action1'){
savedata();
// here I want call another reducer method save data
}
}
When I call action save data of reducer 2 from reducer 1 , it does not work. How to dispatch action of reducer 2 from reducer 1.
Edit 1: my middleware code is as below
export default function clientMiddleware(client) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
const { promise, types, ...rest } = action; // eslint-disable-line no-redeclare
if (!promise) {
return next(action);
}
const [REQUEST, SUCCESS, FAILURE] = types;
next({ ...rest, type: REQUEST });
const actionPromise = promise(client, dispatch);
actionPromise.then(
result => next({ ...rest, result, type: SUCCESS }),
error => next({ ...rest, error, type: FAILURE })
).catch(error => {
next({ ...rest, error, type: FAILURE });
});
return actionPromise;
};
}
Dispatching an action inside a reducer is not a good move. As i understand, you have to do some update synchronously. One way is, once the first reducer is updated, where ever your are consuming that reducer go and inside componentWillReceiveProps or componentDidUpdate do something like.
NOTE: before dispatching you have to import the configurestore and create a const dispatch from store.
componentWillReceiveProps(nextProps)
{
//only if user was not there previously and now user is there
if(!this.props.user && nextProps.user)
{
dispatch({type: SECOND_ACTION, payLoad})
}
}

Dispatching multiple actions using redux-thunk updates component only once

I'm using redux-thunk for dispatching multiple action from one dispatch within a component.
export function fetchQuestions() {
return function(dispatch, getState) {
dispatch({type: "QUESTION_FETCH_PENDING"});
axios.get(`http://${API_ROOT}/api/questions/list`, {
headers: {'JWT': getState().users.token}
})
.then((response) => {
//if registration is successful tell it to reducer and authorize user
dispatch({type: "QUESTION_FETCH_SUCCESS", payload: response});
})
.catch((err) => {
if (err.response.data == "login") {
dispatch(unauthorizedRequest());
}
dispatch({
type: "QUESTION_FETCH_FAIL",
payload: err
});
});
}
}
The problem is that I want component to be updated on each dispatch that are inside of wrapping dispatch. They all proceed to reducer, and I can see them logged in console.
But that does not happen, component is only updated after first dispatch.
You can see, "FEED RENDER" message that is called each time render() called, and it is not called after remaining dispatches.
Thank you in advance!
UPDATE 1: Here is code for my reducer
export default function reducer(state={
questions: [],
questionPending: false,
questionSuccess: false,
questionFetching: false,
questionFetched: false,
error: null
}, action) {
switch(action.type) {
case "QUESTIONS_FETCH_PENDING": {
return {...state, questionFetching: true, questionFetched: false}
}
case "QUESTIONS_FETCH_SUCCESS": {
return {...state, questionFetching: false, questionFetched: true, questions: action.payload}
}
case "QUESTIONS_FETCH_FAIL": {
return {...state, questionFetching: false, questionFetched: false, error: action.payload}
}
case "QUESTION_ADD_PENDING": {
return {...state, questionPending: true, questionSuccess: false}
}
case "QUESTION_ADD_SUCCESS": {
return {...state, questionPending: false, questionSuccess: true}
}
case "QUESTION_ADD_FAIL": {
return {...state, questionPending: false, questionSuccess: false}
}
}
return state;
}
And for injecting store, I simply use #connect decorator:
#connect((store) => {
return {
questions: store.questions,
isAuth: store.users.isAuth
};
})
Problematic component:
export default class Feed extends React.Component {
componentWillMount() {
this.props.dispatch(fetchQuestions());
}
componentWillUpdate() {
console.log(this.props);
if (!this.props.isAuth) {
this.props.router.push('/auth');
}
}
logout() {
this.props.dispatch(logoutUser());
}
render() {
console.log("FEED RENDER!");
let questions = this.props.questions.questions.map((question) => {<questionFeed question={question}/>});
return (
<div>
<button onClick={this.logout.bind(this)}>LOGOUT</button>
THIS IS FEED!
</div>
);
}
}
The problem was simple TYPO: the names of actions didn't match to ones that are in reducers.
You can see I was dispatching QUESTION_FETCH_FAIL and QUESTION_FETCH_SUCCESS, but handling QUESTIONS_FETCH_FAIL and QUESTIONS_FETCH_SUCCESS.
SOLUTION: According to best practices of redux, you should always store action names in variables and share them with reducer.
NOTE: Don't fall to same dellusion as me, logged action in console does not mean it has reached the reducer
SPECIAL THANKS: To #OB3 for noting typo

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