Redux Saga not calling actions on click event - redux

I am using Redux Saga to handle async requests. I have tried to go onto to other posts but there were not of help for me
The issue
I have created a route in my file called IndexRoute, my issue is that when it loads the page, it automatically loads my saga function, when it should only be called when the user clicks on a button calling an action creator. Also, when I call an action inside of my index.js, an action is called, however, the saga function is not called. I am very confused, any help will be appreciated thank you
route.js
import { getAsyncInjectors } from 'utils/asyncInjectors';
import globalSagas from 'containers/App/sagas';
import App from 'containers/App';
const errorLoading = (err) => {
console.error('Dynamic page loading failed', err); // eslint-disable-line no-console
};
const loadModule = (cb) => (componentModule) => {
cb(null, componentModule.default);
};
export function createRoutes(store, auth) {
// create reusable async injectors using getAsyncInjectors factory
const { injectReducer, injectSagas } = getAsyncInjectors(store);
// injectReducer('global', globalReducer);
injectSagas(globalSagas);
const routes = [
{
path: '/',
name: 'main',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('containers/Main/actions'),
System.import('containers/Main/reducer'),
System.import('containers/Main/sagas'),
System.import('containers/Main'),
]);
const renderRoute = loadModule(cb);
importModules.then(([actions, reducer, sagas, component]) => {
injectReducer('main', reducer.default);
injectSagas(sagas.default);
renderRoute(component);
});
importModules.catch(errorLoading);
},
indexRoute: {
path:'/',
name:'posts',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('containers/Dashboard/actions'),
System.import('containers/Dashboard/reducer'),
System.import('containers/Dashboard/sagas'),
System.import('containers/Dashboard'),
]);
const renderRoute = loadModule(cb);
importModules.then(([actions, reducer, sagas, component]) => {
injectReducer('posts', reducer.default);
injectSagas(sagas.default);
renderRoute(component);
});
importModules.catch(errorLoading);
},
},
childRoutes: [
{
path: '/reports',
name: 'Reports List',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('containers/Reports/reducer'),
System.import('containers/Reports/sagas'),
System.import('containers/Reports'),
]);
const renderRoute = loadModule(cb);
importModules.then(([reducer, sagas, component]) => {
injectReducer('reportsList', reducer.default);
injectSagas(sagas.default);
renderRoute(component);
});
importModules.catch(errorLoading);
},
childRoutes: [
{
path: '/reports/new',
name: 'Create a Report',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('containers/Reports/CreateReport'),
]);
const renderRoute = loadModule(cb);
importModules.then(([component]) => {
renderRoute(component);
});
importModules.catch(errorLoading);
},
},
],
},
{
path: 'matrixView(/:reportId)',
name: 'Matrix View',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('containers/MatrixView/reducer'),
System.import('containers/MatrixView/sagas'),
System.import('containers/MatrixView'),
]);
const renderRoute = loadModule(cb);
importModules.then(([reducer, sagas, component]) => {
injectReducer('matrixView', reducer.default);
injectSagas(sagas.default);
renderRoute(component);
});
importModules.catch(errorLoading);
},
},
{
path: '/forbidden',
name: 'No Access',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('containers/NoAccess'),
]);
const renderRoute = loadModule(cb);
importModules.then(([component]) => {
renderRoute(component);
});
importModules.catch(errorLoading);
},
},
],
},
{
path: '/login',
name: 'login',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('containers/Login'),
]);
const renderRoute = loadModule(cb);
importModules.then(([component]) => {
renderRoute(component);
});
importModules.catch(errorLoading);
},
},
{
path: '/signup',
name: 'signup',
getComponent(nextState, cb) {
const importModules = Promise.all([
System.import('containers/Signup'),
]);
const renderRoute = loadModule(cb);
importModules.then(([component]) => {
// injectReducer('signup', reducer.default);
// injectSagas(sagas.default);
renderRoute(component);
});
importModules.catch(errorLoading);
},
},
];
return {
component: App,
// path: '/',
// indexRoute: { onEnter: (nextState, replace) => replace('/account/me') },
childRoutes: routes,
};
}
actions.js
import {
CREATE_MATRIX_REQUEST,
CREATE_MATRIX_SUCCESS,
CREATE_MATRIX_ERROR
} from './constants';
export function createMatrixRequest() {
return { type: CREATE_MATRIX_REQUEST};
}
export function createMatrixSuccess(data) {
return { type: CREATE_MATRIX_SUCCESS, data };
}
export function createMatrixError(error) {
return { type: CREATE_MATRIX_ERROR , error };
}
reducer.js
/*
* The reducer takes care of state changes in our app through actions
*/
import { fromJS } from 'immutable';
import {
CREATE_MATRIX_REQUEST,
CREATE_MATRIX_SUCCESS,
CREATE_MATRIX_ERROR
} from './constants';
// The initial application state
const initialState = fromJS({
success: '',
error: ''
});
// Takes care of changing the application state
function createMatrixReducer(state = initialState, action) {
switch (action.type) {
case CREATE_MATRIX_REQUEST:
console.log('hello')
return state;
case CREATE_MATRIX_SUCCESS:
console.log('second hello')
return state.set('success', action.payload);
case CREATE_MATRIX_ERROR:
return state.set('error', action.payload);
default:
return state;
}
}
export default createMatrixReducer;
sagas.js
import { call, put } from 'redux-saga/effects';
import { createMatrix } from './utils';
import { CREATE_MATRIX_REQUEST, CREATE_MATRIX_SUCCESS, CREATE_MATRIX_ERROR } from './constants';
export function* createMatrixSaga(action) {
console.log('Generator called')
yield put({ type:CREATE_MATRIX_REQUEST});
try {
const data = yield call(createMatrix);
yield put({type: CREATE_MATRIX_SUCCESS, success: data})
} catch (error) {
yield put({type: CREATE_MATRIX_ERROR, error: error })
}
}
export default [
createMatrixSaga,
];
index.js
/*
* Dashboard
*
**/
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { Input, Button } from 'muicss/react';
import { Link } from 'react-router';
import { createMatrixRequest } from './actions';
import { UserIsAuthenticated } from 'config.routes/UserIsAuthenticated';
import styles from './styles.scss';
class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
domain: '',
};
this.inputChange = this.inputChange.bind(this);
this.clearInput = this.clearInput.bind(this);
this.createMatrix = this.createMatrix.bind(this);
}
inputChange(event) {
const name = event.target.name;
const value = event.target.value;
this.setState({
[name]: value,
});
}
clearInput(){
this.setState({
domain: ''
})
}
createMatrix(){
this.props.createMatrixRequest();
}
render() {
console.log(this.props, 'This are the props')
return (
<div className={styles.dashboardContainer}>
<div className={styles.dashboardBody}>
<h1>Let's Get Started</h1>
<h5>Begin by entering a domain</h5>
<Input
className={styles.domainInput}
label="Domain Name"
type="text"
name="domain"
value={this.state.domain}
floatingLabel="true"
onChange={this.inputChange}
required
/>
<Button
variant="raised"
type="button"
onClick={this.createMatrix}
disabled={this.state.domain.length === 0}
>
</Button>
<h5 onClick={this.clearInput}><Link>Clear</Link> input</h5>
</div>
</div>
);
}
}
export function mapDispatchToProps(dispatch) {
return {
createMatrixRequest: () => dispatch(createMatrixRequest()),
};
}
function mapStateToProps(state){
return { matrix: state };
}
export default UserIsAuthenticated(connect(mapStateToProps, mapDispatchToProps)(Dashboard));
I also tried adding it to the global sagas file that I import routes.js, but it didn't help
This filed is named the same as my above file, but it stays in another folder called App
sagas.js
// This file contains the sagas used for async actions in our app. It's divided into
// "effects" that the sagas call (`authorize` and `logout`) and the actual sagas themselves,
// which listen for actions.
// Sagas help us gather all our side effects (network requests in this case) in one place
import { take, call, put, race } from 'redux-saga/effects';
import { browserHistory } from 'react-router';
import auth from 'utils/auth';
import { toastr } from 'lib/react-redux-toastr';
import {
SENDING_REQUEST,
LOGIN_REQUEST,
REGISTER_REQUEST,
SET_AUTH,
LOGOUT,
FETCH_USER,
CHANGE_FORM,
REQUEST_ERROR,
SET_USER,
CLEAR_USER,
CREATE_MATRIX_REQUEST,
CREATE_MATRIX_ERROR,
CREATE_MATRIX_SUCCESS
} from './constants';
/**
* Effect to handle authorization
* #param {string} email The email of the user
* #param {string} password The password of the user
* #param {object} options Options
* #param {boolean} options.isRegistering Is this a register request?
*/
export function* authorize({ name, email, password, accountType, isRegistering }) {
// We send an action that tells Redux we're sending a request
yield put({ type: SENDING_REQUEST, sending: true });
// We then try to register or log in the user, depending on the request
try {
// let salt = genSalt(email);
// let hash = hashSync(password, salt);
let response;
// For either log in or registering, we call the proper function in the `auth`
// module, which is asynchronous. Because we're using generators, we can work
// as if it's synchronous because we pause execution until the call is done
// with `yield`!
if (isRegistering) {
response = yield call(auth.register, name, email, password, accountType);
} else {
response = yield call(auth.login, email, password);
}
return response;
} catch (error) {
console.log('hi');
// If we get an error we send Redux the appropiate action and return
yield put({ type: REQUEST_ERROR, error: error.message });
return false;
} finally {
// When done, we tell Redux we're not in the middle of a request any more
yield put({ type: SENDING_REQUEST, sending: false });
}
}
/**
* Effect to handle logging out
*/
export function* logout() {
// We tell Redux we're in the middle of a request
yield put({ type: SENDING_REQUEST, sending: true });
// Similar to above, we try to log out by calling the `logout` function in the
// `auth` module. If we get an error, we send an appropiate action. If we don't,
// we return the response.
try {
const response = yield call(auth.logout);
yield put({ type: SENDING_REQUEST, sending: false });
return response;
} catch (error) {
yield put({ type: REQUEST_ERROR, error: error.message });
return error.message;
}
}
/**
* Log in saga
*/
export function* loginFlow() {
// Because sagas are generators, doing `while (true)` doesn't block our program
// Basically here we say "this saga is always listening for actions"
while (true) {
// And we're listening for `LOGIN_REQUEST` actions and destructuring its payload
const request = yield take(LOGIN_REQUEST);
const { email, password } = request.data;
// A `LOGOUT` action may happen while the `authorize` effect is going on, which may
// lead to a race condition. This is unlikely, but just in case, we call `race` which
// returns the "winner", i.e. the one that finished first
const winner = yield race({
auth: call(authorize, { email, password, isRegistering: false }),
logout: take(LOGOUT),
});
// If `authorize` was the winner...
if (winner.auth) {
// ...we send Redux appropiate actions
yield put({ type: SET_AUTH, newAuthState: true }); // User is logged in (authorized)
yield put({ type: SET_USER, user: winner.auth });
yield put({ type: CHANGE_FORM, newFormState: { email: '', password: '' } }); // Clear form
yield call(forwardTo, '/'); // Go to dashboard page
// If `logout` won...
} else if (winner.logout) {
// ...we send Redux appropiate action
yield put({ type: SET_AUTH, newAuthState: false }); // User is not logged in (not authorized)
yield call(logout); // Call `logout` effect
yield call(forwardTo, '/login'); // Go to root page
}
}
}
/**
* Log out saga
* This is basically the same as the `if (winner.logout)` of above, just written
* as a saga that is always listening to `LOGOUT` actions
*/
export function* logoutFlow() {
while (true) {
yield take(LOGOUT);
yield put({ type: SET_AUTH, newAuthState: false });
yield call(logout);
yield put({ type: CLEAR_USER });
yield call(forwardTo, '/login');
toastr.success('Success!', 'You are now logged out.');
}
}
/**
* Get user information saga
*/
export function* getUserFlow() {
while (true) {
yield take(FETCH_USER);
try {
const response = yield call(auth.getUserInfo);
yield put({ type: SET_USER, user: response });
} catch (error) {
yield put({ type: REQUEST_ERROR, error: error.message });
return error.message;
}
}
}
/**
* Register saga
* Very similar to log in saga!
*/
export function* registerFlow() {
while (true) {
// We always listen to `REGISTER_REQUEST` actions
const request = yield take(REGISTER_REQUEST);
const { name, email, password, accountType } = request.data;
// We call the `authorize` task with the data, telling it that we are registering a user
// This returns `true` if the registering was successful, `false` if not
const wasSuccessful = yield call(authorize, {name, email, password, accountType, isRegistering: true });
// If we could register a user, we send the appropiate actions
if (wasSuccessful) {
yield put({ type: SET_AUTH, newAuthState: true }); // User is logged in (authorized) after being registered
yield put({ type: CHANGE_FORM, newFormState: { name: '', password: '' } }); // Clear form
yield put({ type: LOGIN_REQUEST, data: { email, password } });
forwardTo('/dashboard'); // Go to dashboard page
}
}
}
// The root saga is what we actually send to Redux's middleware. In here we fork
// each saga so that they are all "active" and listening.
// Sagas are fired once at the start of an app and can be thought of as processes running
// in the background, watching actions dispatched to the store.
export default [
loginFlow,
logoutFlow,
registerFlow,
getUserFlow,
];
// Little helper function to abstract going to different pages
export function* forwardTo(location) {
yield call(browserHistory.push, location);
}

Found an answer, basically I forgot to add a watcher to my saga file, now it works perfectly!
import { call, put } from 'redux-saga/effects';
import { takeEvery } from 'redux-saga/effects'
import { createMatrix } from './utils';
import { CREATE_MATRIX_REQUEST, CREATE_MATRIX_SUCCESS, CREATE_MATRIX_ERROR } from './constants';
export function* createMatrixSaga(action) {
try {
const data = yield call(createMatrix);
yield put({type: CREATE_MATRIX_SUCCESS, success: data})
} catch (error) {
yield put({type: CREATE_MATRIX_ERROR, error: error })
}
}
function* watchFetchData() {
yield takeEvery(CREATE_MATRIX_REQUEST, createMatrixSaga)
}
export default [
watchFetchData,
];

Related

How to create a function that returns new session format with extra key value pair

I am using NextJS with NextAuth with google and email providers. Unfortunately, the session returns only few fields that does not include userId of the user from the database.
I created however a function that I intend to use with every getServerSideProps request. The function returns the following:
{
user: {
name: 'daniel sas',
email: 'emailofuser#gmail.com',
image: 'https://lh3.gooleusercontent.com/a/AEdFTp6r44ZwqcfJORNnuYtbVv_LYbab-wv5Uyxk=s96-c',
userId: 'clbcpc0hi0002sb1wsiea3q5d'
},
expires: '2022-12-17T20:18:52.580Z'
}
The problem is I am getting an error that does not allow me to pass the props in the page:
Error: Your `getServerSideProps` function did not return an object. Did you forget to add a `return`?
In the function I get the user by the email, and attach the userId.
import { getSession } from "next-auth/react";
import prisma from './prisma'
// This function get the email and returns a new session object that includes
// the userId
export const requireAuthentication = async context => {
const session = await getSession(context);
const errorOrUserNotFound = () => {
return {
redirect: {
destination: '/signup',
permanent: false
}
}
}
// If there is no user or there is an error ret to signup page
if (!session) {
errorOrUserNotFound();
}
// If the user is not found return same redirect to signup
else {
try {
const user = await prisma.user.findUnique({where: { email: session.user.email }});
if (!user) return errorOrUserNotFound();
// Must return a new session here that contains the userId...
else {
const newSession = {
user: {
...session.user,
userId: user.id
},
expires: session.expires
};
console.log(newSession);
return {
props: {
session: newSession
}
}
}
}
catch (error) {
if (error) {
console.log(error);
}
}
}
}
The react component looks like this. In the getServerSideProps i return the await function. The problem is that when I log the prop in the serverside, I get the following:
{
props: { session: { user: [Object], expires: '2022-12-17T20:18:52.580Z' } }
}
However, if i log the props in the clientside, I get an empty object...
//Clientside compoen
import { getSession } from "next-auth/react"
import { Fragment, useState } from "react";
import { requireAuthentication } from "../../lib/requireAuthentication";
import CreateListModal from "./CreateListModal";
const DashboardPage = props => {
const [loading, setloading] = useState(false);
console.log(props);
return (
<section className="border-4 border-orange-800 max-w-5xl mx-auto">
<CreateListModal userId={props.userId} loading={loading} setloading={setloading} />
</section>
)
}
export const getServerSideProps = async context => {
const session = await getSession(context);
const reqAuth = await requireAuthentication(context);
console.log(reqAuth);
return reqAuth
}
export default DashboardPage;

How to pass query params to a redirect in NextJS

I redirect users to the login page, when they try to access a page without authentication.
I then wanna show them a Message.
But I am not able to pass parameters in the redirect. What causes the issue and how to do it properly?
// PAGE NEEDS AUTH / REDIRECT TO LOGIN WITH MESSAGE
// application
import { GetServerSideProps } from 'next';
import SitePageProducts from '../../components/site/SitePageProducts';
import axios from 'axios';
import { getSession } from 'next-auth/react';
import url from '../../services/url';
import { ProductFields } from '../../lib/ebTypes';
function Page() {
return <SitePageProducts />;
}
export default Page;
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getSession(context)
if (session) {
const products = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/products`, {
}).then(res => {
console.log('res :>> ', res);
return res.data.products as ProductFields[]
}).catch(err => console.log(err));
console.log('products :>> ', products);
return {
props: {
loading: true,
token: session.user.token,
}
}
} else {
return {
redirect: {
permanent: false,
destination: url.accountSignIn().href,
props: { test: "Message from inside Redirect" }
},
props: {
params: { message: "Message from inside props" },
query: {
message: 'Message from inside props query'
},
message: 'Message from inside props root'
},
};
}
}
// LOGIN PAGE, SHOULD CONSUME AND SHOW MESSAGE WHY LOGIN IS NEEDED
import { GetServerSideProps } from 'next';
import AccountPageLogin from '../../components/account/AccountPageLogin';
import url from '../../services/url';
import { getSession } from "next-auth/react"
function Page(props: any) {
return <AccountPageLogin {...props} />;
}
export default Page;
export const getServerSideProps: GetServerSideProps = async (ctx) => {
// ALL CTX queries / props are empty?????
// CURRENT: query:{} --- EXPECTING: query: {message: "MY MESSAGE"}
console.log('ctx accountpagelogin::: :>> ', ctx);
const session = await getSession(ctx)
if (session) {
return {
redirect: {
destination: url.accountDashboard().href,
permanent: false,
},
};
}
return {
props: {},
};
};

Access redux state value on a React + Redux + Hooks + Typescript web app

I am trying to access the redux state to display its value on my website. I am using React redux hooks with functional components and Typescript.
Situation:
I have a store with two reducers: UI and user. The initial state is:
{
user: {
authenticated: false,
credentials: {}
},
UI: {
loading: false,
errors: null
}
}
When the user signs in, the signinUser action takes place and correctly changes the redux state. For example, for an invalid signin, the redux state is:
{
user: {
authenticated: false,
credentials: {}
},
UI: {
loading: false,
errors: {
general: 'wrong credentials, please try again'
}
}
}
Problem:
I am trying to acces the UI.errors so I can display them on my website. i have a function in my Signin component thnamed submitForm that calls the signinUser action that correctly dispatches the actions. My problem is that after that I want to retrieve the state.ui.errors and I can't figure out how to.
I have tried all this:
componentWillRecieveProps(nextProps) { ... } this solution is for class components and I am using functional components
useSelector((state: StoreState) => state.UI); If I do it inside submitForm is invalid because React Hooks don't allow to call inside a function. If I do it outside, it fetches the old state.
Here are my files (the parts related to this issue)
store.tsx
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
// Reducers
import userReducer from './reducers/userReducer';
import uiReducer from './reducers/uiReducer';
const initialState = {};
const middleware = [thunk];
const reducers = combineReducers({
user: userReducer,
UI: uiReducer
});
const store = createStore(
reducers,
initialState,
compose(
applyMiddleware(...middleware),
(window as any).__REDUX_DEVTOOLS_EXTENSION__ &&
(window as any).__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
userActions.tsx
import { SET_USER, SET_ERRORS, CLEAR_ERRORS, LOADING_UI } from '../types';
import axios from 'axios';
// Interfaces
import { ISigninForm } from '../../utils/types';
// Redux
import { Dispatch } from 'redux';
import { useDispatch } from 'react-redux';
export const signinUser = (
userData: ISigninForm,
dispatch: Dispatch,
handleDialogClose: () => void
) => {
console.log('signinuser in userActions');
dispatch({ type: LOADING_UI });
axios
.post('/signin', userData)
.then((res) => {
const FBIdToken = `Bearer ${res.data.token}`;
localStorage.setItem('FBIdToken', FBIdToken);
axios.defaults.headers.common['Authorization'] = FBIdToken;
getUserData(dispatch);
dispatch({ type: CLEAR_ERRORS });
handleDialogClose();
// history.push("/profile"); // this will redirect to a page not built yet
})
.catch((err) => {
dispatch({
type: SET_ERRORS,
payload: err.response.data
});
});
};
export const getUserData = (dispatch: Dispatch) => {
console.log('getUserData');
axios
.get('/user')
.then((res) => {
console.log('/user', res);
dispatch({
type: SET_USER,
payload: res.data
});
})
.catch((err) => console.log('err', err));
};
uiReducer.tsx
import { SET_ERRORS, CLEAR_ERRORS, LOADING_UI, IAction } from '../types';
const initialState = {
loading: false,
errors: null
};
export default function (state = initialState, action: IAction) {
switch (action.type) {
case SET_ERRORS:
return {
...state,
loading: false,
errors: action.payload
};
case CLEAR_ERRORS:
return {
...state,
loading: false,
errors: null
};
case LOADING_UI:
return {
...state,
loading: true
};
default:
return state;
}
}
userReducer.tsx
import {
SET_USER,
SET_AUTHENTICATED,
SET_UNAUTHENTICATED,
IAction
} from '../types';
const initialState = {
authenticated: false,
credentials: {}
};
export default function (state = initialState, action: IAction) {
switch (action.type) {
case SET_AUTHENTICATED:
return {
...state,
authenticated: true
};
case SET_UNAUTHENTICATED:
return initialState;
case SET_USER:
console.log('SET_USER', action);
return {
authenticated: true,
...action.payload
};
default:
return state;
}
}
Signin.tsx
function Signin({ history }: RouteComponentProps): JSX.Element {
// States
const [dialogOpen, setDialogOpen] = React.useState(false);
const [errorsAPI, setErrorsAPI] = React.useState<ISigninErrors>({});
const [loading, setLoading] = React.useState(false);
// Dialog
const handleDialogOpen = () => {
setDialogOpen(true);
};
const handleDialogClose = () => {
setDialogOpen(false);
};
// Form
const { register, handleSubmit, errors } = useForm<ISigninForm>();
const submitForm = (data: ISigninForm) => {
signinUser(data, dispatch, handleDialogClose);
};
return (
// HTML content
);
}
export default withRouter(Signin);
Solution:
I had the solution in front of me this whole time, but I was not using the function in the right way.
const state = useSelector((state: StoreState) => state);
This is called inside the Signin function component. Then when I am returning the HTML object, I just call
{state.UI.errors !== null && 'general' in state.UI.errors && (
<p>{state.UI.errors.general}</p>
)}

redux not picking up an object dispatched via actions

I created a rootSaga in sagas.js as
function* fetchStuff(action) {
try {
yield put({type: 'INCREMENT'})
yield call(delay, 1000)
yield put({type: 'DECREMENT'})
const highlights = yield call(API.getStuff, action.data.myObject);
} catch (e) {
yield put({type: 'FETCH_STUFF_FAILED', message: e});
}
}
export default function* rootSaga() {
yield takeEvery('INIT_LOAD', fetchStuff);
}
I am calling the INIT_LOAD after thirdParty.method:
class myClass extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.load();
}
load = () => {
this.init = () => {
this.myObject = thirdParty.method(event => {
const action = {
type: 'INIT_LOAD',
payload: {
myObject: this.myObject
}
};
store.dispatch(action);
});
};
this.init();
};
render() {
return (
<div id="render-here" />
);
}
Passing the this.myObject in the action that is dispatched does not trigger the saga. If I change the action payload to a string, like the following, the saga is triggered.
const action = {
type: 'INIT_LOAD',
payload: {
myObject: 'this.myObject'
}
};
Why am I unable to pass this.myObject but a string is ok?
UPDATE: It is not a saga issue. I replicated the same issue with just plain redux. The rootReducer as
export default function rootReducer(state = initialState, action) {
switch (action.type) {
case 'INIT_LOAD':
return Object.assign({}, state, { myObject: action.payload.myObject });
default:
return state;
}
}
As I mentioned in the comment below, assigning it to an object Obj does not change the issue
let Obj = {};
...
load = () => {
this.init = () => {
Obj.myObject = thirdParty.method(event => {
const action = {
type: 'INIT_LOAD',
payload: {
myObj: Obj
}
};
store.dispatch(action);
});
};
this.init();
};
UPDATE2
I cleaned the code up & simply dispatched an action in the component that triggers the saga. Inside the saga is where I do the init(). I ran into another issue where the object that I was trying to save in the redux store has active socket sessions (which were given me cross-domain issues). Although I didn't solve my original problem, not storing a socket object made my problem go away.

redux observable: Why don`t can get all actions in test

I'm trying to test a 'redux observable epic' but the test fail because not all actions are in store.getActions() the strange is the store.dispatch function runs.
Epic and actions
export const VERIFY_SESION = 'auth/VERIFY_SESION';
export const SET_POLICIES_ACCEPTED = 'auth/SET_POLICIES_ACCEPTED';
export const AUTHENTICATE = 'auth/AUTHENTICATE';
export function setPoliciesAccepted(wereAccepted: boolean) {
return {
wereAccepted,
type: SET_POLICIES_ACCEPTED,
};
}
export function verifySesion() {
return {
type: VERIFY_SESION,
};
}
export function authenticate(token) {
return {
token,
type: AUTHENTICATE,
};
}
export function verifySesionEpic(action$, store) {
return action$
.ofType(VERIFY_SESION)
.switchMap(async () => {
try {
store.dispatch(setBlockLoading(true));
const token = await AsyncStorage.getItem('token');
if (token !== null) {
store.dispatch(setBlockLoading(false));
return authenticate(token);
}
const policiesWereAccepted = await AsyncStorage.getItem('policiesWereAccepted');
store.dispatch(setBlockLoading(false));
return setPoliciesAccepted(policiesWereAccepted);
} catch (error) {
return setMessage(error.message);
}
});
}
test
describe('actions/auth', () => {
let store;
const asyncStorageGetStub = stub(AsyncStorage, 'getItem');
beforeEach(() => {
store = mockStore();
});
afterEach(() => {
asyncStorageGetStub.restore();
});
it('Should call authenticate if token', () => {
const token = 'mitoken';
asyncStorageGetStub.withArgs('token').returns(Promise.resolve(token));
store.dispatch(verifySesion());
expect(store.getActions()).toContain({ type: AUTHENTICATE, token });
});
});
Test result
1) "actions/auth Should call epic for verifySesion:
Error: Expected [ { type: 'auth/VERIFY_SESION' } ] to include { token: 'mitoken', type: 'auth/AUTHENTICATE' }"
Note
im sure that the conditional token !== null pass
I was to add a timeout before getAction because the 'AUTHENTICATE' actions is added after.
it('Should call authenticate if token', (done) => {
const token = 'mitoken';
asyncStorageGetStub.withArgs('token').returns(Promise.resolve(token));
store.dispatch(verifySesion());
setTimeout(() => {
expect(store.getActions()).toContain({ type: AUTHENTICATE, token });
done();
}, 1000);
});

Resources