I am using redux saga in my React Native app. It all works except for one thing. For one of my actions when it is dispatched the first time, it stops at 'const newUserLogAction = yield take('CREATE_USER_LOG')' in the code below. So the console log 'saga callCreateUserLog take' is not printed, nothing happens. But if I dispatch the same action again, it works. I have other actions and they all work fine the first time.
saga file:
function * createUserLog () {
yield takeEvery('CREATE_USER_LOG', callCreateUserLog)
}
export function * callCreateUserLog () {
try {
console.log('saga callCreateUserLog start')
const newUserLogAction = yield take('CREATE_USER_LOG')
console.log('saga callCreateUserLog take')
console.log('newUserLogAction' + FJSON.plain(newUserLogAction))
const data = newUserLogAction.data
const newUserLog = yield call(api.createUserLog, data)
yield put({type: 'CREATE_USER_LOG_SUCCEEDED', newUserLog: newUserLog})
} catch (error) {
yield put({type: 'CREATE_USER_LOG_FAILED', error})
}
}
action file:
export const createUserLog = (data) => {
console.log('action create user log data = ' + FJSON.plain(data))
return ({
type: 'CREATE_USER_LOG',
data}
)
}
Even on the first dispatch, the data is printed correctly here, so the payload is correct.
react native functions doing the dispatching:
clickContinue = () => {
var event = {
'userId': this.props.user.id,
'eventDetails': {
'event': 'Agreed to the ISA Declaration'
}
}
this.props.dispatch(createUserLog(event))
}
You don't need to use take effect. Action object will be passed by takeEvery.
export function * callCreateUserLog (newUserLogAction) {
try {
console.log('saga callCreateUserLog start')
console.log('newUserLogAction' + FJSON.plain(newUserLogAction))
const data = newUserLogAction.data
const newUserLog = yield call(api.createUserLog, data)
yield put({type: 'CREATE_USER_LOG_SUCCEEDED', newUserLog: newUserLog})
} catch (error) {
yield put({type: 'CREATE_USER_LOG_FAILED', error})
}
}
Related
I joined a big/medium project, I am having a hard time creating my first redux-saga-action things, it is going to be a lot of code since they are creating a lot of files to make things readable.
So I call my action in my componentDidMount, the action is being called because I have the alert :
export const fetchDataRequest = () => {
alert("actions data");
return ({
type: FETCH_DATA_REQUEST
})
};
export const fetchDataSuccess = data => ({
type: FETCH_DATA_SUCCESS,
payload: {
data,
},
});
This is my history saga : ( when I call the action with this type, The function get executed )
export default function* dataSaga() {
// their takeEverymethods
yield takeEvery(FETCH_DATA_REQUEST, fetchData);
}
This is what has to be called : ( I am trying to fill my state with data in a json file : mock )
export default function* fetchTronconsOfCircuit() {
try {
// Cal to api
const client = yield call(RedClient);
const data = yield call(client.fetchSomething);
// mock
const history = data === "" ? "" : fakeDataFromMock;
console.log("history : ");
console.log(history);
if (isNilOrEmpty(history)) return null;
yield put(fetchDataSuccess({ data: history }));
} catch (e) {
yield put(addErr(e));
}
}
And this is my root root saga :
export default function* sagas() {
// many other spawn(somethingSaga);
yield spawn(historySaga);
}
and here is the reducer :
const fetchDataSuccess = curry(({ data }, state) => ({
...state,
myData: data,
}));
const HistoryReducer = createSwitchReducer(initialState, [
[FETCH_DATA_SUCCESS, fetchDataSuccess],
]);
The method createSwitchReducer is a method created by the team to create easily a reducer instead of creating a switch and passing the action.type in params etc, their method is working fine, and I did exactly what they do for others.
Am I missing something ?
I feel like I did everything right but the saga is not called, which means it is trivial problem, the connection between action and saga is a common problem I just could not figure where is my problem.
I do not see the console.log message in the console, I added an alert before the try-catch but got nothing too, but alert inside action is being called.
Any help would be really really appreciated.
yield takeEvery(FETCH_DATA_REQUEST, fetchData);
should be
yield takeEvery(FETCH_DATA_REQUEST, fetchTronconsOfCircuit);
I am trying to debounce a method within a Vuex action that requires an external API.
// Vuex action:
async load ({ state, commit, dispatch }) {
const params = {
period: state.option.period,
from: state.option.from,
to: state.option.to
}
commit('SET_EVENTS_LOADING', true)
const res = loadDebounced.bind(this)
const data = await res(params)
console.log(data)
commit('SET_EVENTS', data.collection)
commit('SET_PAGINATION', data.pagination)
commit('SET_EVENTS_LOADING', false)
return data
}
// Debounced method
const loadDebounced = () => {
return debounce(async (params) => {
const { data } = await this.$axios.get('events', { params })
return data
}, 3000)
}
The output of the log is:
[Function] {
cancel: [Function]
}
It is not actually executing the debounced function, but returning to me another function.
I would like to present a custom debounce method which you can use in your vuex store as
let ongoingRequest = undefined;
const loadDebounced = () => {
clearTimeout(ongoingRequest);
ongoingRequest = setTimeout(_ => {
axios.get(<<your URL>>).then(({ data }) => data);
}, 3000);
}
This method first ensures to cancel any ongoing setTimeout in the pipeline and then executes it again.
This can be seen in action HERE
I'm making an app that fetch events object from firestore to display it on a map.
I implemented redux-saga to do the async call to firestore API on componentDidMount in order to display the results on the map.
I have 3 actions (LOAD_EVENTS_LOADING / LOAD_EVENTS_SUCCESS / LOAD_EVENTS_ERROR) so that I can display a loading component before rendering the results.
saga.js :
> import { put, call, takeLatest } from 'redux-saga/effects' import {
> getEventsFromGeoloc } from '../../firebaseAPI/APImethods'
>
> function* fetchEvents(action) {
> try {
>
> const events = yield call(getEventsFromGeoloc, {latMarker : action.payload.latMarker, longMarker: action.payload.longMarker,
> circleRadius: action.payload.circleRadius});
> yield put({type: 'LOAD_EVENTS_SUCCESS', fetchedEvents: events});
> } catch (e) {
> yield put({type: 'LOAD_EVENTS_ERROR', error: e.message});
> } }
>
> export function* eventsSaga() {
> yield takeLatest('LOAD_EVENTS_LOADING', fetchEvents); }
>
> export default eventsSaga;
My problem is that in my saga the action "LOAD_EVENTS_SUCCESS" is dispatched before the API call ends.
How do I make sure the API call is completed before dispatching the "LOAD_EVENTS_SUCCESS" action ?
Thanks for your help !
Here is my API method :
import firebase from 'react-native-firebase';
import { GeoFirestore } from 'geofirestore';
export const getEventsFromGeoloc = (payload) => {
let events =[]
const geoFirestore = new GeoFirestore(firebase.firestore());
const geoCollection = geoFirestore.collection('events');
const query = geoCollection.near({
center: new firebase.firestore.GeoPoint(payload.latMarker, payload.longMarker),
radius: payload.circleRadius
});
query.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
const idEvent = doc.id
const eventData = doc.data()
events.push({idEvent, eventData})
});
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});
return events;
}
getEventsFromGeoloc is not returning a promise for redux-saga to await, that's the problem
Just return a promise that resolve with the events:
export const getEventsFromGeoloc = (payload) => {
// ...
return query.get()
.then(function(querySnapshot) {
const events = []
querySnapshot.forEach(function(doc) {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
const idEvent = doc.id
const eventData = doc.data()
events.push({idEvent, eventData})
});
return events
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});
}
call(fn, ...args) fn: Function - A Generator function, or normal function which either returns a Promise as result, or any other value. Refer this docs In your case you are returning events from your getEventsFromGeoloc func even when your response has not yet arrived.
Firebase works differently than you would expect. The firebase queries return immediately, they don't wait for the query to finish and be processed by your code before it returns.
What I ended up doing in my own apps, was instead of calling the success/fail action from the original saga function, I called them from within my query callback, something like this:
query.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
const idEvent = doc.id
const eventData = doc.data()
events.push({idEvent, eventData})
});
yield put({type: 'LOAD_EVENTS_SUCCESS', fetchedEvents: events});
})
.catch(function(error) {
console.log("Error getting documents: ", error);
yield put({type: 'LOAD_EVENTS_ERROR', error: error.message});
});
NOTE: This will also require you make your getEventsFromGeoloc function a generator function, or a saga, to use the yield statements as well as import the put saga effect.
OTHER NOTE: You could make the function more generic by passing in a callback function for success and failure and just call the callback instead of hard coding the action you are dispatching like this. Or you could pass in the action type and payload keys to build the action inside the completion callback for firebase.
Let me know if that wasn't helpful enough, or if you have questions about what I've put up here.
thanks all for your answers. I did a saga with my getEventsFromGeoloc and with a yield for the query its working like I expected : It waits for the complete query to run and then return the array of events and dispatch the LOAD_EVENTS_SUCCESS action.
Here is my saga :
import { put, takeLatest } from 'redux-saga/effects'
import firebase from 'react-native-firebase';
import { GeoFirestore } from 'geofirestore';
function* getEventsFromGeoloc(action) {
try{
let events =[]
const geoFirestore = new GeoFirestore(firebase.firestore());
const geoCollection = geoFirestore.collection('events');
const query = geoCollection.near({
center: new firebase.firestore.GeoPoint(action.payload.latMarker, action.payload.longMarker),
radius: action.payload.circleRadius
});
yield query.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
const idEvent = doc.id
const eventData = doc.data()
events.push({idEvent, eventData})
})})
yield put({type: 'LOAD_EVENTS_SUCCESS', fetchedEvents: events});
}
catch(error) {
console.log("Error getting documents: ", error);
yield put({type: 'LOAD_EVENTS_ERROR', error: error.message});
}
}
export function* eventsSaga() {
yield takeLatest('LOAD_EVENTS_LOADING', getEventsFromGeoloc);
}
export default eventsSaga;
In react-native backhandler listener react to callback function and act appropriately.
I need to read my store and depending on it, return true or false.
But I cant use select effect in normal function and I cant affect listener callback function from "watchBackButton" function.
export function* backButtonListen() {
return eventChannel(emitter => {
const backHandlerListener = BackHandler.addEventListener(
"hardwareBackPress",
() => {
emitter("back pressed");
}
);
return () => {
backHandlerListener.remove();
};
});
}
export function* watchBackButton() {
const chan = yield call(backButtonListen);
try {
while (true) {
let back = yield take(chan);
}
}
Since event channels are not bidirectional, I don't think there is a way to get some current state from saga to event channel using the select effect.
However, it is possible to access the store directly. There are multiple ways to get the store instance to the event channel. See my other answer here.
Using e.g. the context method you could do something like this:
// redux.js
...
const store = createStore(...);
sagaMiddleware.runSaga(rootSaga, {store});
// root-saga.js
export default function * rootSaga(context) {
yield setContext(context);
yield fork(watchBackButton);
}
// watch-back-button.js
export function* backButtonListen() {
const store = yield getContext('store');
return eventChannel(emitter => {
const backHandlerListener = BackHandler.addEventListener(
"hardwareBackPress",
() => {
emitter("back pressed");
return store.getState().foo === 'bar';
}
);
return () => {
backHandlerListener.remove();
};
});
}
export function* watchBackButton() {
const chan = yield call(backButtonListen);
try {
while (true) {
let back = yield take(chan);
}
}
Good day. I have the following problem:
I have an item editor.
How it works: I push 'Add' button, fill some information, click 'Save' button.
_onSaveClicked function in my react component handles click event and call function from service, which sends params from edit form to server and return promise.
_onSaveClicked implements
.then(response => {
console.log('I\'m in then() block.');
console.log('response', response.data);
})
function and waits for promise result. It works in real situation.
I created fake service and placed it instead of real service.
Service's function contains:
return Promise.resolve({data: 'test response'});
As you can see fake service return resolved promise and .then() block should work immediatly. But .then() block never works.
Jest test:
jest.autoMockOff();
const React = require('react');
const ReactDOM = require('react-dom');
const TestUtils = require('react-addons-test-utils');
const expect = require('expect');
const TestService = require('./service/TestService ').default;
let testService = new TestService ();
describe('TestComponent', () => {
it('correct test component', () => {
//... some initial code here
let saveButton = TestUtils.findRenderedDOMComponentWithClass(editForm, 'btn-primary');
TestUtils.Simulate.click(saveButton);
// here I should see response in my console, but I don't
});
});
React component save function:
_onSaveClicked = (data) => {
this.context.testService.saveData(data)
.then(response => {
console.log('I\'m in then() block.');
console.log('response', response.data);
});
};
Service:
export default class TestService {
saveData = (data) => {
console.log('I\'m in services saveData function');
return Promise.resolve({data: data});
};
}
I see only "I'm in services saveData function" in my console.
How to make it works? I need to immitate server response.
Thank you for your time.
You can wrap your testing component in another one like:
class ContextInitContainer extends React.Component {
static childContextTypes = {
testService: React.PropTypes.object
};
getChildContext = () => {
return {
testService: {
saveData: (data) => {
return {
then: function(callback) {
return callback({
// here should be your response body object
})
}
}
}
}
};
};
render() {
return this.props.children;
}
}
then:
<ContextInitContainer>
<YourTestingComponent />
</ContextInitContainer>
So your promise will be executed immediately.