fetching data using redux - reactjs

I'm new to redux and I'm trying to fetch some data in my slice file, then put it in my state to use it across my app.
so I read the documentation in redux website. it says:
"Let's start by adding a thunk that will make an AJAX call to retrieve a list of posts. We'll import the client utility from the src/api folder, and use that to make a request to '/fakeApi/posts'."
and the code is:
import { createSlice, nanoid, createAsyncThunk } from '#reduxjs/toolkit'
import { client } from '../../api/client'
const initialState = {
posts: [],
status: 'idle',
error: null
}
export const fetchPosts = createAsyncThunk('posts/fetchPosts', async () => {
const response = await client.get('/fakeApi/posts')
return response.data
})
so now I'm confused. How can I create the client file to use it?
and then, how can I save it in my state to re-use it?
it would be a huge help if you guide me!

Oh yeah now i understand what you want, client is just like fetch, i assume they are using axios , then in client.js file they are exporting axios at the end.
An example, client.js file:
import axios from "axios";
export const client = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
Then import it whereever you want:
import { client } from '../../api/client'
But you can also use axios directly without creating any instances .
As i said before you may use fetch instead, or any other http request package, but actually with axios you have more power and you can easily find a lot of documentations

You can get your reducer state with the use of useSelector and make sure
you write correct reducer state name instead of counter.
const count = useSelector((state) => state.counter.value);
You can dispatch your action by useDispatch hook and make sure you write correct action name instead of decrement.
const dispatch = useDispatch();
dispatch(decrement())
import of this two hooks
import { useSelector, useDispatch } from 'react-redux';
You can save your api response in posts state like this:
export const fetchPosts = createAsyncThunk('posts/fetchPosts', async ()
=> {
const response = await client.get('/fakeApi/posts')
state.posts = response.data
})
Full demo example of redux-toolkit: https://github.com/priyen27/redux-toolkit-demo

thanks for these answers. so I used it and now I get this error:
XHR failed loading: GET "https://excuser.herokuapp.com/v1/excuse"
that's the api link I want.
I used fetch as well and it worked correctly, but I don't know how to store it's data in my state. I used this function:
export async function fetchMyAPI() {
let response = await fetch(`https://excuser.herokuapp.com/v1/excuse`)
let data = await response.json()
return data[0].excuse
}
when I use it in my component and at the end I set is to some const it works perfect. but when I use it directly (like setData(fetchMyAPI())) it returns a promiss and I can't access data. what should I do? how can I store it in my state?
note that I fetch data in my slice component.
my final get api function:
const fetchExcuses = createAsyncThunk('excuses/fetchExcuses', async () => {
const response = await client.get('excuser.herokuapp.com/v1/excuse')
let data = await response.json()
})

Related

What is the correct way to pass parameters to a React-query useQuery method that uses Axios

I am currently building a Ruby on Rails Webpacker application with a React front end. I am at the point where I would like to create all the quires I need to make calls to my Rails API. I was loosely following this tutorial https://www.youtube.com/watch?v=0bKc_ch6MZY (https://github.com/daryanka/react-query-tutorial/blob/master/src/containers/Post.js, https://github.com/daryanka/react-query-tutorial/blob/master/src/Queries.js), in order to write some axios based query functions that I could use with react-query. I had no problem with getting the queries to behave as expected when the url for the endpoint was a hard coded string. When I attempted to pass in a parameter to make dynamic urls I ran into the issue of not having access to said parameter; specifically the "prodId" parameter. I did however notice that the "prodId" was inside the "key" parameter array like so:
queryKey: Array(2)
0: "product"
1: "1"
length: 2
enter code here
I could just access it from there but that approach does seem a little off, I also did not find any examples or documentation that attempted to access a parameter from the query key array. I would like to know what it is I am doing incorrectly with regards to passing in parameters? Were there some syntax changes in react-query that I am not taking into account?
react-query#^3.17.2
webpacker (5.2.1)
axios#^0.21.1
//Product.js
import axios from "axios"
import { getProduct } from "../../queries/products"
import { useQuery } from "react-query"
const prodId= '1'
const { data } = useQuery(['product', prodId], getProduct)
//queries/products.js
import axios from 'axios'
export const getProduct = async (key, { prodId }) => {
console.log(opid)
const { data } = await axios.get(`/api/v1/products/${prodId}`)
return data
}
The query function that you pass to react-query gets a queryContext injected, which is an object that consists of the queryKey (and some more information if you are using an infinite query). So yes, one correct way to access dependencies is through the queryKey:
export const getProduct = async ({ queryKey }) => {
const [_, prodId] = queryKey
const { data } = await axios.get(`/api/v1/products/${prodId}`)
return data
}
const { data } = useQuery(['product', prodId], getProduct)
Another way is to use inline anonymous functions, which is well documented in the docs in: If your query function depends on a variable, include it in your query key
export const getProduct = async (prodId) => {
const { data } = await axios.get(`/api/v1/products/${prodId}`)
return data
}
const { data } = useQuery(['product', prodId], () => getProduct(prodId))
I'm using the following (typescript) to send parameters to my custom useQuery hook.
import { useQuery } from 'react-query'
import service from '../api'
const queryKey = 'my-query-key'
type useProductsParams = Parameters<typeof service.listProducts>
const useProducts = (...params: useProductsParams) => {
return useQuery(queryKey, () => service.getProduct(...params))
}
export default useProducts

How to dispatch data to redux from the common api request file?

I have created a common js file to call the service requests. I want to store the fetched data in my redux managed store. But I am getting this error saying Invalid hook call. Hooks can only be called inside of the body of a function component.I think this is because I am not using react-native boilerplate for this file. But the problem is I don't want to I just want to make service requests and responses.
import { useDispatch } from "react-redux";
import { addToken } from "../redux/actions/actions";
const { default: Axios } = require("axios");
const dispatch = useDispatch();
const handleResponse=(response, jsonResponse)=> {
// const dispatch = useDispatch(); //-----also tried using dispatch here
const jsonRes = jsonResponse;
const { status } = response;
const { errors } = Object.assign({}, jsonRes);
const resp = {
status,
body: jsonResponse,
errors,
headers: response.headers,
};
console.log(resp, 'handle response');
return await dispatch(addToken(resp.body.token))
};
const API = {
makePostRequest(token) {
Axios({
url: URL,
...req,
timeout: 30000
}).then(res =>
console.log('going to handle');
await handleResponse(res, res.data)
})
}
export default API
I know there would be some easy way around but I don't know it
Do not use useDispatch from react-redux, but dispatch from redux.
You need to use redux-thunk in your application.
Look at the example in this article Redux Thunk Explained with Examples
The article has also an example of how to use redux with asynchronous calls (axios requests in your case).
I suggest to refactored your api to differentiate two things:
fetcher - it will call your api, e.g. by axios and return data in Promise.
redux action creator (thunk, see the example in the article) - it will (optionally) dispatch REQUEST_STARTED then will call your fetcher and finally will dispatch (REQUEST_SUCCESS/REQUEST_FAILURE) actions.
The latter redux action creator you will call in your react component, where you will dispatch it (e.g. with use of useDispatch)

Use getState to access key in redux state for API call

I'm a little new to using thunk getState I have been even trying to console.log the method and get nothing. In state I see that loginReducer has they key property which I need to make API calls. status(pin): true
key(pin): "Ls1d0QUIM-r6q1Nb1UsYvSzRoaOrABDdWojgZnDaQyM"
Here I have a service:
import axios from 'axios'
import {thunk, getState} from 'redux-thunk'
import MapConfig from '../components/map/map-config'
const origin = 'https://us.k.com/'
class KService {
getNorthAmericaTimes() {
return (dispatch, getState) => {
const key = getState().key
console.log('This is time key,', key)
if (key) {
dispatch(axios.get(`${origin}k51/api/datasets/k51_northamerica?key=${key}`))
}
}
// const url = `${origin}k51/api/datasets/k51_northamerica?key=${urlKey}`
// return axios.get(url)
}
}
export default new K51Service()
However in my corresponding action I get that Uncaught TypeError: _kService2.default.getNorthAmericaTimes(...).then is not a function
This is what the action function looks like :
export function getKNorthAmericaTime(dispatch) {
KService.getNorthAmericaTimes().then((response) => {
const northAmericaTimes = response.data[0]
dispatch({
type: ActionTypes.SET_NORTH_AMERICA_TIMES,
northAmericaTimes
})
})
}
I'm assuming it probably has to do with the if block not getting executed.
You should move your axios.get() method to your action creator and pass the promise to redux thunk, then when the promise is resolved dispatch the action with the response data so it can be processed by the reducer into the app's state.
actions
import axios from "axios";
export function fetchData() {
return (dispatch, getState) => {
const key = getState().key;
const request = axios.get();// use your request code here
request.then(({ response}) => {
const northAmericaTimes = response.data[0]
dispatch({ type: ActionTypes.SET_NORTH_AMERICA_TIMES, payload: northAmericaTimes});
});
};
}
Here's a very simple example of using axios with redux-thunk:
https://codesandbox.io/s/z9P0mwny
EDIT
Sorry, I totally forgot that you need to go to the state before making the request.
As you can see go to the state in your function, get the key from it, make the request and when the promise is resolved, dispatch the action with the response data. I've updated the live sample so you can see it working.
Again sorry...

Cancel requests on route change (React/Redux/Axios)

Is there convenient way to cancel all sending request on any route changes using axios, redux-thunk, redux? I know that axios has cancellation token which should be added to every request and I can call source.cancel(/* message */) to cancel it.
P.S. Currently I handle this in componentWillUnmount. Maybe there is something better?
The easiest way which I found is to store source in state, and use source.cancel if request is sending.
componentWillUnmount() { if(isLoading) { source.cancel() } }
Very simple solution would be to declare isMounted variable and set it to false when component unmounts.
Other way of handling this issue is, (I'm not sure about axios but) XMLHttpRequest has abort method on it. You could call xhr.abort() on componentWillUnmount.
Check it here: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/abort
And finally there is an industry solution :) from Netflix UI engineers. They have written a redux middleware for RxJS and using RxJS operators to fire up and cancel the requests.
The library is called redux-observable. I recommend to take a look at examples: https://redux-observable.js.org/docs/recipes/Cancellation.html
You can watch the talk about it here: https://youtu.be/AslncyG8whg
I found a simple solution (without redux) to do that. All you need is a cancelToken in your axios requests. After, use the useHook to detect route changes. Then, cancel the requests with the TOKEN when the route is unmounted and to generate a new TOKEN to make a new request. See the Axios Doc to more details (https://github.com/axios/axios).
Route.tsx file
import React, { useEffect } from 'react';
import { Route, RouteProps, useLocation } from 'react-router-dom';
import API from 'src/services/service';
const CustomRoute = (props: RouteProps) => {
const location = useLocation();
// Detect Route Change
useEffect(() => {
handleRouteChange();
return () => {
handleRouteComponentUnmount();
};
}, [location?.pathname]);
function handleRouteChange() {
// ...
}
function handleRouteComponentUnmount() {
API.finishPendingRequests('RouteChange');
}
return <Route {...props} />;
};
export default CustomRoute;
Service.ts file
import { Response } from 'src/models/request';
import axios, {AxiosInstance, AxiosResponse } from 'axios';
const ORIGIN_URL = 'https://myserver.com'
const BASE_URL = ORIGIN_URL + '/api';
let CANCEL_TOKEN_SOURCE = axios.CancelToken.source();
function generateNewCancelTokenSource() {
CANCEL_TOKEN_SOURCE = axios.CancelToken.source();
}
export const axiosInstance: AxiosInstance = axios.create({
baseURL: BASE_URL,
});
const API = {
get<DataResponseType = any>(
endpoint: string,
): Promise<AxiosResponse<Response<DataResponseType>>> {
return axiosInstance.get<Response<DataResponseType>>(endpoint, {
cancelToken: CANCEL_TOKEN_SOURCE.token,
});
},
// ...Another Functions
finishPendingRequests(cancellationReason: string) {
CANCEL_TOKEN_SOURCE.cancel(cancellationReason);
generateNewCancelTokenSource();
},
};
export default API;

mapStateToProps does not set the data in component props

I'm just getting started with React. I successfully used axios to get data from http and use an action to push the data. I can output the data at mapStateToProps but it does not set the data as a prop in the class. Here's my code with comments about the availability of the data.
import React from 'react';
import { connect } from 'react-redux';
import { fetchCountries } from '../../actions/actions';
import _ from 'lodash';
class TheClass extends React.Component
{
constructor(props)
{
super(props);
}
componentDidMount()
{
this.props.fetchCountries();
console.log('Fetching', this.props.countries); // !! UNDEFINED !!
}
}
function mapStateToProps(state)
{
console.log('Countries:', state.countries) // -> I get the data
return { countries: state.countries }
}
export default connect(mapStateToProps, { fetchCountries })(TheClass);
actions.js
import axios from 'axios';
export const FETCH_COUNTRIES = `fetch_countries`;
const COUNTRIES_URL = `http://api.stagingapp.io/location/v1/public/country`;
export function fetchCountries()
{
const request = axios.get(COUNTRIES_URL);
console.log(request); // -> I get the data
return {
type: FETCH_COUNTRIES,
payload: request
}
}
fetchCountries is an asynchronous operation so you can't expect the result just after calling fetchCountries as you are trying to do in componentDidMount.
If you are getting the result in connect function, then you will get the result in render function after successful network call.
Put your console here:
render() {
console.log('Fetching', this.props.countries);
}
I'd imagine that state.countries gets populated by whatever response you get from your asynchronous HTTP request in fetchCountries().
Only once this request resolves, should you get the country data. When you call fetchCountries() and immediately afterwards try to print out the value of countries, the request has not yet resolved (gotten a response), which is why you wont get any data.
Your fetch countries request in Asynchronous request, so you can't expect countries to be in store just after calling the fetchCountries() function. You will get countries data when react will re render on arrival of countries data from api.
Your function getCountries return an object with payload = a Promise return by axios, so you don't have your data when you call the function.
To make Async request you should add redux-thunk middleware, after that in your component file create a function
const mapStateToProps = (dispatch) => ({
fetchCountries: bindActionsCreator(fetchCountries, dispatch)
})
and pass this function in 2nd argument to your connect function.
In your actions.js change your function getCountries like so:
export const fetchCountries = () => (dispatch) => {
dispatch({type: FETCH_START})
axios.get(COUNTRIES_URL)
.then(response => response.data)
.then(data => dispatch({type: FETCH_COUNTRIES, payload: data})
.catch(errors => dispatch({type: FETCH_ERRORS})
}
With that, in your reducer you can set a variable loading to true when request start and pass this variable to false when Promise is resolved/rejected and after that you can create a condition to your component to be sure you have your data!

Resources