Reputation: 4035
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 })
})
}
}
Upvotes: 2
Views: 7777
Reputation: 1586
You can write a custom middleware to solve this problem like this:
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.
Upvotes: 0
Reputation: 968
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
})
Upvotes: 2
Reputation: 9812
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
Upvotes: 1