I am trying to make an API request using Axios in React-Redux environment. On the console everything seems to be fine, however if I try to access any of the data I either get undefined or empty array.
This is my component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { discoverMovie } from '../actions'
//Home component
class Home extends Component {
//make request before the render method is invoked
componentWillMount(){
this.props.discoverMovie();
}
//render
render() {
console.log('movie res ',this.props.movies.movies.res);
console.log('movie ',this.props.movies);
return (
<div>
Home
movie
</div>
)
}
};
const mapStateToProps = (state) => {
return{
movies : state.movies
}
}
export default connect(mapStateToProps, { discoverMovie })(Home);
This is my action
import { DISCOVER_MOVIE } from '../constants';
import axios from 'axios';
//fetch movie
const fetchMovie = () => {
const url = 'https://api.themoviedb.org/3/discover/movie?year=2018&primary_release_year=2018&page=1&include_video=false&include_adult=false&sort_by=vote_average.desc&language=en-US&api_key=72049b7019c79f226fad8eec6e1ee889';
let result = {
res : [],
status : ''
};
//make a get request to get the movies
axios.get(url).
then((res) => {
result.res = res.data.results;
result.status = res.status;
return result;
});
//return the result after the request
return result;
}
//main action
const discoverMovie = () =>{
const result = fetchMovie();
//return the action
return {
type : DISCOVER_MOVIE,
payload : result
}
}
export default discoverMovie;
This is the reducer
import { DISCOVER_MOVIE } from '../constants';
//initial state
const initialState = {
movies : {},
query : '',
};
//export module
export default (state = initialState, actions) =>{
switch(actions.type){
case DISCOVER_MOVIE :
return {
...state,
movies : actions.payload
};
default :
return state;
}
}
this is the log that I get from the console
as you can see if I log the entire object I see all data, however if go deep and try to access the result I either get an undefined or an empty array and using redux-dev-tools I noticed that the state does not contain any value.
I read on internet including this portal similar issue but could not find any solution for my issue.
Solution
From official docs:
You may use a dedicated status field in your actions
Basically you need to dispatch action for each state to make an async action to work properly.
const searchQuery = () => {
return dispatch => {
dispatch({
type : 'START',
})
//make a get request to get the movies
axios.get(url)
.then((res) => {
dispatch({type : 'PASS', payload : res.data});
})
.catch((err) => {
dispatch({type : 'FAILED', payload : res.error});
});
}
With redux-thunk it's pretty simple to set up. You just have to make some changes to your store. Out the box, I'm pretty sure redux isn't the most friendly with async and that's why thunk is there.
import { ..., applyMiddleware } from "redux";
import thunk from "redux-thunk";
...
const store = createStore(reducer, applyMiddleware(thunk));
...
Then in your action you'll need to return dispatch which will handle your logic for your axios call.
const fetchMovie = () => {
return dispatch => {
const url = //Your url string here;
axios.get(url).then(res => {
dispatch(discoverMovie(res.data.results, res.status);
}).catch(err => {
//handle error if you want
});
};
};
export const discoverMovie = (results, status) => {
return {
type: DISCOVER_MOVIE,
payload: results,
status: status
};
};
Your reducer looks fine, though with the way my code is typed you'll have status separately. You can combine them into it's own object before returning in discoverMovie, if you need status with the results.
This is my first answer on stack so let me know if I can clarify anything better!
Related
Newbie to Redux here, I have tried to follow a couple tutorials and I am not clear of how Redux actually works. It was mentioned that the store of Redux is to store the state of the whole tree. I have created and used actions, reducers, and store for my program and it works.
The question is, how do I retrieve what is in the store? Lets say after updating my component, how can I retrieve the value inside the component and to post it?
How can I know what changed in my dropdown list and to retrieve it?
Full code in Sandbox here https://codesandbox.io/s/elated-goldberg-1pogb
store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './RootReducer';
export default function configureStore() {
return createStore(
rootReducer,
applyMiddleware(thunk)
);
}
ProductsList.js
import React from "react";
import { connect } from "react-redux";
import { fetchProducts } from "./SimpleActions";
class ProductList extends React.Component {
constructor(props)
{
super(props);
this.state = {
selecteditems: '',
unitPrice: 0
}
}
componentDidMount() {
this.props.dispatch(fetchProducts());
}
componentDidUpdate(prevProps, prevState) {
if(prevState.selecteditems !== this.state.selecteditems)
{
this.setState((state, props) => ({
unitPrice: ((state.selecteditems * 1).toFixed(2))
}));
}
}
render() {
const { error, loading, products } = this.props;
if (error) {
return <div>Error! {error.message}</div>;
}
if (loading) {
return <div>Loading...</div>;
}
return (
<div>
<select
name="sel"
className="sel"
value={this.state.selecteditems}
onChange={(e) =>
this.setState({selecteditems: e.target.value})}
>
{products.map(item =>
<option key={item.productID} value={item.unitPrice}>
{item.itemName}
</option>
)}
</select>
<p>Unit Price: RM {this.state.unitPrice} </p>
</div>
);
}
}
const mapStateToProps = state => {
const products = state.productsReducer.items;
const loading = state.productsReducer.loading;
const error = state.productsReducer.error;
return {
products,
loading,
error,
}
};
export default connect(mapStateToProps)(ProductList);
SimpleAction.js
export function fetchProducts() {
return dispatch => {
dispatch(fetchProductsBegin());
return fetch('http://localhost:55959/api/products')
.then(handleErrors)
.then(res => res.json())
.then(results => {
dispatch(fetchProductsSuccess(results));
return results;
})
.catch(error => dispatch(fetchProductsFailure(error)));
};
}
function handleErrors(response) {
if(!response.ok) {
throw Error (response.statusText);
}
return response;
}
export const FETCHPRODUCTS_BEGIN = 'FETCHPRODUCTS_BEGIN';
export const FETCHPRODUCTS_SUCCESS = 'FETCHPRODUCTS_SUCCESS';
export const FETCHPRODUCTS_FAILURE = 'FETCHPRODCUTS_FAILURE';
export const fetchProductsBegin = () => ({
type: FETCHPRODUCTS_BEGIN
});
export const fetchProductsSuccess = products => ({
type: FETCHPRODUCTS_SUCCESS,
payload: {products}
});
export const fetchProductsFailure = error => ({
type: FETCHPRODUCTS_FAILURE,
payload: {error}
});
Thanks in advance!
You will need to pass your action handlers to connect function
connect(mapStateToProps,{actions})(ProductList).
how do I retrieve what is in the store? Lets say after updating my component, how can I retrieve the value inside the component and to post it?
if you want to see how is store change, you can add redux-logger to middleware to see that. when store change, it's likely a props change, you can handle this in function componentDidUpdate.
How can I know what changed in my dropdown list and to retrieve it?
values in dropdown is controlled by "const products = state.productsReducer.items;", productsReducer is controlled by actions you passed in dispatch like this: "this.props.dispatch(fetchProducts());".
I think you should add redux-logger to know more how to redux work, it show on console step by step. It will help you learn faster than you think :D
to retrieve it you forgot the selecteditems
const mapStateToProps = state => {
const products = state.productsReducer.items;
const loading = state.productsReducer.loading;
const error = state.productsReducer.error;
const selecteditems = state.prodcuts.selecteditems;
return {
products,
loading,
error,
selecteditems
};
};
To change it you should connect another function like
const mapDispatchToProps = dispatch => {
return {
onChangeDropdownSelection: (selected)=> dispatch(actions.setSelectedDropdown(selected))
}
}
I have problem with fetching data from URL.
When I write data inside of a file, app works great, but when I try to call same data from URL, I get error.
I made a test with small app where everything was inside of a App.js file, and it worked. But new app is kinda devided in multiple files, and this is where problem starts.
Here is events.js where I call data and code works:
import {
TOGGLE_FAVORITE_EVENT
} from '../const';
import toggle from './toggle';
let data = [
{
type: 'PARTY',
title: 'Party in the Club',
adress: 'New York',
date: '9. 9. 2019.',
image: '',
text: [
'Party description...'
],
coordinates: [50, 50],
id: 'events_1'
}
];
let events = (state = data, action) => {
switch(action.type){
case TOGGLE_FAVORITE_EVENT:
return toggle(state, action.payload.id);
default:
return state;
}
}
export default events;
This is how I try to fetch data, which doesn't work:
import {
TOGGLE_FAVORITE_EVENT
} from '../const';
import toggle from './toggle';
// WP REST API
const REQUEST_URL = 'http://some-url.com/test.json';
let data = fetch(REQUEST_URL)
.then(response => response.json() )
.then(data => console.log(data) )
.catch(error => console.log(error));
let events = (state = data, action) => {
switch(action.type){
case TOGGLE_FAVORITE_EVENT:
return toggle(state, action.payload.id);
default:
return state;
}
}
export default events;
NOTE: .json file should be fine, becasue it works in small app.
I think you are trying to initialize the state with the content of a json file loaded from an URL: if I were you, I would create an action specifically to do that. You'll need a library to handle asynchronous processes, like redux-thunk or redux-saga.
Here is a quick example with redux-thunk:
// store
import thunk from 'redux-thunk'
import { createStore, applyMiddleware } from 'redux'
import reducer from 'state/reducers'
export const configureStore = () => {
/* thunk is a redux middleware: it lets you call action creators that return a function instead of
an object. These functions can call dispatch several times (see fetchFavorites) */
const middlewares = [thunk]
let store = createStore(reducer, applyMiddleware(...middlewares))
return store
}
// actions
// standard action returning an object
export const receiveFavorites = function(response){
return {
type: "RECEIVE_FAVORITES",
response
}
}
// this action returns a function which receives dispatch in parameter
export const fetchFavorites = function(){
return function(dispatch){
console.log('send request')
return fetch('http://some-url.com/test.json')
.then(response => response.json())
.then(response => {
dispatch(receiveFavorites(response))
})
.catch(error => {
console.log(error)
})
}
}
Now, with a reducer implemented for the action RECEIVE_FAVORITES, you can call the function fetchFavorites: it will send the request and fill the state however you do it in the reducer.
I am new to React-Redux and I have a problem with redux action object. When I am printing the object to the console, it displays correctly as shown below:
Please check my codes.
FeedPage.jsx
class FeedPage extends React.Component {
componentDidMount() {
const { id } = this.props.match.params;
this.props.dispatch(feedActions.getById(id));
console.log("props", this.props);
}
render() {
const { user, feed } = this.props;
...
function mapStateToProps(state) {
const { feed, authentication } = state;
const { user } = authentication;
console.log(JSON.stringify(feed));
return {
user,
feed
};
}
const connectedFeedPage = connect(mapStateToProps)(FeedPage);
export { connectedFeedPage as FeedPage };
reducer.js
export function feeds(state = {}, action) {
switch (action.type) {
case feedConstants.GETBYID_REQUEST:
return {
loading: true
};
case feedConstants.GETBYID_SUCCESS:
console.log("GETBYID_SUCCESS: " + JSON.stringify(action.feed));
return {
feed: action.feed
};
case feedConstants.GETBYID_FAILURE:
return {
error: action.error
};
...
service.js
function getById(id) {
const requestOptions = {
method: 'GET',
headers: authHeader()
};
return fetch(`${config.apiUrl}/feed/${id}`, requestOptions).then(handleResponse);
}
actions.js
function getById(id) {
return dispatch => {
dispatch(request());
feedService.getById(id)
.then(
feed => dispatch(success(feed)),
error => dispatch(failure(error.toString()))
);
};
function request() { return { type: feedConstants.GETBYID_REQUEST } }
function success(feed) { return { type: feedConstants.GETBYID_SUCCESS, feed } }
function failure(error) { return { type: feedConstants.GETBYID_FAILURE, error } }
}
UPDATE:
root reducer
import { combineReducers } from 'redux';
import { authentication } from './authentication.reducer';
import { registration } from './registration.reducer';
import { users } from './users.reducer';
import { alert } from './alert.reducer';
import { feeds } from './feeds.reducer';
const rootReducer = combineReducers({
authentication,
registration,
users,
alert,
feeds
});
export default rootReducer;
This is the screenshot of the logs:
So it is clear that feed object is not empty. But when I am referencing it, it is undefined. Please help as I am stuck and cannot move forward. Any help is great appreciated. Thank you!
In your root reducer, you are saying that the item "feeds" will contain what your feedReducer gives it. In your feedReducer you return "feed: action.feed".
So, in your mapStateToProps, you should be mapping "feeds", not "feed". When you then read the feeds value, it will contain an object such as { feed: xxx } where xxx is what your action originally had in it after your API call.
There's an issue with your reducer. You're mutating component state as it store doesn't get updated when we mutate the state and component doesn't re render
case feedConstants.GETBYID_SUCCESS:
console.log("GETBYID_SUCCESS: " + JSON.stringify(action.feed));
return {
...state,
feed: action.feed
};
in your case Redux doesn't detect a difference in state and won't notify your components that the store has changed and you should return a new copy of state with the necessary change. Hope this would solve the issue
Let me know if the issue still persists.
I try to deal with ajax data using axom in my learning react,redux project and I have no idea how to dispatch an action and set the state inside a component
In component will mount
componentWillMount(){
this.props.actions.addPerson();
}
Store
import { createStore, applyMiddleware } from "redux";
import rootReducer from "../reducers";
import thunk from "redux-thunk";
export default function configureStore() {
return createStore(rootReducer, applyMiddleware(thunk));
}
In Action :
import * as types from "./action-types";
import axios from "axios";
export const addPerson = person => {
var response = [];
axios
.get(`&&&&&&&&&&&`)
.then(res => {
response = res.data;
return {
type: types.ADD_PERSON,
response
};
});
};
In reducer
import * as types from "../actions/action-types";
export default (state = [], action) => {
console.log("action======>", action);
switch (action.type) {
case types.ADD_PERSON:
console.log("here in action", action);
return [...state, action.person];
default:
return state;
}
};
I am getting Actions must be plain objects. Use custom middleware for async actions.
You should use dispatch for async function. Take a look of the redux-thunk's documentation: https://github.com/gaearon/redux-thunk
In Action:
import * as types from "./action-types";
import axios from "axios";
export const startAddPerson = person => {
return (dispatch) => {
return axios
.get(`https://599be4213a19ba0011949c7b.mockapi.io/cart/Cart`)
.then(res => {
dispatch(addPersons(res.data));
});
}
};
export const addPersons = personList => {
return {
type: types.ADD_PERSON,
personList
};
}
In PersonComponent:
class Person extends Component {
constructor(props){
super(props);
}
componentWillMount() {
this.props.dispatch(startAddPerson())
}
render() {
return (
<div>
<h1>Person List</h1>
</div>
);
}
}
export default Redux.connect()(Person);
You need two actions here: postPerson and addPerson.
postPerson will perform the API request and addPerson will update the store:
const addPerson = person => {
return {
type: types.ADD_PERSON,
person,
}
}
const postPerson = () => {
return (dispatch, getState) => {
return axios.get(`http://599be4213a19ba0011949c7b.mockapi.io/cart/Cart`)
.then(res => dispatch(addPerson(res.data)))
}
}
in your component, call postPerson()
You use the redux-thunk library which gives you access to the "getState" and "dispatch" methods. I see that that has been added by Chenxi to your question. Run your async operation first within your action and then call "dispatch" with your simple action action creator, which will return the simple object that redux is looking for.
Here is what your async action creator and your simple action creator(broken out into two action creators) will look like:
export const addPersonAsync = (person) => {
return (dispatch) => {
var response = [];
axios
.get(`http://599be4213a19ba0011949c7b.mockapi.io/cart/Cart`)
.then(res => {
response = res.data;
dispatch(addPerson(response));
});
};
};
export const addPerson = (response) => ({
type: types.ADD_PERSON,
response
});
From your component, you'll now call the "addPersonAsync" action creator.
When I try to access to the response object in my component it doesnt throw me an error but it wont either print. I do get access to the response in the component but thats it, i cant actually print something.
Actions File
import axios from 'axios';
import { FETCH_USERS, FETCH_USER } from './types';
const BASE_URL = "http://API_URL/endpoint/"
export function fetchUsers(id,first_name, last_name, dob) {
const request = axios.post(`${BASE_URL}member-search?patientId=${id}&firstName=${first_name}&lastName=${last_name}&dateOfBirth=${dob}&length=10`).then(response => { return response; })
return {
type: FETCH_USERS,
payload: request
};
}
export function fetchUser(id) {
const request = axios.get(`${BASE_URL}members/${id}/summary/demographics`).then(response => { return response; })
return{
type: FETCH_USER,
payload: request
};
}
My reducer file
import _ from 'lodash';
import {
FETCH_USERS, FETCH_USER
} from '../actions/types';
export default function(state = [], action) {
switch (action.type) {
case FETCH_USER:
return { ...state, [action.payload.data.member.id]: action.payload.data.member };
// return [ action.payload.data.member, ...state ];
case FETCH_USERS:
return _.mapKeys(action.payload.data.searchResults, 'id');
}
return state;
}
And finally my component where Im trying to render some results of the response.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { fetchUser } from '../actions';
class PatientWrapper extends Component{
componentDidMount() {
const { id } = this.props.match.params;
this.props.fetchUser(id);
}
render(){
const { user } = this.props;
console.log('this.props response: ',user);
if(!user){
return <div>loading...</div>;
}
return(
<div>
Name: {user.firstName}
Last Name: {user.lastName}
</div>
)
}
}
function mapStateToProps({ users }, ownProps) {
// return { users };
return { user: users[ownProps.match.params.id] };
}
export default connect (mapStateToProps, { fetchUser })(PatientWrapper);
I uploaded a Screenshot img of the response : http://prntscr.com/fbs531
What is wrong with my code?
The issue is that in fetchUser action you use a Promise and return it in payload field. This promise does not contain any information you need like response data. So to fix the issue you need to dispatch action only when response is retrieved (e.g. in then success callback).
To implement it you need to pass mapDispatchToProps in the second argument in connect function for your component and pass dispatch function to your action:
function mapDispatchToProps(dispatch) {
return {
fetchUser: id => fetchUser(id, dispatch)
}
}
Then in the action just do the following
function fetchUser(id, dispatch) {
const request = axios.get(`${BASE_URL}/${id}`)
.then(response => dispatch({
type:FETCH_USER,
payload: response
}));
}
For complete example see JSFiddle