Concat arrays in redux reducer - reactjs

I'm using an Unsplash API, and trying to make pagination. I don't want to replace one page with another, but to call more images results and concate it to an existing ones. This is my action:
export const getUnsplashImages = (page = 1) => async (dispatch) => {
dispatch({ type: GET_UNSPLASH_LOADING });
try {
const { data } = await unsplashUrl.get(
`${Constants.UNSPLASH_PHOTOS}?page=${page}&${Constants.UNSPLASH_CLIENT_ID}&per_page=13`
);
dispatch({ type: GET_UNSPLASH, payload: data });
} catch (error) {
dispatch({ type: GET_UNSPLASH_ERROR });
}
};
And I'm trying to do the concatenation in reducer:
const initState = {
images: [],
loading: false,
error: '',
};
export default function (state = initState, action) {
switch (action.type) {
...
case GET_UNSPLASH:
console.log('STATE', state);
return {
...state,
loading: false,
images: [...state.images, action.payload],
};
...
default:
return state;
}
}
But I'm getting an array inside an array. I tried to do it like this images: [...state.images, ...action.payload], But result is not as I expected. I'm getting a result of 26 images, instead of concatenating the results

Assuming payload: data is an array that you want to concat within images, you need to spread it:
images: [...state.images, ...action.payload]

Related

Getting response data from post request when using axios

I'm trying to integrate the shippo api into a project that I am working on. I'm creating a post api request in my paymentActions.js document that is accessed on my PlaceOrderScreen.js. Currently, I am need to get the response sent from the api in my backend to my PlaceOrderScreen.js. In paymentActions.js, data from dispatch({ type: PAYMENT_SUCCESS, payload: data }) is returning my necessary response. How can I get this response in my PlaceOrderScreen.js? I was going to try to use useEffect(), but it was giving me an error when I was trying to put that within const handleSubmit.... I would really appreciate any help or advice. Thank you!
paymentActions.js
import Axios from 'axios';
import {
PAYMENT_REQUEST,
PAYMENT_SUCCESS,
PAYMENT_FAIL,
} from '../constants/paymentConstants';
import { BASE_URL } from '../constants/app.constants';
export const paymentInfo = (paymentMethodType, currency, userId) => async (dispatch) => {
dispatch({ type: PAYMENT_REQUEST, payload: paymentMethodType, currency, userId});
try {
const { data } = await Axios.post( 'api/stripe/pay', {
paymentMethodType,
currency,
userId,
})
dispatch({ type: PAYMENT_SUCCESS, payload: data })
}
catch (error) {
dispatch({
type: PAYMENT_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
I need to get data from dispatch({ type: PAYMENT_SUCCESS, payload: data }) (the response) within:
const handleSubmit = async (e) => {
e.preventDefault();
if (!stripe || !elements) {
return;
}
const paymentMethodType = 'card';
const currency = 'usd';
const {error: backendError, clientSecret} = dispatch(paymentInfo(paymentMethodType, currency, userId));
if (backendError) {
//addMessage(backendError.message);
console.log(backendError)
return;
}
console.log('Client secret returned')
console.log(clientSecret)
const {error: stripeError, paymentIntent} = await stripe.confirmCardPayment(clientSecret,
{
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
name: 'Jenny Rosen',
},
},
},
);
};
in my PlaceOrderScreen.js
paymentReducers.js
import {
PAYMENT_REQUEST,
PAYMENT_SUCCESS,
PAYMENT_FAIL,
} from '../constants/paymentConstants';
export const paymentInfoReducer = (state = {info:[]}, action) => {
switch (action.type) {
case PAYMENT_REQUEST:
return { ...state, loading: true };
case PAYMENT_SUCCESS:
return { ...state, loading: false, info: action.payload };
case PAYMENT_FAIL:
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};
Inside your reducer, you will add a key in the PAYMENT_REQUEST case. here we got the data that will come back from that request. That's what you want. then you will use it similar to your usage of the info and error key inside the PAYMENT_SUCCESS and PAYMENT_FAIL case.
import {
PAYMENT_REQUEST,
PAYMENT_SUCCESS,
PAYMENT_FAIL,
} from '../constants/paymentConstants';
export const paymentInfoReducer = (state = {info:[]}, action) => {
switch (action.type) {
case PAYMENT_REQUEST:
return { ...state, loading: true, success: action.payload };
case PAYMENT_SUCCESS:
return { ...state, loading: false, info: action.payload };
case PAYMENT_FAIL:
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};

Redux Action not returning JSON?

I'm struggling to figure out why my redux action is not returning the JSON from the GET request, even though when I submit the GET request in Postman, I can access the information?
The error I have returning is: Profile Not Found. Yet, like I said when I do the Postman request, it's working fine.
This Redux Action doesn't work:
// Get profile by id for admins
export const getUserProfile = (id) => dispatch => {
dispatch(setProfileLoading());
axios.get(`/admin/profile/${id}`)
.then(res =>
dispatch({
type: GET_PROFILE,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
}
Here is the Admin route which works in Postman and is returning the JSON?
router.get('/admin/profile/:id', passport.authenticate('jwt', {
session: false
}), (req, res) => {
const errors = {};
User.findOne({
user: req.user.id
})
.then(user => {
if (req.user.role === 'admin') {
Profile.findById(req.params.id)
.then(profile => {
res.json(profile);
})
.catch(err => res.status(404).json({
profilenotfound: 'Profile not found'
}));
} else {
res.json({unauthorized: 'User is unauthorized to view this area'})
}
})
.catch(err => res.status(404).json(err));
});
Here is the reducer:
import { GET_PROFILE, PROFILE_LOADING, CLEAR_CURRENT_PROFILE, GET_PROFILES }
from '../actions/types';
const initialState = {
profile: null,
profiles: null,
loading: false
}
export default function(state = initialState, action) {
switch(action.type) {
case PROFILE_LOADING:
return {
...state,
loading: true
}
case GET_PROFILE:
return {
...state,
profile: action.payload,
loading: false
}
case GET_PROFILES:
return {
...state,
profiles: action.payload,
loading: false
}
case CLEAR_CURRENT_PROFILE:
return {
...state,
profile: null
}
default:
return state;
}
}
As mentionned in the comments the problem is that you are not passing the id, you need to pass the id when you call your Redux action in your component for example if you call your getUserProfile method it should be something like that:
componentDidMount() {
const {getUserProfile} = this.props; // This is destructuring for better readability
// Here you need to pass your id for example 1234 or get it from params or from wherever you want...
getUserProfile(1234);
}

How do I avoid using separate _PENDING _FULFILLED and _REJECTED actions with redux thunk?

I am writing my actions and reducers with thunks that dispatch _PENDING, _FULFILLED, and _REJECTED actions. However, I am wanting a better solution to avoid the boilerplate. I am migrating to Typescript which doubles this boilerplate by requiring an interface for each _PENDING, _FULFILLED, and _REJECTED action. It is just getting out of hand. Is there a way to get the same/similar functionality of my code without having three action types per thunk?
localUserReducer.js
const initialState = {
fetching: false,
fetched: false,
user: undefined,
errors: undefined,
};
export default function (state = initialState, action) {
switch (action.type) {
case 'GET_USER_PENDING':
return {
...state,
fetching: true,
};
case 'GET_USER_FULFILLED':
return {
...state,
fetching: false,
fetched: true,
user: action.payload,
};
case 'GET_USER_REJECTED':
return {
...state,
fetching: false,
errors: action.payload,
};
default:
return state;
}
}
localUserActions.js
import axios from 'axios';
export const getUser = () => async (dispatch) => {
dispatch({ type: 'GET_USER_PENDING' });
try {
const { data } = await axios.get('/api/auth/local/current');
dispatch({ type: 'GET_USER_FULFILLED', payload: data });
} catch (err) {
dispatch({ type: 'GET_USER_REJECTED', payload: err.response.data });
}
};
I may have a huge misunderstand of redux-thunk as I am a newbie. I don't understand how I can send _REJECTED actions if I use the implementation of Typescript and redux-thunk documented here: https://redux.js.org/recipes/usage-with-typescript#usage-with-redux-thunk
There is a way to get the similar functionality without having three action types per thunk, but it will have some impact on the rendering logic.
I'd recommend pushing the transient aspect of the async calls down to the data. So rather than marking your actions as _PENDING, _FULFILLED, and _REJECTED, mark your data that way, and have a single action.
localUser.js (new file for the user type)
// Use a discriminated union here to keep inapplicable states isolated
type User =
{ status: 'ABSENT' } |
{ status: 'PENDING' } |
{ status: 'FULLFILLED', data: { fullName: string } } |
{ status: 'REJECTED', error: string };
// a couple of constructors for the fullfilled and rejected data
function dataFulFilled(data: { fullName: string }) {
return ({ status: 'FULLFILLED', data });
}
function dataRejected(error: string) {
return ({ status: 'REJECTED', error });
}
localUserReducer.js
const initialState: { user: User } = { user: { status: 'ABSENT' } };
export default function (state = initialState, action): { user: User } {
switch (action.type) {
case 'USER_CHANGED':
return {
...state,
user: action.payload
};
default:
return state;
}
}
localUserActions.js
import axios from 'axios';
export const getUser = () => async (dispatch) => {
dispatch({ type: 'USER_CHANGED', payload: { status: 'PENDING' } });
try {
const { data } = await axios.get('/api/auth/local/current');
dispatch({ type: 'USER_CHANGED', payload: dataFulFilled(data) });
} catch (err) {
dispatch({ type: 'USER_CHANGED', payload: dataRejected(err.response.data) });
}
};
This will also remove the need for the multiple boolean fields (fetching and fetched) and isolate the various data states from accidental modification.
The changes to the render logic will be necessary, but will likely be an improvement. Rather than combinations of nested if-else statements using the booleans, a single switch can be used to handle the four cases of the data state.
Then you can invoke something like this from your render function...
function userElement(user: User) {
switch (user.status) {
case 'ABSENT':
return <></>;
case 'PENDING':
return <div>Fetching user information...Please be patient...</div>;
case 'FULLFILLED':
return <div>{user.data.fullName}</div>;
case 'REJECTED':
return <h1>The error is: {user.error}</h1>
}
}
I hope that helps. Good luck!

React: cannot retrieve data from a server (dispatch)

it's my first time working with React and I'm having a few problems when I try to get some data. My problem is inside getChargesByUser() method where I just written two console.logs, the first one it shows but the second one not, that means that I'm getting inside the method but not into the return (dispatch) => {...} The weird thing is that it's not the only method I have with the return (dispatch) => {...} like loadUserList() and it's the only I cannot have access and don't know why
const SHOW_PAYMENTS = 'SHOW_PAYMENTS';
const initialState = {
open_drawer: false,
open_search: false,
list_users: [],
list_selectusers: [],
countries: [],
states: [],
list_payments: [],
user: [],
useremail: [],
save: [],
eventtype: '',
user_redirect: '',
saveloading: false
};
export default function reducer(state = initialState, action) {
switch (action.type) {
case SHOW_PAYMENTS:
return Object.assign({}, state, {list_payments: action.payload, eventtype: 'payments'});
default:
return state;
}
}
export function loadUserList(page, perpage) {
/* I HAVE ACCESS HERE */
return (dispatch) => {
/* I ALSO HAVE ACCESS HERE */
axios.get(`${config.host}/v1/user?page=${page}&perpage=${perpage}`, {
headers: {'x-asd-apikey': config.apikey}
}).then(response => {
dispatch({type: LOAD_USERLIST, payload: response.data.data});
}).catch(error => {
dispatch({type: LOAD_USERLIST_ERROR, payload: error.response.data});
});
};
}
export function getChargesByUser(userid) {
console.log('yes'); //<-- I have access here
return (dispatch) => {
console.log('nope'); // I have not accesss here
axios.get(`http://localhost:7770/v1/payments/${userid}/resume`, {
headers: {'x-asd-apikey': config.apikey}
}).then(response => {
console.log('response: ', response.data.data);
dispatch({type: SHOW_PAYMENTS, payload: response.data.data.records});
}).catch(error => {
console.log('error: ', error.response.data);
dispatch({type: SHOW_PAYMENTS, payload: { options: error.response.data}});
});
};
}
And this is where I call the method
#connect(
state => ({
eventtype: state.users.eventtype,
list_payments: state.users.list_payments,
}, usersActions)
)
static propTypes = {
eventtype: PropTypes.any,
list_payments: PropTypes.any,
getChargesByUser: PropTypes.func.isRequired,
params: PropTypes.object
}
componentDidMount() {
console.log('params: ', this.props.params.userid);
this.props.getChargesByUser(this.props.params.userid);
}
When inside promises you need to return a promise-like object to continue the chain.
So: you need to return axios.get(... if you want to go inside then/catch after (axios call returns promises).
Sorry, I was like all the day with the problem and didn't notice that the problem it was at the #connect() where my code was
#connect(
state => ({
eventtype: state.users.eventtype,
list_payments: state.users.list_payments,
}, usersActions)
)
and it's supposed to be like this:
#connect(
state => ({
eventtype: state.users.eventtype,
list_payments: state.users.list_payments
}),
usersActions)

Redux - why loading everything in state at root

I am trying to understand Redux and having some difficulty.
I understand the concept of combineReducer, ie ....
var reducer = combineReducers({
user: userReducer,
products: productsReducer
})
But what if I have thousands of products, only available on the products page. I do not understand why I need to load them at root; to me this will slow the initial start up of the app for something that will not be needed unless the user goes to the products page.
Is this just the way it is with redux?
In Redux apps, you always build your entire state at the start. With Redux you have one store and one state - everything should trickle down from that one state to props on your components. However, that does not mean you actually need to load all the data into the state at launch, only that the structure needs to be there. This is why you should set up an initial state object for each reducer.
Let's say you have thousands of product records that you load from the database. In your products reducer you could do something like this:
const initialState = {
data: []
};
//use ES6 default parameters
function productsReducer (state = initialState, action) {
switch (action.type) {
case 'GET_PRODUCTS':
//return data from action
return {
data: action.result
};
default:
return state;
}
}
This means that when you start your app, if you use the full reducer you declared in your post, your application state will look like this:
{
user: {},
products: {
data: []
}
}
products.data will be an empty array until you fire an action that actually requires you to load the products data (i.e. you go to the Products page in your app or something). It's true that the products data will remain in your state if you then go elsewhere in your app, but this is a great thing - the next time you render the Products page you will already have the data at your disposal without having to do a database lookup.
In our app, we made an API for the products and it has limit of 15 per page. So our reducer goes like this.
collection: {
"total": 0,
"per_page": 0,
"current_page": 0,
"last_page": 0,
"from": 0,
"to": 0,
data: []
},
isFetching: false,
isFetchingError: false
on the first load we fetched limited amount of products, then we made a pagination out of it.. using selectors in redux https://github.com/rackt/reselect
Loading a thousands of data will get your app very slow.
const paginated = (state = initialState, action) => {
switch (action.type) {
case FETCH_PAGINATED_PRODUCTS:
return {
...state,
isFetching: true,
isFetchingError: false
};
case FETCH_PAGINATED_PRODUCTS_SUCCESS:
return {
...state,
collection: action.payload,
isFetching: false
};
case FETCH_PAGINATED_PRODUCTS_ERROR:
return {
...state,
isFetching: false,
isFetchingError: true
};
default:
return state
we have used axios for request:
https://github.com/mzabriskie/axios
Here's how we implement axios in redux-async
export function getAll(page = 1) {
return (dispatch, getState) => {
const state = getState();
const { filters } = state.products.paginated;
if ( state.products.paginated.isFetching ) {
return;
}
dispatch({ type: FETCH_PAGINATED_PRODUCTS });
return axios
.get(`products?page=${page}&limit=16&filters=${JSON.stringify(filters)}`)
.then((res) => dispatch({
type: FETCH_PAGINATED_PRODUCTS_SUCCESS,
payload: res.data
}))
.catch((res) => dispatch({
type: FETCH_PAGINATED_PRODUCTS_ERROR,
/*payload: res.data.error,*/
error: true
}));
}
}
export function get(id) {
return (dispatch, getState) => {
const state = getState();
if ( state.products.resource.isFetching ) {
return;
}
dispatch({ type: FETCH_PRODUCT });
return axios
.get(`products/${id}`)
.then((res) => dispatch({
type: FETCH_PRODUCT_SUCCESS,
payload: res.data.data
}))
.catch((res) => dispatch({
type: FETCH_PRODUCT_ERROR,
/*payload: new Error(res.data.error),*/
error: true
}));
}

Resources