I want to add/change a redux state when the data is received from the backend. This state controls a loading spinner.
The code below is what I thought that should work.
What am I missing?
CouriersActions.js
import axios from "axios";
import { toastOnError } from "../../utils/Utils";
import { GET_COURIERS, ADD_STATE_LOADING } from "./CouriersTypes";
export const addStateLoading = (state_loading) => ({
type: ADD_STATE_LOADING,
state_loading,
});
export const getCouriers = () => dispatch => {
var tempx = {show: true};
addStateLoading(tempx);
axios
.get("/api/v1/couriers/")
.then(response => {
dispatch({
type: GET_COURIERS,
payload: response.data
});
var tempx = {show: false};
addStateLoading(tempx);
})
.catch(error => {
toastOnError(error);
});
};
A simple way of solving this kind issue, create custom hook for all services and where ever you need it.
export const useCouriers = () => {
const dispatch = useDispatch();
const getCouriers = async () => {
try {
dispatch(addStateLoading({ show: true }));
const response = await axios.get("/api/v1/couriers/");
dispatch({
type: GET_COURIERS,
payload: response.data,
// I think this should be response.data.data
});
} catch (error) {
toastOnError(error);
} finally {
dispatch(addStateLoading({ show: false }));
}
};
return { getCouriers };
};
Inside component
const { getCouriers } = useCouriers();
// call where you need
If you want to use redux, check redux-toolkit, it helps a lot the development with redux.
https://redux-toolkit.js.org/
#rahul-sharma answer helped me to find this answer. I just called addStateLoading inside of dispatch.
CouriersActions.js
import axios from "axios";
import { toastOnError } from "../../utils/Utils";
import { GET_COURIERS, ADD_STATE_LOADING } from "./CouriersTypes";
export const addStateLoading = (state_loading) => ({
type: ADD_STATE_LOADING,
state_loading,
});
export const getCouriers = () => dispatch => {
var tempx = {show: true};
addStateLoading(tempx);
axios
.get("/api/v1/couriers/")
.then(response => {
dispatch({
type: GET_COURIERS,
payload: response.data
});
dispatch(addStateLoading({ show: false }));
})
.catch(error => {
toastOnError(error);
});
};
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 making a movie and I want to add pagination in the MoviesFromGenre component. Initially, MoviesFromGenre gets rendered based on the id from GenresList.
I want to add Pagination but I don't know how to update the state in useReducer when I click next/prev buttons?
import React, { useEffect, useReducer, useState } from 'react';
import { useParams } from 'react-router-dom'
import axios from 'axios';
import { API_URL, API_KEY } from '../api/config.js';
import Movies from './Movies'
const initialState = {
loading: true,
error: '',
movies: []
};
const reducer = (state, action ) => {
switch(action.type) {
case 'FETCH_SUCCESS':
return {
loading: false,
movies: action.payload,
error: '',
};
case 'FETCH_ERROR':
return {
loading: false,
movies: [],
error: 'Error'
};
default:
return state;
}
};
function MoviesFromGenre () {
const [state, dispatch] = useReducer(reducer, initialState);
const { id } = useParams();
const [pageNumber, setPageNumber] = useState(1)
useEffect(() => {
axios
.get(
`${API_URL}discover/movie?api_key=${API_KEY}&language=en-US&with_genres=${id}`
)
.then(response => {
dispatch({
type: 'FETCH_SUCCESS',
payload: response.data
})
})
.catch(err => {
dispatch({
type: 'FETCH_ERROR'
})
})
}, [])
const nextPage = () => {
axios
.get(
`${API_URL}discover/movie?api_key=${API_KEY}&language=en-US&with_genres=${id}&page=${pageNumber}`
)
.then(response => {
console.log(response.data)
})
setPageNumber(pageNumber+1)
}
const prevPage = () => {
axios
.get(
`${API_URL}discover/movie?api_key=${API_KEY}&language=en-US&with_genres=${id}&page=${pageNumber}`
)
.then(response => {
console.log(response.data)
})
setPageNumber(pageNumber-1)
}
return (
<div>
<Movies state={state}/>
<button onClick={prevPage}>Prev</button>
<button onClick={nextPage}>Next</button>
</div>
)
}
export default MoviesFromGenre;
I created a repository on GitHub.
I want to update the movies state when I click on next or prev buttons.
A Reddit user managed to solve my problem.
He suggested that I include pageNumber as a dependency in my useEffect hook, so it will run whenever pageNumber changes.
function MoviesFromGenre () {
const [state, dispatch] = useReducer(reducer, initialState);
const { id } = useParams();
const [pageNumber, setPageNumber] = useState(1)
useEffect(() => {
axios
.get(
`${API_URL}discover/movie?api_key=${API_KEY}&language=en-US&with_genres=${id}&page=${pageNumber}`
)
.then(response => {
dispatch({
type: 'FETCH_SUCCESS',
payload: response.data
})
})
.catch(err => {
dispatch({
type: 'FETCH_ERROR'
})
})
}, [pageNumber])
const nextPage = () => {
setPageNumber(pageNumber+1)
}
const prevPage = () => {
setPageNumber(pageNumber-1)
}
//....
}
i'm newbie here, i'm stuck. i want to change value from false to true, to stop shimmering when data sucessfully to load.
i have action like this
import axios from "axios";
import { CONSTANT_LINK } from "./constants";
import { GET } from "./constants";
import { ERROR } from "./constants";
import { connect } from 'react-redux';
export const addData = () => {
return (dispatch) => {
axios
.get(CONSTANT_LINK)
.then((res) => {
dispatch(addDataSuccess(res.data));
})
.catch((err) => {
dispatch(errorData(true));
console.log("error");
});
};
};
const addDataSuccess = (todo) => ({
type: GET,
payload: todo,
});
const errorData = (error) => ({
type: ERROR,
payload: error,
});
and this is my homepage which influential in this matter
const [shimmerValue, setShimmerValue] = useState(false)
useEffect(() => {
setShimmerValue(true)
dispatch(addData());
}, []);
<ShimmerPlaceholder visible={shimmerValue} height={20}>
<Text style={styles.welcomeName}>Welcome,Barret</Text>
</ShimmerPlaceholder>
i dont understand how it works
You can pass callback like this
const [shimmerValue, setShimmerValue] = useState(false);
const updateShimmerValue = () => {
setShimmerValue(true);
}
useEffect(() => {
// setShimmerValue(true) // remove this from here
dispatch(addData(updateShimmerValue)); // pass callback as param here
}, []);
Callback call here like
export const addData = (callback) => {
return (dispatch) => {
axios
.get(CONSTANT_LINK)
.then((res) => {
....
callback(); // trigger callback like this here
})
.catch((err) => {
....
});
};
};
you can use it:
const [shimmerValue, setShimmerValue] = useState(false)
useEffect(() => {
setState(state => ({ ...state, shimmerValue: true }));
dispatch(addData());
}, [shimmerValue]);
In pure react, I have written a function that I call in componentDidMount ():
getTasks = (userId, query, statusTask, pageNumber) => {
let check = {};
axios({
url: `/api/v1/beta/${userId}`,
method: 'GET'
})
.then(res => {
check = res.data;
if (res.data) {
this.setState({
checkRunning: res.data,
checkRunningId: res.data.id
});
this.utilizeTimes(res.data.task_id);
}
})
.catch(error => {
console.log(error);
})
.then(() => {
const params = {
sort: 'name'
};
if (query) {
params['filter[qwp]'] = query;
if (this.state.tasks[0]) {
this.setState({
selectedId: this.state.tasks[0].id,
selectedTabId: this.state.tasks[0].id
});
}
}
axios({
url: '/api/v1//tasks',
method: 'GET',
params
})
.then(res => {
if (res.status === 200 && res.data) {
this.setState({
tasks: res.data,
lengthArrayTasks: parseInt(res.headers['x-pagination-total-count'])
});
if (!check && res.data && res.data[0]) {
this.setState({
selectedTabId: res.data[0].id,
});
this.load(res.data[0].id);
}
let myArrayTasks = [];
myArrayTasks = res.data;
let findObject = myArrayTasks.find(task => task.id === this.state.runningTimerTask.id);
if (
!findObject &&
this.state.runningTimerTask &&
this.state.runningTimerTask.id &&
this.state.query === ''
) {
this.setState({
tasks: [this.state.runningTimerTask, ...myArrayTasks]
});
}
}
})
.catch(error => {
console.log(error);
});
});
};
I am trying to rewrite it to redux, but with poor results. First it makes one request / api / v1 / beta / $ {userId}, writes the answer in the variable check. check passes to the nextthen. In the next then carries out the request '/ api / v1 // tasks' Can somebody help me? I am asking for some tips. Is this somehow complicated?
So far, I've managed to create something like this:
store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
actions
export const RUNNING_TIMER = 'RUNNING_TIMER';
export const GET_TASKS = 'GET_TASKS';
export const FETCH_FAILURE = 'FETCH_FAILURE';
export const runningTimer = (userId, query, statusTask, pageNumber) => dispatch => {
console.log(userId);
axios({
url: `/api/v1/beta/${userId}`,
method: 'GET'
})
.then(({ data }) => {
dispatch({
type: RUNNING_TIMER,
payload: data
});
})
.catch(error => {
console.log(error);
dispatch({ type: FETCH_FAILURE });
})
.then(() => {
const params = {
sort: 'name'
};
axios({
url: '/api/v1//tasks',
method: 'GET',
params
})
.then(({ data }) => {
dispatch({
type: GET_TASKS,
payload: data
});
})
.catch(error => {
console.log(error);
});
});
};
reducer
import { RUNNING_TIMER, GET_TASKS } from '../actions';
const isRunningTimer = (state = {}, action) => {
const { type, payload } = action;
switch (type) {
case RUNNING_TIMER:
return {
checkRunningTimer: payload,
checkRunningTimerId: payload && payload.id ? payload.id : null
};
break;
case GET_TASKS:
return {
tasks: payload,
lengthArrayTasks: parseInt(action.headers['x-pagination-total-count'])
};
default:
return state;
}
};
const rootReducer = combineReducers({ isRunningTimer });
export default rootReducer;
App
class App extends Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
componentDidMount() {
this.props.runningTimer();
}
render() {
return (
<div>
</div>
);
}
}
const mapStateToProps = state => {
const { isRunningTimer } = state;
return {
isRunningTimer
};
};
const mapDispatchToProps = dispatch => ({
runningTimer: (userId, query, statusTask, pageNumber) => dispatch(runningTimer()),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
Number 1 Consider your state design.
I find it useful to consider what the state object would look like at a given point in time.
Here is an example of initialState used in an application of mine.
const initialState = {
grocers: null,
coords: {
latitude: 37.785,
longitude: -122.406
}
};
This is injected at the createStore.
Breaking down your application state object/properties, should assist you in making your actions simpler as well.
Number 2
Consider breaking down your actions.
My thoughts, decouple the action code, at the .then at the second .then .(Consider saving the results somewhere in a user: object)
.then(response => {
const data = response.data.user;
setUsers(data);})
.catch(error => {
console.log('There has been a problem with your fetch operation: ' + error.message);
})
function setUsers(data){
dispatch({
type: FETCH_USERS,
payload: data
});
}
This refers to the S in SOLID design principles. Single Responsibility Principle.
https://devopedia.org/solid-design-principles
Number 3
Consider this, if the 'getUser' info fetch fails.
Having the process/response separated will allow the application to be debugged more cleanly. In example, the user api failed or the getTask api failed, etc.
More resources on redux.
https://redux.js.org/introduction/learning-resources#thinking-in-redux
Extending previous answer from #Cullen, this is what I did:
Since you already have a action to GET_TODOS, just make the action creator for runningTimer to do one and only one thing - make API call to /api/v1/beta/<userId> and dispatch respective actions.
export const runningTimer = (
userId,
query,
statusTask,
pageNumber
) => dispatch => {
return axios({
url: `/api/v1/beta/${userId}`,
method: "GET"
})
.then(({ data }) => {
dispatch({
type: RUNNING_TIMER,
payload: data
});
})
.catch(error => {
console.log(error);
dispatch({ type: FETCH_FAILURE });
});
};
Update props of your app component to read store data.
...
const mapStateToProps = state => {
const { isRunningTimer, todos, todo } = state;
return {
todos,
todo,
isRunningTimer,
};
};
const mapDispatchToProps = dispatch => ({
getTodos: () => dispatch(getTodos()),
getTodo: id => dispatch(getTodo(id)),
runningTimer: (userId, query, statusTask, pageNumber) => dispatch(runningTimer(userId)),
});
...
Update the implementation of componentDidMount to dispatch isRunningTimer -
componentDidMount() {
...
// call with userId 1
this.props.runningTimer(1).then(() => {
console.log(this.props);
// additional params for getTasks
const params = {
sort: 'name'
};
// another call for getTodos with names sorted
this.props.getTodos(params);
});
...
Note: You need to update your getTodos action to take in an optional params arguments (which is initialized to empty object if not passed).
Hope this helps you.
Live sandbox for this is present here - https://stackblitz.com/edit/react-redux-more-actions
Check out React-boilerplate. Great boilerplate for react and redux. They use redux-saga and redux-hooks as well.
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.