redux dispatch not work when use in axios interceptors - javascript

I want to show snackbar when axios return error.
I use redux for config snackbar.
axiosAgent.interceptors.response.use(
function(response) {
return response;
},
function(error) {
console.log('object')
const dispatch = useDispatch();
dispatch({
type: ActionsTypes.CHANGE_SNACKBAR_CONFIG,
payload: { variant: "error", message: Strings.errors.problem }
});
dispatch({ type: ActionsTypes.CHANGE_SNACKBAR_SHOW, payload: true });
return Promise.reject(error);
}
);
and here is my snackbar component:
export default function Snackbar() {
const open = useSelector(state => state.Snackbar.showSnackbar);
const config = useSelector(state => state.Snackbar.snackbarConfig);
const dispatch = useDispatch();
const handleClose = (event, reason) => {
if (reason === "clickaway") {
return;
}
dispatch({ type: ActionsTypes.CHANGE_SNACKBAR_SHOW, payload: false });
};
useEffect(() => {
console.log(config);
}, [config]);
return (
<SB open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity={config.variant}>
{config.message}
</Alert>
</SB>
);
}
but doesn't work.
When I dispatch it from component it works but here not works.

Hooks, such as useDispatch(), can only be used in the body of a function component. You cannot use useDispatch in an interceptor. If you need to dispatch an action in an interceptor, you will need to have a reference to the store object, and call store.dispatch(/* action */)

Related

getting typeError while using dispatch from react context-api

I have created a context store using react context API and I a function to dispatch action on the state but every time I call the function I am getttype errorpeError: dispatch is not a function```
the implementation is of login for the user in react using context API to store the state
my context provider / auth context provider
const INITIAL_STATE = {
user: JSON.parse(localStorage.getItem("user")) || null,
isFetching: false,
error: false,
};
export const AuthContext = createContext(INITIAL_STATE);
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
useEffect(() => {
localStorage.setItem("user", JSON.stringify(state.user));
}, [state.user]);
return (
<AuthContext.Provider
value={{
user: state.user,
isFetching: state.isFetching,
error: state.error,
dispatch: dispatch,
}}
>
{children}
</AuthContext.Provider>
);
};
and every time I useContext to get the values of user,isFetching,error,dispatch
I am getting undefined for dispatch but the other values are coming correct isFetching is coming false as it should be initially but the value of dispatch is coming undefined.
where I am using the context
const { isFetching, dispatch } = useContext(AuthContext);
const handleClick = (e) => {
e.preventDefault();
console.log({ isFetching, dispatch });
dispatch({ type: "LOGIN_START" });
e.preventDefault();
loginCall(
{ email: email.current.value, password: password.current.value },
dispatch
);
};
login call function
export const loginCall = async (userCredential, dispatch) => {
dispatch({ type: "LOGIN_START" });
try {
const res = await axios.post("/auth/login", userCredential);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data });
} catch (err) {
dispatch({ type: "LOGIN_FAILURE", payload: err });
}
};
You need to import AuthContextProvider in index.js
and add this snipet
<AuthContextProvider>
<App/>
</AuthContextProvider>

How to Pass Id correctly to Rest API Endpoint from React

I'm trying to fetch data through endpoint from Django Rest Framework
endpoint is :
/api/v1/categories/nested/{id}/
Problem is when I'm requesting with id, Django server show this error :
ValueError: Field 'id' expected a number but got 'undefined'.
[07/Feb/2022 15:53:01] "GET /api/v1/categories/nested/undefined/ HTTP/1.1" 500 162581
As this suggest I'm unable to Pass id correctly,
So need littl help to fix that
I'm using actions > reducer > store > component approach using react redux
action.js
export const listCategoryDetails = (id) => async (dispatch) => {
try {
dispatch({ type: CATEGORY_DETAIL_REQUEST });
const { data } = await axios.get(`/api/v1/categories/nested/${id}`); // Purpose to show nested brands[]
dispatch({
type: CATEGORY_DETAIL_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: CATEGORY_DETAIL_FAIL,
payload:
error.response && error.response.data.detail
? error.response.data.detail
: error.message,
});
}
};
reducer.js
export const categoryDetailsReducer = (
state = { category: { } },
action
) => {
switch (action.type) {
case CATEGORY_DETAIL_REQUEST:
return { loading: true, ...state };
case CATEGORY_DETAIL_SUCCESS:
return { loading: false, category: action.payload };
case CATEGORY_DETAIL_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
store.js
const reducer = combineReducers({
categoryDetail: categoryDetailsReducer,
});
component
function CategoryDetail({ match, history }) {
// const { id } = useParams();
// console.log(id);
const dispatch = useDispatch();
const categoryList = useSelector((state) => state.categoryList);
const { loading, error, categories , page, pages} = categoryList;
useEffect(() => {
dispatch(listCategoryDetails());
}, [dispatch, match]);
return <div>
{categories.map(category => (
<Col key={category.id} sm={12} md={8} lg={4} xl={3} >
<h1><strong>{category.title}</strong></h1>))}
</div>;
}
export default CategoryDetail;
const id = ...
and pass it to dispatch function dispatch(listCategoryDetails(id))
Before
// const { id } = useParams();
// console.log(id);
const dispatch = useDispatch();
const categoryList = useSelector((state) => state.categoryList);
const { loading, error, categories , page, pages} = categoryList;
useEffect(() => {
dispatch(listCategoryDetails());
}, [dispatch, match]);
After
const { id } = useParams();
console.log(id);
const dispatch = useDispatch();
const categoryList = useSelector((state) => state.categoryList);
const { loading, error, categories , page, pages} = categoryList;
useEffect(() => {
dispatch(listCategoryDetails(id));
}, [dispatch, match]);
Inside UseEffect You Are Not Passing id Variable So its Saying Id Is Undefined

Why are my action creators being called in the wrong order when using thunk?

I was experimenting with redux-thunk and action creators and noticed some strange behavior that I don't understand. When I call the action creator functions they don't get called in the order I want them to. This is my App.js component
class App extends Component {
handleSave = () => {
this.props.postData({
name:this.props.activity,
type_name: this.props.type
})
this.props.fetchList()
this.props.fetchData()
}
handleClick = () => {
this.props.fetchData()
}
componentDidMount() {
this.props.fetchData()
this.props.fetchList()
}
render() {
return (
<div className="App">
<Router>
<Nav />
<Route exact path='/' render={props => <Home {...props} clickProp={this.handleClick} saveProp={this.handleSave}/>} />
<Route exact path='/activities' render={props => <ListContainer {...props} numItems={this.props.list.length} listProp={this.props.list}/>} />
</Router>
</div>
);
}
}
const mapStateToProps = state => {
return {
activity: state.activity,
type: state.type,
list: state.list
}
}
const actions = {fetchData, fetchList, postData}
export default connect(mapStateToProps, actions)(App);
When I click on a button in the Home child component the handleSave function gets called which is then supposed to post an item on to my list and then fetch the updated list so that it can be shown in my list. When I do this, this.props.fetchList() gets called first even though it is the second function being called. I have placed a console.log(action) inside my reducer and this is what is printed.
{type: "FETCH_LIST", payload: Array(86)}
{type: "POST_DATA", payload: {…}}
{type: "FETCH_DATA", payload: {…}}
The only way that I can get the FETCH_LIST to happen after the POST_DATA is if I call fetch_data() a second time like so
handleSave = () => {
// something weird going on here
this.props.fetchList()
this.props.postData({
name:this.props.activity,
type_name: this.props.type
})
this.props.fetchList()
this.props.fetchData()
}
I really want to be able to get my code to work without having to call the same function twice if it is possible. Finally, this is what my action creators look like.
export default function fetchData() {
return (dispatch) => {
const url = 'http://www.boredapi.com/api/activity/'
fetch(url)
.then(res => res.json())
.then(activity => { dispatch({type: "FETCH_DATA", payload: activity})})
}
}
export default function fetchList() {
return (dispatch) => {
const url = 'http://localhost:3001/activities'
fetch(url)
.then(res => res.json())
.then(list => { dispatch({type: "FETCH_LIST", payload: list})})
}
}
export default function postData(activity) {
return (dispatch) => {
const url = 'http://localhost:3001/activities'
const config = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({activity})
}
fetch(url, config)
.then(r => r.json())
.then(activity => {dispatch({type: "POST_DATA", payload: activity})})
}
}
My only guess is that this is happening because these actions are asynchronous. So I have also tried to change the order in which these functions are called and no matter what FETCH_LIST always happens before POST_DATA.
You can convert those action creators to async versions and then you can await them so they execute in order.
https://medium.com/#gaurav5430/async-await-with-redux-thunk-fff59d7be093
For example on your fetchData
function fetchData() {
return async (dispatch) => {
const url = 'http://www.boredapi.com/api/activity/'
try{
const res = await fetch(url)
const activity = await res.json();
dispatch({type: "FETCH_DATA", payload: activity})
}
catch (error) {
console.log(error);
}
}
Once they are async you can await them in your handleSave (once you convert it to async) This will ensure they get called in order.
handleSave = async () => {
await this.props.postData({
name:this.props.activity,
type_name: this.props.type
})
await this.props.fetchList()
await this.props.fetchData()
}

Converting functions from pure react to redux react

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.

Timeout for RefreshView in React Native Expo App

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.

Categories

Resources