How do I export saga and combine? - redux

I'm trying to separate sagas into multiple files.
In analytics.js:
const logEvent = function* logEvent() {
while (true) {
const action = yield take(action_type.LOG_EVENT)
let params = {
...action.payload.eventParams,
}
firebaseAnalytics.logEvent(
action.payload.eventName,
params
)
}
}
export const analyticsSagas = [
logEvent
]
I'm trying to use it like from another file index.js:
import { analyticsSagas } from 'analytics'
const rootSaga = function* rootSaga() {
yield all([
analyticsSagas,
])
}
But it doesn't seem that sagas are being run with the approapriate actions.

Export your saga function from analytics.js like this:
export function* logEvent() {
// your code here
}
Then in your index.js, you can just import it and then invoke the saga using yield*.
import { logEvent } from 'filePath/analytics.js';
export function* rootSaga() {
yield* logEvent();
}

Related

Use variables (or store) in all page/component, in nuxt3

I am using nuxt3, pinia.
I can use the user, is_login variables in a specific vue page, as seen below.
import { useAuthStore } from "~/stores/myCustomAuthStore";
import { storeToRefs } from 'pinia'
const authStore = useAuthStore();
const {user, is_login} = storeToRefs(authStore)
What I want is to use the user, is_login variables in another page (or component) without writing the 4 lines of code above.
I think I need to use a plugin or module or nuxtApp.provide, how should I do it detaily?
------ what i tried is -------
I made plugins/common.ts
import { useAuthStore } from "~/stores/myCustomAuthStore";
import { storeToRefs } from 'pinia'
export default defineNuxtPlugin((nuxtApp) => {
const authStore = useAuthStore();
const {user, is_login} = storeToRefs(authStore)
nuxtApp.provide('user', user.value)
nuxtApp.provide('is_login', is_login.value)
}
and I put below code every
const is_login = useNuxtApp().$is_login
const user = useNuxtApp().$user
This is not work.
You can write a composable for this (see https://nuxt.com/docs/guide/directory-structure/composables#composables-directory):
Create a composables/use-auth.ts file in the "composables" directory
// composables/use-auth.ts
import { useAuthStore } from '~/stores/myCustomAuthStore';
import { storeToRefs } from 'pinia';
export const useAuth = () => {
const pinia = usePinia();
const authStore = useAuthStore(pinia);
const { user, is_login } = storeToRefs(authStore);
return {
user,
is_login,
};
}
Then in your component you can use it like:
<script setup>
const { user, is_login } = useAuth();
</script>

Correct type for spawn when running flow-typed?

I'm running a project using Flowtype v0.75 and redux-saga
Currently I'm experiencing some issues trying to determine how to provide the right types for my sagas, specially when I'm trying to combine them:
ActivateAccountSaga.js
// #flow
import Alert from 'react-s-alert';
import { combineSagas, handleError } from 'util/Saga';
import { call, put, take, all } from 'redux-saga/effects';
import { history } from 'modules/LocationModule';
import {
activateAccount,
sendActivationEmail,
sendActivationEmailPending,
sendActivationEmailFulfilled,
sendActivationEmailRejected,
} from 'bundles/Auth/modules/ActivateAccountModule';
import AuthAPI from 'bundles/Auth/apis/AuthAPI';
import config from 'config/index';
export function* activateAccountWorker(api: AuthAPI): Generator<*, *, *> {
while (true) {
const { payload } = yield take(activateAccount().type);
try {
const response = yield call([api, api.activateAccount], payload);
yield call(history.push, config.route.identity.signIn);
yield call(Alert.success, response.description);
} catch (e) {
yield call(history.push, config.route.identity.signIn);
yield call(handleError, e);
}
}
}
export function* sendActivationEmailWorker(api: AuthAPI): Generator<*, *, *> {
while (true) {
const { payload } = yield take(sendActivationEmail().type);
try {
yield put(sendActivationEmailPending());
const response = yield call([api, api.sendActivationMail], payload);
yield put(sendActivationEmailFulfilled(response));
yield call(Alert.success, response.description, { timeout: 30000 });
yield call(history.push, config.route.identity.signIn);
} catch (e) {
yield put(sendActivationEmailRejected(e));
yield call(handleError, e);
}
}
}
export function* activateAccountSaga(api: AuthAPI): Generator<*, *, *> {
yield all(combineSagas([
[activateAccountWorker, api],
[sendActivationEmailWorker, api],
]));
}
const api = new AuthAPI();
export default [activateAccountSaga, api];
And this is the util/Saga.js
// #flow
import get from 'lodash/get';
import Alert from 'react-s-alert';
import { actions } from 'react-redux-form';
import { call, put, spawn, all, select } from 'redux-saga/effects';
export const refineSaga = (saga: * | Array<*>) => {
// Saga is a generator function without params
if (typeof saga === 'function') {
return [saga];
}
// Ensures that a saga in the form [saga, params...] was given
if (Array.isArray(saga) && typeof saga[0] === 'function') {
return saga;
}
throw new TypeError(`Unexpected saga type: ${saga.toString()}`);
};
export const combineSagas = (sagas: Array<Function | *>): any[] => sagas.map(saga => spawn(...refineSaga(saga)));
When running flow v0.75 I get the following error:
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/bundles/Auth/sagas/ActivateAccountSaga.js:48:6
generator function [1] requires another argument from function type [2] in type argument Fn.
src/bundles/Auth/sagas/ActivateAccountSaga.js
[1] 16│ export function* activateAccountWorker(api: AuthAPI): Generator<*, *, *> {
:
45│
46│ export function* activateAccountSaga(api: AuthAPI): Generator<*, *, *> {
47│ yield all(combineSagas([
48│ [activateAccountWorker, api],
49│ [sendActivationEmailWorker, api],
50│ ]));
51│ }
flow-typed/npm/redux-saga_v0.16.x.js
[2] 863│ <R, Fn: () => R>(fn: Fn): SpawnEffect<null, Fn, []>,
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/bundles/Auth/sagas/ActivateAccountSaga.js:48:29
A callable signature is missing in AuthAPI [1] but exists in function type [2] in type argument Fn.
src/bundles/Auth/sagas/ActivateAccountSaga.js
45│
[1] 46│ export function* activateAccountSaga(api: AuthAPI): Generator<*, *, *> {
47│ yield all(combineSagas([
48│ [activateAccountWorker, api],
49│ [sendActivationEmailWorker, api],
50│ ]));
51│ }
flow-typed/npm/redux-saga_v0.16.x.js
[2] 863│ <R, Fn: () => R>(fn: Fn): SpawnEffect<null, Fn, []>,
As you can see flow tells me that I should provide another argument, but I'm doing it in an array based fashion, what is the alternative so flow is happy with it?
The issue was in here:
export const refineSaga = (saga: * | Array<*>): Array<*> =>
and
export const combineSagas = (sagas: any[]): any[] => sagas.map(saga => spawn(...refineSaga(saga)));

Throtting same saga/epic action with takeLatest on different actions

I'm new on redux-saga/observable stuff. But I couldn't handle my scenario which looks so fit on these. So; I want to call API if any changes happen on the form. But I don't want to call API a lot because of the performance reasons.
Basically, when I trigger SLIDERCHANGED, TEXTBOXCHANGED, CHECKBOXCHECKED, I want to call also getData function. But I need to put a delay to check other actions. For example; If SLIDERCHANGED is triggered, It should wait for 1sn and if TEXTBOXCHANGED is triggered at that time, It will be canceled and wait for 1sn more to call the getData function. So that's why I tried to implement Redux-saga or redux-observable.
I have actions types;
const SLIDERCHANGED = 'APP/sliderchanged';
const TEXTBOXCHANGED = 'APP/textboxchanged';
const CHECKBOXCHECKED = 'APP/checkboxchecked';
const LOADING = 'APP/loading';
const LOADDATA = 'APP/loaddata';
const ERRORLOADDATA = 'APP/errorloaddata';
I have also actions;
export function updateSliderValue(val) {
return { type: SLIDERCHANGED, val };
}
export function updateTextboxValue(val) {
return { type: TEXTBOXCHANGED, val };
}
export function updateCheckboxValue(val) {
return { type: CHECKBOXCHECKED, val };
}
export function loading() {
return { type: LOADING };
}
export function loadData(data) {
return { type: LOADDATA, data };
}
export function errorLoadData(err) {
return { type: ERRORLOADDATA, err };
}
export function getData(vehicleInfo) { // redux-thunk ASYNC function
return (dispatch, getState) => {
dispatch(loading());
return dispatch(apiCall('/getAllData', {}))
.then(payload => {
dispatch(loaddata(payload));
})
.catch(error => {
dispatch(errorLoadData(error));
});
};
}
With redux-saga, I did this but it doesn't work. It calls a getData function on each change with 1sn delay.
import { put, throttle } from 'redux-saga/effects';
import {
SLIDERCHANGED,
TEXTBOXCHANGED,
CHECKBOXCHECKED
} from './constants';
import { getData } from './actions';
function* onFormUpdate() {
yield put(getData());
}
export function* watchFormChange() {
yield throttle(
10000,
[SLIDERCHANGED, TEXTBOXCHANGED, CHECKBOXCHECKED],
onFormUpdate
);
}
With redux-observable, Also somehow I get the same error.
import { ofType } from 'redux-observable';
import { delay, map, debounceTime } from 'rxjs/operators';
import {
SLIDERCHANGED,
TEXTBOXCHANGED,
CHECKBOXCHECKED
} from './constants';
import { getData } from './actions';
export const onFormUpdate = action => {
return action.pipe(
ofType(SLIDERCHANGED, TEXTBOXCHANGED, CHECKBOXCHECKED),
debounceTime(1000),
map(() => getData())
);
};
Does anyone have any idea or opinion to make this happen?
Without having tested it, I think you can write a solution like this:
import { delay } from 'redux-saga'
import { put } from 'redux-saga/effects'
function* onFormUpdate() {
yield delay(1000)
yield put(getData())
}
export function* watchFormChange() {
yield takeLatest(
[SLIDERCHANGED, TEXTBOXCHANGED, CHECKBOXCHECKED],
onFormUpdate,
)
}
The idea here is that takeLatest will cancel onFormUpdate as soon as another one of the three actions is dispatched. So while onFormUpdate is in delay and then canceled, the next step, which is put, will not be called anymore.

Why am i getting "Error: Actions must be plain objects. Use custom middleware for async actions." error?

I am continuously getting " Actions must be plain objects. Use custom middleware for async actions." error and I am totally stuck here. What am I doing wrong here please help me figure out and help me get out of this error.
This is my index.js file where I have integrated redux store to the app.
import "babel-polyfill";
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux'
import { composeWithDevTools } from 'redux-devtools-extension';
import { createStore, applyMiddleware, combineReducers, compose} from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootSaga from './sagas'
import { postsReducer } from './reducers/posts'
import Routes from './routes';
import './styles/style.css'
const rootReducer = combineReducers({ postsReducer })
const sagaMiddleware = createSagaMiddleware()
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(sagaMiddleware)))
sagaMiddleware.run(rootSaga)
ReactDOM.render((
<Provider store={store}>
<Router><Routes /></Router>
</Provider>
), document.getElementById('root'))
this is my saga.js
import { take, put, call, fork, select, takeEvery, all, takeLatest } from 'redux-saga/effects'
import PostApi from './api/postApi';
import { gotPosts } from './actions/celebrity';
import { POSTS } from '../types'
export function* getAllPosts () {
const posts = yield call(PostApi.getPosts, {})
console.log('postssss', posts)
yield put(gotPosts(posts.data))
}
export function* watchGetPosts () {
yield takeLatest(POSTS, getAllPosts)
}
export default function* root() {
yield all([ fork(watchGetPosts) ])
}
this is my action.js
import { POSTS } from '../../types';
export const gotPosts = (data) => {
return {
type: POSTS,
data,
}
}
export const getPosts = () => dispatch => {
dispatch(gotPosts);
}
this is component page where i dispatched action.
import React, { Component } from 'react';
import { Card, Row, Col } from 'antd';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux'
import { getPosts } from '../actions/celebrity';
const { Meta } = Card;
class MainPage extends Component {
componentDidMount () {
console.log(this.props)
this.props.getPosts();
}
render() {
return <Row type="flex" className="main" justify="center" align="between">
......
</Row>
}
}
const mapStateToProps = state => {
return {
posts: state.postsReducer
}
}
const mapDispatchToProps = dispatch => ({
getPosts: () => {
dispatch(getPosts());
},
});
export default connect(mapStateToProps, mapDispatchToProps)(MainPage);
postsReducer
export const postsReducer = (state = [], action) => {
console.log(action)
switch(action.type){
case POSTS:
return action.data;
default:
return state;
}
}
You can't dispatch function w/o middleware support.
Problem originates from mapDispatchToProps:
{
getPosts: () => { dispatch(getPosts()); }
}
tracing down to your actions.js, getPosts() returns dispatch => dispatch(gotPosts), which is actually a function not an action(plan javascript object), redux dispatch by default doesn't recognize functions, unless you use middleware to enhance it, redux thunk for example.
Since you already have redux saga for async flow, simply dispatch an action from mapDispatchToProps should be fine, also consider create separate actions to differentiate POSTS_REQUEST, POSTS_RECEIVE, POSTS_FAILURE if possible.
import {POST} from '....../actionTypes'
...
{
getPosts: () => { dispatch({ type: POST }); }
}

Thunks not dispatching

Can anyone see what's wrong with my thunks? The inner code is never called but the outer code is. This is an example thunk:
export function selectCustomer(customerId) {
console.log("This appears in the console fine");
return (dispatch, getState) => {
console.log("This doesn't.. why don't you run..?");
dispatch(loadCustomerToEdit(customerId));
}
};
This is how I'm wiring it up to the component events:
import React, { Component, PropTypes } from 'react';
import CustomerEditForm from './CustomerEditForm.jsx';
import { editCustomer, selectCustomer, selectNewCustomer, saveCustomer } from '../redux/action_creators.jsx';
export const CustomerContainer = React.createClass({
componentWillMount() {
const customerId = FlowRouter.getParam('_id');
if (customerId) {
this.sub = Meteor.subscribe('CustomerCompany.get', customerId, this.setCustomerInState);
} else {
this.props.selectNewCustomer();
}
},
setCustomerInState() {
console.log("setCustomerInState");
this.props.selectCustomer(FlowRouter.getParam('_id'));
},
// Snip
render() {
console.log("CustomerContainer.render()", this.props);
if (this.sub && !this.sub.ready) {
return (<h1>Loading</h1>);
}
return (
<CustomerEditForm
customer = {this.props.customer}
onChange = {this.props.onChange}
onSave = {this.props.onSave}
errors = {this.props.customer.errors}
isValid = {this.props.customer.isValid}
salesRegionOptions={SalesRegions.find().fetch()}
/>
);
}
});
CustomerContainer.propTypes = {
customer: PropTypes.object,
onSave: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
selectCustomer: PropTypes.func.isRequired,
selectNewCustomer: PropTypes.func.isRequired
};
function mapStateToProps(state) {
console.log("CustomerContainer.mapStateToProps", state)
return {
customer: state.userInterface.customerBeingEdited
};
}
function mapDispatchToProps(dispatch) {
//console.log("CustomerContainer.mapDispatchToProps", Actions.customerSave)
return {
onSave: saveCustomer,
onChange: editCustomer,
selectCustomer,
selectNewCustomer
};
}
export default connect(mapStateToProps, mapDispatchToProps
)(CustomerContainer);
And this is my store setup:
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import rootReducer from './reducers.jsx';
import thunk from 'redux-thunk';
const middleware = [ thunk ]
const createStoreWithMiddleware = applyMiddleware(...middleware)(createStore)
const store = createStoreWithMiddleware(rootReducer)
export default store;
You'll no doubt recognise a lot of this code as it is adapted from the excellent examples in the redux documentation.
The selectCustomer function is called, so mapDispatchToProps seems to have wired the selectCustomer function to the component, it's just that the method returned by selectCustomer isn't called.
The problem is your mapDispatchToProps function. react-redux does not automatically wrap your action creators if you pass it a function, only if you pass it an object! (or if you bind them manually with bindActionCreators)
Try changing your connect call to this, and it should work:
connect(mapStateToProps, {
onSave: saveCustomer,
onChange: editCustomer,
selectCustomer,
selectNewCustomer
})(YourComponent);

Resources