React redux-saga on server side doesn't take action after browser reload - node.js

I have some problems with my universal react app runing with saga. I'm rendering react on server. One of my react component executes redux action that should be catched by saga listener on server.
Here is abstract example
// *Header.js*
class Header extends React.PureComponent {
componentWillMount() {
this.props.doAction()
}
....
}
export default connect(null, {doAction})(Header)
// *actions.js*
function doAction() {
return {
type: "action"
}
}
// *saga.js*
function* doAsyncAction(action) {
console.log(action);
}
function* watchAction() {
yield takeEvery("action", doAsyncAction);
}
export default [
watchAction(),
];
// *sagas.js* --> root saga
import 'regenerator-runtime/runtime';
import saga from './saga';
import anotherSaga from './anotherSaga'
export default function* rootSaga() {
yield all([].concat(saga).concat(anotherSaga));
}
// *configureStore.js*
const sagaMiddleware = createSagaMiddleware();
const middleware = applyMiddleware(sagaMiddleware);
...
sagaMiddleware.run(require('./sagas').default);
And after first run node process - it runs and give me console log, but
when I just refresh browser and function doAsyncAction is never executed
Please help, what I'm doing wrong ?

You need to change this:
function doAction() {
return {
type: "action"
}
}
to this:
const mapDispatchtoProps = (dispatch) => {
return {
doAction: () => dispatch({type: "action"})
}
}
export default connect(null, mapDispatchtoProps)(Header)
Client.js setup below for saga middleware:
const sagaMiddleware = createSagaMiddleware()
const createStoreWithMiddleware = applyMiddleware(sagaMiddleware)(createStore)
let store = createStoreWithMiddleware(rootReducers)
sagaMiddleware.run(rootSaga)
The above is implemented where ever you are implementing your store.

Related

React and Easybase - Invalid hook call. Hooks can only be called inside of the body of a function component

I am trying to use React and Easybase (database). I'm having some issues however.
This is in the SolanaSignature.tsx file.
import { useWallet } from '#solana/wallet-adapter-react';
import bs58 from 'bs58';
import React, { FC, useCallback } from 'react';
import ReactDOM from 'react-dom';
import { sign } from 'tweetnacl';
import AddUser from './mainstorage';
export const SignMessageButton : FC = () => {
const { publicKey, signMessage } = useWallet();
const onClick = useCallback(async () => {
try {
if (!publicKey) throw new Error('Wallet not connected!');
if (!signMessage) throw new Error('Wallet does not support message signing! Please use a wallet such as Phantom or Solflare! NOTE: Some Ledgers wallets are not supported!');
const message = new TextEncoder().encode('Omega Protocol - Signature verification for Bold Badgers.');
const signature = await signMessage(message);
if (!sign.detached.verify(message, signature, publicKey.toBytes())) throw new Error('Invalid signature!');
//alert(`Message signature: ${bs58.encode(signature)}`);
AddUser();
} catch (error: any) {
alert(`Signing failed: ${error?.message}`);
}
}, [publicKey, signMessage]);
return signMessage ? (<button className="wallet-adapter-button wallet-adapter-button-trigger shine" onClick={onClick} disabled={!publicKey}>Verify</button>) : null;
};
and then the mainstorage file:
import { useEffect } from 'react';
import { useEasybase } from 'easybase-react';
const AddUser = () => {
const { db } = useEasybase();
useEffect(() => {
db('OMEGABB').insert({ walletid: "test", discordid: "test", signature: "test", valid: false, lastvalid: new Date() }).one()
.then(() => console.log("Success!"));
}, [])
return (
{/* ... */}
);
}
export default AddUser;
What is happening however when I click the button is that it comes up with a warning: Hooks can only be called inside the body of a function component.
This does work in the initial index file (aka the parent file) but does not work here. Right now this is only a dummy/test but trying to get it writing to the database.
Thanks!
As per React's documentation:
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns.
Currently, you're attempting to call a hook inside the onClick handler - AddUser is a custom hook since it also uses hooks and the better name for it should be useAddUser.
I suggest to make some improvements by returning a function from your custom hook that you can call to add a new user, e.g.:
export const useAddUser = () => {
const {db} = useEasybase()
const addUser = React.useCallback(() => {
db('OMEGABB')
.insert(/*...*/)
.then(/*...*/)
.catch(/*...*/)
}, [db])
return {
addUser,
/*...*/
}
}
Then, you can use useAddUser in the following way:
const {useAddUser} from './mainstorage'
const SignMessageButton: FC = () => {
const {publicKey, signMessage} = useWallet()
const {addUser} = useAddUser();
const onClick = React.useCallback(
async () => {
try {
// ...
addUser()
} catch (error) {/*...*/}
},
[publicKey, signMessage, addUser]
)
/*...*/
}

Jest: How to test a component that dispatch an API action

I am new to testing,
I am trying to understand if it's possible to test an API call that dispatch from the component.
Is it possible to wait for a response from the server?
import React from 'react';
import { render, fireEvent, cleanup, waitForElement } from 'react-testing-library';
import 'jest-dom/extend-expect'
import App from 'src/app';
import { store } from 'stories/index.stories.js';
import { Provider, connect } from 'react-redux';
const renderComponent = () => (<App />);
it('renders correctly', () => {
const { container, getByText, queryAllByText, getByPlaceholderText } = renderComponent();
expect(container).not.toBeEmpty();
expect(getByText(/Discover/i)).toBeTruthy();
const discoverBtn = getByText(/Discover/i);
fireEvent.click(discoverBtn); // this will dispatch an action from the component
//what should i do next ?
});
This is how I do it.
First I put all my fetch requests in a separate file, say /api/index.js. In this way I can easily mock them in my tests.
Then I perform the actions that a user would do. Finally I check that the API got called and that the DOM was updated correctly.
import { aFetchMethod } from './api'
// With jest.mock our API method does nothing
// we don't want to hit the server in our tests
jest.mock('./api')
it('renders correctly', async () => {
const { getByText } = render(<App />)
aFetchMethod.mockResolvedValueOnce('some data that makes sense for you')
fireEvent.click(getByText('Discover'))
expect(aFetchMethod).toHaveBeenCalledTimes(1)
expect(aFetchMethod).toHaveBeenCalledWith('whatever you call it with')
// Let's assume you now render the returned data
await wait(() => getByText('some data that makes sense for you'))
})

Puppeteer mock page request object

import { Page } from 'puppeteer/lib/Page';
export class MonitorRequestHelper {
public static monitorRequests(page: Page, on = false) {
if(on) {
page.on('request', req => {
if (['image', 'font', 'stylesheet'].includes(req.resourceType())) {
// Abort requests for images, fonts & stylesheets to increase page load speed.
req.abort();
} else {
req.continue();
}
});
} else {
return true;
}
}
}
I am trying to mock and spy the function to check if it got called at least once.
Also, it would be helpful if some explain me how to mock and spy event-emitter object.
The source code is available on https://github.com/Mukesh23singh/puppeteer-unit-testing
If you want to test that your logic in monitorRequests works, you need to pass in a fake Page object with an event emitter interface that produces a fake request that you can test on.
Something like:
import {spy} from 'sinon';
// Arrange
const fakePage = { on(type, cb) { this[type] = cb; } }; // "event emitter"
const fakeRequest = {
abort: sinon.spy(),
resourceType() { return 'image'; }
};
monitorRequests( fakePage, true );
// Act
// trigger fake request
fakePage['request'](fakeRequest);
// Assert
assert(fakeRequest.abort.called);

ESM import not working with redux, Node 10.1.0

//index.mjs
import { createStore } from 'redux'
import todoApp from './reducers'
const store = createStore(todoApp)
import {
addTodo,
toggleTodo,
setVisibilityFilter,
VisibilityFilters
} from './actions'
// Log the initial state
console.log(store.getState())
// Every time the state changes, log it
// Note that subscribe() returns a function for unregistering the listener
const unsubscribe = store.subscribe(() =>
console.log(store.getState())
)
// Dispatch some actions
store.dispatch(addTodo('Learn about actions'))
store.dispatch(addTodo('Learn about reducers'))
store.dispatch(addTodo('Learn about store'))
store.dispatch(toggleTodo(0))
store.dispatch(toggleTodo(1))
store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED))
// Stop listening to state updates
unsubscribe()
//reducers.mjs
import { combineReducers } from 'redux'
import {
ADD_TODO,
TOGGLE_TODO,
SET_VISIBILITY_FILTER,
VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilter
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
case TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
default:
return state
}
}
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
// actions.mjs
/*
* action types
*/
export const ADD_TODO = 'ADD_TODO'
export const TOGGLE_TODO = 'TOGGLE_TODO'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'
/*
* other constants
*/
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETED: 'SHOW_COMPLETED',
SHOW_ACTIVE: 'SHOW_ACTIVE'
}
/*
* action creators
*/
export function addTodo(text) {
return { type: ADD_TODO, text }
}
export function toggleTodo(index) {
return { type: TOGGLE_TODO, index }
}
export function setVisibilityFilter(filter) {
return { type: SET_VISIBILITY_FILTER, filter }
}
Edit- added code
I am working through the Redux Basics tutorial from the Redux docs, and am having trouble getting import to work properly.
I currently have 3 files for my todo-list app:
actions.mjs
reducers.mjs
index.mjs
I have done an npm init -y, and npm install --save redux. I copy and pasted the source code directly from the Redux docs for all 3 files.
With the command: node --experimental-modules index, I get the error:
SyntaxError: The requested module 'redux' does not provide an export named 'combineReducers'
I would expect similar error messages for other named exports of Redux...
I have had success with refactoring back to CommonJS using require, module.exports, .js file extensions, and the command: node index.js

Receive Nodejs events in React

I am working on a project that will use React as my client and Nodejs as my server. My design is that the Nodejs server will listen to some external data streams, process the data, save the data in MongoDB and then emit some events to React. The server-side code is like
const EventEmitter = require('events');
const WebSocket = require('ws');
const myEmitter = new EventEmitter();
const ws = new WebSocket('wss://someurl');
ws.on('message', (data) => {
........
/*
preprocess and do the mongodb stuff
*/
myEmitter.emit('someevent', data)});
});
My question is, how can I listen for such an event in my React client? If I stick with this approach, do I need to pass in myEmitter to my React components?
I am new to React so please let me know if there is any better way to solve the problem.
do I need to pass in myEmitter to my React components?
no... your client side and serverside code should be separate. You can use a client-side SocketIO app like socket.io.
if you're going to be listening for a bunch of different events in different components, consider using an enhancer style wrapper
function withSocket (event?, onEvent?) { // note: this is TS
return (Component) => {
class WithSocketEvent extends Component {
constructor (props) {
super(props)
this.socket = io.connect(SOCKET_ENDPOINT)
}
componentDidMount () {
if (event && onEvent) {
this.socket.on(event, onEvent)
}
}
componentWillUnmount () {
this.socket && this.socket.close()
}
render () {
return (
<Component
{ ...this.props }
socket={ this.socket }
/>
)
}
}
return WithSocketEvent
}
}
// usage
class HasSocketEvent extends Component {
componentDidMount () {
// handle the event in the component
this.props.socket.on("someEvent", this.onSocketEvent)
}
onSocketEvent = (event) => {
}
render () {
}
}
// handle the event outside the component
export default withSocket("someEvent", function () {
// so something
})(HasSocketEvent)
// or
export default withSocket()(HasSocketEvent)

Resources