Where to store socket connection in react-redux? - reactjs

Tried two ways:
call connectToServer() from action creator within Starter component in componentDidMount(); and dispatch like this:
let socket = new Socket('ws://address/socket');
socket.connect();
dispatch({
type: Constants.SESSION_SAVE_SOCKET,
socket: socket,
});
const lobbyChannel = socket.channel('lobby');
lobbyChannel.join()
.receive('ok', () => {
dispatch({
type: Constants.SESSION_LOBBYCHANNEL_RECEIVE_OK,
});
dispatch({
type: Constants.SESSION_SAVE_LOBBYCHANNEL,
lobbyChannel: lobbyChannel,
});
}).receive('error', (payload) => {
dispatch({
type: Constants.SESSION_LOBBYCHANNEL_RECEIVE_ERROR,
});
});
Next I receive state by redux's mapStateToProps connect.
The result is component is called four times and props are empty at the result.
put all logic into the reducer, but the result is: component is rendered with empty props (undefined properties) and moment after I see in console logs that connection is established, but component is already rendered.
How to deal with such a issue? Thanks for any suggestions.

The way I found that works is to setup your own middleware for the socket like so.
import {createStore, applyMiddleware} from 'redux';
import startWs, {wsMiddleware} from './ws.api';
function handleData(state = {data1: {}}, action) {
switch (action.type) {
case 'ApiGotData': return Object.assign({}, state, {data1: action.data});
default: return state;
}
}
const store = createStore(handleData, applyMiddleware(wsMiddleware));
startWs(store);
export default store;
import * as Actions from './Actions';
var socket = null;
const newData = {
'React version': '15',
'Project': 'Redux with socket.io',
'currentDateTime': new Date().toLocaleString()
};
export function wsMiddleware() {
return (next) => (action) => {
if (socket && action.type === 'ApiGetData') {
console.log('ApiGetData');
socket.emit('client:GetData', {});
} else if (socket && action.type === 'ApiSetData') {
console.log('ApiSetData');
socket.emit('client:SetData', action.data);
}
return next(action);
};
}
export default function (store) {
socket = new io();
socket.on('server:GetDataDone', (data) => {
console.log('GetDataDone');
store.dispatch(Actions.apiGotData(data));
});
socket.on('server:SetDataDone', () => {
console.log('SetDataDone');
store.dispatch(Actions.apiGetData());
});
store.dispatch(Actions.apiSetData(newData));
}
The project example is ReduxSocketIO at https://github.com/jmarkstevens/ReactPatterns.

Related

redux to dispatch response and a return message

In my action i am dispatching the type and the payload but what if i also want the res.status and a return JSON message to be included into my props. How would i do so in my action and reducer?
action
export const fetchUserPosts = () => (dispatch) => {
fetch(`${currentPort}/user/recipes`,
{
withCredentials: true,
credentials: 'include',
})
.then((res) => {
if (res.status !== 401) return res.json().then((data) => data);
return { message: { msgBody: 'UnAuthorized' }, msgError: true };
})
.then((posts) => dispatch({
type: FETCH_USER_POSTS,
payload: posts,
}));
};
reducer
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_USER_POSTS:
return {
...state,
fetchUsersPosts: action.payload,
};
default:
return state;
}
}
You can combine multiple items into a single payload. I would create different actions for success and error. Using the same action complicates the reducers logic. It's also easier to work with async/await then with nested promises.
This is a working example that uses SpaceX open API:
const FETCH_USER_POSTS_SUCCESS = 'FETCH_USER_POSTS_SUCCESS'
const FETCH_USER_POSTS_FAILED = 'FETCH_USER_POSTS_FAILURE'
const fetchPostSuccessAction = (payload) => ({
type: 'FETCH_USER_POSTS_SUCCESS',
payload,
})
const fetchPostFailureAction = (payload) => ({
type: 'FETCH_USER_POSTS_FAILURE',
payload,
})
const fetchUserPosts = () => async dispatch => {
const res = await fetch('https://api.spacexdata.com/v3/launches/latest');
if (res.status !== 401) {
const { ships: posts } = await res.json();
dispatch(fetchPostSuccessAction({
posts,
status: res.status,
}))
} else {
dispatch(fetchPostFailureAction({
message: { msgBody: 'UnAuthorized' },
}))
}
};
fetchUserPosts()(console.log)
The reducer can handle the object by destructuring it, and the properties to the new state in any way you need. You can also change other properties, for example changing errMsg to true or false according to the action's type:
export default function (state = initialState, { type, payload }) {
switch (type) {
case FETCH_USER_POSTS_SUCCESS: {
const { posts, status } = payload;
return {
...state,
status,
fetchUsersPosts: posts,
msgError: false,
message: null
};
}
case FETCH_USER_POSTS_FAILURE: {
const { message } = payload;
return {
...state,
status: 401,
fetchUsersPosts: null,
msgError: true,
message
};
}
default:
return state;
}
}
If I am following correctly you are using this action inside of a component to send a fetch. You don't have access to the components props with the reducer. You can send the http request in the component and use that to store the response in the state. Or use connect from 'react-redux' package to map the redux store to access the fetch result.
import { connect } from 'react-redux'
const component = props => {
//to access redux state in component use props.myprop
return <div>{props.myprops.title}</div>
}
const mapStateToProps = state => {
return{
myprop: state.fetchUsersPosts
}
}
export default connect(mapStateToProps)(component)
If this was what you were looking for you can learn more at https://react-redux.js.org/api/connect

Redux Thunk action with axios returning multiple values

I have a React app that uses redux-thunk and axios to fetch an API. The action fires successfully, but returns multiple payloads which means it is firing multiple times.
How can I make it fire only one time?
Code
Actions
import Axios from "axios";
import { fetchEnglishLeagueTable } from "./ActionTypes";
export function fetchEnglishTable() {
var url = "https://api.football-data.org/v2/competitions/PL/matches";
var token = "52c8d88969d84ac9b17edb956eea33af";
var obj = {
headers: { "X-Auth-Token": token }
};
return dispatch => {
return Axios.get(url, obj)
.then(res => {
dispatch({
type: fetchEnglishLeagueTable,
payload: res.data
});
})
.catch(e => {
console.log("Cant fetch ", e);
});
};
}
Reducers
import { fetchEnglishLeagueTable } from "../actions/ActionTypes";
const initialState = {
EnglishTable: {}
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case fetchEnglishLeagueTable:
return {
...state,
EnglishTable: action.payload
};
default:
return state;
}
};
export default rootReducer;
Page
const League = props => {
useEffect(() => {
props.getLeagueTable();
}, [props.leagueTable]);
console.log(props.leagueTable);
return <p>ihi</p>;
};
const mapStateToProps = state => ({
leagueTable: state.EnglishTable
});
const mapDispatchToProps = dispatch => {
return { getLeagueTable: () => dispatch(fetchEnglishTable()) };
};
export default connect(mapStateToProps, mapDispatchToProps)(League);
Store
import rootReducer from "./Reducer";
import thunk from "redux-thunk";
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
Here is what it returns
Just remove leagueTable from useEffect's dependency array, so it'll fetch them only once component is mounted. Because now you have a loop:
Get leagues -> leagueTable updates -> useEffect sees that leagueTable changed in dependency array and calls to get leagues again and we've got a loop.
const League = props => {
useEffect(() => {
props.getLeagueTable();
}, []); // <~ no props.leagueTable here
console.log(props.leagueTable);
return <p>ihi</p>;
};
Hope it helps :)

socket.io-client redirect to redux thunk middleware

I am using redux-socket.io
I am sending an action from backend server to client from server.js
soc.emit('action', { type: 'SOCKET_TAG', data: tag });
In store.js I am creating a store with socket io Middleware
import { createStore, applyMiddleware } from "redux";
...
import createSocketIoMiddleware from 'redux-socket.io';
import io from 'socket.io-client';
let socket = io('ws://127.0.0.1:4000');
let socketIoMiddleware = createSocketIoMiddleware(socket, "server/");
let store = createStore(rootReducer, applyMiddleware(thunk, logger, socketIoMiddleware));
...
Actually when I emit the socket action SOCKET_TAG from backend the action is executed in the userReducer
const userReducer = (state = initState, action) => {
switch (action.type) {
case actionTypes.SOCKET_TAG:
console.log(actionTypes.SOCKET_TAG, action.data)
return state;
default:
return state
}
But I would like to execute this in the middleware
export const initUser = () => {
const db = client.getServiceClient(RemoteMongoClient.factory, "mongodb-atlas").db(dbName);
return async (dispatch, getState) => { // async call to database
let user; // User found with the tag
client.auth.loginWithCredential(new AnonymousCredential()).then(u =>
user = db.collection(collectionName).findOne({ tag })
).then(() => {
dispatch({ type: actionTypes.INIT_USER, user });
}).catch((err) => {
dispatch({ type: actionTypes.ERROR, err });
});
}
}
I'm new to redux-thunk, thank you.

redux/redux-observable error: Actions must be plain objects

I'm using redux with redux-observable and get this strange error:
Actions must be plain objects. Use custom middleware for async >actions.
/* Component.jsx */
import React from "react"
import { serialNumberCheck } from '../actions'
const Component = props => {
...
<button
onClick={() => props.serialNumberCheck('123456789123456')}
>
Check
</button>
...
}
const mapDispatchToProps = dispatch =>
bindActionCreators({serialNumberCheck}, dispatch)
export default compose(
reduxForm({
...
}),
withStyles(styles),
connect(mapDispatchToProps)
)(Component)
/* actions.js */
export const SERIAL_NUMBER_CHECK = 'SERIAL_NUMBER_CHECK'
export const SERIAL_NUMBER_CHECK_SUCCESS = 'SERIAL_NUMBER_CHECK_SUCCESS'
export const serialNumberCheck = (serialNumber) => ({
type: SERIAL_NUMBER_CHECK,
payload: serialNumber
})
export const serialNumberCheckSuccess = (data) => ({
type: SERIAL_NUMBER_CHECK,
payload: data
})
/* epics.js */
...
import { serialNumberCheck } from "../actions"
import ... from 'rxjs'
...
function serialNumberCheckEpic(action$) {
return action$
.ofType(SERIAL_NUMBER_CHECK)
.switchMap((data) => {
return ajax.getJSON(`http://localhost:3004/sn/?sn=${data.payload}`)
.map((data) => data)
})
.map(data => {
if(data.length !== 0) {
serialNumberCheckSuccess({success: true});
}
})
}
...
export const rootEpic = combineEpics(
...
serialNumberCheckEpic
);
/* reducer.js */
import {
SERIAL_NUMBER_CHECK_SUCCESS,
} from '../actions'
...
export default function epicReducer(state = initialState, action) {
switch (action.type) {
case SERIAL_NUMBER_CHECK_SUCCESS:
return {
...state,
success: action.payload
}
}
}
/* JSON-SERVER RESPONSE */
[
{
"id": 1,
"sn": "123456789123456"
}
]
Inside component i'am calling function serialNumberCheck() and passing inside sierial number that we need to check.
Inside Epic im passing serial number to json-server that checks if this number exists in my "database". If serial number exists, server response is .json containing some parameters.
So if response isn't empty we need to write success: true inside redux store.
But in the end we get successfull GET request, and then error: Actions must be plain objects. Use custom middleware for async actions., but no changes inside redux-store and nothing from SERIAL_NUMBER_CHECK_SUCCESS action.
Finally, I found the solution. I've just missed the return before calling action inside my epic.
function serialNumberCheckEpic(action$) {
return action$
.ofType(SERIAL_NUMBER_CHECK)
.switchMap((data) => {
return ajax.getJSON(`http://localhost:3004/sn/?sn=${data.payload}`)
.map((data) => data)
})
.map(data => {
if(data.length !== 0) {
+ return serialNumberCheckSuccess({success: true});
}
})
}

how to call another reducer method with redux react

Reducer 1 code is as below. I want to call another reducer method after successful authetication of user. so its based of response of reducer 1 , I want to call method/action of reducer 2.
const LOGIN = 'redux-example/auth/LOGIN';
const LOGIN_SUCCESS = 'redux-example/auth/LOGIN_SUCCESS';
const LOGIN_FAIL = 'redux-example/auth/LOGIN_FAIL';
import { browserHistory } from 'react-router';
import { apiurl } from '../../Constants';
import {savedata} from '../../redux/modules/new';
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case LOGIN:
return {
...state,
loggingIn: true
};
case LOGIN_SUCCESS:
return {
...state,
loggingIn: false,
user: action.result
};
case LOGIN_FAIL:
return {
...state,
loggingIn: false,
user: null,
loginError: action.error
};
default:
return state;
}
}
export function login(page,email,password) {
var querystring = require('querystring');
if(action == undefined) action = null;
var data = querystring.stringify({
email: email,
password: password
});
return {
types: [LOGIN, LOGIN_SUCCESS, LOGIN_FAIL],
promise: (client) => client.post(apiurl + 'ajax/login', {
data: data
}).then(response => {
//console.log(response);
switch(page){
case 'signin':
if(response.auth == 'true') {
redirectuser(response);
}
break;
default:
break;
}
return response;
})
.catch( error => Promise.reject(error))
};
}
export function redirectuser(response) {
console.log('response is as below');
console.log(response);
if(response.action == 'action1'){
savedata();
// here I want call another reducer method save data
}
}
When I call action save data of reducer 2 from reducer 1 , it does not work. How to dispatch action of reducer 2 from reducer 1.
Edit 1: my middleware code is as below
export default function clientMiddleware(client) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
const { promise, types, ...rest } = action; // eslint-disable-line no-redeclare
if (!promise) {
return next(action);
}
const [REQUEST, SUCCESS, FAILURE] = types;
next({ ...rest, type: REQUEST });
const actionPromise = promise(client, dispatch);
actionPromise.then(
result => next({ ...rest, result, type: SUCCESS }),
error => next({ ...rest, error, type: FAILURE })
).catch(error => {
next({ ...rest, error, type: FAILURE });
});
return actionPromise;
};
}
Dispatching an action inside a reducer is not a good move. As i understand, you have to do some update synchronously. One way is, once the first reducer is updated, where ever your are consuming that reducer go and inside componentWillReceiveProps or componentDidUpdate do something like.
NOTE: before dispatching you have to import the configurestore and create a const dispatch from store.
componentWillReceiveProps(nextProps)
{
//only if user was not there previously and now user is there
if(!this.props.user && nextProps.user)
{
dispatch({type: SECOND_ACTION, payLoad})
}
}

Resources