How should I log every dispatch call to a Redux store? - javascript

I want to debug my Redux application by logging every action sent to the dispatcher in my the console.
What is generally regarded as the best approach for doing this?

You can use a simple logging middleware, like the example in the redux middleware docs:
const logger = store => next => action => {
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
console.groupEnd()
return result
}
Or use the redux-logger module, which basically does the same thing.
With TypeScript, use this annotation (where TStore is the type-name of the root of your state:
import { Middleware } from 'redux';
// ...
const logger: Middleware<{},TState> = store => next => action => {
console.group(action.type)
// etc

Related

Simulate actioncable websocket receive in webdriverIo

Is there a way during webdriverio runtime to simulate an actioncable receive?
I am using a fork of the package action-cable-react called actioncable-js-jwt for Rails actioncable js connections. Both of these packages are no longer maintained, but actioncable-js-jwt was the only actioncable for react package I could find that supported jwt authentication. I am building an app in my company's platform and jwt authentication is required.
The problem I am running into is that I have a react component which dispatches a redux action to call an api. The api returns a 204, and the resulting data is broadcasted out from Rails to be received by the actioncable connection. This triggers a dispatch to update the redux store with new data. The component does actions based on new data compared to the initial value on component load, so I cannot simply just set initial redux state for wdio - I need to mock the actioncable receive happening.
The way the actioncable subscription is created is:
export const createChannelSubscription = (cable, receivedCallback, dispatch, channelName) => {
let subscription;
try {
subscription = cable.subscriptions.create(
{ channel: channelName },
{
connected() {},
disconnected(res) { disconnectedFromWebsocket(res, dispatch); },
received(data) { receivedCallback(data, dispatch); },
},
);
} catch (e) {
throw new Error(e);
}
return subscription;
};
The receivedCallback function is different for each channel, but for example the function might look like:
export const handleUpdateRoundLeaderWebsocket = (data, dispatch) => {
dispatch({ type: UPDATE_ROUNDING_LEADER, round: data });
};
And the redux state is used here (code snippets):
const [currentLeader, setCurrentLeader] = useState(null);
const userId = useSelector((state) => state.userId);
const reduxStateField = useSelector((state) => state.field);
const onChange = useCallback((id) => {
if (id !== currentLeader) {
if (id !== userId && userId === currentLeader) {
setShow(true);
} else {
setCurrentLeader(leaderId);
}
}
}, [currentLeader, userId]);
useEffect(() => {
onChange(id);
}, [reduxStateField.id, onChange]);
Finally, my wdio test currently looks like:
it('has info dialog', () => {
browser.url('<base-url>-rounding-list-view');
$('#my-button').click();
$('div=Continue').click();
// need new state after the continue click
// based on new state, might show an info dialog
});
Alternatively, I could look into manually updating redux state during wdio execution - but I don't know how to do that and can't find anything on google except on how to provide initial redux state.

How do I store a non serialisable variable in a react - redux state without breaking it if I really must (like saving a device connection via WebAPIs)

I keep getting the statement "do not save non-serializable variables in your state" in almost every google search result - But what happens when I really should?
Progect: I am building an app for deviceS connected via SerialPort (using SerialPort WebAPI).
I wish to save the connection instance since I use it throughout all my application and I am honestly tired of passing the instance down and up whenever I need it without react knowing to re-render data and display new data - which is important for me too.
Steps that I have done:
It was easy to ignore the non-serializable error using serializableCheck: false:
export default configureStore({
reducer: {
serialport: SerialPortDevicesReducer,
bluetooth: BluetoothDevicesReducer,
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
thunk,
serializableCheck: false
}).concat(logger),})
But now I am facing the big problem:
Whenever I create a connection I get the object that handles that specific SerialPort device object that is connected.
deviceReducer: {
id: 1,
instance: SerialPort{[attr and methods here]},
...
}
Whenever I use methods like open(), write() or read() it changes the main connection instance object and breaks with that known error:
Error: Invariant failed: A state mutation was detected between
dispatches, in the path 'serialport.0.instance.readable'. This may
cause incorrect behavior
Since It's not serializable I cannot clone it (which I think is the reason?) and then re-assign it + I think cloning a connection instance will cause other device-connection issues.
I ended up writing the connect method case directly in the state with a "promise" new variable to handle the result.
// click in a react component
const handleConnect = () => {
try {
if ( dispatch(connect(device)) ) {
setActiveStep((prevActiveStep) => prevActiveStep + 1)
return true
}
}
catch (e) {
console.error("Device cannot connect: ", e)
}
}
// In a file that trigges dispatch() to the reduces
const connect = (deviceId) => async (dispatch, getState) => {
try {
dispatch({
type: "serialport/connect",
payload: deviceId
})
} catch(e) {
console.log(e)
}
}
// in reducer
const SerialPortDevicesReducer = (state = initialState, action) => {
switch (action.type) {
case 'serialport/connect':
try {
return {
...state,
[action.payload]: {
...state[action.payload],
promise: state[action.payload].instance.open({baudRate: 115200})
}
}
} catch (e) {
console.error("Cannot run promise inside reducer: ", e)
}
This is the only workaround I currently found. And this basically forces me to handle (maybe some complex) things in the reducer instead of just passing data to it. I tried applying the same for the write method:
// click in component
const handleExecute = (command) => {
try {
dispatch(writeToSP(device1.device, command))
} catch (e) {
console.log(e)
}
}
// In file which trigges the dispatch()
const writeToSP = (deviceId, command = "Z !\n") => async (dispatch) => {
let startTime = new Date().getTime()
let encoder = new TextEncoder()
try {
dispatch({
type: "serialport/write",
payload: {
id: deviceId,
// cmd: encoder.encode(command),
// startTime
}
})
} catch (e) {
console.error("error writing: ", e)
}
}
// in reducer
...
case 'serialport/write':
try {
const writer = state[action.payload.id].instance.writable.getWriter()
} catch (e) {
console.error("Cannot run promise inside reducer: ", e)
}
and again, get the error of "Error: Invariant failed: A state mutation was detected..." which I am guessing a result of it changing other attributes in the SerialPort instance.
Having packages like redux-promise-middleware are awesome, but it seems like an object in my state is the one responsible for its own promise and changes.
How do I handle this specific situation?
Simple: don't put it into Redux. Redux is made for data, not for arbirtary external libraries/dependency injection.
If that value will never change after initialization and you do the initialization outside of React, just put it into a global variable that is exported.
If that value will change over time, you should use the Dependency Injection mechanism of React for it: Context. This is really what context is made for - not sharing state values, but global dependencies.

What is the best way to fetch api in redux?

How to write best way to fetch api resource in react app while we use redux in application.
my actions file is actions.js
export const getData = (endpoint) => (dispatch, getState) => {
return fetch('http://localhost:8000/api/getdata').then(
response => response.json()).then(
json =>
dispatch({
type: actionType.SAVE_ORDER,
endpoint,
response:json
}))
}
is it best way to fetch api?
The above code is fine.But there are few points you should look to.
If you want to show a Loader to user for API call then you might need some changes.
You can use async/await the syntax is much cleaner.
Also on API success/failure you might want to show some notification to user. Alternatively, You can check in componentWillReceiveProps to show notification but the drawback will be it will check on every props changes.So I mostly avoid it.
To cover this problems you can do:
import { createAction } from 'redux-actions';
const getDataRequest = createAction('GET_DATA_REQUEST');
const getDataFailed = createAction('GET_DATA_FAILURE');
const getDataSuccess = createAction('GET_DATA_SUCCESS');
export function getData(endpoint) {
return async (dispatch) => {
dispatch(getDataRequest());
const { error, response } = await fetch('http://localhost:8000/api/getdata');
if (response) {
dispatch(getDataSuccess(response.data));
//This is required only if you want to do something at component level
return true;
} else if (error) {
dispatch(getDataFailure(error));
//This is required only if you want to do something at component level
return false;
}
};
}
In your component:
this.props.getData(endpoint)
.then((apiStatus) => {
if (!apiStatus) {
// Show some notification or toast here
}
});
Your reducer will be like:
case 'GET_DATA_REQUEST': {
return {...state, status: 'fetching'}
}
case 'GET_DATA_SUCCESS': {
return {...state, status: 'success'}
}
case 'GET_DATA_FAILURE': {
return {...state, status: 'failure'}
}
Using middleware is the most sustainable way to do API calls in React + Redux applications. If you are using Observables, aka, Rxjs then look no further than redux-observable.
Otherwise, you can use redux-thunk or redux-saga.
If you are doing a quick prototype, then making a simple API call from the component using fetch is good enough. For each API call you will need three actions like:
LOAD_USER - action used set loading state before API call.
LOAD_USER_SUCC - when API call succeeds. Dispatch on from then block.
LOAD_USER_FAIL - when API call fails and you might want to set the value in redux store. Dispatch from catch block.
Example:
function mounted() {
store.dispatch(loadUsers());
getUsers()
.then((users) => store.dispatch(loadUsersSucc(users)))
.catch((err) => store.dispatch(loadUsersFail(err));
}

Dependency Injection in a redux action creator

I'm currently building a learner React/Redux Application and I can not wrap my head around how to do dependency injection for services.
To be more specific: I have a BluetoothService (which abstracts a 3rd Party Library) to scan for and connect to other devices via bluetooth. This service gets utilized by the action creators, something like this:
deviceActionCreators.js:
const bluetoothService = require('./blueToothService')
function addDevice(device) {
return { type: 'ADD_DEVICE', device }
}
function startDeviceScan() {
return function (dispatch) {
// The Service invokes the given callback for each found device
bluetoothService.startDeviceSearch((device) => {
dispatch(addDevice(device));
});
}
}
module.exports = { addDevice, startDeviceScan };
(I am using the thunk-middleware)
My Problem however is: how to inject the service itself into the action-creator?
I don't want that hard-coded require (or importin ES6) as I don't think this is a good pattern - besides making testing so much harder. I also want to be able to use a mock-service while testing the app on my work station (which doesn't have bluetooth) - so depending on the environment i want another service with the same interface injected inside my action-creator. This is simply not possible with using a static import.
I already tried making the bluetoothService a parameter for the Method itself (startDeviceScan(bluetoothService){}) - effectively making the method itself pure - but that just moves the problem to the containers using the action. Every container would have to know about the service then and get an implementation of it injected (for example via props).
Plus when I want to use the action from within another action I end up with the same problem again.
The Goal:
I want to decide on bootstrapping time which implemenation to use in my app.
Is there a good way or best practice for doing this?
React-thunk supports passing an arbitrary object to a thunk using withExtraArgument. You can use this to dependency-inject a service object, e.g.:
const bluetoothService = require('./blueToothService');
const services = {
bluetoothService: bluetoothService
};
let store = createStore(reducers, {},
applyMiddleware(thunk.withExtraArgument(services))
);
Then the services are available to your thunk as a third argument:
function startDeviceScan() {
return function (dispatch, getstate, services) {
// ...
services.bluetoothService.startDeviceSearch((device) => {
dispatch(addDevice(device));
});
}
}
This is not as formal as using a dependency-injection decorator in Angular2 or creating a separate Redux middleware layer to pass services to thunks---it's just an "anything object" which is kind of ugly---but on the other hand it's fairly simple to implement.
You can use a redux middleware that will respond to an async action. In this way you can inject whatever service or mock you need in a single place, and the app will be free of any api implementation details:
// bluetoothAPI Middleware
import bluetoothService from 'bluetoothService';
export const DEVICE_SCAN = Symbol('DEVICE_SCAN'); // the symbol marks an action as belonging to this api
// actions creation helper for the middleware
const createAction = (type, payload) => ({
type,
payload
});
// This is the export that will be used in the applyMiddleware method
export default store => next => action => {
const blueToothAPI = action[DEVICE_SCAN];
if(blueToothAPI === undefined) {
return next(action);
}
const [ scanDeviceRequest, scanDeviceSuccess, scanDeviceFailure ] = blueToothAPI.actionTypes;
next(createAction(scanDeviceRequest)); // optional - use for waiting indication, such as spinner
return new Promise((resolve, reject) => // instead of promise you can do next(createAction(scanDeviceSuccess, device) in the success callback of the original method
bluetoothService.startDeviceSearch((device) => resolve(device), (error) = reject(error)) // I assume that you have a fail callback as well
.then((device) => next(createAction(scanDeviceSuccess, device))) // on success action dispatch
.catch((error) => next(createAction(scanDeviceFailure, error ))); // on error action dispatch
};
// Async Action Creator
export const startDeviceScan = (actionTypes) => ({
[DEVICE_SCAN]: {
actionTypes
}
});
// ACTION_TYPES
export const SCAN_DEVICE_REQUEST = 'SCAN_DEVICE_REQUEST';
export const SCAN_DEVICE_SUCCESS = 'SCAN_DEVICE_SUCCESS';
export const SCAN_DEVICE_FAILURE = 'SCAN_DEVICE_FAILURE';
// Action Creators - the actions will be created by the middleware, so no need for regular action creators
// Applying the bluetoothAPI middleware to the store
import { createStore, combineReducers, applyMiddleware } from 'redux'
import bluetoothAPI from './bluetoothAPI';
const store = createStore(
reducers,
applyMiddleware(bluetoothAPI);
);
// Usage
import { SCAN_DEVICE_REQUEST, SCAN_DEVICE_SUCCESS, SCAN_DEVICE_FAILURE } from 'ACTION_TYPES';
dispatch(startDeviceScan([SCAN_DEVICE_REQUEST, SCAN_DEVICE_SUCCESS, SCAN_DEVICE_FAILURE]));
You dispatch the startDeviceScan async action, with the action types that will be used in the creation of the relevant actions. The middleware identifies the action by the symbol DEVICE_SCAN. If the action doesn't contain the symbol, it dispatches it back to the store (next middleware / reducers).
If the symbol DEVICE_SCAN exists, the middleware extracts the action types, creates and dispatches a start action (for a loading spinner for example), makes the async request, and then creates and dispatches a success or failure action.
Also look at the real world redux middle example.
Can you wrap your action creators into their own service?
export function actionCreatorsService(bluetoothService) {
function addDevice(device) {
return { type: 'ADD_DEVICE', device }
}
function startDeviceScan() {
return function (dispatch) {
// The Service invokes the given callback for each found device
bluetoothService.startDeviceSearch((device) => {
dispatch(addDevice(device));
});
}
}
return {
addDevice,
startDeviceScan
};
}
Now, any clients of this service will need to provide an instance of the bluetoothService. In your actual src code:
const bluetoothService = require('./actual/bluetooth/service');
const actionCreators = require('./actionCreators')(bluetoothService);
And in your tests:
const mockBluetoothService = require('./mock/bluetooth/service');
const actionCreators = require('./actionCreators')(mockBluetoothService);
If you don't want to specify the bluetooth service every time you need to import the action creators, within the action creators module you can have a normal export (that uses the actual bluetooth service) and a mock export (that uses a mock service). Then the calling code might look like this:
const actionCreators = require('./actionCreators').actionCreators;
And your test code might look like this:
const actionCreators = require('./actionCreators').mockActionCreators;
I created a dependency-injecting middleware called redux-bubble-di for exactly that purpose. It can be used to inject an arbitrary number of dependencies into action creators.
You can install it by npm install --save redux-bubble-di or download it.
Your example using redux-bubble-di would look like this:
//import { DiContainer } from "bubble-di";
const { DiContainer } = require("bubble-di");
//import { createStore, applyMiddleware } from "redux";
const { createStore, applyMiddleware } = require("redux");
//import reduxBubbleDi from "redux-bubble-di";
const reduxBubbleDi = require("redux-bubble-di").default;
const bluetoothService = require('./blueToothService');
DiContainer.setContainer(new DiContainer());
DiContainer.getContainer().registerInstance("bluetoothService", bluetoothService);
const store = createStore(
state => state,
undefined,
applyMiddleware(reduxBubbleDi(DiContainer.getContainer())),
);
const startDeviceScan = {
bubble: (dispatch, bluetoothService) => {
bluetoothService.startDeviceSearch((device) => {
dispatch(addDevice(device));
});
},
dependencies: ["bluetoothService"],
};
// ...
store.dispatch(startDeviceScan);

Isomorphic Redux app not registering Redux-Thunk?

I seem to have a weird bug. I'm currently using Redux isomorphically and am also including redux-thunk as the middleware for async actions. Here's what my store config looks like:
// Transforms state date from Immutable to JS
const transformToJs = (state) => {
const transformedState = {};
for (const key in state) {
if (state.hasOwnProperty(key)) transformedState[key] = state[key].toJS();
}
return transformedState;
};
// Here we create the final store,
// If we're in production, we want to leave out development middleware/tools
let finalCreateStore;
if (process.env.NODE_ENV === 'production') {
finalCreateStore = applyMiddleware(thunkMiddleware)(createStore);
} else {
finalCreateStore = applyMiddleware(
createLogger({transformer: transformToJs}),
thunkMiddleware
)(createStore);
}
// Exports the function that creates a store
export default function configureStore(initialState) {
const store = finalCreateStore(reducers, initialState);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('.././reducers/index', () => {
const nextRootReducer = require('.././reducers/index');
store.replaceReducer(nextRootReducer);
});
}
return store;
}
The weird part about this is that I don't think there's anything wrong with this file because my createLogger is applied just fine. It logs out all my actions and state, but the minute I return a function instead of an object in an action creator, the execution is lost. I've tried throwing in debugger statements, which never hit and reordering the middleware also doesn't seem to help.
createUser(data) {
// This `debugger` will hit
debugger;
return (dispatch) => {
// This `debugger` will NOT hit, and any code within the function will not execute
debugger;
setTimeout(() => {
dispatch(
AppActionsCreator.createFlashMessage('yellow', 'Works!')
);
}, 1000);
};
},
Has anyone experienced something like this before?
DOH! I wasn't dispatching the action. I was only calling the action creator. Gonna have to get used to that with Redux!
How I thought I was invoking an action:
AppActionCreators.createFlashMessage('some message');
How to actually invoke an action in Redux:
this.context.dispatch(AppActionCreators.createFlashMessage('some message'));
Where dispatch is a method provided by the Redux store, and can be passed down to every child component of the app through React's childContextTypes

Categories

Resources