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...
Related
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)
In my react/redux project, I call a function from my action to fetch a data from an api. Fetch starts the api request... but react doesn't recognize dispatch()
function getAuthenticatedUser() {
....
return fetch("my.api/path", requestHeaders)
.then(response => handleResponse(response))
.then(response=>{
return response.json()
}).then(responseJson =>{
dispatch(requestSuccess(responseJson.user))
})
....
function requestSuccess(....
....
Then, I wrapped around return dispatch as follows. Now it outputs no error, but fetch() doesn't start any api requests. (No requests in Network/XHR)
return dispatch => {
return fetch("my.api/path", requestHeaders)
.then(response => handleResponse(response))
.then(response=>{
return response.json()
}).then(responseJson =>{
dispatch(requestSuccess(responseJson.user))
})
}
What am I missing?
I found the solution. Firstly I want to thanks to 3 comments on the question.
I firstly installed redux-thunk. I added a middleware to my store:
import thunk from "redux-thunk";
...
export const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
Than, I imported store in my component and dispatched my action function. (Previously I was calling it directly)
constructor(props) {
....
store.dispatch(userActions.getAuthenticatedUser())
Now fetch and dispatchers within fetch work fine.
I'm working on a Redux app in which many filter components can change the nature of a search to be performed. Any time the state of one of those filter components changes, I want to re-run a search action. I can't seem to call the search action from each of the filter components correctly, however.
Here's the main search action:
// actions/search.js
import fetch from 'isomorphic-fetch';
import config from '../../server/config';
export const receiveSearchResults = (results) => ({
type: 'RECEIVE_SEARCH_RESULTS', results
})
export const searchRequestFailed = () => ({
type: 'SEARCH_REQUEST_FAILED'
})
export const fetchSearchResults = () => {
return (dispatch, getState) => {
// Generate the query url
const query = getSearchQuery(); // returns a url string
return fetch(query)
.then(response => response.json()
.then(json => ({
status: response.status,
json
})
))
.then(({ status, json }) => {
if (status >= 400) dispatch(searchRequestFailed())
else dispatch(receiveSearchResults(json))
}, err => { dispatch(searchRequestFailed()) })
}
}
fetchSearchResults works fine when I call it from connected React components. However, I can't call that method from the following action creator (this is one of the filter action creators):
// actions/use-types.js
import fetchSearchResults from './search';
export const toggleUseTypes = (use) => {
return (dispatch) => {
dispatch({type: 'TOGGLE_USE_TYPES', use: use})
fetchSearchResults()
}
}
Running this yields: Uncaught TypeError: (0 , _search2.default) is not a function. The same happens when I run dispatch(fetchSearchResults()) inside toggleUseTypes.
How can I resolve this problem and call the fetchSearchResults method from the actions/use-types.js action?
I see 2 errors:
You're importing the fetchSearchResults function incorrectly.
This is where the TypeError _search2.default is coming from:
Uncaught TypeError: (0 , _search2.default) is not a function
You're dispatching the fetchSearchResults action/thunk incorrectly
Error 1: Incorrect import
// This won't work. fetchSearchResults is not the default export
import fetchSearchResults from './search';
// Use named import, instead.
import {fetchSearchResults} from './search';
Error 2: Incorrect action usage
// This won't work, it's just a call to a function that returns a function
fetchSearchResults()
// This will work. Dispatching the thunk
dispatch(fetchSearchResults())
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!
I found the same question here, but without a proper answer I am looking for.
I am developing a simple application with CRUD operations. On the edit page, after the component gets mounted (componentDidMount()), the app dispatches an action to retrieve a specific post details:
dispatch({ type: FETCH_POST, id: 'post-id' })
I am using redux-saga and want the above call to return a Promise so that I can access the API response.
Right now, without a callback/Promise, I ended up with defining a new state in store (like post_edited) and connect/map it to props in the component for edit page.
What would be the best possible way to deal with this kind of situation?
Could you please provide more information about your issue? I'm not sure if I understand your issue properly, but the common practice is:
API.js
function apiCallToFetchPost(id) {
return Promise.resolve({name: 'Test});
}
postSaga.js
function* fetchPostSaga({id}) {
try {
const request = yield call(apiCallToFetchPost, id);
// -> in post reducer we will save the fetched data for showing them later
yield put({type: FETCH_POST_SUCCESS, payload: request});
} catch (error) {
yield put({type: FETCH_POST_SUCCESS_FAILURE, error})
}
}
export function* onBootstrap() {
yield takeLatest(FETCH_POST, fetchPostSaga);
}
There's a package that does exactly what the OP requested, i.e. arranges that dispatch() can return a promise: #adobe/redux-saga-promise
Using it, you define a "promise action" creator via:
import { createPromiseAction } from '#adobe/redux-saga-promise'
export const fetchPostAction = createPromiseAction('FETCH_POST')
The dispatch() of a "promise action" will return a promise:
await dispatch(fetchPostAction({ id: 'post-id' }))
The saga might look like:
import { call, takeEvery } from 'redux-saga/effects'
import { implementPromiseAction } from '#adobe/redux-saga-promise'
import { fetchPostAction } from './actions'
function * fetchPostSaga(action) {
yield call(implementPromiseAction, action, function * () {
const { id } = action.payload
return yield call(apiCallToFetchPost, id)
})
}
export function * rootSaga() {
yield takeEvery(fetchPostAction, fetchPostSaga);
}
It will resolve the promise with the value returned by apiCallToFetchPost or reject if apiCallToFetchPost throws an error. It also dispatches secondary actions with the resolution/rejection that you can access in a reducer. The package provides middleware you have to install to make it work.
(Disclaimer, I'm the author)
I am the developer of #teroneko/redux-saga-promise. It was initially forked from #adobe/redux-saga-promise but now it has been completelly revamped to use createAction from #reduxjs/toolkit to support TypeScript.
To keep in touch with the example of #ronen, here the TypeScript equivalent.
Create promise action (creator):
import { promiseActionFactory } from '#teroneko/redux-saga-promise'
export const fetchPostAction = promiseActionFactory<void>().create<{ id: string }>('FETCH_POST')
To dispatch a promise action (from creator):
// promiseMiddleware is required and must be placed before sagaMiddleware!
const store = createStore(rootReducer, {}, compose(applyMiddleware(promiseMiddleware, sagaMiddleware)))
await store.dispatch(fetchPostAction({ id: 'post-id' }))
To resolve/reject the promise action (from saga):
import { call, takeEvery } from 'redux-saga/effects'
import { implementPromiseAction } from '#teroneko/redux-saga-promise'
import { fetchPostAction } from './actions'
function * fetchPostSaga(action: typeof fetchPostAction.types.triggerAction) {
yield call(implementPromiseAction, action, function * () {
const { id } = action.payload
return yield call(apiCallToFetchPost, id)
})
// or for better TypeScript-support
yield call(fetchPostAction.sagas.implement, action, function * () {
const { id } = action.payload
return yield call(apiCallToFetchPost, id)
})
}
export function * rootSaga() {
yield takeEvery(fetchPostAction, fetchPostSaga);
}
So what's going on?
promise action (creator) gets created
promise action (from creator) gets created and
dispatched to store.
Then the promise action gets converted to a awaitable promise action where its deferred version is saved into the meta property. The action is immediatelly returned and
passed to saga middleware.
The now awaitable promise action is qualified to be used in implementPromiseAction that nothing else does than resolving or rejecting the deferred promise that is saved inside the meta property of the awaitable promise action.
See README for more features and advanced use cases.
Another solution
onSubmit: (values) => {
return new Promise((resolve, reject) => {
dispatch(someActionCreator({ values, resolve, reject }))
});
}
In saga:
function* saga() {
while (true) {
const { payload: { values, resolve, reject } } = yield take(TYPE)
// use resolve() or reject() here
}
}
Reference: https://github.com/redux-saga/redux-saga/issues/161#issuecomment-191312502