Sending an action from within a socket.io event in a saga - react-native

I am trying to make my login flow work using socket.io and redux-saga.
There is no problem making the socket connection and validating the user, but when I try to dispatch an action from the "loginSuccess"-event, nothing is happening
The login saga looks like this:
import { takeLatest, call, put, take } from "redux-saga/effects";
import * as types from "../actions/types";
import connect from "../constants/socketConnection";
function* doLogin(action) {
const socket = yield call(connect);
socket.emit("login", {
username: action.username,
password: action.password
});
socket.on("loginSuccess", (response) => {
yield put({type: types.SET_CURRENT_USER, currentUser: response.user})
});
}
export function* login() {
yield takeLatest(types.LOGIN, doLogin);
}
Is it not possible to send an action from within a socket.on()?
The syntax is correct (it works in my other projects), but VS code says that the "put" and "response" are never used and Prettier cannot auto format when the put is there, so it is not registered. No errors are showing though.

Related

API resolved without sending a response for /api/products, this may result in stalled requests. (How can i make a get operation)?

import dbConnect from '../../../utils/mongo'
import Product from '../../../models/Product'
export default async function handler(req, res) {
const {method} =req;
dbConnect();
if(method ==="GET"){
try{
const products = await Product.find();
res.status(200).json(products);
}
catch(err){
res.status(500).json(err);
}
}
if(method ==="POST"){
try{
const product = await Product.create(req.body);
res.status(201).json(product);
}
catch(err){
res.status(500).json(err);
}
}
}
So here I want to use res.status(200).json(products); to perform the get operation,
however, I'm receiving the error:
API resolved without sending a response for /api/products, this may result in stalled requests
When trying to run the get request with postman or axios, however post request works just fine. Any suggestions to solve it would be welcome
For anyone still looking for a solution for this, export this from the api route, you can just add the following code to your endpoint and it should resolve that :
export const config = {
api: {
externalResolver: true,
},
}
note that this will disable the warning!

Enzyme integration testing: axios.get call not being executed in redux-saga

I am trying to setup tests for some an action creator that is triggering a redux saga.
My saga retrieves a word from a local flask server (will always return the same word) and then displays that word. This is not my real-life case but I tried to start with something easy...
My action creator and saga work as expected when I trigger them with a button in my react app (the word is retrieved from the server, stored in my redux store and the displayed with a selector in my react component), but I cannot get the test to succeed.
I would like to test only the redux part, not the actual rendered react component (not sure if that is part of my problem or not)
I use Enzyme for tests, my store is created correctly and can dispatch the action. I can also see that my saga is being called with the console logs:
My test code:
import { Store } from 'redux';
import { RootState } from '../root.reducer';
import { storeFactory } from '../../../test/testUtils';
import { getSecretWord } from './secret-word.actions';
describe('getSecretWord action creator', () => {
let store: Store<RootState>;
beforeEach(() => {
store = storeFactory();
});
test('add response word to state', () => {
const secretWord = 'party';
store.dispatch(getSecretWord());
const newState = store.getState();
console.log('new state: ' + newState.secretWord);
expect(newState.secretWord).toBe(secretWord);
});
});
and my saga function:
export function* getSecretWordSaga(action: getSecretWordAction): Generator<ForkEffect | CallEffect | PutEffect, void, unknown>
{
try {
console.log('getSecretWordSaga() saga started');
console.log('before axios query call:');
const response:any = yield call(api.get, '/api/word');
// const response = {data: { word: 'party'}, status:200}
console.log('axios query returned: ');
console.log(response);
yield put(setSecretWord(response.data.word));
console.log('getSecretWordSaga() saga finsshed');
} catch (err) {
console.log('error occured:');
console.log(err);
console.log('getSecretWordSaga() saga finsshed with errors');
}
}
export function* getSecretWordSagaStart(): Generator<
ForkEffect<never>,
void,
unknown
> {
yield takeLatest(SecretWordActionTypes.GET_SECRET_WORD, getSecretWordSaga);
}
The axios api is very basic and it includes two interceptors for logging purposes:
import axios from 'axios';
export const api = axios.create({
baseURL: 'http://localhost:5000',
responseType: 'json',
});
api.interceptors.request.use(request => {
console.log('Starting Request', JSON.stringify(request, null, 2))
return request
})
api.interceptors.response.use(response => {
console.log('Response:', JSON.stringify(response, null, 2))
return response
})
I can see in the logs (in "npm test") that I get log for the line "before axios query call:' and one console.log for the request interceptor (everything looks fine there), but no more logs afterwards (neither success nor error)
If I comment out the "yield call.." and hardcode the response (like in the commented out line below), my saga runs through the end and my test succeeds.
Why is the yield Call(api.get, '/api/word') not being executed (and I don't get any error message)?
The code is my opinion correct as it is running fine when executed in react. My flask server is obviously also running and I can see in the flask app than no call to the api are being made by the running tests.
I obviously plan to mock that api call but was also running into some problems there, that's why I first wanted to get the real api call working.
After trying many different ways for adding a timeout, setting the testing function to async and adding a setTimeout in a promise did work.
It's not ideal as I have to set the timeout to a specific value, but I could not figure out a better way to get it working.
test("add response word to state", async () => {
const secretWord = 'party';
store.dispatch(getSecretWord());
await new Promise(res => setTimeout(res, 1000));
const newState = store.getState();
console.log('new state: ' + newState.secretWord);
expect(newState.secretWord).toBe(secretWord);
});

Testing debounced asynchronous request with moxios and fakeTimers

I’m trying to test an axios call in a debounced method, but moxios.wait() always times out if I add fake timers.
The test works without the clock, if the debounce time is set small enough (e.g. 10ms) but that doesn’t help testing proper debouncing.
I’ve tried experimenting with Vue.nextTick as well as making the callback to it() async, but I seem to only go further into the weeds. What’s the right approach here?
Here’s a component and test in one, that shows the problem:
import Vue from 'vue'
import { mount } from 'vue-test-utils'
import axios from 'axios'
import moxios from 'moxios'
import _ from 'lodash'
import expect from 'expect'
import sinon from 'sinon'
let Debounced = Vue.component('Debounced',
{
template: `<div><button #click.prevent="fetch"></button></div>`,
data() {
return {
data: {}
}
},
methods: {
fetch: _.debounce(async () => {
let data = await axios.post('/test', {param: 'example'})
this.data = data
}, 100)
}
}
)
describe.only ('Test axios in debounce()', () => {
let wrapper, clock
beforeEach(() => {
clock = sinon.useFakeTimers()
moxios.install()
wrapper = mount(Debounced)
})
afterEach(() => {
moxios.uninstall()
clock.restore()
})
it ('should send off a request when clicked', (done) => {
// Given we set up axios to return something
moxios.stubRequest('/test', {
status: 200,
response: []
})
// When the button is clicked
wrapper.find('button').trigger('click')
clock.tick(100)
moxios.wait(() => {
// It should have called axios with the right params
let request = moxios.requests.mostRecent()
expect(JSON.parse(request.config.data)).toEqual({param: 'example'})
done()
})
})
})
About test timeout exception: moxios.wait relies on the setTimeout but we replaced our setTimeout with custom js implementation, and to make moxios.waitwork we should invoke clock.tick(1) after wait call.
moxios.wait(() => {
// It should have called axios with the right params
let request = moxios.requests.mostRecent()
expect(JSON.parse(request.config.data)).toEqual({param: 'example'})
done()
});
clock.tick(1);
This will allow test to enter the callback body...But it will fail again with exception that request is undefined.
Main problem: The problem is that fake timers are calling all callbacks synchronously(without using macrotask event loop) but Promises still uses event loop.
So your code just ends before any Promise "then" will be executed. Of course you can push your test code as microtasks to access request and response data, for example (using await or wrap it as then" callback):
wrapper.find('button').trigger('click');
// debounced function called
clock.tick(100);
// next lines will be executed after an axios "then" callback where a request will be created.
await Promise.resolve();
// axios promise created, a request is added to moxios requests
// next "then" is added to microtask event loop
console.log("First assert");
let request = moxios.requests.mostRecent()
expect(JSON.parse(request.config.data)).toEqual({ param: 'example' });
// next lines will be executed after promise "then" from fetch method
await Promise.resolve();
console.log("Second assert");
expect(wrapper.vm.data.data).toEqual([]);
done();
I added another assertion for data to show that your code should "wait" for 2 "then" callbacks: axios internal "then" with your request creation and your "then" from fetch method.
As you see I removed wait call because it actually do nothing if you want to wait for Promises.
Yes, this is a dirty hack and closely related to axios implementation itself, but I don't have better advice and firstly I just tried to explain the issue.

How to instantiate an instance of a class that uses redux Connect

I have an API class that has various methods that communicate to a backend like "login, register, createPost" etc. I am connecting this class to a reducer. The reducer contains the state of the user info, which I want to be accessible in my Api class:
import axios from 'axios';
import React, { Component } from 'react';
import { connect } from 'react-redux';
#connect(state => ({
api: state.api,
}) )
export default class Api extends Component {
export const login = async({args}) => {
const url = this.props.api.url.concat('/login/');
const config = {
headers: {
'X-CSRFTOKEN': this.props.api.token
}
};
try {
const data = await axios.post(url, {"username": args.username, "password": args.password}, config);
this.props.api.key = data.data.token;
this.props.api.user = data.data.user;
return data;
} catch (e) {
throw e;
}
}
};
async createPost(args (content of the post)) {
try {
const url = this.props.api.url.concat('/post/PostList');
const Response = await axios.post(url, {...args}, !**this.props.api.key**! );
return Response;
} catch (e) {
throw e;
}
}
In the first method, I set the imported state.key and state.user (connected via redux) information, and I want to access that in the second method (this.props.api.key I surrounded by stars). I am trying to do it this way because I have a multitude of actions on different screens, and users have to pass their authentication information to the api method they're calling on top of whatever they're trying to do in order to be able to execute whatever respective action. I figure that it's easier to pass the user info in my Api class instead of importing the Api state into every different file I call the actions in.
The issue I'm running into is I can't instantiate a new object of api like
const api = new Api();
Because it gives me an error "cannot read property store of undefined," so I can't call the actions api.login(withArgs) in respective files, and if I make the methods static they won't have access to this.props.whatever
How do I instantiate a class that's connected to the global state of redux, or how can I access the info in that global state outside of my reducer file?
Since Api extends React.Component, why are you trying to instantiate the class yourself vs. letting React render it for you?
ReactDOM.render(<Api store={store} />)
or if you are not using JSX
ReactDOM.render(React.createElement(Api, { store })

Dispatch action on Auth0's lock.on('authenticated') event

I want to implement the new Auth0 Lock 10 in my React/Redux app.
I've checked on the internet, but nothing matches my question. There's a tutorial here, but it uses the Popup mode instead of the Redirect (default now) mode. Another one parses the url, which is useless in Lock 10.
Here's the flow:
The Auth0Lock gets instantiated when my app starts
When the user clicks on the login button, it shows the Lock widget (lock.show()) and dispatches LOGIN_REQUEST
The lock does its authentication on auth0.com (redirects out of my localhost)
Redirect back to my localhost after successful login, the Auth0Lock get instantiated again
I wait for an lock.on('authenticated') event to dispatch LOGIN_SUCCESS
And here is my actions/index.js code:
import Auth0Lock from 'auth0-lock'
export const LOGIN_REQUEST = 'LOGIN_REQUEST'
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
export const LOGIN_ERROR = 'LOGIN_ERROR'
function loginRequest() {
return {
type: LOGIN_REQUEST
}
}
function loginSuccess(profile) {
return {
type: LOGIN_SUCCESS,
profile
}
}
function loginError(error) {
return {
type: LOGIN_ERROR,
error
}
}
// import AuthService to deal with all the actions related to auth
const lock = new Auth0Lock('secret', 'secret', {
auth: {
redirectUrl: 'http://localhost:3000/callback',
responseType: 'token'
}
})
lock.on('authenticated', authResult => {
console.log('Im authenticated')
return dispatch => {
return dispatch(loginSuccess({}))
}
})
lock.on('authorization_error', error => {
return dispatch => dispatch(loginError(error))
})
export function login() {
lock.show()
return dispatch => {return dispatch(loginRequest())}
}
Now when I click on the login button, redux logger shows me LOGIN_REQUEST action dispatched, I see the lock widget, I can login, it redirects to auth0.com then back to my localhost:3000/callback with a pretty token. Everything is fine, I see the Im authenticated message in my console, but redux logger doesn't show me that the LOGIN_SUCCESS action has been dispatched.
I'm new to Redux, and I guess I'm missing one thing, but I cannot get grab of it. Thanks!
I finally put in inside actions.js, I created a new function called checkLogin()
// actions.js
const authService = new AuthService(process.env.AUTH0_CLIENT_ID, process.env.AUTH0_DOMAIN)
// Listen to authenticated event from AuthService and get the profile of the user
// Done on every page startup
export function checkLogin() {
return (dispatch) => {
// Add callback for lock's `authenticated` event
authService.lock.on('authenticated', (authResult) => {
authService.lock.getProfile(authResult.idToken, (error, profile) => {
if (error)
return dispatch(loginError(error))
AuthService.setToken(authResult.idToken) // static method
AuthService.setProfile(profile) // static method
return dispatch(loginSuccess(profile))
})
})
// Add callback for lock's `authorization_error` event
authService.lock.on('authorization_error', (error) => dispatch(loginError(error)))
}
}
And in the constructor of my App component, I call it
import React from 'react'
import HeaderContainer from '../../containers/HeaderContainer'
class App extends React.Component {
constructor(props) {
super(props)
this.props.checkLogin() // check is Auth0 lock is authenticating after login callback
}
render() {
return(
<div>
<HeaderContainer />
{this.props.children}
</div>
)
}
}
App.propTypes = {
children: React.PropTypes.element.isRequired,
checkLogin: React.PropTypes.func.isRequired
}
export default App
See here for full source code: https://github.com/amaurymartiny/react-redux-auth0-kit
My Reactjs knowledge is limited, but this was starting to be to long for a comment...
Should you not be calling store.dispatch(...) from the lock events?
Having those events return a function won't do anything unless someone invokes the function that is returned and to my knowledge Lock does not do anything with the return value of the callback function you pass as an event handler.
I think what's happening is auth0 redirects the browser window to the login authority (auth0 itself, Facebook, Google, etc.) then redirects you back to your app, which reloads your page, essentially wiping out all state. So your dispatch is sent, then the page reloads, which wipes out your state. Logging in appears to work if you use localStorage instead of redux state, but I'm not sure how that's going to affect all the other state I will need to put in my app.