Why do my promises not actually update the state in Redux?
I'm using redux-promise-middleware. When I make a call to my API, it goes through the promise steps of _PENDING and _FULFILLED, but the state is never updated to reflect the changes.
How do I do this properly, so that I actually get my data.
Here's a picture of my state:
As you can see, isFetched does not become true after the promise is fulfilled, and data is never loading the returned response data into itself.
This is my API helper:
class UserAPI {
...
async testPhone(user) {
await axios.post(this.testPhonePath, {
phone: user.phone
})
.then(function(response) {
return response.data
})
.catch(function(error) {
return error.response.data
})
}
}
My action:
import { UserAPI } from '../../constants/api'
const userAPI = new UserAPI()
export const TEST_USER_PHONE = 'TEST_USER_PHONE'
export const testUserPhone = (user) => ({
type: TEST_USER_PHONE,
payload: userAPI.testPhone(user)
})
And my reducer:
import {
TEST_USER_PHONE
} from './actions'
const INITIAL_STATE = {
testedByPhone: {
data: [],
isFetched: false,
error: {
on: false,
message: null
}
}
}
export default (state = INITIAL_STATE, action) => {
switch(action.type) {
case '${TEST_USER_PHONE}_PENDING':
return INITIAL_STATE
case '${TEST_USER_PHONE}_FULFILLED':
return {
testedByPhone: {
data: action.payload,
isFetched: true,
error: {
on: false,
message: null
}
}
}
case '${TEST_USER_PHONE}_REJECTED':
return {
testedByPhone: {
data: [],
isFetched: true,
error: {
on: true,
message: action.payload
}
}
}
default:
return state
}
}
Here's my Store
import { createStore, applyMiddleware, compose } from 'redux'
import promiseMiddleware from 'redux-promise-middleware'
import reducers from './reducers'
const middleware = [
promiseMiddleware()
]
if (__DEV__) {
const logger = require('redux-logger')
middleware.push(logger())
}
const enhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
export default createStore(
reducers,
undefined,
enhancers(applyMiddleware(...middleware))
)
The reason it isn't working, it is that you use a standard string instead of JS templates.
Replace:
'${TEST_USER_PHONE}_REJECTED'
With:
`${TEST_USER_PHONE}_REJECTED`
I suspect you wanted to use either
testPhone(user) {
return axios.post(this.testPhonePath, {
phone: user.phone
}).then(function(response) {
return response.data
}, function(error) {
return error.response.data
});
}
or
async testPhone(user) {
try {
const response = await axios.post(this.testPhonePath, {
phone: user.phone
});
return response.data
} catch(error) {
return error.response.data
}
}
but not that current mix which always returns a promise for undefined - it only uses await but not return.
Related
I am new to React Redux. I am not sure what is wrong on my code. There is no error on the terminal but when I take a look on the browser there is a TypeError. ItemsProduct was on the props. I was wondering why it returns an error when I am trying to access the properties.
productDescription.js
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import axios from "axios";
import {
fetchProductsRequests,
fetchProductsSuccess,
fetchProductError,
} from "../../actions/productActions";
class ProductDescription extends Component {
componentDidMount() {
this.props.fetchProducts();
}
render() {
return (
<>
<div className="grid grid-cols-3 gap-6 mb-10">
<div className="col-start-2 col-end-4">
<h4>{this.props.itemsProduct[0].name}</h4>
</div>
</div>
</>
);
}
}
const mapStateToProps = (state, ownProps) => {
return {
itemsProduct: state.rootProduct.products.filter(
(prod) => prod.id == ownProps.match.params.id
),
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchProducts: () => {
dispatch(fetchProductsRequests());
axios
.get("http://localhost:3000/js/products.json")
.then((response) => {
dispatch(fetchProductsSuccess(response.data));
})
.catch((error) => {
dispatch(fetchProductError(error.message));
});
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ProductDescription);
productActions.js
export const FETCH_PRODUCTS_REQUESTS = "FETCH_PRODUCTS_REQUESTS";
export const FETCH_PRODUCTS_SUCCESS = "FETCH_PRODUCTS_SUCCESS";
export const FETCH_PRODUCTS_ERROR = "FETCH_PRODUCTS_ERROR";
export const fetchProductsRequests = () => {
return {
type: FETCH_PRODUCTS_REQUESTS,
};
};
export const fetchProductsSuccess = (product) => {
return {
type: FETCH_PRODUCTS_SUCCESS,
payload: product,
};
};
export const fetchProductError = (error) => {
return {
type: FETCH_PRODUCTS_ERROR,
payload: error,
};
};
productReducer.js
const initialState = {
loading: true,
products: [],
error: "",
};
const productReducer = (state = initialState, action) => {
switch (action.type) {
case "FETCH_PRODUCTS_REQUESTS":
return {
...state,
loading: true,
};
case "FETCH_PRODUCTS_SUCCESS":
return {
loading: false,
products: action.payload,
error: "",
};
case "FETCH_PRODUCTS_ERROR":
return {
loading: false,
products: [],
error: action.payload,
};
default:
return state;
}
};
export default productReducer;
Root Reducer
import { combineReducers } from "redux";
import productReducer from "./productReducer";
const rootReducer = combineReducers({
rootProduct: productReducer,
});
export default rootReducer;
You can do a quick check if there is data coming from your axios by doing this (it will prevent any undefined or null values)
dispatch(fetchProductsSuccess(response.data || 'no data'));
Also you should return your state in the reducer as follows:
case "FETCH_PRODUCTS_SUCCESS":
return {
...state,
loading: false,
products: action.payload,
error: "",
};
case "FETCH_PRODUCTS_ERROR":
return {
...state,
loading: false,
products: [],
error: action.payload,
};
Your
itemsProduct: state.rootProduct.products.filter(
(prod) => prod.id == ownProps.match.params.id
),
may return an empty array meaning you will not be able to retrieve that object in your view
<h4>{this.props.itemsProduct[0].name}</h4>
In my web app, I want to fetch urls from an API. Also, I want to fetch categories for these items.
index.js:
componentDidMount () {
this.props.fetchUrls();
this.props.fetchCategories();
}
Im fetching the urls first like that:
export const fetchUrlsSuccess = urls => ({
type: FETCH_URLS_SUCCESS,
payload: { urls }
});
export const fetchUrls = () => dispatch => {
dispatch(fetchUrlsBegin());
return fetch(`${api}/urls`)
.then(handleErrors)
.then(res => res.json())
.then(json => {
dispatch(fetchUrlsSuccess(json));
return json.urls;
})
.catch(error => dispatch(fetchUrlsFailure(error)));
};
fetching categories:
export const fetchCategoriesSuccess = categories => ({
type: FETCH_CATEGORIES_SUCCESS,
payload: { categories }
});
export const fetchCategoriesFailure = error => ({
type: FETCH_CATEGORIES_FAILURE,
payload: { error }
});
export function fetchCategories() {
return dispatch => {
dispatch(fetchCategoriesBegin());
return fetch(`${api}/categories`)
.then(handleErrors)
.then(res => res.json())
.then(json => {
dispatch(fetchCategoriesSuccess(json));
return json.categories;
})
.catch(error => dispatch(fetchCategoriesFailure(error)));
};
}
url reducer:
import {
FETCH_URLS_BEGIN,
FETCH_URLS_SUCCESS,
FETCH_URLS_FAILURE
} from "../actions/types";
export default function urlReducer(state = [], action) {
switch (action.type) {
case FETCH_URLS_BEGIN:
console.log("url fetch begin", state);
return {
...state,
loading: true,
error: null
};
case FETCH_URLS_SUCCESS:
console.log("url fetch success", state);
return {
...state,
loading: false,
items: action.payload.urls
};
case FETCH_URLS_FAILURE:
console.log("url fetch error", state);
return {
...state,
loading: false,
error: action.payload.error,
items: []
};
default:
return state;
}
}
categories reducer:
import {
FETCH_CATEGORIES_BEGIN,
FETCH_CATEGORIES_SUCCESS,
FETCH_CATEGORIES_FAILURE
} from "../actions/types";
export default function categoriesReducer(state = [], action) {
switch (action.type) {
case FETCH_CATEGORIES_BEGIN:
console.log("categories fetch begin", state);
return {
...state,
loading: true,
error: null
};
case FETCH_CATEGORIES_SUCCESS:
console.log("categories fetch success", state);
return {
...state,
loading: false,
items: action.payload.categories
};
case FETCH_CATEGORIES_FAILURE:
console.log("categories fetch fail", state);
return {
...state,
loading: false,
error: action.payload.error,
items: []
};
default:
return state;
}
}
combining reducers in index of reducers:
import { combineReducers } from "redux";
import urlReducer from "./urlReducer";
import categoriesReducer from "./categoriesReducer";
import modalReducer from "./modalReducer";
export default combineReducers({
urls: urlReducer,
modal: modalReducer,
categories: categoriesReducer
});
create store :
import { createStore, applyMiddleware, compose } from "redux";
import { persistStore, persistReducer } from "redux-persist";
import thunk from "redux-thunk";
import storage from "redux-persist/lib/storage";
import rootReducer from "../reducers";
const persistConfig = {
key: "root",
storage
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const middleware = [thunk];
let store = createStore(
persistedReducer,
compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
let persistor = persistStore(store);
export { store, persistor };
For the categories, I do the same. Then I combine both reducers.
What happens is that the state.urls. Items get overwritten and state.categories.items holds state instead. I don't understand why.
output of the redux dev-tool after the second fetch:
I'm pretty new to redux and don't understand the state management...
I use JWT token authentication for auth.
When I access to localhost:4000/api/refresh with token, it verify if token is expired, and return refreshed token with status code 200.
And middleware detect if token is valid and return to 200 or 401.
Backend is works perfectly. But frontend got some errors.
I use redux for global state manage.
Here is my codes.
[reducers.js]
// Actions
const LOGIN_TRUE = 'LOGIN_TRUE';
const LOGIN_FALSE = 'LOGIN_FALSE';
const CHECK_TOKEN = 'CHECK_TOKEN';
const REFRESH_TOKEN = 'REFRESH_TOKEN';
// Action Creators
function loginTrue() {
return {
type: LOGIN_TRUE
}
}
function loginFalse() {
return {
type: LOGIN_FALSE
}
}
function checkToken() {
return {
type: CHECK_TOKEN
}
}
function refreshToken() {
return {
type: REFRESH_TOKEN
}
}
// Reducer
const initialState = {
isLogin: false
}
function reducer(state = initialState, action) {
switch(action.type) {
case LOGIN_TRUE:
return applyLoginTrue(state, action);
case LOGIN_FALSE:
return applyLoginFalse(state, action);
case CHECK_TOKEN:
return applyCheckToken(state, action);
case REFRESH_TOKEN:
return applyRefreshToken(state, action);
default:
return state;
}
}
// Reducer Functions
function applyLoginTrue(state) {
return {
...state,
isLogin: true
}
}
function applyLoginFalse(state) {
return {
...state,
isLogin: false
}
}
function applyCheckToken(state) {
const token = localStorage.getItem('token');
if(token !== null) {
return {
...state,
isLogin: true
}
} else {
return {
...state,
isLogin: false
}
}
}
function applyRefreshToken(state) {
console.log(state);
const token = localStorage.getItem('token');
if(token !== null) {
fetch("http://localhost:4000/api/refresh", {
method: "POST",
headers: {
'Authorization':`JWT ${token}`
}
})
.then(res => {
if(res.status === 200) {
return res.json();
} else {
console.log("applyRefreshToken() res.status is not 200");
}
})
.then(json => {
localStorage.clear();
localStorage.setItem('token', json.token);
return {
...state,
isLogin: true
}
})
} else {
console.log("applyRefreshToken() token is null");
return {
...state,
isLogin: false
}
}
}
// Export Action Creators
export const actionCreators = {
loginTrue,
loginFalse,
checkToken,
refreshToken
};
// Export Reducer
export default reducer;
After wrote the reducer.js, I made dummy component to test it.
componentDidMount() {
const { refreshToken } = this.props;
refreshToken();
}
render () {
const { isLogin } = this.props;
return (
<div className="wrap">
{ isLogin ? "Under Construction" : "Login please" }
</div>
)
}
export default connect(mapStateToProps, mapDispatchToProps)(index);
But it throw errors like this -> TypeError: Cannot read property 'isLogin' of undefined
I can't find where is the error occured.
Because loginTrue(), loginFalse(), checkToken() works perfectly.
Is there any solution about this?
Thanks.
[mapStateToProps.js]
const mapStateToProps = (state) => {
const { isLogin } = state;
return {
isLogin
}
}
export default mapStateToProps;
[mapDispatchToProps.js]
import { bindActionCreators } from 'redux';
import { actionCreators } from './reducer';
const mapDispatchToProps = (dispatch) => {
return {
loginTrue: bindActionCreators(actionCreators.loginTrue, dispatch),
loginFalse: bindActionCreators(actionCreators.loginFalse, dispatch),
checkToken: bindActionCreators(actionCreators.checkToken, dispatch),
refreshToken: bindActionCreators(actionCreators.refreshToken, dispatch)
}
}
export default mapDispatchToProps;
applyRefreshToken is async, and therefore returns undefined.
So when your reducer executes:
case REFRESH_TOKEN:
return applyRefreshToken(state, action);
You actually set your state to undefined which eventually sets this.props to undefined and hence the error.
Either run the async logic and dispatch the new state after getting the response or use thunk / saga / any other middleware which will enable you to have async action creators.
I am still learning React-Redux. I understand how to retrieve simple JSON arrays. However, I am not sure how to call a nested object. I am trying to grab the title and am viewing this in the console:
Object
data
:
Object
data
:
Object
data
:
Object
after
:
"t3_5t0hy2"
before
:
null
children
:
Array[25]
0
:
Object
data
:
Object
title
:
"The Google Analytics Setup I Use on Every Site I Build (by Philip Walton)"
dataAction.js
import axios from 'axios';
export function fetchData(){
return function(dispatch){
axios.get("https://www.reddit.com/r/webdev/top/.json")
.then((response) => {
dispatch({ type: "FETCH_DATA_FULFILLED", payload: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_DATA_REJECTED", payload: err})
})
}
}
export function addData(id, text){
return {
type: 'ADD_DATA',
payload:{
id,
title,
},
}
}
export function updateData(id, text){
return {
type: 'UPDATE_DATA',
payload: {
id,
title,
},
}
}
export function deleteData(id){
return {
type: 'DELETE_DATA',
payload: id
}
}
Layout.js (component)
import React from "react"
import { connect } from "react-redux"
import { fetchUser } from "../actions/userActions"
import { fetchPartner } from "../actions/projectActions"
import { fetchData } from "../actions/dataActions"
#connect((store) => {
return {
user: store.user.user,
userFetched: store.user.fetched,
partner: store.partner.partner,
partnerFetched: store.partner.fetched,
data: store.data.data
};
})
export default class Layout extends React.Component {
componentWillMount() {
this.props.dispatch(fetchUser())
this.props.dispatch(fetchPartner())
this.props.dispatch(fetchData())
}
render() {
const { user, partner, data } = this.props;
//const mappedData = data.map(data => <li>{data.title}</li>)
return <div>
<h1>{user.name}{user.age}</h1>
<h1>{partner.title}</h1>
<ul>{data.title}</ul>
</div>
}
}
Reducer.js
export default function reducer(state={
data: {
data: {}
},
fetching: false,
fetched: false,
error: null,
}, action) {
switch(action.type){
case "FETCH_DATA":{
return {...state, fetching:true}
}
case "FETCH_DATA_REJECTED":{
return {...state, fetching: false, error: action.payload}
}
case "FETCH_DATA_FULFILLED":{
return {...state, fetching: false, fetched: true, data: action.payload}
}
case "ADD_DATA":{
return {...state, data: [...state.data, action.payload]}
}
case "UPDATE_DATA":{
const { id, title } = action.payload
const newData = [...state.data]
const dataToUpdate = newData.findIndex(data => data.id === id)
newData[dataToUpdate] = action.payload;
return {...state, data: newData}
}
case "DELETE_DATA":{
return {...state, data: state.data.filter(data => data.id !== action.payload)}
}
}
return state
}
When this issue is solved, the next step would be to iterate through the object, which I'm also not sure how to achieve.
As you are sending payload: response.data You can go further in the object structure and send the actual data in payload.
Once you send the payload you would need a reducer which will change the state. Follow this tutorial on how to create reducer.
http://blog.scottlogic.com/2016/05/19/redux-reducer-arrays.html
Then once the state is updated, you will have the code reading the values from state. Once the state change the React will automatically update or render and you can write your logic.
I'm loading data from an API using Redux & React. Despite successfully pulling the data and applying it to the state, it's throwing an error:
Uncaught TypeError: Cannot read property 'payload' of undefined.
This occurs after the FETCH_PRODUCT_LISTINGS_PENDING action type in the console.
React Component:
import React from 'react';
import { connect } from 'react-redux';
import store from '../../../store';
import * as ProductListingActions from '../actions/ProductListingActions';
#connect((store) => {
return {
productListing: store.productListing.products
}
})
export default class ProductListingContainer extends React.Component {
constructor(data) {
super();
this.props = data;
this.props.dispatch(ProductListingActions.fetchProductListings());
}
render() {
return <div></div>;
}
}
Reducer:
import CookieHandler from '../../../modules/CookieHandler';
const cookieHandler = new CookieHandler;
export default function reducer(
state = {
products: [],
fetching: false,
fetched: false,
error: null
}, action) {
switch(action.type) {
case "FETCH_PRODUCT_LISTINGS_PENDING":
return {
...state,
fetching: true,
}
break;
case "FETCH_PRODUCT_LISTINGS_REJECTED":
return {
...state,
fetching: false,
error: action.payload
}
break;
case "FETCH_PRODUCT_LISTINGS_FULFILLED":
return {
...state,
fetching: false,
fetched: true,
products: action.payload.data.default
}
break;
}
return state;
}
Actions:
import Config from '../../../Config.js';
import store from '../../../store.js';
import axios from 'axios';
export function fetchProductListings() {
store.dispatch({
type: "FETCH_PRODUCT_LISTINGS",
payload: axios.get(Config.getConfigAPIUrl() + '/cartel/products')
})
}
Any help would be appreciated
You're dispatching a call to dispatch, rather than dispatching an object.
this.props.dispatch(ProductListingActions.fetchProductListings());
function fetchProductListings() {
store.dispatch({
type: "FETCH_PRODUCT_LISTINGS",
payload: axios.get(Config.getConfigAPIUrl() + '/cartel/products')
})
}
if you inline this:
this.props.dispatch(
store.dispatch({
type: "FETCH_PRODUCT_LISTINGS",
payload: axios.get(Config.getConfigAPIUrl() + '/cartel/products')
})
)
Your action creator should not be calling dispatch, it should just return an action:
export function fetchProductListings() {
return {
type: "FETCH_PRODUCT_LISTINGS",
payload: axios.get(Config.getConfigAPIUrl() + '/cartel/products')
}
}
Keep in mind though, axios.get is asynchronous, so payload will be promise. You may want to consider adding redux-thunk to handle the fulfillment of the promise.
I was recently using redux-toolkit for fetching api, and I faced the same problem. When I checked the api result, I saw my payload value was undefined.
I solved this problem by simply returning the result of my api data.
export const getPosts = createAsyncThunk("posts/getPosts", async ()=> {
const res = await axios.get(`${baseURL}/posts/1`)
return res.data;
});