Why does redux-persist not work right on ajax calls? - react-native

So I loaded data with axios in my action like this:
import * as types from './actionTypes';
import axios from 'axios';
export function fetchQuestions(city) {
return function (dispatch) {
axios.get('http://rest.learncode.academy/api/test123/tweets')
.then((response) => {
dispatch({type: "FETCH_QUESTIONS_SUCCESS", payload: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_QUESTIONS_FAILURE", payload: err})
})
}
};
And it loads into questions in my reducer:
const initialState = {
fetching: false,
fetched: false,
questions: [],
error: null,
}
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case types.FETCH_QUESTIONS_SUCCESS:
return {
...state,
questions: action.payload
};
case types.FETCH_QUESTIONS_FAILURE:
return {
...state,
error: action.payload
};
default:
return state;
}
}
I call it like this on the first page of my app in the constructor:
constructor(props) {
super(props);
if(!this.props.questions){
this.props.actions.fetchQuestions();
}
}
So with that I want to only use it when questions is empty.
On another page when I do console.log(questions) it tells me questions is empty. While I did load it before so it should still be loaded since I am using redux-persist.
When I click a button on that page and console.log(questions) again it does show the data.

Related

Invariant Violation : Invalid Hooks Call. Hooks Call can only be made inside body of function components

Hi am getting this error at Connect() function in LoginScreen.js. On commenting it its working fine. I am guessing either my Store is not properly setup or i am not able to connect LoginScreen Component to Redux Store.
LoginScreen.js
import React, { Component } from "react";
import PhoneInput from "react-native-phone-input";
import { connect } from "react-redux";
import { View, StatusBar } from "react-native";
import { Container, Item, Input, Button, Text } from "native-base";
import {
phoneChanged,
codeChanged,
onCodeDispatched,
onPhoneLogin,
clearAuth,
onSignOut
} from "../Actions/AuthActions";
//import firebase from "react-native-firebase";
import { auth } from "../Config/firebase";
export class LoginScreen extends Component {
}
export default connect(
null, // passing null just for testing
null
)(LoginScreen);
Store.js
import ReduxThunk from "redux-thunk";
import { createStore, applyMiddleware, compose } from "redux";
import reducer from "../Reducers/index";
let composeEnhancers = compose;
/* eslint no-undef: 0 */
if (__DEV__) {
/* eslint no-underscore-dangle: 0 */
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
}
const store = createStore(
reducer,
{},
composeEnhancers(applyMiddleware(ReduxThunk))
);
export { store };
AuthReducer.js
import {
LOGIN_FAIL,
LOGIN_SUCCESS,
LOGIN_USER,
PHONE_CHANGED,
CODE_SENT_ERROR,
CODE_CHANGED,
CODE_DISPATCHED,
LOGIN_USER_PHONE,
CODE_SENT,
CODE_NOT_CONFIRMED,
LOGOUT,
SET_USER_OBJECT,
CLEAR_AUTH
} from "../Actions/ActionTypes";
const INITIAL_STATE = {
phone: "+30",
user: null,
message: "",
loading: false,
codeInput: "",
confirmResult: null
};
const AuthReducer = (state = INITIAL_STATE, action) => {
console.log(action);
switch (action.type) {
case PHONE_CHANGED:
return {
...state,
phone: action.payload
};
case CODE_CHANGED:
return {
...state,
codeInput: action.payload
};
case LOGIN_USER:
return {
...state,
loading: true,
message: ""
};
case LOGIN_USER_PHONE:
return {
...state,
loading: true,
message: "Sending code...",
phone: action.payload
};
case CODE_DISPATCHED:
return {
...state,
loading: true,
message: ""
};
case CODE_SENT:
return {
...state,
loading: true,
message: "Code has been sent!",
confirmResult: action.payload
};
case CODE_SENT_ERROR:
return {
...state,
loading: false,
message: `Sign in with Phone number error: ${action.payload}`,
confirmResult: null
};
case SET_USER_OBJECT:
return {
...state,
user: action.payload
};
case CODE_NOT_CONFIRMED:
return {
...state,
message: `Code confirmation error: ${action.payload}`
};
case LOGIN_SUCCESS:
return {
...INITIAL_STATE,
user: action.payload,
message: "login Success"
};
case LOGIN_FAIL:
return {
...state,
message: "Authentication Failed.",
loading: false,
password: "",
phone: "+91"
};
case LOGOUT:
return {
...state,
message: "",
user: null
};
case CLEAR_AUTH:
return {
...state,
...INITIAL_STATE
};
default:
return state;
}
};
export default AuthReducer;
Root.js
import React from "react";
import { Provider } from "react-redux";
import { Navigator } from "./Navigation/Index";
import { store } from "./Store/Index";
export default class Root extends React.Component {
render() {
return (
<Provider store={store}>
<Navigator />
</Provider>
);
}
}
AuthActions.js
//import { firebase } from "react-native-firebase";
import * as actionTypes from "./ActionTypes";
import { auth } from "../Config/firebase";
const phoneChanged = text => {
return {
type: actionTypes.PHONE_CHANGED,
payload: text
};
};
const onLoginSuccess = (dispatch, user) => {
dispatch({
type: actionTypes.LOGIN_SUCCESS,
payload: user
});
};
const signOut = dispatch => {
dispatch({
type: actionTypes.LOGOUT
});
};
const onPhoneLogin = phone => {
return dispatch => {
dispatch({
type: actionTypes.LOGIN_USER_PHONE
});
auth
.signInWithPhoneNumber(phone)
// sign in success
.then(confirmResult => {
onCodeSent(dispatch, confirmResult);
})
// sign in error
.catch(error => onCodeSentError(dispatch, error));
};
};
const codeChanged = text => {
return {
type: actionTypes.CODE_CHANGED,
payload: text
};
};
const onCodeSent = (dispatch, confirmResult) => {
dispatch({
type: actionTypes.CODE_SENT,
payload: confirmResult
});
};
const onCodeConfirmError = (dispatch, error) => {
dispatch({
type: actionTypes.CODE_NOT_CONFIRMED,
payload: error
});
};
const onCodeDispatched = code => {
return (dispatch, getState) => {
getState()
.auth.confirmResult.confirm(code)
.then(user => onLoginSuccess(dispatch, user))
.catch(error => onCodeConfirmError(dispatch, error));
};
};
const onCodeSentError = (dispatch, error) => {
dispatch({
type: actionTypes.CODE_SENT_ERROR,
payload: error
});
};
const onSignOut = () => {
return dispatch => {
auth
.signOut()
.then(() => signOut(dispatch))
.catch(error => console.log(error));
};
};
const clearAuth = () => {
return dispatch => {
dispatch({
type: actionTypes.CLEAR_AUTH
});
};
};
export {
onSignOut,
clearAuth,
codeChanged,
onPhoneLogin,
phoneChanged,
onCodeDispatched
};
The Idea is basically to call LoginScreen which is part of 'Auth' StackNavigator and Render PhoneNumberInput and OTP.
React-redux (> 7.0.1) uses hook & React-native (< 0.59) doesn’t support Hooks yet.
You can run npm ls react-native in your application folder to check which version you’re using.
If you find more than one of them, this might also create problems. more on it
There are two solution
Upgrade the react native version to 0.59.0
OR
Downgrade the react redux version to 6.0.1
I hope it help you.

Use a global alert across the react-native app

I'm a beginner for react-native and I need to alert to the user based on a status which will be retrieved from an API in every 15 seconds. For this I'm using react-native-background-timer in my main component to call the service. But when app is in some other screen (component) even though the service executes perfectly in the main component, it doesn't update it's props or status depending on the result it received (I guess this should be because I'm in a some other screen and props of main component will not be updated). Due to that alert will not be triggered if app is not in the main component
Can anyone please suggest me an approach for this?
class Home extends Component{
constructor(props){
super(props)
this._onPopUpShowed = this._onPopUpShowed.bind(this)
}
componentDidMount(){
//Initial call after the launch
this.props.fetchLiveOrderData()
//Start timer for polling
const intervalId = BackgroundTimer.setInterval(() => {
isBackgroudLoad=true
this.props.fetchLiveOrderData()
}, 1000*15);
}
render(){
const{payload,isFetching,isError,isSuccess} = this.props.liveOrderData
return(
//Render UI depending on the data fetched
);
}
}
//map state to props
const mapStateToProps = state => {
return {
liveOrderData: state.liveOrderData
}
}
//map dispatch to props
const mapDispatchToProps = dispatch => {
return {
fetchLiveOrderData : () => dispatch(fetchLiveOrderData())
}
}
export default connect(mapStateToProps, mapDispatchToProps) (Home)
liveOrderReducer.js
import {
FETCHING_LIVE_ORDER_DATA, FETCHING_LIVE_ORDER_DATA_SUCCESS, FETCHING_LIVE_ORDER_DATA_ERROR
} from '../constants'
const initialState = {
payload: [],
msg:[],
isFetching: true,
isError: false,
isSuccess: false
}
export default liveOrderReducer = (state = initialState, action) => {
switch(action.type){
case FETCHING_LIVE_ORDER_DATA :
return {
...state,
payload: [],
msg:[],
isFetching: true,
isError: false,
isSuccess: false
}
case FETCHING_LIVE_ORDER_DATA_SUCCESS :
return {
...state,
payload: action.data,
msg:[],
isFetching: false,
isError: false,
isSuccess:true
}
case FETCHING_LIVE_ORDER_DATA_ERROR :
return {
...state,
payload: [],
msg:action.msg,
isFetching: false,
isError: true,
isSuccess:false
}
default:
return state
}
}
index.js
import {
FETCHING_LIVE_ORDER_DATA, FETCHING_LIVE_ORDER_DATA_SUCCESS, FETCHING_LIVE_ORDER_DATA_ERROR
} from '../constants'
import api from '../lib/api'
export const getLiveOrderData = () => {
return {
type : FETCHING_LIVE_ORDER_DATA
}
}
export const getLiveOrderDataSuccess = data => {
return {
type : FETCHING_LIVE_ORDER_DATA_SUCCESS,
data
}
}
export const getLiveOrderDataFailure = () => {
return {
type : FETCHING_LIVE_ORDER_DATA_ERROR
}
}
export const fetchLiveOrderData = () => {
return(dispatch) => {
dispatch(getLiveOrderData())
api.getOrder().then(resp => {
dispatch(getLiveOrderDataSuccess(resp))
}).catch((err) => {
dispatch(getLiveOrderDataFailure(err))
})
}
}
Move the notification code to the container or the root component. This will ensure you will receive notifications even if the user moved away from the home screen.

React native redux props not updated after calling an action

I'm new to react, react native, and redux, I have a react native app that has this login in render
render() {
return (<TouchableOpacity style={Style.btnSubmit} onPress={this.onLoginPressed.bind(this)}>
<Text style={Style.btnText}>Login</Text>
</TouchableOpacity>)
}
and the onLoginPressed function is here
onLoginPressed() {
const { username, password } = this.props.form;
this.props.login({ username, password}); // the login is using the fetch api
// props are not updated with the new state values
console.log(this.props)
}
Everything is working correctly but
the props doesn't update in the onLoginPressed function, however, when I console log the props inside the render function, it's updated.
I understand that redux do a full rendering, but I just don't really understand if it should update the props after calling the login.
Thank you
Update
Here is the end of the component
function mapStateToProps(state) {
return {
...state.login
}
}
function mapDispatchToProps(dispatch) {
return {
login: (formData) => dispatch(login(formData)),
facebookLogin: (formData) => dispatch(facebookLogin(formData)),
setUsername: (username) => dispatch(setUsername(username)),
setPassword: (password) => dispatch(setPassword(password)),
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Login);
here is the action
import { Host, Endpoints } from '../config/server';
import { loginActions } from '../config/constants';
/*
* state props
- form
- inProgress
- error
- data
*/
export function login(form) {
return (dispatch) => {
dispatch(loggingIn(true));
fetch(Host + Endpoints.auth.login, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(form)
})
.then(res => res.json())
.then(res => {
dispatch(loggingIn(false));
res.error ? dispatch(loginError(res.error)) :
dispatch(loginSuccess(res.data));
})
.catch(err => dispatch(loginError(err)));
}
}
export function facebookLogin(data) {
return (dispatch) => {
dispatch(loggingIn());
fetch(Host + Endpoints.auth.facebookLogin, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(data)
})
.then(res => res.json())
.then(data => dispatch(loginSuccess(data)))
.catch(err => dispatch(loginError(err)));
}
}
export function setUsername(username) {
return {
type: loginActions.setUsername,
username
}
}
export function setPassword(password) {
return {
type: loginActions.setPassword,
password
}
}
function loginSuccess(data) {
return {
type: loginActions.LoginSuccess,
data
}
}
function loginError(error) {
return {
type: loginActions.LoginError,
error
}
}
function loggingIn(val) {
return {
type: loginActions.LoggingIn,
inProgress: val
}
}
and here is the reducer
import { loginActions } from '../config/constants';
const initialState = {
form: {
username: '',
password: ''
},
data: null,
inProgress: false,
error: null
};
export default function loginReducer(state = initialState, action) {
switch(action.type) {
case loginActions.LoggingIn:
return {
...state,
inProgress: action.inProgress
}
case loginActions.LoginError:
return {
...state,
error: action.error,
}
case loginActions.LoginSuccess:
return {
...state,
inProgress: false,
error: null,
data: action.data
}
case loginActions.setUsername:
return {
...state,
form: {
username: action.username,
password: state.form.password
}
}
case loginActions.setPassword:
return {
...state,
form: {
username: state.form.username,
password: action.password
}
}
default:
return {
...state
}
}
}
and the reducer index file
import { combineReducers } from 'redux';
import login from './login';
const rootReducer = combineReducers({
login
});
export default rootReducer;
and the configureStore file
import { createStore, applyMiddleware } from 'redux'
import reducers from './reducers'
import thunk from 'redux-thunk'
export default function configureStore() {
let store = createStore(reducers, applyMiddleware(thunk))
return store
}
of course the root is wrapped with the provider passing the store.
You are doing console.log in the same function call that dispatch the login actions. That won’t work, because JavaScript is non-blocking, it will not wait for the login action to complete and update the props before calling console.log
Try console.log in something like componentWillReceiveProps.

How to make external api calls in redux react

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

Redux fetch data from api

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.