eventChannel emitter action is not taken from redux-saga - reactjs

I have create saga watcher to connecting websocket and listen received data. const payload = yield take(socketChannel); is waiting for take received message, but it not all received from
emit({type: 'WEBSOCKET_MESSAGE_RECEIVED', message});
Can someone help to find the issue?
function createWebSocketConnection() {
return new Promise((resolve) => {
websocket.onOpen(() => {
makeRequests(websocket);
resolve(websocket);
});
websocket.connect(true);
});
}
function createSocketChannel(socket) {
return eventChannel((emit) => {
socket.onMessage((message) => {
if (message.path) {
console.log('Emitting received data...');
return emit({type: 'WEBSOCKET_MESSAGE_RECEIVED', message});
}
});
socket.onClose(() => {
emit(END);
});
socket.onError(() => {
emit(END);
});
const unsubscribe = () => {
socket.onMessage = null;
};
return unsubscribe;
});
}
function* listenForSocketMessages() {
let socket;
let socketChannel;
try {
socket = yield call(createWebSocketConnection);
socketChannel = yield call(createSocketChannel, socket);
// tell the application that we have a connection
yield dispatch(ActionCreators.wsClientOpened());
while (true) {
// wait for a message from the channel
const payload = yield take(socketChannel);
console.log('new payload');
// a message has been received, dispatch an action with the message payload
yield dispatch(createAction(payload.path, payload));
}
}
catch (error) {
// yield dispatch(ActionCreators.wsClientError());
}
finally {
if (yield cancelled()) {
// close the channel
socketChannel.close();
// close the WebSocket connection
socket.close();
}
else {
// yield dispatch(ActionCreators.wsClientError());
}
}
}
const createAction = (type: string, payload?: any) => ({
type,
payload,
});
export default function* watchConnectWebsocket() {
// starts the task in the background
const socketTask = yield fork(listenForSocketMessages);
// when DISCONNECT action is dispatched, we cancel the socket task
yield take(WsActionTypes.WEBSOCKET_CLOSED);
yield cancel(socketTask);
yield dispatch(ActionCreators.wsClientClosed());
}

Related

How To Know If Dispatch Is Success or No In Redux Saga

So i already create the createMonsterStart and it will hit my API, my API is returning response code, and I want to alert success when the response code is 00 otherwise it will alert failed, how can i achieve that? here is my code:
const onSubmitHandler = () => {
dispatch(createMonsterStart(monster))
if(dispatch success){
alert("success")
}else{
alert("error")
}
}
And here is the redux saga code:
export function* createMonsterAsync({ payload: { monster } }) {
try {
const user = yield select(getUser)
const a = yield call(createMonster, user.user.token, monster)
if (a.error) {
yield put(createMonsterFailure(a.error))
return false
}
const monsters = yield call(fetchMonsterAsync)
yield put(createMonsterSuccess(monsters))
} catch (error) {
yield put(createMonsterFailure(error))
}
}

React websocket message coming before response

I am having a case now with websockets.
I am using Promise to read response and message from socket. Afterwards I compare them and if they have the same id, it goes through.
However, most of the time socket message is arriving (fast) before response and as a result I cannot compare socket message with response id.
const init = {
get(...args) {
return request.get(...args);
},
post(...args) {
// return request.post(...args)
return new Promise((resolve, reject) => {
let response = {};
request
.post(...args)
.then((res) => {
console.log("RESPONSE====>", res);
response = res;
})
.catch((err) => reject(err));
webSocket.onmessage = (mes) => {
try {
// console.log(JSON.parse(mes.data))
let { correlation_id: socketId, status_code } = JSON.parse(mes.data);
console.log("MESSAGE====>", socketId);
if (socketId === response.message) {
resolve(response);
} else if (status_code > 300) {
reject({ status_code });
}
} catch (e) {
console.log(e);
}
};
// resolve(response)
});
}
export default init;
Above is my code for axios requests. If you know how to resolve it, kindly help here.

React redux Sagas, wait for a Saga to set state

I have some component with a code like this:
const startLogin = (code) => {
dispatch(login({ code }));
const publicKeyFromLocalST = window.localStorage.getItem('push_public_key');
setPublicKey(publicKeyFromLocalST);
// etc
When I dispatch the saga login it will store some data in localStorage.
I need to execute the 3rd line (setPublicKey) after that data be actually indeed in localStorage.
How can "await" for dispatch(login({ code })); to be completed before setPublicKey?
Two options:
Execute the setPublicKey function inside the worker saga, you can control the workflow in the worker saga easily with yield.
function* login(action) {
const response = yield call(apiCall);
if (response.error) {
yield put({ type: actionType.LOGIN_FAIL });
} else {
yield put({ type: actionType.LOGIN_SUCCESS, data: response.data });
const publicKeyFromLocalST = window.localStorage.getItem('push_public_key');
setPublicKey(publicKeyFromLocalST);
}
}
Promisify the dispatch(login({code})), you should create a helper function like this:
const loginAsyncCreator = (dispatch) => (payload) => {
return new Promise((resolve, reject) => dispatch(loginCreator(payload, { resolve, reject })));
};
You need to pass the resolve/reject to worker saga via action.meta, then you can decide when to resolve or reject the promise. Then, you can use async/await in your event handler. See below example:
import { call, put, takeLatest } from 'redux-saga/effects';
import { createStoreWithSaga } from '../../utils';
const actionType = {
LOGIN: 'LOGIN',
LOGIN_FAIL: 'LOGIN_FAIL',
LOGIN_SUCCESS: 'LOGIN_SUCCESS',
};
function apiCall() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ error: null, data: 'login success' });
}, 2000);
});
}
function* login(action) {
console.log('action: ', action);
const {
meta: { resolve, reject },
} = action;
const response = yield call(apiCall);
console.log('response: ', response);
if (response.error) {
yield put({ type: actionType.LOGIN_FAIL });
yield call(reject, response.error);
} else {
yield put({ type: actionType.LOGIN_SUCCESS, data: response.data });
yield call(resolve, response.data);
}
}
function* watchLogin() {
yield takeLatest(actionType.LOGIN, login);
}
const store = createStoreWithSaga(watchLogin);
function loginCreator(payload, meta) {
return {
type: actionType.LOGIN,
payload,
meta,
};
}
const loginAsyncCreator = (dispatch) => (payload) => {
return new Promise((resolve, reject) => dispatch(loginCreator(payload, { resolve, reject })));
};
const loginAsync = loginAsyncCreator(store.dispatch);
async function startLogin() {
await loginAsync({ code: '1' });
console.log('setPublicKey');
}
startLogin();
The logs:
action: {
type: 'LOGIN',
payload: { code: '1' },
meta: { resolve: [Function (anonymous)], reject: [Function (anonymous)] }
}
response: { error: null, data: 'login success' }
setPublicKey

Use eventChannel to get real time data from Firestore

I would like to sync my data in React with Firestore, each time some data is changed I would like to have an update/refresh of my data.
For fetching the data I have:
function* syncCustomers() {
try {
const response = yield call(fireBaseBackend.getCustomersAPI);
yield put(loadCustomers(response));
} catch (error) {
yield put(customerError(error));
}
}
Where my getCustomersAPI is:
getCustomersAPI = () => {
return new Promise((resolve, reject) => {
firebase.firestore().collection("customers").onSnapshot((snap) => {
let documents = [];
snap.docs.forEach(doc => {
let result = doc.data();
result.uid = doc.id;
documents.push(result);
});
resolve(documents);
});
});
};
This works to get my data, for the real time I implemented:
function* usersChannelSaga () {
try {
while (true) {
const response = yield call(fireBaseBackend.getCustomersAPI);
yield put(loadCustomers(response));
}
}
catch (error) {
yield put(customerError(error));
}
}
But I would like to use the eventChannel for this. How does this need to be implemented?
I tried:
function* usersChannelSaga () {
const channel = eventChannel(emit => fireBaseBackend.syncCustomers(emit));
try {
while (true) {
const data = yield take(channel);
yield put(loadCustomers(response));
}
}
catch (error) {
yield put(customerError(error));
}
}
Where syncCustomers is :
syncCustomers(emit){
return firebase.firestore().collection("customers").onSnapshot(emit);
}
Anyone an idea how to solve this?

Standard way of reconnecting to webSocket server in redux-Saga?

I'm trying to connect from my react App to websocket server using redux-saga and want to capture the connection loss (server error, reboot) so that to reconnect say in intervals of 4 seconds until the connection is again back. The problem is on reconnecting to webSocket the redux store does not get updated anymore.
I tried using eventChannel of redux-saga as per following code. Unfortunately there was not or at least I couldn't find any documentation answering ws reconnect in redux-saga.
import {eventChannel} from 'redux-saga';
import {all, takeEvery, put, call, take, fork} from 'redux-saga/effects'
import {INITIALIZE_WS_CHANNEL} from "../../constants/ActionTypes"
import {updateMarketData} from "../actions"
function createEventChannel() {
return eventChannel(emit => {
//Subscribe to websocket
const ws = new WebSocket('ws://localhost:9000/rates');
ws.onopen = () => {
console.log("Opening Websocket");
};
ws.onerror = error => {
console.log("ERROR: ", error);
};
ws.onmessage = e => {
return emit({data: JSON.parse(e.data)})
};
ws.onclose = e => {
if (e.code === 1005) {
console.log("WebSocket: closed");
} else {
console.log('Socket is closed Unexpectedly. Reconnect will be attempted in 4 second.', e.reason);
setTimeout(() => {
createEventChannel();
}, 4000);
}
};
return () => {
console.log("Closing Websocket");
ws.close();
};
});
}
function * initializeWebSocketsChannel() {
const channel = yield call(createEventChannel);
while (true) {
const {data} = yield take(channel);
yield put(updateMarketData(data));
}
}
export function * initWebSocket() {
yield takeEvery(INITIALIZE_WS_CHANNEL, initializeWebSocketsChannel);
}
export default function* rootSaga() {
yield all ([
fork(initWebSocket)
]);
}
UPDATE
To complete the accepted answer by #azundo for someone looking for a complete example of websocket & redux-saga I'm adding following code:
function * initializeWebSocketsChannel() {
console.log("going to connect to WS")
const channel = yield call(createEventChannel);
while (true) {
const {data} = yield take(channel);
yield put(updateMarketData(data));
}
}
export function * startStopChannel() {
while (true) {
yield take(START_CHANNEL);
yield race({
task: call(initializeWebSocketsChannel),
cancel: take(STOP_CHANNEL),
});
//if cancel wins the race we can close socket
ws.close();
}
}
export default function* rootSaga() {
yield all ([
startStopChannel()
]);
}
The START_CHANNEL and STOP_CHANNEL actions can be called in componentDidMount and componentWillUnmount of react component life cycle, respectively.
The reason this isn't working is because your recursive call to createEventChannel is not being yielded to the saga middleware redux-saga has no way of knowing of the subsequent event channel creations. You'll want your recursive function to be defined within the event channel instead, see code below, so there is only one eventChannel that is always hooked into the store.
Also note the addition of emitting END on an expected socket close so that you don't leave the eventChannel open forever if you don't reconnect.
import {eventChannel, END} from 'redux-saga';
let ws; //define it here so it's available in return function
function createEventChannel() {
return eventChannel(emit => {
function createWs() {
//Subscribe to websocket
ws = new WebSocket('ws://localhost:9000/rates');
ws.onopen = () => {
console.log("Opening Websocket");
};
ws.onerror = error => {
console.log("ERROR: ", error);
};
ws.onmessage = e => {
return emit({data: JSON.parse(e.data)})
};
ws.onclose = e => {
if (e.code === 1005) {
console.log("WebSocket: closed");
// you probably want to end the channel in this case
emit(END);
} else {
console.log('Socket is closed Unexpectedly. Reconnect will be attempted in 4 second.', e.reason);
setTimeout(() => {
createWs();
}, 4000);
}
};
}
createWs();
return () => {
console.log("Closing Websocket");
ws.close();
};
});
}

Resources