I am making an async call using fetch and then trying to set state by dispatching an action based on the result of the json data returned.
I am using a QR code reader to read a code which is passed to my didScan method.
didScan(code) {
if (this.state.showCamera){
this.props.pushNewRoute('finder');
getAppointment(code)
.then((appointment)=>{
if (appointment.valid){
this.props.appointmentDetails(appointment);
this.props.resetRoute('summary');
}else{
this.props.resetRoute('error');
}
})
.catch((error) => {
this.props.resetRoute('error');
});
this.setState({showCamera: false});
}
}
I am using react-redux to bind my actions to my dispatchers like this:
function bindActions(dispatch){
return {
resetRoute:(route)=>dispatch(resetRoute(route)),
pushNewRoute:(route)=>dispatch(pushNewRoute(route)),
appointmentDetails:(details)=>dispatch(appointmentDetails(details))
}
}
export default connect(null, bindActions)(Scanner);
but when the promise is returned by my getAppointment service it fails when it tries to do the routing.
this.props.resetRoute('summary');
The error is
Possible unhandled promise rejection{id:0}
Reducers may not dispatch actions
None of my reducers dispatch any actions and the code works fine when I take it out of the Promise .then() block.
Here is the simple getAppointment fetch service for completeness:
export function getAppointment(id:string) {
return fetch('http://myurl/' + id + '/')
.then((response) => response.json())
.catch((error) => {
console.error(error);
return error;
});
}
Any help greatly appreciated.
I'm not sure what your syntax is for binding the actions, haven't seen it before. Here's a sample of code that I made for a project where I do a get request and then set the response as state:
SearchBar.jsx (this does a http request to Solr and gets a JSON object back, then sets that object as the state)
import React, {Component} from 'react';
import httpClient from 'axios';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {setResponse} from '../actions/index'
class SearchBar extends Component {
constructor(props) {
super(props);
this.search = this.search.bind(this);
}
search() {
let q = document.getElementById('searchbar').value;
httpClient(`/search?q=${q}`, { baseURL: window.location.href })
.then( resp => {
console.log(resp);
this.props.setResponse(resp);
});
}
render() {
return (
<div>
<input type='text' id='searchbar'/>
<button onClick={this.search}>Search</button>
</div>
);
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({setResponse: setResponse}, dispatch);
}
export default connect(null, mapDispatchToProps)(SearchBar);
This is the action:
export const setResponse = (res) => {
return {
type: 'RESPONSE_RECEIVED',
payload: res
}
};
This is the reducer:
export default function (state = null, action) {
switch (action.type) {
case 'RESPONSE_RECEIVED':
return action.payload;
break;
}
return state;
}
Which is exported to a combining function (though there is only one reducer atm):
import {combineReducers} from 'redux';
import ResponseReducer from './reducer-response';
const allReducers = combineReducers({
response: ResponseReducer
});
export default allReducers;
Related
I have been trying to use redux and redux-thunk to help get a json file from a api and have been getting a warning stating that action must be a plain object. I am really confused as to where the issue is in the code. i have tried following many other stackoverflow posts and a couple of guides online and have not really got a good grasp of where I am going wrong. I understand that this is a problem with how I am referencing async and dispatch but do not know how to fix it.
This is the function that causes the warning to appear in the simulator
export const fetchData = url => {
console.log("Should enter async dispatch");
return async (dispatch) => {
dispatch(fetchingRequest());
fetch("https://randomuser.me/api/?results=10")
.then(response => {
if (response.ok) {
let json = response.json();
dispatch(fetchingSuccess(json));
console.log("JSON", json);
}
})
.catch(error => {
dispatch(fetchingFailure(error));
console.log("Error", error);
});
};
};
Here is the output in the console
Possible Unhandled Promise Rejection (id: 0):
Error: Actions must be plain objects. Use custom middleware for async actions.
Error: Actions must be plain objects. Use custom middleware for async actions.
Edit: including setup of middleware
I have the middleware setup in the index.js file of my app
index.js
import { AppRegistry } from "react-native";
import App from "./App";
import { name as appName } from "./app.json";
import { Provider } from "react-redux";
import React, { Components } from "react";
import { createStore, applyMiddleware } from "redux";
import appReducer from "./src/data/redux/reducers/appReducer";
import thunk from "redux-thunk";
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const store = createStoreWithMiddleware(appReducer);
console.log("Store", store.getState());
const AppContainer = () => (
<Provider store = {store}>
<App />
</Provider>
);
AppRegistry.registerComponent(appName, () => AppContainer);
I learned this implementation of store from a Youtube Tutorial.
Edit 2: Adding in the fetchData call
I call fetchData in a _onPress function like this
_onPress = () => {
const {fetchData} = this.props;
let url = "https://randomuser.me/api/?results=10";
fetchData(url);
console.log("should have fetched");
};
this is how my app has been connected to redux
const mapStateToProps = state => {
return { response: state };
};
const mapStateToDispatch = dispatch => ({
fetchData: url => dispatch(fetchData(url)),
});
export default connect(
mapStateToProps,
mapStateToDispatch
)(SearchScreen);
these are the action in my app
export const fetchingRequest = () => {
{
type: FETCHING_REQUEST;
}
};
export const fetchingSuccess = json => {
{
type: FETCHING_SUCCESS;
payload: json;
}
};
export const fetchingFailure = error => {
{
type: FETCHING_FAILURE;
payload: error;
}
};
I was able to figure out the problem thanks to working through the steps in the comments thanks to Michael Cheng. I ended up finding that the problem was that i had actions with plain objects but they were not returning anything.
The original actions were
export const fetchingRequest = () => {
{
type: FETCHING_REQUEST;
}
};
export const fetchingSuccess = json => {
{
type: FETCHING_SUCCESS;
payload: json;
}
};
export const fetchingFailure = error => {
{
type: FETCHING_FAILURE;
payload: error;
}
};
to this
export const fetchingRequest = () => {
return {
type: FETCHING_REQUEST
}
};
export const fetchingSuccess = json => {
return {
type: FETCHING_SUCCESS,
payload: json
}
};
export const fetchingFailure = error => {
return {
type: FETCHING_FAILURE,
payload: error
};
};
with including the return for each action
My question is simple as how can i get city and country , on axios fetch with multiple parameters on URL with Redux?
in the ROOT_URL the parameters are city and country change
const URL = `http://api.openweathermap.org/data/2.5/weather?q=${city},${country}&appid=${API_KEY}&units=metric`;
my actions
import axios from 'axios';
const API_KEY = "a3de5cffde10c377d199699b3da6fc6f";
export function getWeather (city, country) {
const URL = `http://api.openweathermap.org/data/2.5/weather?q=${city},${country}&appid=${API_KEY}&units=metric`;
return(dispatch)=>{
return axios.get(URL)
.then(res => {
dispatch(changeWeather(res));
}).catch(err =>{
console.log('error', err);
});
}
}
const changeWeather = weather => ({
type: 'CHANGE_WEATHER',
weather
});
the reducers
const weatherDefaultState = {
city:undefined,
country:undefined,
};
export default (state=weatherDefaultState,action)=>{
switch(action.type) {
case 'CHANGE_WEATHER':
return {...state, weather: action.weather}
default: return state;
}
};
and the component with redux, runs but the button does not perform any action, what is the error ?
import React from 'react';
import {connect} from 'react-redux';
import { getWeather } from '../actions/weather';
class Weather extends React.Component {
loadWeather = () => {
const API_KEY = 'a3de5cffde10c377d199699b3da6fc6f';
const URL = `http://api.openweathermap.org/data/2.5/weather?q=Merida,mx&appid=${API_KEY}&units=metric`;
return(dispatch)=>{
return axios.get(URL)
.then(res => {
dispatch(getWeather(res));
}).catch(err =>{
console.log('error', err);
});
}
}
render() {
return(<div>
<WeatherForm
weather={this.props.weather}
handleClick={this.loadWeather}
/>
</div>
)
}
}
class WeatherForm extends React.Component {
render() {
return(<div>
<button
className='boton-color'
style={{backgroundColor:'darkcyan'}}
onClick={()=>{this.props.handleClick() }}
type="button" >
<p
className="card-text-correo">
clima </p></button>
</div>)
}
}
const mapStateToProps = (state) => {
return state
};
export default connect(mapStateToProps)(Weather);
According to your code, you don't make HTTP call at all.
Inside your loadWeather function, which is just method on the component, you return a function which takes dispatch as an argument, this isn't correct.
You have your async action, which is great ( I assume you have installed and connected redux-thunk properly.
So, you need to get access to this action inside your component. This should be made using connect module which you installed and connected. But it also takes another parameter to connect actions to props.
const mapStateToProps = (state) => {
return state
};
/* This one you should add */
const mapDispatchToProps = dispatch => {
return {
getWeatherDispatch: (city, country) => {
dispatch(getWeather(city, country))
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Weather);
As a result, you can invoke your action inside a component using this.props.getWeatherDispatch(yourCity, yourCountry)
I am just starting with Redux and External APIs. For learning, I wanted to consume API from NASA (https://api.nasa.gov/). I don't know what I do wrong. I can't render anything on screen. I get "Uncaught TypeError: Cannot read property '1' of undefined" in the console.
I've read several Q&A in stackoverflow... But I didn't find out where is the problem in my code.
I'd really appreciate any opinion. I need a clue... Thanks in advance.
CONTAINER
import React from 'react';
import { Component } from 'react';
import { connect } from 'react-redux'
import { showTutusers } from '../actions/index';
import { bindActionCreators } from "redux";
class TutuserListContainer extends Component {
componentWillMount() {
this.props.showTutusers()
}
render() {
return (
<div>
<h3> { this.props.tutusers.photos[0].id}</h3><br />
<h3> { this.props.tutusers.photos[1].id}</h3><br />
</div>
);
}
}
function mapStateToProps(state) {
return {
tutusers: state.tutuser.tutusers
}
}
export default connect(mapStateToProps, { showTutusers })(TutuserListContainer)
REDUCERS - INDEX
import {combineReducers} from 'redux';
import { showTutusers } from './tutusers'
const allReducers = combineReducers({
tutuser: showTutusers,
});
export default allReducers
REDUCER
import { FETCH_TUTUSERS_START, FETCH_TUTUSERS_ERROR, RECEIVED_TUTUSERS } from '../actions/action-types';
const initialState = {
fetching: false,
fetched: false,
tutusers: [],
error: null
}
export function showTutusers(state = initialState, action) {
switch (action.type) {
case FETCH_TUTUSERS_START: {
return {...state, fetching: true}
break;
}
case FETCH_TUTUSERS_ERROR: {
return {...state, fetching: false, error: action.payload}
break;
}
case RECEIVED_TUTUSERS: {
return {...state, fetching: false, fetched: true, tutusers: action.payload}
break;
}
}
return state
}
ACTION-TYPES
export const SHOW_TUTUSERS = 'SHOW_TUTUSERS';
export const FETCH_TUTUSERS_START = 'FETCH_TUTUSERS_START';
export const FETCH_TUTUSERS_ERROR = 'FETCH_TUTUSERS_ERROR';
export const RECEIVED_TUTUSERS = ' RECEIVED_TUTUSERS';
ACTIONS - INDEX
import * as types from '../actions/action-types';
import axios from 'axios';
import store from '../stores/store';
export function showTutusers() {
return (dispatch, getState) => {
store.dispatch( { type: types.FETCH_TUTUSERS_START} )
axios.get('https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&api_key=_____MY_KEY____')
.then((response) => {
store.dispatch( { type: types.RECEIVED_TUTUSERS, payload: response.data } )
// console.log(">>> response.data", response.data)
})
.catch((err) => {
dispatch({type: "FETCH_TUTUSERS_ERROR", payload: err})
})
}
}
STORE
import { createStore, applyMiddleware, compose } from 'redux';
import allReducers from '../reducers';
import thunk from 'redux-thunk';
// import promise from 'redux-promise';
import createLogger from 'redux-logger';
import promise from 'redux-promise-middleware';
const middleware = applyMiddleware(thunk, promise(), createLogger());
const store = createStore(
allReducers,
compose(middleware, window.devToolsExtension ? window.devToolsExtension() : f => f)
);
export default store;
You need to wait until the response is received and only then try to access the photos, something like this should help:
<div>
{this.props.tutusers.fetched && <div>
<h3> { this.props.tutusers.photos[0].id}</h3><br />
<h3> { this.props.tutusers.photos[1].id}</h3><br />
</div>}
</div>
You also need to pass all the data about the request:
function mapStateToProps(state) {
return {
tutusers: state.tutuser
}
}
I got a solution in this stackoverflow question: react redux-thunk component doesn't render this.props
Basically I only needed an if else statement:
render() {
var component;
if (this.props.tutusers) {
component = this.functionToRenderTutusers()
} else {
component = <h3>Loading...</h3>;
}
return (
<div>
{component}
</div>
);
};
functionToRenderTutusers() {
return this.props.tutusers.photos.map((tutuser) => {
return (
<div>{tutuser.id}</div>
)
})
}
My code is as below:
const LOAD = 'redux-example/LOAD';
const LOAD_SUCCESS = 'redux-example/LOAD_SUCCESS';
const LOAD_FAIL = 'redux-example/LOAD_FAIL';
import axios from 'axios';
const initialState = {
loaded: false
};
export default function info(state = initialState, action = {}) {
switch (action.type) {
case LOAD:
return {
...state,
loading: true
};
case LOAD_SUCCESS:
return {
...state,
loading: false,
loaded: true,
data: action.result
};
case LOAD_FAIL:
return {
...state,
loading: false,
loaded: false,
error: action.error
};
default:
return state;
}
}
export function load() {
return {
types: [LOAD, LOAD_SUCCESS, LOAD_FAIL],
promise: (client) => client.get('http://example.com/getdata')
};
}
I am using https://github.com/erikras/react-redux-universal-hot-example example as starter kit. I want to make promise based api call to example.com/api.But I am not able to do it with async call.I get error in middleware that can not read promise of undefined.My middleware code is as below.
export default function clientMiddleware(client) {
return ({dispatch, getState}) => {
return 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);
actionPromise.then(
(result) => next({...rest, result, type: SUCCESS}),
(error) => next({...rest, error, type: FAILURE})
).catch((error)=> {
console.error('MIDDLEWARE ERROR:', error);
next({...rest, error, type: FAILURE});
});
return actionPromise;
};
};
}
MY component code is as below
import React, {Component, PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {load} from 'redux/modules/info';
#connect(state => ({info: state.info.data}),dispatch => bindActionCreators({load}, dispatch))
export default class InfoBar extends Component {
static propTypes = {
info: PropTypes.object,
load: PropTypes.func.isRequired
}
render() {
const {info, load} = this.props; // eslint-disable-line no-shadow
const styles = require('./InfoBar.scss');
return (
<div className={styles.infoBar + ' well'}>
<div className="container">
This is an info bar
{' '}
<strong>{info ? info.message : 'no info!'}</strong>
<span className={styles.time}>{info && new Date(info.time).toString()}</span>
<button className="btn btn-primary" onClick={load}>Reload from server</button>
</div>
</div>
);
}
}
this is only the reducer. You would want to create an action. An action triggers the event that will make the redux store update its state. The basic flow of redux for something like this goes like:
Mount a component
Dispatch an action
Dispatched action in turn will update the store via the Provider component
this will trigger a re-render of the component.
The following is a basic example using fetch.
import fetch from 'isomorphic-fetch';
export function getUsers() {
return dispatch => {
dispatch({ type: REQUEST_USERS });
return fetch('/api/v1/users')
.then(res => res.json())
.then(users => {
dispatch({ type: RECEIVE_USERS, payload: users });
return users;
});
}
}
Then you can call this in your component level item.
import { getUsers } from 'actions/users';
class UserList extends Component {
componentDidMount() { dispatch(getUsers()) }
}
Check out the example
I am trying to fetch some data from an api using Redux. My code looks like this:
Action:
// Import libraries
import axios from 'axios';
// Import types
import {
GET_ALL_PICKS
} from './types';
export const getAllPicks = ({ token }) => {
const getPicks = (dispatch) => {
axios({
method: 'get',
url: 'http://myapi/',
headers: {
Authorization: `Bearer ${token}`
}
})
.then((response) => {
console.log(response.data); // First log here returns data just fine
dispatch({
type: GET_ALL_PICKS,
payload: response.data
});
})
.catch((error) => {
console.log(error);
});
};
return getPicks;
};
Reducer:
// Import types
import {
GET_ALL_PICKS
} from '../actions/types';
// Set Initial State
const INITIAL_STATE = {
allPicks: {},
loading: false,
error: ''
};
// Make pick reducers
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_ALL_PICKS:
return { ...state, allPicks: action.payload }; // Logging action.payload here returns data just fine
default:
return state;
}
};
Component:
// Import Libraries
import React, { Component } from 'react';
import { Text } from 'react-native';
import { connect } from 'react-redux';
import {
getAllPicks
} from '../actions/picks';
// Make Component
class HomeScreen extends Component {
// Fetch Data
componentWillMount() {
const { token } = this.props;
this.props.getAllPicks({ token });
}
// Test response
componentDidMount() {
console.log(this.props.allPicks); // This log returns empty object, why?!
}
render() {
return (
<Text>Test</Text>
);
}
}
const mapStateToProps = ({ auth, picks }) => {
const { token } = auth;
const { allPicks } = picks;
return {
token,
allPicks
};
};
export default connect(mapStateToProps, { getAllPicks })(HomeScreen);
When I run the app I see the data in the action console.log and if I run a console.log(action.payload) in the reducer I see the data just fine but in component I see an empty array which suggests I'm not hooking up the data in my reducer correctly? Here's a screen shot of the logs:
I have also tried this in my reducer after some Googling:
return Object.assign({}, state, {
allPicks: action.payload
});
but again I got the same result. Can anyone explain to me what I am doing wrong?
You are confusing the component lifecycle and the API lifecycle.
In practice, what's happening is:
componentWillMount
getAllPicks
componentDidMount (at which point, the API didn't return, the picks are empty)
[... wait for the API to return]
then the API returns with the data, but too late
What you need to do then is check for your "picks" state in the render() function, which will be updated each time your state changes (which happens when the API returns), thanks to the connect() function.
You can also check that the picks are updated properly using componentWillUpdate, not componentDidMount which again has nothing to do with the props being updated.