ReactJS, SocketIO & Redux - What is the right design - reactjs

What is the best pattern for a large multi-component ReactJS app to integrate with a socket io back-end server?
Here are some of the requirements
The React application must connect to the backend (a server using a Flask-based socket.io implementation) upon login. The connection must be torn down on logout
The connection should be managed from a central place (as in I don't want every component to connect/disconnect to socket-io). This should also be a place to manage life-cycle of the socketio connection (disconnect, reconnect, etc).
Back-end will send async updates (a.k.a statistics for various components). These stats must be handled by the common socketio handling instance and pushed into redux store. The message body will have enough information to demux the messages.
The components themselves, should (preferably) not know about socket-io, they operate on the redux state.
Components can dispatch actions, that could potentially result in sending out a socketio message (to back-end).
[I don't have a use-case where a client needs to talk to another client.]
Few questions:
What is the right way to design this? At what point should I connect to socket-io?
I have a basic-layout component which is used by all pages. Is this the right place to hook up socket-io call? However, I see that this component is unloaded/loaded for each page. Somehow, this doesn't feel like the right place
I have seen some examples where every page opens a socketio connection. I am not sure if this is the right model?

I think you should probably store your socket on a Context, so that you can create your socket on your index.js file, like I did here :
import React from "react";
import App from "./App";
import socketIOClient from "socket.io-client";
import { MainProvider } from "./context/MainContext.js";
const ENDPOINT = "http://127.0.0.1:1234"; //Your backend endpoint
ReactDOM.render(
<React.StrictMode>
<MainProvider value={{socket: socketIOClient(ENDPOINT)}}>
<App />
</MainProvider>
</React.StrictMode>,
document.getElementById("root")
);
Then as it is on the context, you can access it in any component, like that :
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import MainContext from "./context/MainContext"; //import your context here
const myComponent = props => {
const myContext = useContext(MainContext); //your context is stored in a prop
return (
<div></div>
);
};
myComponent.propTypes = {
};
export default myComponent;
Finally you can access your socket in the component by calling myContext.socket

Related

ApolloConsumer VS import client

Why should I use ApolloConsumer instead of importing directly the client in my module ?
From the doc I should do somthing like :
// Module A.js initiate client
const client = new ApolloClient({
// init cache, links, typeDefs...
});
export default client;
// Module index.jsx
import client from 'A';
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root'));
// Any other component not using Query or Mutation
const Other = () => (
<ApolloConsumer>
{
client => {
// use client
}
}
</ApolloConsumer>);
But why not just import client without ApolloConsumer ?
// Module OtherBis
import client from 'A';
const AltOther () => {
// do something with client
return <div></div>;
};
Probably for the same reasons why you shouldn't import store directly:
Similarly, while you can reference your store instance by importing it directly, this is not a recommended pattern in Redux. If you create a store instance and export it from a module, it will become a singleton. This means it will be harder to isolate a Redux app as a component of a larger app, if this is ever necessary, or to enable server rendering, because on the server you want to create separate store instances for every request.
In my opinion, ApolloConsumer component is created for support JSX just like react-relay for relay. It is not necessary. Actually I have never used the ApolloConsumer. Moreover there are hooks(eg. useQuery, useMutation, useSubscription) which can do everything. They just make a tool. Whether use it or not is up to you.

How do I write a storybook story for a component that has redux-connected component as grandchild?

We are building a Storybook UI library from our existing code base. The code wasn't written with component driven development in mind. There are many instances where a component renders descendants that are connected to the Redux store.
E.g., Parent (connected) -> Child (unconnected) -> Grandchild (connected)
Now if I'm building a story for Parent, I understand how to pass hard-coded data as a prop to an immediate child component in order to avoid Redux all together. However, I can't figure out how to do this when the connected component is more deeply nested.
Ideally I don't want to have to use Redux at all for stories, but even if I do initialize a Redux store and wrap the parent component in a Provider as described here, would this even work to connect the grandchild component?
Any ideas would be helpful.
When using storybook you can add a Decorator for all stories (see link for most updated API).
It is common to wrap your stories with the state manager store provider in order to not break the story avoiding "adding a store for each story".
// # config.js
import { configure, addDecorator } from '#storybook/react';
import React from 'react';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from 'reducers/root.reducer';
const store = createStore(rootReducer);
addDecorator(S => (
<Provider store={store}>
<S />
</Provider>
));
configure(require.context('../src', true, /\.stories\.js$/), module);
Note that you can avoid connecting all your components with redux-hooks which in addition removes all the boilerplate code of redux.
React Redux now offers a set of hook APIs as an alternative to the existing connect() Higher Order Component. These APIs allow you to subscribe to the Redux store and dispatch actions, without having to wrap your components in connect().
If you want to solve the problem within your story file (and just fetch your store), use decorator like this:
import React from "react";
import { Provider } from 'react-redux'
import Parent from "./Parent";
import { store } from "../../../redux/store";
export default = {
title: "pages/Parent",
component: Parent,
decorators : [
(Story) => (<Provider store={store}><Story/></Provider>)
]
};
Sidenote, if this gives you the error useNavigate() may be used only in the context of a <Router> component., then you may need <MemoryRouter><Provider store={store}><Story/></Provider></MemoryRouter> (import {MemoryRouter} from 'react-router-dom')

Upgrading to Redux v6

I'm trying to upgrade to Redux V6 but am confused on how to use the createContext function and it's necessity. I know my store is created successfully, but when I try to run my app I get
Could not find "store" in the context of "Connect(ConfiguredApp)".
Either wrap the root component in a , or pass a custom React
context provider to and the corresponding React context
consumer to Connect(ConfiguredApp) in connect options.
Which tells me that my provider is not properly passing down the store for connect to grab. What am I doing wrong? Thanks!
import 'babel-polyfill';
import React from 'react';
import {render} from 'react-dom';
import {Provider} from 'react-redux';
import {ConnectedRouter} from 'connected-react-router';
import {history, store} from './store/store';
import Routes from './routes';
const customContext = React.createContext(null);
render(
<Provider store={store} context={customContext}>
<ConnectedRouter history={history} context={customContext}>
<Routes />
</ConnectedRouter>
</Provider>, document.getElementById('app'),
);
You almost definitely shouldn't be creating and passing in a custom context instance. That's only if, for some very specific reason, you want to use a context instance other than the default one that React-Redux already uses internally. (A hypothetical reason to do this would be if you are using one store for your whole app component tree, but there's a specific subtree that needs to receive data from a different store instead.)
If you actually wanted to use your own custom context instance, then you would need to pass the same context instance to both <Provider> and each connected component in the app that needs to receive data from that <Provider>.
Looking at the connected-react-router docs, they do claim that in CRR version 6, you can pass a context instance to <ConnectedRouter>, but that shouldn't be necessary here.
More specifically, if you look at the error message, it says the problem is in Connect(ConfiguredApp). So, it's your own connected component that is saying there's a context mismatch.
Ultimately, the answer here is to delete the context arguments from both <Provider> and <ConnectedRouter>. You don't need them.

Exposing React child component to JavaScript on webpage

I'm pretty new to rxjs, and trying to understand what's needed here to expose the Chat object in the Bot Framework, as I need to call some methods in it I'll add. I essentially need access to the created Chat component from the webpage, which right now has a BotChat.App. There's also a BotChat.Chat, but that doesn't seem to be the instance I need access to.
The following is used from the Bot Framework by calling BotChat.App({params});
That in turn creates a Chat component (eventually in App.tsx below). I need to basically expose the Chat instance that is used, as I want to modify it.
BotChat.ts (Complete)
export { App, AppProps } from './App';
export { Chat, ChatProps } from './Chat';
export * from 'botframework-directlinejs';
export { queryParams } from './Attachment';
export { SpeechOptions } from './SpeechOptions'
export { Speech } from './SpeechModule'
import { FormatOptions } from './Types';
// below are shims for compatibility with old browsers (IE 10 being the main culprit)
import 'core-js/modules/es6.string.starts-with';
import 'core-js/modules/es6.array.find';
import 'core-js/modules/es6.array.find-index';
And here in App.tsx note the Chat component used below. That is what I need to expose up through the webpage. A bit confused as to if it's exporting a "Chat" type as opposed to getting access to the instance of Chat being used in App.tsx. Hope this makes some sense :)
App.tsx (Complete)
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Chat, ChatProps } from './Chat';
import * as konsole from './Konsole';
export type AppProps = ChatProps;
export const App = (props: AppProps, container: HTMLElement) => {
konsole.log("BotChat.App props", props);
ReactDOM.render(React.createElement(AppContainer, props), container);
}
const AppContainer = (props: AppProps) =>
<div className="wc-app">
<Chat { ...props } /> //<--------------This is what I want to get
//access to on the webpage, which currently
//only uses BotChat.App() to initialize the
//web chat control. Not sure how to expose
//this _instance_ to App.tsx and then expose
//that instance to the webpage.
</div>;
Web Chat has a Redux store and a RxJS stream that you can expose to interact with the <Chat> component. In React, you can, but people usually don't expose any functions out of it. Short reason: the contract of a React component is props, not functions.
For RxJS stream that you can access the chat history, you can look at the backchannel sample. But it's a read-only stream.
For other interactivities, look at Store.ts to see what actions it is using. Then, expose the Redux store at Chat.ts (easy hack: thru window variable, better: make a callback props at <Chat>).
If you need more interactivities that is not in the existing Redux actions, feel free to add some more. For example, this pull request should give you a sense of injecting chat history.

Is Middleware required to use web sockets in React Redux Application

I am trying to implement a socket based react(redux) application. the socket events has to be dispatched to the store so the reducers would act upon it.
Now the way I think is keep the store in one file and refer to that from Socket events File and whenever socket messages are received then emit an event to store via socket.dispatch
in store.js
import { createStore } from 'redux';
let store = createStore(appStore);
export default store
in SocketJs file
import store from './store';
import io from socketio.client;
let Socket = {
createSocketIoConnection = function(){
var socket = io(url, {reconnection: false, transports: ['websocket']});
socket.on('data', this.emitToStore);
},
emitToStore: function(data){
store.dispatch();
}
}
the same store is used in main file ad passed as props to Provider
import store from './store';
<Provider store={store}>
<App> ...
</Provider>
Here I am not using any middleware. Is this a right approach.
No, you don't have to use middleware, but middleware is the right place to put this kind of behavior. In fact, there's already many existing websocket middlewares you can use.

Resources