Reputation: 10009
I just started a new project using infinitered/ignite.
I've added my getUserToken
function to the APITestScreen
So I know that the function works as expected, but I'm not able to hook the method up with the onPress
function to the button I added to the LaunchScreen.
I have imported it to the view, but nothing happens when I click the button. I have added an alert and a console.log, but they are not triggered. What should I do to get the fetchUserToken to run when I click the button?
The entire project posted posted at Github.
my view
import getUserToken from '../Sagas/AuthSagas.js';
<RoundedButton text="Fetch token" onPress={ getUserToken } />
App/Redux/AuthRedux.js
import { createReducer, createActions } from 'reduxsauce'
import Immutable from 'seamless-immutable'
/* ------------- Types and Action Creators ------------- */
const { Types, Creators } = createActions({
tokenRequest: ['username'],
tokenSuccess: ['token'],
tokenFailure: null
})
export const AuthTypes = Types
export default Creators
/* ------------- Initial State ------------- */
export const INITIAL_STATE = Immutable({
token: null,
fetching: null,
error: null,
username: null
})
/* ------------- Reducers ------------- */
// request the token for a user
export const request = (state, { username }) =>
state.merge({ fetching: true, username, token: null })
// successful token lookup
export const success = (state, action) => {
const { token } = action
return state.merge({ fetching: false, error: null, token })
}
// failed to get the token
export const failure = (state) =>
state.merge({ fetching: false, error: true, token: null })
/* ------------- Hookup Reducers To Types ------------- */
export const reducer = createReducer(INITIAL_STATE, {
[Types.TOKEN_REQUEST]: request,
[Types.TOKEN_SUCCESS]: success,
[Types.TOKEN_FAILURE]: failure
})
App/Sagas/AuthSagas.js
import { call, put } from 'redux-saga/effects'
import { path } from 'ramda'
import AuthActions from '../Redux/AuthRedux'
export function * getUserToken (api, action) {
console.tron.log('Hello, from getUserToken');
alert('in getUserToken');
const { username } = action
// make the call to the api
const response = yield call(api.getUser, username)
if (response.ok) {
const firstUser = path(['data', 'items'], response)[0]
const avatar = firstUser.avatar_url
// do data conversion here if needed
yield put(AuthActions.userSuccess(avatar))
} else {
yield put(AuthActions.userFailure())
}
}
Sagas/index.js
export default function * root () {
yield all([
// some sagas only receive an action
takeLatest(StartupTypes.STARTUP, startup),
// some sagas receive extra parameters in addition to an action
takeLatest(GithubTypes.USER_REQUEST, getUserAvatar, api),
// Auth sagas
takeLatest(AuthTypes.TOKEN_REQUEST, getUserToken, api)
])
}
Upvotes: 4
Views: 6131
Reputation: 4502
Sagas are great because they allow long running processes to control application flow in a completely decoupled manner, and can be sequenced via actions, allowing you to parallelise/cancel/fork/reconcile sagas to orchestrate your application logic in a centralised place (ie think of it as being able to link together actions, incorporating side effects along the way)
By importing your generator function and calling it directly like a normal function won't work and if it did would be bypassing saga functionality, for example if you press a second time or third time on that button, it will always execute the entire generator again from start to finish, which as they involve async operations could result in you say trying to store or use a token that is then immediately invalidated by a subsequent saga
Better practice would be to have your saga always listening for specific actions to trigger further worker sagas, keeping them decoupled, and allowing them to control their own flow.
In this case you would dispatch an action onPress
, and have a long running parent saga listening for that action which then hands off to your current one to do the actual work. This listening saga would then have control over cancelation of previous invocations using takeLatest
would cancel the previous saga invocation, so that a subsequent button press while the previous was still in flight would always take precedence, and your token cannot accidentally go stale
// AuthActions.js
// add a new action (or more probably adapt fetchUserToken to suit)...
export const GET_USER_TOKEN = 'auth/get-user-token'
export const getUserToken = (username) => ({
type: GET_USER_TOKEN,
payload: username
})
// view
import {getUserToken} from './AuthActions'
// this now dispatches action (assumes username is captured elsewhere)
// also assumes store.dispatch but that would more likely be done via `connect` elsewhere
<RoundedButton text="Fetch token" onPress={ () => store.dispatch(getUserToken(this.username)) } />
// AuthSagas.js
import api from 'someapi'
import actions from 'someactions'
import {path} from 'ramda'
import {put, call, takeLatest} from 'redux-saga/effects'
import AuthActions from '../Redux/AuthRedux'
// this will be our long running saga
export function* watchRequestUserToken() {
// listens for the latest `GET_USER_TOKEN` action,
// `takeLatest` cancels any currently executing `getUserToken` so that is always up to date
yield takeLatest(AuthActions.GET_USER_TOKEN, getUserToken)
}
// child generator is orchestrated by the parent saga
// no need to export (unless for tests) as it should not be called by anything outside of the sagas
function* getUserToken (action) { // the actual action is passed in as arg
const username = action.payload
// make the call to the api
const response = yield call(api.getUser, username)
if (response.ok) {
const firstUser = path(['data', 'items'], response)[0]
const avatar = firstUser.avatar_url
// do data conversion here if needed
yield put(AuthActions.userSuccess(avatar))
} else {
yield put(AuthActions.userFailure())
}
}
// main.js (example taken from https://redux-saga.js.org/) adapted to suite
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import {reducer} from './AuthRedux'
import {watchRequestUserToken} from './AuthSagas'
// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
// mount it on the Store
export const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
// then run the saga
sagaMiddleware.run(watchRequestUserToken)
Upvotes: 5
Reputation: 2294
On button you are calling fetchUserTocken
but in script you define getUserToken
.
Upvotes: 0