I have banged around in this for a while now. Looked at this question but its not quite what I want.
In a nutshell... I have const expression = to a function that is chained to another function that makes an API call in a separate file to LoginContainer but in the same folder -(its called reducer.js but has the actions as well at this stage). If successful it receives a token which it saves in local storage. this works fine.
Here it is.
import { fetch, addTask } from 'domain-task'
import { saveJwt, clearJwt } from '../auth/jwt'
import { handleErrors } from '../utils/http'
const REQUEST_LOGIN_TOKEN = 'REQUEST_LOGIN_TOKEN'
const RECEIVE_LOGIN_TOKEN = 'RECEIVE_LOGIN_TOKEN'
const ERROR_LOGIN_TOKEN = 'ERROR_LOGIN_TOKEN'
const REQUEST_USER = 'REQUEST_USER'
const RECEIVE_USER = 'RECEIVE_USER'
const ERROR_USER = 'ERROR_USER'
// ******************* action
export const requestLoginToken = (username, password) =>
(dispatch, getState) => {
dispatch({ type: REQUEST_LOGIN_TOKEN, payload: username })
const payload = {
userName: username,
password: password,
}
const task = fetch('/api/jwt', {
method: 'POST',
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json;charset=UTF-8'
},
})
.then(handleErrors)
.then(response => response.json())
.then(data => {
dispatch({ type: RECEIVE_LOGIN_TOKEN, payload: data })
saveJwt(data)
})
.catch(error => {
clearJwt()
dispatch({ type: ERROR_LOGIN_TOKEN, payload: error.message })
})
addTask(task)
return task
}
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import Login from './Login'
import { requestLoginToken } from './reducer'
class LoginContainer extends Component {
static contextTypes = {
router: PropTypes.object.isRequired
}
componentWillReceiveProps(nextProps) {
if (nextProps.isAuthorised) {
this.context.router.push('/')
}
}
submit = (values) => {
console.log('got values!', values)
this.props.requestLoginToken(values.username, values.password)
}
render() {
return (
<Login onSubmit={this.submit} />
)
}
}
const mapStateToProps = (state) => ({
isAuthorised: state.login.isAuthorised,
})
const mapDispatchToProps = (dispatch) => ({
requestLoginToken: (username, password) => dispatch(requestLoginToken(username, password)),
//requestSelectData: (values = {}) => dispatch(requestSelectData(values = {})),
})
export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer)
In the loginContainer (above), Once the "userName" and "password" have been entered and the submit button clicked, the expression "requestLoginToken" is called.
My Problem
I want to fetch a significant amount of data based on the above expression "requestLoginToken" successfully saving a JWT token into local storage. It does this successfully now with the right username and password.
I know I can't make another call from within the expression "requestLoginToken" using a ".then" as it specifically needs to retrieve and then save a token first - I have to wait till it finishes to know if I have a token. I need to run a second expression that only gets run if this promise is successful ie via a conditional statement. "If (JWT) etc"
1) Could someone tell me where and how I add this conditional statement. Im thinking its in the Logincontainer in the submit? ..how do would I structure the condition?
2) Where and how do I add the const = function for the retrieval of the data eg if I place it in another separate file do I still or even need to register it in mapDispatchToProps in the loginContainer etc
EDIT
Taking Nate Kimball's answer and running with it. Decided to split it out into its own "const" called "selectData" which I plan to call right underneath the line "saveJwt(data)".
However I find I am now getting an error:
Unexpected Token , expected
Its on the very last line of the following code block below.. (right curly bracket has red under it) checked it for sytax but cant workout why.
I think the approach is correct though.
const selectData = () => {
dispatch({ type: REQUEST_SELECT_DATA })
const token = jwt.access_token
const headers = new Headers({
'Authorization': `Bearer ${token}`
})
const selectData = fetch('/api/SelectData/SelectData', {
method: 'GET',
headers,
})
.then(handleErrors)
.then(response => response.json())
.then(data => {
dispatch({ type: RECEIVE_SELECT_DATA, payload: data })
.catch(error => {
clearJwt()
dispatch({ type: ERROR_SELECT_DATA, payload: error.message })
})
}
}
I don't see any reason why you couldn't nest a second fetch from within your action after a successful call:
export const requestLoginToken = (username, password) =>
(dispatch, getState) => {
dispatch({ type: REQUEST_LOGIN_TOKEN, payload: username })
const payload = {
userName: username,
password: password,
}
const task = fetch('/api/jwt', {
method: 'POST',
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json;charset=UTF-8'
},
})
.then(handleErrors)
.then(response => response.json())
.then(data => {
dispatch({ type: RECEIVE_LOGIN_TOKEN, payload: data })
saveJwt(data)
// Since this is where you receive your login token,
// You can dispatch an action to acknowledge you're fetching:
dispatch({ type: SECOND_DATA_FETCHING })
// This is also where you would make your next call:
fetch('/api/etc', { ...config })
.then(response => {
// You can use your reducer to both inform a successful call &
// store the received data
dispatch({ type: SECOND_DATA_SUCCESS, payload: response.data })
})
.catch(error => {
// Let your app know the call was unsuccessful:
dispatch({ type: SECOND_DATA_FAILED, payload: error.message })
})
// Note: if you don't like the nested ugliness, you could optionally
// put this entire nested fetch chain into a separate action and just
// dispatch that when you get your token.
})
.catch(error => {
clearJwt()
dispatch({ type: ERROR_LOGIN_TOKEN, payload: error.message })
})
addTask(task)
return task
}
At that point, all you need to do is update your mapStateToProps function in your component to receive the data and/or the status of that second layer of fetched data:
// Make sure you have a default status for that second data
// just in case your token call fails.
const mapStateToProps = (state) => ({
isAuthorised: state.login.isAuthorised,
secondData: state.login.secondData,
secondDataStatus: state.login.secondDataStatus
})
You can use requestLoginToken in another action creator:
function loginAndFetch() {
return function(dispatch, getState) {
dispatch(requestLoginToken()).then(token => {
return fetch(...) // use token here
})
}
}
As an alternative, you could save the token you got to the store, then have another component listen to changes to the token and dispatch another action when the token changes.
class Container extends Component {
componentDidUpdate(prevProps, prevState) {
if (this.props.token != prevProps.token) {
dispatch(fetchSignificantAmountOfData())
}
}
}
Container is connected and maps the stored token into props.token
You can write a custom middleware to solve this problem like this:
https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/redux/middleware/clientMiddleware.js
And then, you can use the action like this:
export function myAction() {
return {
types: [LOAD, SUCESS, FAIL],
promise: (client) => client.get('/some_api')
};
}
The middleware will dispatch the LOAD reducer first, then if promise is resolve, it call SUCESS; Otherwise, FAIL is called.
Related
I hope you can help me.
I'm trying to get a response from an API and use that information in another file.
I have 3 files:
api.jsx
import axios from 'axios';
export const api = (url, data) => {
const { path, method } = url;
let result ={};
axios({
method: method,
url: path,
data: data
})
.then(res => {
result = res.data;
console.log(res.data);
})
.catch(err => console.error(err));
return result;
};
url.jsx
export const URL = {
users:
{
getAllUsers: { path:'/users', method: 'post'},
login: { path:'/login', method: 'post'},
register: { path:'/register', method: 'post'},
version: { path:'/', method: 'get'},
}
}
app.js (within the render)
const data = {
email: 'hello#world.com',
password: '12345',
};
let result = api(URL.users.login, data);
console.log(result);
In the api file i get the proper response but in the react component no. I am aware that It's a problem of sync as i get first the console of app.jsx and later on the console of the api.jsx but i would like to respect the current structure or make something similar.
Any idea how to fix this without many changes?
PS. sorry about the mess. I tried to highlight all the code but for some reason it is not working fine.
You want to return a Promise in api.jsx
api.jsx
export const api = (url, data) => {
const { path, method } = url
return axios({ // the axios call returns a promise because of its .then; you can just return it
method: method,
url: path,
data: data
})
.then(res => {
return res.data;
})
.catch(err => {
console.error(err)
})
}
I am trying to use Axios post to create a user in my Django Rest Framework api.
Currently getting "Request failed with status code 400" when trying to post.
It works perfectly fine in postman.
drfServer.js
import axios from 'axios';
export default axios.create({
baseURL: 'https://example.com'
});
AuthContext.js
const signup = (dispatch) => async ({ email, password }) => {
try {
const response = await drfApi.post('/user/',
{
data: {
username: email,
password: password
}
}
);
// await AsyncStorage.setItem('token', response.data.token);
// dispatch({ type: 'signin', payload: response.data.token });
// navigate('Task')
} catch (err) {
console.log(err.message)
dispatch({ type: 'add_error', payload: 'Something went wrong with sign up' })
}
};
I tried using fetch and it works. But with Axios I am not getting it right.
Any ideas how to make it work?
Can you try this code.
const signup = ({email,password}) => dispatch => {
return axios({
method: "post",
url: "your api url",
data: {
username: email,
password
})
.then(result => {
console.log(result.data);
})
.catch(error => {
console.log(error);
})
};
You can find the axios example code here https://github.com/axios/axios
Maybe you can try this :
const response = await drfApi.post('/user/', {
username: email,
password: password
}
);
As using axios.post will automatically take the 2nd param and make it an object with data key
So I am testing this api request inside my react-redux application:
import $ from 'jquery';
window.$ = $;
const API_KEY = '<api-key>';
const ROOT_URL = `https://api.behance.net/v2/users?client_id=${API_KEY}`;
export const FETCH_USER = 'FETCH_USER';
export function fetchUser(users) {
const request = $.ajax({
url: `${ROOT_URL}&q=${users}`,
type: 'get',
data: { users: {} },
dataType: 'jsonp'
})
.done(response => {
console.log('Request:', request);
})
.fail(error => {
console.log('Ajax request fails');
console.log(error);
});
return {
type: FETCH_USER,
payload: request
};
}
However, in the Chrome console for Request: I am getting an object with readyState instead of a Promise, do I even need to have the package redux-promise at this point?
I see what you are trying to do, but I don't think is a good idea to send a promise to a reducer instead I will recommend you to use the middleware redux-thunk.
I will rewrite your action this way
export const fetchUser= ()=>(dispatch,getState)=>{
let params={
method:'get',
body:{ users: {} },
}
fetch( `${ROOT_URL}&q=${users}`, params).then
.then(response => response.json())
.then((response) =>{
dispatch({
type:Const.ON_RESPONSE_OK,
payload:response
})
})
.catch(error => {
dispatch({
type:Const.ON_RESPONSE_ERROR,
payload:Error
})
});
}
and rewrite the reducer to handle the payloads
Into my actionCreators I call some rest API endpoints on action e.g. UPDATE_CART_ITEM etc.
At first I was using axios like so return axios()...:
export const login = (username, password) => (dispatch) => {
dispatch(requestLogin())
const URL = `${USERS_URL}?username=${username}&password=${password}`
return axios(URL)
.then((response) => {
return response.data
})
.then((user) => {
dispatch(loginSuccess(user))
// save the user on localStorage
localStorage.setItem('user', JSON.stringify(user))
// direct the logedin user to the games page
history.push('/')
})
.catch(() => {
return dispatch(loginFailure())
})
}
Now I use async/await like so:
// On payload i have the obj: {prId: 'xxxxx'}
export const updateCartItem = (payload) => async (dispatch) => {
const response = await fetch('cart/update',
{
body: JSON.stringif(payload),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
method: 'POST',
})
// I m not sure if i have to return the result
const result = await response.json()
// I dispatch the action with the payload
await dispatch(return {
payload,
type: UPDATE_CART_ITEM,
})
} catch (err) {
dispatch(cartActionFail(err))
}
}
So, inside the updateCartItem function, how should I handle the result ?
Since I'm passing payload to the reducer, it seems that I don't need it.
You probably want to do something like this:
dispatch({ payload: response, type: UPDATE_CART_ITEM })
dispatch(return { /*...*/ }) doesn't make sense as far as I know, and dispatch() doesn't return a promise so there's no point in awaiting on it.
In general, if you want to replace promise chains with async/await, then you want to replace foo.then(bar => { baz(bar); }) with const bar = await foo; baz(bar);
If you need to consume your result immediatly, then you should dispatch an action like UPDATE_CART_ITEM_SUCCEED, otherwise do nothing.
BTW, I recommend you to use redux-saga or redux-thunk to handle your app side effects such as API calls.
If you use the same payload for your action creator what happens if something goes wrong at your backend? Your backend side won't change but your state won't be aware of that and updates itself with payload. This is why you should use some error check here. Also, personally I use last result as a payload to my action creators, not the original payload.
export const updateCartItem = payload => async ( dispatch ) => {
try {
const response = await fetch(
"cart/update",
{
body: JSON.stringif( payload ),
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
method: "POST",
}
);
if ( !response.ok ) { throw new Error( "response error" ); }
const result = await response.json();
return dispatch( {
payload: result,
type: UPDATE_CART_ITEM,
} );
} catch ( error ) {
return dispatch( cartActionFail( error.message ) );
}
};
You can change and enhance this logic according to your needs. As #vkarpov15 pointed out dispatch does not use return explicitly and it does not return a promise, hence you don't need await there.
I am developing an application where there are lots of async actions. I wanted to go with redux-saga but most have insisted to continue with redux-thunk. In redux-thunk, inside each action we have to work with async operation using then, dispatch, catch, etc. This makes looks actions so messy and lots of code will be repeated. I wanted to create a generic dataLoader for the use of redux-thunk and axios but could not consider for both post(might be token or not) and get option.
Here is my attempt:
export class Company {
/**
* Generic api data loader
*/
static dataLoader(apiUri, onSuccess, onError, data, ...actionArguments) {
const requestURL = `${API_BASE}${apiuri}`;
try {
let options;
if (data !== undefined) {
// if we have data to post
options = {
method: 'POST',
url: requestURL,
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
};
}
}
return function(dispatch) {
axios(options)
.then(response => {
dispatch({
type: onSucess,
payload: response.data
});
})
.catch(error => {
dispatch({ type: onError, payload: err});
});
}
}
static get(apiUri, onSuccess, onError, ...actionArguments) {
return this.dataLoader(apiUri, onSuccess, onError, undefined, ...actionArguments);
}
/*
* Shorthand POST function
*/
static post(apiUri, onSuccess, onError, data, ...actionArguments) {
return this.dataLoader(apiUri, onSuccess, onError, data, ...actionArguments);
}
}
I want to convert the following code to further this one:
export function showResultofApartment() {
return (dispatch) => {
dispatch({ type: 'APARTMENT_FETCH_START' });
const token = localStorage.getItem('token');
return axios.get(`${API_URL}/newoffers/apartment/`)
.then((response) => {
console.log('response apart', response.data);
dispatch({ type: 'APARTMENT_FETCH_SUCCESS', payload: response.data });
})
.catch((err) => {
dispatch({ type: 'APARTMENT_FETCH_FAILURE', payload: err });
});
};
}
to such or more efficient than this:
export function showResultofApartment() {
return(dispatch) => {
dispatch({ type: APARTMENT_FETCH_START });
const token = localStorage.getItem('token');
return Company.get('/apartments', APARTMENT_FETCH_SUCCESS, APARTMENT_FETCH_ERROR);
// if post then Company.post('/apartment', APARTMENT_POST_SUCCESS, APARTMENT_POST_ERROR, data)
}
}
This way it is considering only post request(if data !== undefined). How should i handle for both get and post efficiently?
Okay, why don't you handle it like this:
Company.js
import { merge } from 'lodash';
import axios from 'axios';
function getHeaders() {
return {
'Content-Type': 'application/json'
};
}
export class Company {
static callAPI(endpoint, extendedOptions, onSuccess, onError) {
const initalHttpData = {
method: 'GET', // By default it's GET in case you didnt specify anything
headers: getHeaders(),
url: `${API_BASE}${endpoint}`
};
// merge takes care of replacing or adding the specific key's provided via the extendedOptions
const options = merge(initalHttpData, extendedOptions);
// Fire the request for the prepared options.
let request = axios(options);
// The request once fired, needs it's success handler and error handler.
return function(dispatch) {
request
.then(response => {
dispatch({
type: onSucess,
payload: response.data
});
})
.catch(error => {
dispatch({ type: onError, payload: err});
});
}
};
}
Then we can use actions to specifically pass things to this api util:
GET API call:
// GET Action
export function showResultofApartment() {
return (dispatch) => {
dispatch({ type: APARTMENT_FETCH_START });
const token = localStorage.getItem('token');
// FOR GET API
return Company.callApi('/apartments', {}, APARTMENT_FETCH_SUCCESS, APARTMENT_FETCH_ERROR);
}
}
POST API call:
// POST Action
export function showResultOfAppartmentPost() {
return (dispatch) => {
dispatch({ type: APARTMENT_FETCH_START });
const token = localStorage.getItem('token');
// This will merge, essentially replace the method=GET once it gets called.
const extendedOptions = {
method: 'POST',
body: JSON.stringify(data),
headers: {
'X-Requested-With': 'XMLHttpRequest',
}
}
// FOR GET API
return Company.callApi('/apartments', extendedOptions, APARTMENT_FETCH_SUCCESS, APARTMENT_FETCH_ERROR);
}
Thus, giving the action, to define it's own set of API body or requests.