I am new to RTK query and need only one WebSocket connection for my entire application as you can see below I implemented it like an example in GitHub.
I need to somehow send my payload to this WebSocket by subscribing to it.
and then whenever the message comes in I update the other injected Endpoints' cache.
import { ApiSlice } from 'api';
import { instrumentsAdapter } from './marketSlice';
const socket = new WebSocket(process.env.REACT_APP_SOCKET_BASE_URL);
const socketConnected = new Promise((resolve, reject) => {
// Connection opened
try {
socket.addEventListener('open', (event) => {
resolve(event);
});
} catch (err) {
console.log('err', err);
reject(err);
}
});
export const socketApi = ApiSlice.injectEndpoints({
endpoints: (builder) => ({
socketChannel: builder.mutation({
async queryFn(arg) {
await socketConnected;
const { type, topic } = arg;
const sendPayload = { type, path: topic };
socket.send(JSON.stringify(sendPayload));
return { data: { messages: [] } };
},
async onCacheEntryAdded(arg, { cacheDataLoaded, cacheEntryRemoved }) {
console.log('arg', arg);
await cacheDataLoaded;
// Listen for messages
socket.onmessage = (res) => {
const message = JSON.parse(res.data);
try {
// ApiSlice.util.updateQueryData('getInstrumentByRefId', arg, (draft) => {
// console.log('arg', arg);
// draft = { ...message.value, baseVolume: 3 };
// });
} catch (err) {
console.log('err', err);
}
};
await cacheEntryRemoved;
socket.close();
}
})
})
});
export const { useSocketChannelMutation } = socketApi;
after so much reading docs and researching I finally find this solution working but I do not know if this is a best practice or not.
Here is my not-empty ApiSlice.
/* eslint-disable import/prefer-default-export */
// Or from '#reduxjs/toolkit/query' if not using the auto-generated hooks
import { createApi } from '#reduxjs/toolkit/query/react';
import axiosBaseQuery from './axiosBaseQuery';
export const socket = new WebSocket(process.env.REACT_APP_SOCKET_BASE_URL);
const socketConnected = new Promise((resolve, reject) => {
try {
socket.addEventListener('open', (event) => {
resolve(event);
});
} catch (err) {
reject(err);
}
});
// initialize an empty api service that we'll inject endpoints into later as needed
export const ApiSlice = createApi({
reducerPath: 'api',
baseQuery: axiosBaseQuery(),
endpoints: (builder) => ({
subscribeSocket: builder.mutation({
async queryFn(arg) {
await socketConnected;
const sendPayload = { type: 'SUBSCRIBE', path: arg };
socket.send(JSON.stringify(sendPayload));
return { data: { messages: [] } };
}
}),
unsubscribeSocket: builder.mutation({
async queryFn(arg) {
await socketConnected;
const sendPayload = { type: 'UNSUBSCRIBE', path: arg };
socket.send(JSON.stringify(sendPayload));
return { data: { messages: [] } };
}
}),
channel: builder.mutation({
async queryFn(onMessage) {
await socketConnected;
socket.addEventListener('message', onMessage);
return { data: { messages: [] } };
}
})
})
});
export const { useUnsubscribeSocketMutation, useSubscribeSocketMutation, useChannelMutation } =
ApiSlice;
and this is my enhanced Api slice
import { createEntityAdapter } from '#reduxjs/toolkit';
import { ApiSlice } from 'api';
export const instrumentsAdapter = createEntityAdapter({
selectId: (item) => item?.state?.symbol
});
export const marketApi = ApiSlice.injectEndpoints({
overrideExisting: false,
endpoints: (builder) => ({
getMarketMap: builder.query({
query: (type) => ({
url: `/market/map?type=${type}`,
method: 'get'
})
}),
getInstruments: builder.query({
query: (type) => ({
url: `/market/instruments?type=${type}`,
method: 'get'
})
}),
getInstrumentByRefId: builder.query({
query: (refId) => ({
url: `/market/instruments/${refId}/summary`,
method: 'get'
}),
transformResponse: (res) => {
return instrumentsAdapter.addOne(instrumentsAdapter.getInitialState(), res);
},
async onCacheEntryAdded(
arg,
{ updateCachedData, cacheDataLoaded, cacheEntryRemoved, dispatch }
) {
await cacheDataLoaded;
const payload = `instruments.${arg}.summary`;
// subs to socket
dispatch(ApiSlice.endpoints.subscribeSocket.initiate(payload));
// Listen for messages
const onMessage = (res) => {
const message = JSON.parse(res.data);
try {
updateCachedData((draft) => {
instrumentsAdapter.setOne(draft, message.value);
});
} catch (err) {
// eslint-disable-next-line no-console
console.log('err', err);
}
};
dispatch(ApiSlice.endpoints.channel.initiate(onMessage));
await cacheEntryRemoved;
// unsubs to socket
dispatch(ApiSlice.endpoints.unsubscribeSocket.initiate(payload));
}
}),
getCandles: builder.query({
query: ({ refId, bucket, end, limit = 1 }) => ({
url: `/market/instruments/${refId}/candles?bucket=${bucket}&end=${end}&limit=${limit}`,
method: 'get'
})
})
})
});
export const {
useGetMarketMapQuery,
useGetInstrumentByRefIdQuery,
useGetInstrumentsQuery,
useGetCandlesQuery
} = marketApi;
and I try to dispatch my socket endpoints from ApiSlice inside of onCacheEntryAdded.
async onCacheEntryAdded(
arg,
{ updateCachedData, cacheDataLoaded, cacheEntryRemoved, dispatch }
) {
await cacheDataLoaded;
const payload = `instruments.${arg}.summary`;
// subs to socket
dispatch(ApiSlice.endpoints.subscribeSocket.initiate(payload));
// Listen for messages
const onMessage = (res) => {
const message = JSON.parse(res.data);
try {
updateCachedData((draft) => {
instrumentsAdapter.setOne(draft, message.value);
});
} catch (err) {
// eslint-disable-next-line no-console
console.log('err', err);
}
};
dispatch(ApiSlice.endpoints.channel.initiate(onMessage));
await cacheEntryRemoved;
// unsubs to socket
dispatch(ApiSlice.endpoints.unsubscribeSocket.initiate(payload));
}
}),
```
A noticed that onUploadProgress from axios are not being called at all on my Nuxt.js Project. After some debugging I found out it is related to the "#nuxtjs/pwa" and "#nuxtjs/firebase" modules. I use firebase auth, and the PWA module uses a service worker to take care of SSR auth and inject the auth token on outgoing requests.
This modules are interfering somehow on the axios onUploadProgress. I use axios to upload files to other Apis.
Once I remove the "#nuxtjs/pwa" module the onUploadProgress from axios gets called normally.
Does anyone have an idea how to fix that?
The versions of the modules:
"#nuxtjs/axios": "^5.13.6",
"#nuxtjs/firebase": "^7.6.1",
"#nuxtjs/pwa": "^3.3.5",
nuxt.config.js
firebase: {
....
services: {
auth: {
ssr: true,
persistence: 'local',
initialize: {
onAuthStateChangedAction: 'auth/onAuthStateChangedAction',
subscribeManually: false,
},
},
firestore: true,
functions: true,
},
}
pwa: {
meta: false,
icon: false,
workbox: {
importScripts: ['/firebase-auth-sw.js'],
dev: process.env.NODE_ENV === 'development',
},
},
Axios upload
const asset = await $axios.post(uploadUrl, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (progressEvent) => {
console.log('onUploadProgress');
const prog = parseInt(
Math.round((progressEvent.loaded / progressEvent.total) * 100)
);
progress(prog);
},
});
the console.log isn't called at all.
firebase-auth-sw
const ignorePaths = ["\u002F__webpack_hmr","\u002F_loading","\u002F_nuxt\u002F"]
importScripts(
'https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js'
)
importScripts(
'https://www.gstatic.com/firebasejs/8.10.0/firebase-auth.js'
)
firebase.initializeApp({"apiKey":"AIzaSyDUjfwaCRNG72CaPznknOfbNLySkFQvfrs","authDomain":"j-a-developer-web-site.firebaseapp.com","projectId":"j-a-developer-web-site","storageBucket":"j-a-developer-web-site.appspot.com","messagingSenderId":"393360816421","appId":"1:393360816421:web:75c43cac27032d924502cc"})
// Initialize authService
const authService = firebase.auth()
/**
* Returns a promise that resolves with an ID token if available.
* #return {!Promise<?string>} The promise that resolves with an ID token if
* available. Otherwise, the promise resolves with null.
*/
const getIdToken = () => {
return new Promise((resolve) => {
const unsubscribe = authService.onAuthStateChanged((user) => {
unsubscribe()
if (user) {
// force token refresh as it might be used to sign in server side
user.getIdToken(true).then((idToken) => {
resolve(idToken)
}, () => {
resolve(null)
})
} else {
resolve(null)
}
})
})
}
const fetchWithAuthorization = async (original, idToken) => {
// Clone headers as request headers are immutable.
const headers = new Headers()
for (let entry of original.headers.entries()) {
headers.append(entry[0], entry[1])
}
// Add ID token to header.
headers.append('Authorization', 'Bearer ' + idToken)
// Create authorized request
const { url, ...props } = original.clone()
const authorized = new Request(url, {
...props,
mode: 'same-origin',
redirect: 'manual',
headers
})
return fetch(authorized)
}
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url)
const expectsHTML = event.request.headers.get('accept').includes('text/html')
const isSameOrigin = self.location.origin === url.origin
const isHttps = (self.location.protocol === 'https:' || self.location.hostname === 'localhost' || self.location.hostname === '127.0.0.1')
const isIgnored = ignorePaths.some(path => {
if (typeof path === 'string') {
return url.pathname.startsWith(path)
}
return path.test(url.pathname.slice(1))
})
// https://github.com/nuxt-community/firebase-module/issues/465
if (!expectsHTML || !isSameOrigin || !isHttps || isIgnored) {
event.respondWith(fetch(event.request))
return
}
// Fetch the resource after checking for the ID token.
// This can also be integrated with existing logic to serve cached files
// in offline mode.
event.respondWith(
getIdToken().then(
idToken => idToken
// if the token was retrieved we attempt an authorized fetch
// if anything goes wrong we fall back to the original request
? fetchWithAuthorization(event.request, idToken).catch(() => fetch(event.request))
// otherwise we return a fetch of the original request directly
: fetch(event.request)
)
)
})
// In service worker script.
self.addEventListener('activate', event => {
event.waitUntil(clients.claim())
})
sw
const options = {"workboxURL":"https://cdn.jsdelivr.net/npm/workbox-cdn#5.1.4/workbox/workbox-sw.js","importScripts":["/firebase-auth-sw.js"],"config":{"debug":true},"cacheOptions":{"cacheId":"J.A-Developer-Web-Site-dev","directoryIndex":"/","revision":"qIA7lTEhJ6Mk"},"clientsClaim":true,"skipWaiting":true,"cleanupOutdatedCaches":true,"offlineAnalytics":false,"preCaching":[{"revision":"qIA7lTEhJ6Mk","url":"/?standalone=true"}],"runtimeCaching":[{"urlPattern":"/_nuxt/","handler":"NetworkFirst","method":"GET","strategyPlugins":[]},{"urlPattern":"/","handler":"NetworkFirst","method":"GET","strategyPlugins":[]}],"offlinePage":null,"pagesURLPattern":"/","offlineStrategy":"NetworkFirst"}
importScripts(...[options.workboxURL, ...options.importScripts])
initWorkbox(workbox, options)
workboxExtensions(workbox, options)
precacheAssets(workbox, options)
cachingExtensions(workbox, options)
runtimeCaching(workbox, options)
offlinePage(workbox, options)
routingExtensions(workbox, options)
function getProp(obj, prop) {
return prop.split('.').reduce((p, c) => p[c], obj)
}
function initWorkbox(workbox, options) {
if (options.config) {
// Set workbox config
workbox.setConfig(options.config)
}
if (options.cacheNames) {
// Set workbox cache names
workbox.core.setCacheNameDetails(options.cacheNames)
}
if (options.clientsClaim) {
// Start controlling any existing clients as soon as it activates
workbox.core.clientsClaim()
}
if (options.skipWaiting) {
workbox.core.skipWaiting()
}
if (options.cleanupOutdatedCaches) {
workbox.precaching.cleanupOutdatedCaches()
}
if (options.offlineAnalytics) {
// Enable offline Google Analytics tracking
workbox.googleAnalytics.initialize()
}
}
function precacheAssets(workbox, options) {
if (options.preCaching.length) {
workbox.precaching.precacheAndRoute(options.preCaching, options.cacheOptions)
}
}
function runtimeCaching(workbox, options) {
const requestInterceptor = {
requestWillFetch({ request }) {
if (request.cache === 'only-if-cached' && request.mode === 'no-cors') {
return new Request(request.url, { ...request, cache: 'default', mode: 'no-cors' })
}
return request
},
fetchDidFail(ctx) {
ctx.error.message =
'[workbox] Network request for ' + ctx.request.url + ' threw an error: ' + ctx.error.message
console.error(ctx.error, 'Details:', ctx)
},
handlerDidError(ctx) {
ctx.error.message =
`[workbox] Network handler threw an error: ` + ctx.error.message
console.error(ctx.error, 'Details:', ctx)
return null
}
}
for (const entry of options.runtimeCaching) {
const urlPattern = new RegExp(entry.urlPattern)
const method = entry.method || 'GET'
const plugins = (entry.strategyPlugins || [])
.map(p => new (getProp(workbox, p.use))(...p.config))
plugins.unshift(requestInterceptor)
const strategyOptions = { ...entry.strategyOptions, plugins }
const strategy = new workbox.strategies[entry.handler](strategyOptions)
workbox.routing.registerRoute(urlPattern, strategy, method)
}
}
function offlinePage(workbox, options) {
if (options.offlinePage) {
// Register router handler for offlinePage
workbox.routing.registerRoute(new RegExp(options.pagesURLPattern), ({ request, event }) => {
const strategy = new workbox.strategies[options.offlineStrategy]
return strategy
.handle({ request, event })
.catch(() => caches.match(options.offlinePage))
})
}
}
function workboxExtensions(workbox, options) {
}
function cachingExtensions(workbox, options) {
}
function routingExtensions(workbox, options) {
}
store's index.js
export const state = () => ({});
export const actions = {
async nuxtServerInit({ dispatch, commit }, { res }) {
// initialize the store with user if already authenticated
if (res && res.locals && res.locals.user) {
const {
allClaims: claims,
idToken: token,
...authUser
} = res.locals.user;
await dispatch('auth/onAuthStateChangedAction', {
authUser,
claims,
token,
});
}
},
};
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,
];