My current React Native Expo app has a ScrollView that implements RefreshControl. A user pulling down the ScrollView will cause the onRefresh function to be executed, which in turns call an action creator getSpotPrices that queries an API using axios.
Problem: If there is a network problem, the axios.get() function will take very long to time out. Thus, there is a need to implement the timing out of either axios.get() or onRefresh.
How can we implement a timeout function into RefreshControl?
/src/containers/main.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { ScrollView, RefreshControl } from 'react-native';
import MyList from '../components/MyList';
import { getSpotPrices } from '../actions';
class RefreshableList extends Component {
onRefresh = () => {
this.props.getSpotPrices();
}
render() {
return (
<ScrollView
refreshControl={
<RefreshControl
refreshing={this.props.isLoading}
onRefresh={this._onRefresh}
/>
}>
<MyList />
</ScrollView>
)
}
}
const mapStateToProps = (state) => {
return {
isLoading: state.currencies.isLoading,
}
}
const mapDispatchToProps = (dispatch) => {
return {
getSpotPrices: () => dispatch(getSpotPrices()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(RefreshableList);
/src/actions/index.js
import api from "../utils/api";
import * as types from "../types";
import Axios from "axios";
const getSpotPrice = async () => {
try {
const res = await Axios.get(`https://api.coinbase.com/v2/prices/spot`);
return parseFloat(res.data.data.amount);
} catch (err) {
throw new Error(err);
}
};
export const getSpotPrices = () => async dispatch => {
try {
const price = await getSpotPrice();
dispatch({
type: types.CURRENCIES_SET,
payload: price
});
} catch (err) {
dispatch({
type: types.CURRENCIES_FAILED_FETCH,
payload: err.toString()
});
} finally {
dispatch({
type: types.CURRENCIES_IS_LOADING,
payload: false
})
}
};
/src/reducers/currencies.js
import * as types from "../types";
const initialState = {
data: {},
isLoading: false,
};
export default (state = initialState, { type, payload }) => {
switch (type) {
case types.CURRENCIES_SET:
return {
...state,
data: payload,
error: "",
isLoading: false
};
case types.CURRENCIES_FAILED_FETCH:
return {
...state,
error: payload,
isLoading: false
};
case types.CURRENCIES_IS_LOADING:
return {
isLoading: payload
}
default:
return state;
}
};
Check if user is connected internet or not using the react-native-netinfo library
NetInfo.fetch().then(state => {
console.log("Connection type", state.type);
console.log("Is connected?", state.isConnected);
this.setState({ connected: state.isConnected });
});
// Subscribe
const unsubscribe = NetInfo.addEventListener(state => {
console.log("Connection type", state.type);
this.setState({ connected: state.isConnected });
});
// Unsubscribe
unsubscribe(); <- do this in componentwillunmount
Its generally a good practice to add a timeout, in all your api calls, in axios you can easily add a timeout option like:
await axios.get(url, { headers, timeout: 5000 })
so in your case modify the axios call as
await Axios.get(https://api.coinbase.com/v2/prices/spot, { timeout: 5000 } );
I have put timeout of 5 seconds you can modify the parameter according to your need.
Related
I am struggling with an issue with the custom fetch hook.Simply i am trying to test my fetch hook if the data already fetched the hook needs to get data from cache instead of api.
The test case fails and looks like caching mechanism not working, but if i try on the browser with manual prop change caching mechanism works properly.
import { render, waitFor } from "#testing-library/react";
const renderList = (filterParams = testFilterParamsList[0]) =>
render(<List filterParams={filterParams} />);
it("should re-render without fetch", async () => {
const { rerender } = renderList(testFilterParamsList[0]);
rerender(<List filterParams={testFilterParamsList[1]} />);
expect(window.fetch).toHaveBeenCalledTimes(1);
});
// useFetch.js
import {useEffect, useReducer} from "react";
const cache = {};
const FETCH_REQUEST = "FETCH_REQUEST";
const FETCH_SUCCESS = "FETCH_SUCCESS";
const FETCH_ERROR = "FETCH_SUCCESS";
const INITIAL_STATE = {
isPending: false,
error: null,
data: [],
};
const useFetch = ({url, filterOptions}) => {
const [state, dispatch] = useReducer((state, action) => {
switch (action.type) {
case FETCH_REQUEST:return {...INITIAL_STATE, isPending: true};
case FETCH_SUCCESS: return {...INITIAL_STATE, isPending: false, data: action.payload};
case FETCH_ERROR: return {...INITIAL_STATE, isPending: false, error: action.payload};
default: return state;
}
}, INITIAL_STATE);
useEffect(() => {
const fetchData = async () => {
dispatch({type: FETCH_REQUEST});
if (cache[url]) {
const data = cache[url];
dispatch({type: FETCH_SUCCESS, payload: data});
} else {
try {
const response = await window.fetch(url);
let data = await response.json();
cache[url] = data
dispatch({type: FETCH_SUCCESS, payload: data});
} catch (err) {
dispatch({type: FETCH_ERROR, payload: err});
}
}
};
fetchData();
}, [filterOptions, url]);
return state;
};
export default useFetch;
// List.js
import useFetch from "../hooks/useFetch";
export const RocketsList = ({ filterParams }) => {
const { isPending, error, data } = useFetch({
url: "https://api.spacexdata.com/v3/launches/past",
name:filterParams.name,
});
return (
<div>
Doesn't matter
</div>
);
};
I'm facing weird behavior while fetching the records in redux saga. When I try to call an action in useeffect from the class, it is being called in loop multiple times. For that reason, there are infinite api calls.
Can anybody tell me where i'm wrong please? Following is my code.
Reducer
// #flow
import {
FETCH_DOCTOR_PROFILE,
FETCH_DOCTOR_PROFILE_SUCCESS,
FETCH_DOCTOR_PROFILE_ERROR
} from "./actionTypes";
const INIT_STATE = {
error: null,
isLoading: false,
doctorProfile: {}
};
const DoctorProfileReducer = (state = INIT_STATE, action) => {
switch (action.type) {
case FETCH_DOCTOR_PROFILE:
return {
...state,
isLoading: true
};
case FETCH_DOCTOR_PROFILE_SUCCESS:
return {
...state,
isLoading: false,
doctorProfile: action.payload
};
case FETCH_DOCTOR_PROFILE_ERROR:
return {
...state,
isLoading: false,
error: action.error
};
default:
return state;
}
};
export default DoctorProfileReducer;
Action
import {
FETCH_DOCTOR_PROFILE,
FETCH_DOCTOR_PROFILE_SUCCESS,
FETCH_DOCTOR_PROFILE_ERROR
} from "./actionTypes";
export const fetchDoctorProfileAction= () => ({
type: FETCH_DOCTOR_PROFILE
});
export const fetchDoctorProfileSuccessAction= (doctorProfile) => ({
type: FETCH_DOCTOR_PROFILE_SUCCESS,
payload: doctorProfile
});
export const fetchDoctorProfileErrorAction= (error) => ({
type: FETCH_DOCTOR_PROFILE_ERROR,
error: error
});
Saga
import { takeEvery, fork, put, all, call } from 'redux-saga/effects';
// Redux States
import { FETCH_DOCTOR_PROFILE } from './actionTypes';
import { fetchDoctorProfileSuccessAction, fetchDoctorProfileErrorAction } from './actions';
import { fetchDoctorProfileApi } from '../../../services/doctorProfile';
import {FETCH_DOCTOR_PROFILE_URL} from '../../../helpers/urls';
function* fetchDoctorProfileSaga() {
try {
const response = yield call(fetchDoctorProfileApi,FETCH_DOCTOR_PROFILE_URL);
yield put(fetchDoctorProfileSuccessAction(response));
} catch (error) {
yield put(fetchDoctorProfileErrorAction(error));
}
}
export function* watchFetchDoctorProfile() {
yield takeEvery(FETCH_DOCTOR_PROFILE, fetchDoctorProfileSaga)
}
function* doctorProfileSaga() {
yield all([
fork(watchFetchDoctorProfile),
]);
}
export default doctorProfileSaga;
Calling page
useEffect(() => {
props.fetchDoctorProfileAction();
const result = props.doctorProfile;
});
...........
const mapStateToProps = (state) => {
const { error, doctorProfile, pending } = state.DoctorProfileReducer;
return { error , doctorProfile, pending };
}
export default withRouter(connect(mapStateToProps, {fetchDoctorProfileAction})(ProfessionalProfilePrimary));
I think you need to add a condition in your useEffect() hook:
useEffect(() => {
if (!props.doctorProfile && !props.pending) {
props.fetchDoctorProfileAction();
}
const result = props.doctorProfile;
}, [props.doctorProfile, props.pending]);
NOTE: in your component mapStateToProps you have pending but in your store you have isLoading; make sure you are correctly mapping the state to the prop ;)
I have a mern application using redux for state management.
For some reason when I try to map through it, it tells me it's not a function.
It is weird because when I see my props through the console, it shows me it's an array and react knows that I have data in my state. And it also shows the data in my redux dev tools. But when I try to render it gives me that error. Also when i do this.props.products.products it tells me cannot read property of Null.
Here's the github repo
https://github.com/bryanb213/seller
Can anyone explain why
stuff.jsx
import React, { Component } from 'react'
import './stuff.stle.css'
import { getProducts } from '../redux/actions/productActions';
import { connect } from 'react-redux';
class Stuff extends Component {
componentDidMount() {
this.props.getProducts();
}
render() {
console.log('Products from props', this.props)
if (this.props.loading === true) {
return (
<div>Loading...</div >
)
} else {
return(
<div>
{ this.props.products.map(p => (
<h1>{p.name}</h1>
))
}
</div>
)
}
}
}
const mapStateToProps = state => ({
//products from root reducer
products: state.products,
})
export default connect(mapStateToProps, { getProducts })(Stuff);
Action
// Get all products
export const getProducts = () => dispatch => {
axios
.get('http://localhost:5000/api/products/all')
.then(res =>
dispatch({
type: GET_PRODUCTS,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_PRODUCTS,
payload: null
})
);
};
Reducer
import { GET_PRODUCTS } from '../actions/types';
const initialState = {
products: null,
loading: true
}
export default function(state= initialState, action){
switch(action.type){
case GET_PRODUCTS:
console.log('hitting GET_PRODUCTS', action.payload)
return {
...state,
products: action.payload,
loading: false
}
default:
return state
}
}
Server route
router.get('/all', (req, res) => {
Product.find()
.exec()
.then(stuff => {
res.status(200).json(stuff);
})
.catch(err => {
console.log(err);
res.status(500).json({
error: err
});
});
});
postman result
render() {
console.log("Products from props", this.props);
const { loading, products } = this.props;
if (loading === true) {
return <div>Loading...</div>;
} else {
return (
<div>{products && products.products.map(p => <h1>{p.name}</h1>)}</div>
);
}
}
this.props.products value is { products : [..] }, so you have to access it by this.props.products.products, in cases like this it will be easier if you use destructring assignment syntax to get the respected values to avoid some confusion.
I want to get 20 posts by scroll down each time how can i do? my project have big Data and I use redux for get data, can I get data step by step? for example get 20 posts for first time and when a user scroll down load the next 20 posts.
I use React Hooks for develop
my posts component source is:
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import PostItem from './PostItem';
import { getPosts } from '../../actions/post';
const Posts = ({ getPosts, post: { posts, loading } }) => {
useEffect(() => {
getPosts();
}, [getPosts]);
return loading ? <Spinner /> : (
{posts.map(post => (
<PostItem key={post._id} post={post} />
))}
)
}
Posts.propTypes = {
getPosts: PropTypes.func.isRequired,
post: PropTypes.object.isRequired
}
const mapStateToProps = state => ({
post: state.post
});
export default connect(mapStateToProps, { getPosts })(Posts)
my action code is:
import { setAlert } from './alert';
import {
GET_POSTS,
POST_ERROR
} from "../actions/types";
// Get Posts
export const getPosts = () => async dispatch => {
try {
const res = await axios.get('/api/ads');
dispatch({
type: GET_POSTS,
payload: res.data
});
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { msg: err.response.satusText, status: err.response.satus }
});
}
}```
///////////////////////////////
///////////////AND REDUCER IS :
import {
GET_POSTS,
POST_ERROR
} from '../actions/types';
const initialState = {
posts: [],
loading: true,
error: {}
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_POSTS:
return {
...state,
posts: payload,
loading: false
}
case POST_ERROR:
return {
...state,
error: payload,
loading: false
}
default:
return state;
}
}
You can use react-infinite-scroller library. I've tried to change your Posts method, so maybe it would be useful.but as mentioned in comments you should add pagination to your API.
const Posts = ({ getPosts, post: { posts, loading } }) => {
useEffect(() => {
getPosts();
}, [getPosts]);
const itemsPerPage = 20;
const [hasMoreItems, sethasMoreItems] = useState(true);
const [records, setrecords] = useState(itemsPerPage);
const showItems=(posts)=> {
var items = [];
for (var i = 0; i < records; i++) {
items.push( <PostItem key={posts[i]._id} post={posts[i]} />);
}
return items;
}
const loadMore=()=> {
if (records === posts.length) {
sethasMoreItems(false);
} else {
setTimeout(() => {
setrecords(records + itemsPerPage);
}, 2000);
}
}
return <InfiniteScroll
loadMore={loadMore}
hasMore={hasMoreItems}
loader={<div className="loader"> Loading... </div>}
useWindow={false}
>
{showItems()}
</InfiniteScroll>{" "}
}
Working Codesandbox sample with fake data.
I am new to redux and I am having a hard time understanding how to connect the payload of my API call to my state.
Right now my action.js file looks like this:
import ApiService from '../../services/ApiService';
import { reset } from 'redux-form';
//actions
export const getStock = () => {
return {
type: 'GET_STOCK'
}
}
export const getStockPending = () => {
return {
type: 'GET_STOCK_PENDING'
}
}
export const getStockFulfilled = (stock) => {
return {
type: 'GET_STOCK_FULFILLED',
payload: stock
}
}
export const getStockRejected = () => {
return {
type: 'GET_STOCK_REJECTED'
}
}
// async function calls
export function fetchStocksWithRedux() {
const action_type = "GET_STOCK";
const stock = 'AAPL';
return (dispatch) => {
dispatch({type: `${action_type}_PENDING`});
return ApiService.get(`/search?query=${stock}`)
.then(([response, json]) =>{
if(response.status === 200){
dispatch(getStockFulfilled(json))
}
else{
dispatch(getStockRejected())
}
})
}
}
and my reducer.js file looks like this:
const initialState = {
inProgress: false,
stock: {},
stocks: ['NKE', 'AMZN', 'AAPL'],
error: {}
}
export default (state = initialState, action) => {
switch(action.type) {
case 'GET_STOCK_PENDING':
return {
...state,
inProgress: true,
error: false
}
case 'GET_STOCK_FULFILLED':
return {
...state,
stock: action.payload,
inProgress: false
}
case 'GET_STOCK_REJECTED':
return {
...state,
inProgress: false,
error: action.error
}
default:
return state;
}
}
When I go to call my method fetchStocksWithRedux in my component, the network tab in my dev tools shows a 200 status and the response I'm expecting, but the reducer dispatches the 'GET_STOCK_REJECTED' action, but the error hash is empty. What do you think is going wrong?
Here is my component, for reference:
import React, { Component } from 'react';
import { fetchStocksWithRedux } from '../../redux/modules/Stock/actions';
import { connect } from 'react-redux';
class Dashboard extends Component {
componentDidMount() {
this.props.fetchStocksWithRedux()
}
render() {
return (
<div className="uk-position-center">
</div>
)
}
}
export default connect(
state => ({
stocks: state.stocks,
stock: state.stock
})
, { fetchStocksWithRedux }
)(Dashboard);
Thanks. Any advice or guidance would be greatly appreciated!