I am unable to fetch firebase child data with React and Redux-Saga. Can you please check the below code ? Why it is not working with redux-saga.
import { takeLatest, put, call, fork, all, take } from "redux-saga/effects";
import { getContactsSuccess, getContactsFail } from "./actions";
import * as types from "./actionTypes";
import firebaseDb from "../firebase";
export function* onGetContactsStartSync() {
try {
const snapshot = yield firebaseDb.child("contacts").on("value");
yield put(getContactsSuccess(snapshot.val()));
console.log("snapshot", snapshot);
} catch (error) {
yield put(getContactsFail(error));
}
}
export function* onLoadContacts() {
yield takeLatest(types.GET_CONTACTS_START, onGetContactsStartSync);
}
const contactSagas = [fork(onLoadContacts)];
export default function* rootSaga() {
yield all([...contactSagas]);
}
When I tried with redux-thunk then I was able to fetch the data from firebase with the below approach
export const getContacts = () => {
return function (dispatch) {
dispatch(getContactsStart());
firebaseDb.child("contacts").on("value", (snapshot) => {
try {
if (snapshot.val() !== null) {
dispatch(getContactsSuccess(snapshot.val()));
} else {
dispatch(getContactsSuccess({}));
}
} catch (error) {
dispatch(getContactsFail(error));
}
});
};
};
So, what is going wrong with redux-saga approach ? I wanted to replicate the same thing with redux-saga.
I think i am missing some small thing here, which is might be very basic. Let me know if have some solution.
I tried the below approach as well.
function createContactChannel() {
const listener = eventChannel((emit) => {
firebaseDb.child("contacts").on("value", (data) => {
if (data.val() !== null) {
emit(data.val());
}
});
return () => firebaseDb.child("contacts").off(listener);
});
console.log("listener", listener);
return listener;
}
export function* onGetContactsStartSync() {
try {
const contacts = createContactChannel();
yield put(getContactsSuccess(contacts));
} catch (error) {
yield put(getContactsFail(error));
}
}
Related
I have a lot of functions looking like this
doSomething = async (...) => {
try {
this.setState({loading: true});
...
var result = await Backend.post(...);
...
this.setState({loading: false});
} catch(err) {
this.setState({error: err});
}
}
Basically I have 2 variables loading & error that I have to manage for a lot of functions and the code is basically the same for all of them. Since there are no decorators in javascript and I do not wish to install any experimental lib for that how could I wrap this function to remove the duplicated setStates from above ?
Here is my current way, I pass the function as parameter.
We have many API, fetch data form backend, we have to handle error and do something with data.
Only data of service are different, the handling error is the same.
private processServiceResponse(resp: any, doSthWithData: (data: any) => void) {
let { errors } = resp;
if (this.hasError(errors)) {
this.handleServiceErr(errors);
return;
}
let data = resp;
if (resp && resp.data) {
data = resp.data;
}
doSthWithData(data);
}
And here is how i pass function as parameter.
let rest1 = service1.getData();
processServiceResponse(rest1,(data)=>{
//only need to focus with processing data.
})
PS: It's typescript coding.
if you are using function conponents, you can define a custom hook to avoid repeat code
//useFetch.js
import { useState, useEffect } from 'react';
import axios from 'axios';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
setLoading('loading...')
setData(null);
setError(null);
const source = axios.CancelToken.source();
axios.get(url, { cancelToken: source.token })
.then(res => {
setLoading(false);
//checking for multiple responses for more flexibility
//with the url we send in.
res.data.content && setData(res.data.content);
res.content && setData(res.content);
})
.catch(err => {
setLoading(false)
setError('An error occurred. Awkward..')
})
return () => {
source.cancel();
}
}, [url])
return { data, loading, error }
export default useFetch;
usage:
import useFetch from './useFetch';
import './App.css';
function App() {
const { data: quote, loading, error } =
useFetch('https://api.quotable.io/random')
return (
<div className="App">
{ loading && <p>{loading}</p> }
{ quote && <p>"{quote}"</p> }
{ error && <p>{error}</p> }
</div>
);
}
export default App;
You can use a Higher Order Function (a function that takes as argument another function) to make the common loading and error functionality reusable. It is very similar to a decorator pattern. For example:
const doSomething = withLoadingAndErrorHandling(Backend.post, this.setState);
function withLoadingAndErrorHandling(fn, setState) {
return async function(...args) {
try {
setState({loading: true});
var result = await fn(args);
setState({loading: false});
return result;
} catch(err) {
setState({error: err});
}
}
}
I am making a get request from my API (Django REST framework) and this returns data (name,img of products). I am using Redux-saga as my middleware in React-Native with Redux. However, I can't seem to receive the data.
Here is my action for the get request:
export function getProductList() {
type:GET_PRODUCTS_LIST
}
And as well here is my api.js file:
const urlGetProducts = 'https://hero-pregbackend.herokuapp.com/shop/';
const urlOrderUltrascan = 'https://hero-pregbackend.herokuapp.com/ultrascan/order_scan/';
export const getProductsFromApi = function* (){
const response = yield fetch(urlGetProducts, {
method:'GET',
headers:{
'Accept':'application/json, application/xml, text/plain, text/html, *.*',
// 'Content-Type':'application/json',
},
body: '',
});
const products = yield response.status === 200 ? response.json():[]
return products;
}
export const Api = {
getProductsFromApi,
}
My sagas file:
import {GET_PRODUCTS_LIST,GET_PRODUCTS_ERROR} from '../actions/products';
import {ULTRASCAN_SUCCESS,ULTRASCAN_FAIL} from '../actions/ultrascan';
import { takeEvery, call, put, select,takeLatest} from 'redux-saga/effects';
import {Api} from './api';
function* fetchProducts(){
try {
const receivedProducts = yield Api.getProductsFromApi();
yield put({type:GET_PRODUCTS_LIST,receivedProducts:receivedProducts})
}catch(error){
yield put({type:GET_PRODUCTS_ERROR,error});
}
}
export function* productSaga(){
yield takeEvery(GET_PRODUCTS_LIST,fetchProducts);
}
My reducer for the action:
import { GET_PRODUCTS_LIST,GET_PRODUCTS_ERROR,GET_PRODUCT_DETAILS,GET_DETAILS_ERROR} from '../actions/ultrascan';
const initialState = {
productList: [],
// productDetails:[],
isFetching:false,
error:null,
};
const productsReducer = (state = initialState,action) => {
switch(action.type){
case GET_PRODUCTS_LIST:
return {
...state,
// isFetching:true,
productList:action.receivedProducts,
isFetching:false,
}
case GET_PRODUCTS_ERROR:
return {
...state,
error:action.error
}
default:
return state;
}
}
export default productsReducer;
Change your action to pass the response
export function getProductList(receivedProducts) {
return {
type: GET_PRODUCTS_LIST,
receivedProducts
}
}
Also change your API response:
function* fetchProducts(){
try {
const receivedProducts = yield Api.getProductsFromApi();
yield put(getProductList(receivedProducts))
....
I have setup my firebase functions and it works perfectly. The data flow for my app goes thus:
User fills a form and submits {works}
The data is sent to the real-time database {works}
The data is then passed back to the redux store by listening to the database ref. {does not work}
The data is then displayed in a table true the redux store for the user to see his/her submitted info. {does not work because 3 does not work}
Where I'm experiencing the issue is number 3.
Here is my Redux action file:
import {
SAVE_FORM,
UPDATE_STORE
} from "./types";
export const saveForm = user => {
return {
type: SAVE_FORM,
payload: user
};
};
export const updateStore = data => {
return {
type: UPDATE_STORE,
payload: data
};
};
And here is my reducer:
import {
SAVE_FORM,
UPDATE_STORE
} from "../actions/types";
const initialState = {
users: [],
db: ""
};
export default function (state = initialState, action) {
switch (action.type) {
case SAVE_FORM:
return {
...state,
users: [action.payload]
};
case UPDATE_STORE:
return {
db: [action.payload]
};
default:
return state;
}
}
And finally my sagas file:
import {
database
} from "./firebase-config"
import axios from "axios"
import {
put,
fork,
takeEvery,
take
} from 'redux-saga/effects'
import {
eventChannel
} from "redux-saga"
import {
SAVE_FORM,
UPDATE_STORE
} from "./actions/types"
export function* sendRequest({
payload
}) {
//console.log(payload);
yield axios.post('https://dummy.com/user', payload)
.then(res => {
// here will be code
console.log(res)
})
.catch(error => {
console.log(error);
});
};
export function* watchSendAction() {
yield takeEvery(SAVE_FORM, sendRequest);
}
function createEventChannel() {
const listener = eventChannel(emit => {
database.ref('entries/users').on('value', data => emit(data.val()));
return () => database.ref('entries/users').off(listener);
});
return listener;
}
//This is supposed to update my store with the data received by the database
function* startListener() {
const updateChannel = createEventChannel();
while (true) {
const data = yield takeEvery(updateChannel);
yield put(UPDATE_STORE(data));
}
}
export default function* helloSaga() {
yield fork(watchSendAction);
yield fork(startListener);
}
I can't seem to figure what I'm doing wrong.
I suspect the problem is in your startListener saga. You should use either while cycle with the take effect or use takeEvery without while cycle. Right now you are mixing the two together.
Try this:
function* startListener() {
const updateChannel = createEventChannel();
while (true) {
const data = yield take(updateChannel);
yield put(UPDATE_STORE(data));
}
}
or this:
function* startListener() {
const updateChannel = createEventChannel();
yield takeEvery(updateChannel, function*(data) {
yield put(UPDATE_STORE(data));
});
}
I have a UPDATE_DATE action on redux saga.
I want to update multiple resources every time that Date is modified. I would like to implement a listener to action UPDATE_DATE_SUCCESS that triggers a callback. Any idea? I would be great if I can call it from the React component.
Action:
UPDATE_DATE
UPDATE_DATE_SUCCESS
UPDATE_DATE_FAILURE
export const {
updateDate,
OnDateChange,
} = createActions({
[ActionTypes.UPDATE_DATE]: (date) => date,
});
Saga
export function* updateDate(newDate) {
console.log('from sagas to reducer', newDate); // eslint-disable-line no-console
try {
yield put({
type: ActionTypes.UPDATE_DATE_SUCCESS,
payload: newDate,
});
} catch (err) {
/* istanbul ignore next */
yield put({
type: ActionTypes.UPDATE_DATE_FAILURE,
payload: err,
});
}
}
export default function* root() {
yield all([
takeLatest(ActionTypes.UPDATE_DATE, updateDate),
takeEvery(ActionTypes.UPDATE_DATE_SUCCESS, test),
]);
}
Desirable implementation on React Component
componentDidMount() {
const { dispatch } = this.props;
dispatch(onDateChange((newDate) => {
dispatch(getApps(newDate));
dispatch(getPlatforms(newDate));
dispatch(getNetworks(newDate));
dispatch(getCountries(newDate));
dispatch(getFormats(newDate));
dispatch(getDevices(newDate));
dispatch(getNetworkKeys(newDate));
}));
}
Any help? thank you!
The idea behind Redux Sagas is to handle the side-effects of an action/event on the Saga rather than in the components.
I think the way of doing that would be something like this (Note the snippet code is not tested, use it as reference/example only):
// --> Saga <-- //
import { getApps, getPlatforms, getNetworks, getCountries, getFormats, getDevices, getNetworkKeys } from './actions.js';
export function* updateDateWatcher(action) {
const { payload: newDate } = action;
if (!newDate) return;
try {
// dispatch all required actions
yield all([
put(getApps(newDate),
put(getPlatforms(newDate),
put(getNetworks(newDate)),
put(getCountries(newDate)),
put(getFormats(newDate)),
put(getDevices(newDate)),
put(getNetworkKeys(newDate)),
]);
// then trigger a success action if you want to
yield put(updateDataSuccess());
} catch (err) {
// if something wrong happens in the try this will trigger
yield put(updateDateFailure(err));
}
}
export default function* root() {
yield all([
takeLatest(ActionTypes.UPDATE_DATE, updateDateWatcher),
]);
}
// --> Component <-- //
import { updateDate } from './actions.js';
class MyComponent extends Component {
componentDidMount() {
const { onDateChange } = this.props;
const date = new Date();
onDateChange(date); // -> triggers ActionTypes.UPDATE_DATE
}
}
const mapDispatchToProps = dispatch => ({
onDateChange: (newDate) => dispatch(updateDate(newDate)),
});
export default connect(null, mapDispatchToProps)(MyComponent);
I dont quite understand what you are trying to do but... if you just want a listener, why dont just subscribe a take action that listen for an specific action?
function * mySuperSagaListener() {
const listener = yield take('some_action_name')
// calculate the new state here
// here
// here
// when you are done... update the state
const updateState = yield put({type: 'some_action_name_update', payload: newState})
}
Then you components only will be subscribe of the piece of the state via react-redux HOC... and will update according... and if you want to dispatch actions from the components just:
dispatch({type: 'some_action_name'})
Best!
I'm use AsyncStorage, but I think I do not use correcly...
file functions.js
import { AsyncStorage } from 'react-native';
const Functions = {
async storeItem(key, item) {
try {
AsyncStorage.setItem()
var jsonOfItem = await AsyncStorage.setItem(key, JSON.stringify(item));
return jsonOfItem;
} catch (error) {
console.warn(error.message);
}
},
async retrieveItem(key) {
try {
const retrievedItem = await AsyncStorage.getItem(key);
const item = JSON.parse(retrievedItem);
return item;
} catch (error) {
console.warn(error.message);
}
}
}
export default Functions;
File home.js
import Functions from 'Constants/functions';
export default class Home extends Component {
constructor(props) {
super(props);
product = Functions.retrieveItem('products');
console.warn(product);
}
}
console.warn(product) returned
{"_40":0,_65":1,"_55":null,"_72":null}
I believe that this happens because I receive the object before it has been processed. Because if I put a console.warn before the return of the retrieveItem function it shows the object well...
is a Promise so... you need to use
Functions.retrieveItem('products').then((res) => { //do something with res });