Multi Api call using redux and thunk doesn't work properly - javascript

I want to use redux to call API in react native. Redux is ok when I want to call only single API in a component but when I want to dispatch more than one API. it just mix up and i can not handle in componentWillRecieveProps.
this is my component which I want to call API in it:
import React, { Component } from 'react';
import { Alert,View,Text,Button } from 'react-native';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import { generateOTP,getAnother } from '../Actions'
class Splash extends Component {
constructor(props) {
super(props);
this.onPress=this.onPress.bind(this);
this.onClick=this.onClick.bind(this);
}
componentWillReceiveProps(nextProps) {
if (nextProps !== this.props) {
if( nextProps.api.status === 'api_success' && nextProps.api.error===null) {
Alert.alert("yes successfully take data");
}
if(nextProps.myApp.status === 'success' && nextProps.myApp.error === null){
Alert.alert("you call second api");
}
}
}
onClick(){
this.props.dispatch(getAnother());
}
onPress(){
this.props.dispatch(generateOTP());
}
render() {
return (
<View>
<Button title="Click me first" onPress={this.onPress}/>
<Text>Extraa space</Text>
<Text>Extraa space</Text>
<Button title="Click me" onPress={this.onClick}/>
</View>
);
}
}
export default connect(state => state)(Splash)
To tell you exactly when i click on Click me button both condition inside this if (nextProps !== this.props) block will be true and trigger Alert to me.
api-reducer.js:
const initialState = {
data: {},
error: null,
status: null
};
export default function reducer(state = initialState, action) {
switch (action.type) {
case "GENERATE_OTP_PENDING":
// Action is pending (request is in progress)
return {...state, status: 'fetching'}
case "GENERATE_OTP_FULFILLED":
// Action is fulfilled (request is successful/promise resolved)
return {
...state,
error: null,
data: action.payload.data,
status: 'api_success'
}
case "GENERATE_OTP_REJECTED":
// Action is rejected (request failed/promise rejected)
return {
...state,
error: action.payload,
status: 'api_error'
}
default:
return state;
}
};
tagreducer.js:
const initialState = {
data: {},
error: null,
status: null
};
export default function reducer(state = initialState, action) {
switch (action.type) {
case "GET_INFORMATION_PENDING":
// Action is pending (request is in progress)
return {...state, status: 'fetching'}
case "GET_INFORMATION_FULFILLED":
// Action is fulfilled (request is successful/promise resolved)
return {
...state,
error: null,
data: action.payload.data,
status: 'success'
}
case "GET_INFORMATION_REJECTED":
// Action is rejected (request failed/promise rejected)
return {
...state,
error: action.payload,
status: 'error'
}
default:
return state;
}
};
this is the way I index or combine both recuder:
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import api from './api-reducer';
import myApp from './tagreducer';
export default combineReducers({
api,
myApp,
routing: routerReducer,
});
The Actions.js:
import axios from 'axios';
import * as config from '../Config';
export const generateOTP = () => ({
type: 'GENERATE_OTP',
payload: axios({
method: 'get',
url: 'https://jsonplaceholder.typicode.com/posts',
headers: {"Accept": "application/json"}
})
});
export const getAnother = () => ({
type: 'GET_INFORMATION',
payload: axios({
method: 'get',
url: 'https://newsapi.org/v2/sources?apiKey=94da7332cf0a4b1ea685afc9a6829078',
headers: {"Accept": "application/json"}
})
});
and finally my Store.js:
import { applyMiddleware, createStore } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import thunk from 'redux-thunk';
import promiseMiddleware from 'redux-promise-middleware';
import logger from 'redux-logger'
import reducers from './Reducers';
export default function configureStore(history) {
const middleware = applyMiddleware(
promiseMiddleware(),
thunk,
logger,
routerMiddleware(history));
return createStore(reducers, {}, middleware);
}

Related

Why are my User.props always undefined? React-Native and Redux

I'm working with React-Native and Redux.
I need to access my User state on react redux after an action.
I try this with a simple console.log after the action on my Component (console.log(this.props.User)) but it is always undefined.
This is my Combine Reducer:
import { combineReducers } from 'redux'; import User from './userReducer';
export default combineReducers({
User });
This is my User Reducer:
import {
REGISTER_USER,
SIGNIN_USER } from '../types';
export default function(state=INITIAL_STATE, action){
switch(action.type){
case SIGNIN_USER:
return {
...state,
userData:{
uid: action.payload.localId || false,
token: action.payload.idToken || false,
refreshToken: action.payload.refreshToken || false,
}
};
break;
default:
return state
} }
This is my action:
export function signIn(data){
const request = axios({
method:'POST',
url:SIGNIN,
data:{
email: data.email,
password: data.password,
returnSecureToken:true
},
headers:{
"Content-Type":"application/json"
}
}).then(response => {
return response.data
}).catch( e=> {
console.log(e)
});
return {
type: SIGNIN_USER,
payload: request
}
}
Component where I try to get the state after action:
import { connect } from 'react-redux';
import { signIn } from '../Store/Actions/userActions';
import { bindActionCreators } from 'redux';
class LoginComponent extends React.Component {
state = {
email:'',
password:''
};
signInUser () {
try {
this.props.signIn(this.state).then( () => {
**console.log(this.props.User)**
})
} catch (error) {
console.log(error)
}
}
}
const mapStateToProps = (state) => {
return{
User: state.User
}
}
const mapDispatchToProps =( dispatch ) => {
return bindActionCreators({signIn}, dispatch)
}
export default connect(mapDispatchToProps,mapDispatchToProps)(LoginComponent);
The response on log just is: undefined
What is my mistake?? Thanks!
The order of the arguments that you are passing into connect method is wrong it expects mapStateToProps first and mapDispatchToProps as second
parameter. So your code has to be export default connect(mapStateToProps, mapDispatchToProps)(LoginComponent);
Here is the signature
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

Why is my props bringing back the action function, not the date?

I have a react app that is pulling down some data, It turns a promise so I am using Thunk. However when I log this.props, the getShift action prints out as a function.
The log returns:
{getShifts: ƒ}getShifts: ƒ ()proto: Object
Action:
import settings from '../../aws-config.js';
import Amplify, { Auth, API } from 'aws-amplify';
export const GET_STAFF_SHIFTS = 'get_staff_shifts';
export const SHIFTS_LOAD_FAIL = 'shifts_load_fail';
export const getShifts = () => dispatch => {
console.log('Fetching list of shifts for user...');
const request = API.get("StaffAPI", "/shifts", {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
})
.then(response =>
dispatch({
type: 'GET_STAFF_SHIFTS',
payload: response
})
)
.catch(err =>
dispatch({type: 'SHIFTS_LOAD_FAIL'})
)
}
reducer:
import { getShifts, GET_STAFF_SHIFTS} from '../actions';
export default function(state = {}, action) {
switch(action.type){
case GET_STAFF_SHIFTS:
return Object.assign({}, state,{
start_time: action.payload
})
default:
return state;
}
}
Action:
import React, { Component } from 'react';
import Amplify, { Auth, API } from 'aws-amplify';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getShifts} from '../actions/index';
import settings from '../../aws-config.js';
Amplify.configure(settings);
class StaffRota extends Component {
componentWillMount() {
this.props.getShifts();
}
renderPosts(){
console.log(this.props);
return (
<div></div>
);
}
}
function MapDispatchToProps(dispatch) {
return bindActionCreators({ getShifts }, dispatch);
}
export default connect(null, MapDispatchToProps)(StaffRota);
The action creator is supposed to be a function, which is expected. So console logging this.props.getShifts will give you a function.
There are two issues here:
first thing you are dispatching the wrong action type
dispatch({type: 'GET_STAFF_SHIFTS', ... }) instead of dispatch({type: GET_STAFF_SHIFTS, ... }) which you are expecting in your reducer.
secondly you ought to use the redux state via a mapStateToProps function
function MapDispatchToProps(dispatch) {
return bindActionCreators({ getShifts }, dispatch);
}
function MapStateToProps(state) {
return {
shift: state.start_time OR state.your_reducer.start_time
}
}
export default connect(MapStateToProps, MapDispatchToProps)(StaffRota);
And use this state (that is mapped to prop) via this.props.shift.

React + Redux binded state has no data

After one of the Redux tutorials decided to implement that facny action->reducer->store->view chain for simple app with only login part.
Seems like all setted up but when I run my app - in mapStateToProps(currentState) no any sign of the any custom state fields which I expected to see! (default state from reducer). But the action function is fine, as you can see on the screenshot
I can't see whats wrong here so, decided to ask it.
So here is the code
So, first of all - store.js
import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers';
import thunk from 'redux-thunk';
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState, applyMiddleware(thunk));
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers');
store.replaceReducer(nextRootReducer);
});
}
return store;
}
then login reducer
const initialState = {
user: {
name: '',
password: ''
},
fetching: false
}
export default function login(state = initialState, action) {
switch (action.type) {
case LOGIN_REQUEST: {
return { ...state, fetching: true }
}
case LOGIN_SUCCESS: {
return { ...state, user: action.data, fetching: false }
}
case LOGIN_FAIL: {
return { ...state, user: -1, fetching: false }
}
default: {
return state;
}
}
}
and the root (reducers/index.js):
import login from './login/login';
import { combineReducers } from 'redux'
export default combineReducers({
login
});
the action
import {
LOGIN_REQUEST,
LOGIN_SUCCESS,
LOGIN_FAIL
} from '../../constants/login.js';
export function onLoginAttempt(userData) {
return (dispatch) => {
dispatch({
type: LOGIN_REQUEST,
user: userData
})
tryLogin(userData);
}
};
function tryLogin(userData) {
let url = 'SignIn/Login ';
return (dispatch) => {
axios.post(url, userData)
.then((response) => dispatch({
type: LOGIN_SUCCESS,
data: response.data
})).error((response) => dispatch({
type: LOGIN_FAIL,
error: response.error
}))
}
};
So here is entrance point:
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './containers/app.js';
import configureStore from './store/configureStore';
let store = createStore(configureStore);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("content")
);
and here is the app.js (Login is just sompe custom div with two fields nothing special)
import React, { Component } from 'react';
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux';
import Login from '../components/login/Login';
import * as pageActions from '../actions/login/login'
class App extends Component {
render() {
const { user, fetching } = this.props;
const { onLoginAttempt } = this.props.pageActions;
return <div>
<Login name={user.name} password={user.password} fetching={fetching} onLoginAttempt={onLoginAttempt} />
</div>
}
}
function mapStateToProps(currentState) {
return {
user: currentState.user,
fetching: currentState.fetching
}
}
function mapDispatchToProps(dispatch) {
return {
pageActions: bindActionCreators(pageActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
I see that you have state which looks like:
reduxState = {
login: {
user: {
name: '',
password: ''
},
fetching: false
}
}
but then you try to access properties that don't exist.
function mapStateToProps(currentState) {
return {
user: currentState.user,
fetching: currentState.fetching
}
}
I think you need to:
function mapStateToProps(currentState) {
return {
user: currentState.login.user,
fetching: currentState.login.fetching
}
}
You have this line let store = createStore(configureStore); on your entry point. However, inside configureStore, you have a call to createStore()
Basically you're calling something like createStore(createStore(reducers)). That's probably the cause of the problem.
You should probably call it like
let store = configureStore( /* pass the initial state */ )

React Native: Possible Unhandled Promise Rejection (id: 0)

so i'm trying to learn more about Redux through React-Native.
i'm trying to issue a HTTPS request with Axios to pull data from a web-server. everything runs fine, i console log the action and the payload is the data i need. but it still throws a 'Cannot read property 'data' of null' TypeError.
the following is my code:
import React, { Component } from 'react';
import ReduxThunk from 'redux-thunk';
import { StackNavigator } from 'react-navigation';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import reducers from '../reducers';
import ReduxTestObj from '../components/ReduxTestObj';
export default class ReduxTest extends Component {
render() {
return (
<Provider store={createStore(reducers, {}, applyMiddleware(ReduxThunk))}>
<ReduxTestObj />
</Provider>
);
}
}
Here is the ReduxTestObj
import React, { Component } from 'react';
import _ from 'lodash';
import { View } from 'react-native';
import { connect } from 'react-redux';
import DevBanner from './DevBanner';
import Header from './Header';
import { actionCreator } from '../actions';
class ReduxTestObj extends Component {
componentWillMount() {
this.props.actionCreator('urlIWantToGoTo');
}
getBannerText() {
if (this.props.loading) {
return 'PULLING DATA. GIVE ME A SECOND';
}
const rObj = _.map(this.state.data[1].recipient_list);
const rName = [];
for (let i = 0; i < rObj.length; i++) {
rName[i] = rObj[i].team_key.substring(3);
}
const winnerMSG = `Teams ${rName[0]}, ${rName[1]}, ${rName[2]}, ${rName[3]} Won`;
return winnerMSG;
}
render() {
return (
<View>
<Header
text={'Redux Testing'}
/>
<DevBanner
message={this.getBannerText()}
/>
</View>
);
}
}
const mapStateToProps = state => {
return {
data: state.tba.data,
loading: state.tba.loading
};
};
export default connect(mapStateToProps, { actionCreator })(ReduxTestObj);
Here is the Action Creator
import axios from 'axios';
import { TBA_PULL } from './Types';
export const actionCreator = (url) => {
return (dispatch) => {
axios({
method: 'get',
url,
responseType: 'json',
headers: {
'Auth-Key':
'Hidden For Obvious Reasons'
},
baseURL: 'theBaseUrlOfTheWebsite'
}).then(response => {
dispatch({ type: TBA_PULL, payload: response.data });
});
};
};
And Here is the Reducer
import { TBA_PULL } from '../actions/Types';
const INITIAL_STATE = { data: [], loading: true };
export default (state = INITIAL_STATE, action) => {
console.log(action);
switch (action.type) {
case TBA_PULL:
return { ...state, loading: false, data: action.payload };
default:
return state;
}
};
As stated above, the console.log(action) prints out and the data it has is correct. yet it continues to throw the following error:
Error1
I've tried changing things around, googling, searching, gutting out the Action Reducer to make it as basic as possible by just returning a string. and it refuses to work.
Has anyone run into an issue similar to this or know how to fix it?
Thank you for your time.
EDIT: i also console logged 'this' as the first line of getBannerText() in ReduxTestObj and it returned successfully this.props.data as the data i want and this.props.loading as false. Yet it still throws the same error.
Welp. I guess i was just making a mistake.
i was calling This.state.data instead of this.props.data in getBannerText().
that solved the issue.

Multiple dispatch in componentWillMount React

I have two methods in two different actions,
import { fetchCategories } from "../../../actions/elementsActions"
import { fetchPlaces } from "../../../actions/placesActions"
And the componentWillMount method is:
componentWillMount() {
this.props.dispatch(fetchCategories())
this.props.dispatch(fetchPlaces())
}
I want to make sure fetchCategories is fetched before fetchPlaces. Is this the right way of doing it?
UPDATE
Actions:
import axios from "axios";
export function fetchPlaces() {
return function(dispatch) {
axios.get("/getPlaces")
.then((response) => {
console.log(response);
dispatch({type: "FETCH_PLACES_FULFILLED", payload: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_PLACES_REJECTED", payload: err})
})
}
}
Reducer :
export default function reducer(
state={
places: [],
fetching: false,
fetched: false,
error: null,
}, action) {
switch (action.type) {
case "FETCH_PLACES": {
return {...state, fetching: true}
}
case "FETCH_PLACES_REJECTED": {
return {...state, fetching: false, error: action.payload}
}
case "FETCH_PLACES_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
places: action.payload,
}
}
}
return state
}
Store :
import { applyMiddleware, createStore } from "redux"
import logger from "redux-logger"
import thunk from "redux-thunk"
import promise from "redux-promise-middleware"
import reducer from "./reducers"
const middleware = applyMiddleware(promise(), thunk, logger())
export default createStore(reducer, middleware)
Dispatch is synchronous, but this will only guarantee that fetchCategories is fired (not fetched) before fetchPlaces. You need to remove this.props.dispatch(fetchPlaces()) from componentWillMount() and add it inside the then((response) of fetchPlaces() to guarantee that it fires after a successful fetch of fetchPlaces().

Categories