📜 ⬆️ ⬇️

Fast course Redux + websockets for backend

Hello from 2018! The original react-redux-universal-hot-example ceased development in 2017, but it can be assembled on version 6.14.2, there will be errors on version 8 and above. But there is his fork
https://github.com/bertho-zero/react-redux-universal-hot-example , where development continues and more recent versions of Nodejs are supported.

This is a quick guide and training for frontened for backend. In this guide, I solve the problem of quickly building a user interface to a server application in the form of a single-page web application (single page app).


The main goal of my research is the possibility for a reasonable time (for one of normal rights) get a convenient and easy-to-use interface-draft to the server application. We (as developers of the server part) understand that our priority is the server part. When (in a hypothetical project) pros appear in the front, they will do everything beautifully and "correctly."


In the role of an academic task, there is a chat page with some speculative "bot" that works on the server side and accepts the message only through WebSocket. The bot thus performs an echo of your messages (we do not consider the server part here at all).


For the presentation of the material, I need you to have:



What we use


Redux - official documentation located at http://redux.js.org . In Russian there are several options, I personally used mostly https://rajdee.gitbooks.io/redux-in-russian/content/docs/introduction/index.html .


The exec64 article, she became the reason to write this tutorial https://exec64.co.uk/blog/websockets_with_redux/ .


Ready server with react and redux from https://github.com/erikras/react-redux-universal-hot-example (it saves us man-months of time for setting up a large bundle of technologies that are necessary for the modern js project)


Motivation


In general, I am developing a Python application. Wait wait leave


What I needed:



What has been tried:



Selected as a result:



Simplifications and assumptions:



Content



How to read


You will not repeat - skip part 1
Know reactjs - skip part 2
Know redux - skip parts 3, 4 and 5
You know how middleware works in redux - feel free to read part 6 and further in reverse order.


Part one. Initial setting. Customize the page.


Setting up the environment


We need node.js and npm.


We put node.js from the site https://nodejs.org - namely, this guide is written on the 6th version, version 7 was tested - everything works.
npm is installed along with node.js


Next, you need to run npm and update node.js (for windows, everything is the same without npm)


sudo npm cache clean -f sudo npm install -gn sudo n stable 

check


 node -v 

Configure react-redux-universal-hot-example


Everything is laid out in the react-redux-universal-hot-example , the installation instructions are also there.
Here I bring a sequence of actions


  1. Download and unzip the archive / forkey / whatever-as-you-like.
  2. Through node.js command line or terminal go to this folder
  3. Run

 npm install npm run dev 

Go to http: // localhost: 3000 and should see the start page.


If everything is OK, we proceed.


Create a new container


To configure the partition, use the help provided by the react-redux-universal-hot-example command. The original article is here .


 cd ./src/containers && mkdir ./SocketExample 

Copy hello.js there as a page template.


 cp About/About.js Hello/SocketExamplePage.js 

I use Atom for all this, as a really great editor-anything with some goodies.


Rule the copied file


Create a stub under our page. Enter the element <p> . Later we will display the connection status in this item.


 import React, {Component} from 'react'; import Helmet from 'react-helmet'; export default class SocketExamplePage extends Component { render() { return ( <div className="container"> <h1>Socket Exapmle Page</h1> <Helmet title="Socket Exapmle Page"/> <p>Sockets not connected</p> </div> ); } } 

We connect the created page


Add in ./src/containers/index.js new component React


 export SocketExamplePage from './SocketExample/SocketExamplePage'; 

Add to ./src/routes.js to link the transition by /socketexamplepage to the link map


 ... import { App, Chat, Home, Widgets, About, Login, LoginSuccess, Survey, NotFound, SocketExamplePage } from 'containers'; ... { /* Routes */ } <Route path="about" component={About}/> <Route path="login" component={Login}/> <Route path="survey" component={Survey}/> <Route path="widgets" component={Widgets}/> <Route path="socketexamplepage" component={SocketExamplePage}/> ... 

Add to ./src/containers/App/App.js to add an item to the menu


  <LinkContainer to="/socketexamplepage"> <NavItem eventKey={99}>Socket Example Page</NavItem> </LinkContainer> 

Check


 npm run dev 

Commit: https://github.com/valentinmk/react-redux-universal-hot-example/commit/69935996671fc5dd64062143526d1a00b49afcbd


At the moment we have:



Before we begin. I developed everything in the reverse order - first I turned the middleware, then I threw the actions, and only then I screwed up the adequate interface in reactjs. We in the manual will do everything in the right order, because it’s really faster and easier. The minus of my approach is that I used many times more debugging and "crutches" than I really need. We will be rational.

Part two. Designing a future application


First we design the user interface. To do this, we roughly represent what the skeleton of our interface should look like and what actions will take place in it.


The React Beginner's Guide presents an approach for designing dynamic applications on React, from which we will not deviate, but will directly follow it.


Dan Abramov wrote in his documentation a lot about what should be divided in the application and how to organize the structure of the application. We will follow his example.


So, let's begin.


First of all, I want to say that for clarity and debugging, when writing an application, we will add elements that will be removed from the form after the work is finished.


User Interface "Option 1"


We add two new sections to our page.


In the socket connection log we will briefly display current events related to disconnecting the connection. ./src/containers/SocketExample/SocketExamplePage.js file.


 // inside render () { return (...) } <h3>Socket connection log</h3> <textarea className="form-control" rows="1" readOnly placeholder="Waiting ..." value=" index = 2, loaded = true, message = Connected, connected = true index = 1, loaded = false, message = Connecting..., connected = false"/> 

index - the sequence number of the log entry

loaded - whether the item is loaded on the user’s page

message - variable-message for debugging and code visibility
')
connected - a sign if we are connected to the server now

Of course we forgot about the buttons and input fields, add:



  <button className="btn btn-primary btn-sm"> <i className="fa fa-sign-in"/> Connect </button> <button className="btn btn-danger btn-sm"> <i className="fa fa-sign-out"/> Disconnect </button> 

In the message log we will display the sent -> and received messages <- .


 // inside render () { return (...) } <h3>Message log</h3> <ul> <li key="1" className="unstyled"> <span className="glyphicon glyphicon-arrow-right"></span> Socket string </li> <li key="2" className="unstyled"> <span className="glyphicon glyphicon-arrow-left"></span> [ECHO] Socket string </li> </ul> 

Button and input for send message


  <form className="form-inline"> <p></p> <div className="form-group"> <input className="form-control input-sm" type="text" ref="message_text"></input> </div> <button className="btn btn-primary btn-sm"> <i className="fa fa-sign-in"/> Send </button> </form> 

Do not press the Send button.

We check and commit to get the full code.


Commit: https://github.com/valentinmk/react-redux-universal-hot-example/commit/510a59f732a9bf42e070e7f57e970a2307661739


User Interface Option 2. Components.


Let's divide everything into components. Nothing complicated.


Create a new folder in the directory ./src/components call it SocketExampleComponents .


Adding a component occurs in three steps:


1 - create a file with a component in our SocketConnectionLog.js folder


we wrap the contents of the component in the div as React expects from us

 import React, {Component} from 'react'; export default class SocketConnectionLog extends Component { render() { return ( <div> <h3>Socket connection log</h3> <textarea className="form-control" rows="1" readOnly placeholder="Waiting ..." value=" index = 2, loaded = true, message = Connected, connected = true index = 1, loaded = false, message = Connecting..., connected = false"/> <button className="btn btn-primary btn-sm"> <i className="fa fa-sign-in"/> Connect </button> <button className="btn btn-danger btn-sm"> <i className="fa fa-sign-out"/> Disconnect </button> </div> ); } } 

2 - we register our new component in the components/index.js


 export SocketConnectionLog from './SocketExampleComponents/SocketConnectionLog'; 

3 - we rule our page ./src/components/SocketExamplePage.js and instead of the code copied by us we ./src/components/SocketExamplePage.js only one element


 import {SocketConnectionLog} from 'components'; // ... <SocketConnectionLog /> 

Add another new component to the same folder ./src/components/SocketExampleComponents .


Add in three steps


1 - create a file with a component in our SocketMessageLog.js folder


 import React, {Component} from 'react'; export default class SocketMessageLog extends Component { render() { return ( <div> <h3>Message log</h3> <ul> <li key="1" className="unstyled"> <span className="glyphicon glyphicon-arrow-right"></span> Socket string </li> <li key="2" className="unstyled"> <span className="glyphicon glyphicon-arrow-left"></span> [ECHO] Socket string </li> </ul> <form className="form-inline"> <p></p> <div className="form-group"> <input className="form-control input-sm" type="text" ref="message_text"></input> </div> <button className="btn btn-primary btn-sm"> <i className="fa fa-sign-in"/> Send </button> </form> </div> ); } } 

2 - we register our new component in the ./src/components/index.js file


 export SocketMessageLog from './SocketExampleComponents/SocketMessageLog'; 

3 - we rule our page and instead of the code copied by us we interpose only one element


 // ... import {SocketMessageLog} from 'components'; // ... <SocketMessageLog/> 

We are checking. Nothing has changed and this is a success.


Commit:
https://github.com/valentinmk/react-redux-universal-hot-example/commit/97a6526020a549f2ddf91370ac70dbc0737f167b


We finish 2 part.


Part Three Amazing redux


Go straight to Redux.


For this you need:


  1. Create a reducer
  2. Create actions
  3. And connect all this to a common system.

About actions written in the official documentation
About reduser written there

Create a file


Create a file ./src/redux/modules/socketexamplemodule.js and fill it with basic actions and reducers. Here is a basic example, there is a strange discrepancy, everything is proposed to be written in one file, without dividing actions and reducers into a file, well, let's say. All the same - we are all adults here (we are all adults).


Action 1


 export const SOCKETS_CONNECTING = 'SOCKETS_CONNECTING'; export const SOCKETS_DISCONNECTING = 'SOCKETS_DISCONNECTING'; export const SOCKETS_MESSAGE_SENDING = 'SOCKETS_MESSAGE_SENDING'; export const SOCKETS_MESSAGE_RECEIVING = 'SOCKETS_MESSAGE_RECEIVING'; 

We will launch all SOCKETS_MESSAGE_RECEIVING by pressing the buttons, except for the SOCKETS_MESSAGE_RECEIVING event, which we will synthetically call after sending the message. This is done in order to emulate in the development process the currently missing (or at a specific stage) functionality of the server part of the application.


Reducer


Add to the same file.


 export default function reducer(state = initialState, action = {}) { switch (action.type) { case SOCKETS_CONNECTING: return Object.assign({}, state, { loaded: true, message: 'Connecting...', connected: false }); case SOCKETS_DISCONNECTING: return Object.assign({}, state, { loaded: true, message: 'Disconnecting...', connected: true }); case SOCKETS_MESSAGE_SENDING: return Object.assign({}, state, { loaded: true, message: 'Send message', connected: true }); case SOCKETS_MESSAGE_RECEIVING: return Object.assign({}, state, { loaded: true, message: 'Message receive', connected: true }); default: return state; } } 

More details about the structure of the reducer and why Object.assign({}, state,{}); can be read here .


You noticed the initialization state = initialState, which we did not declare (put ESLint or its analogue - greatly simplify the life of a Normal Person). Add an announcement to the reducer. This will be the first state that we will have in our Side when the page loads, or rather the page will be loaded with this initial state.


 const initialState = { loaded: false, message: 'Just created', connected: false, }; 

Action 2


Now we’ll continue with our actions and complete this module. We must describe how they will change the state of the reducer.


Add to the same file.


 export function socketsConnecting() { return {type: SOCKETS_CONNECTING}; } export function socketsDisconnecting() { return {type: SOCKETS_DISCONNECTING}; } export function socketsMessageSending() { return {type: SOCKETS_MESSAGE_SENDING}; } export function socketsMessageReceiving() { return {type: SOCKETS_MESSAGE_RECEIVING}; } 

We connect to a common reducer


At the moment, nothing will change in the application. We include our module in the general designer of reducer'ov.


In the file ./src/redux/modules/reducer.js prescribe the module.


 import socketexample from './socketexamplemodule'; 

and include it in the general structure of the resultant reducer


 export default combineReducers({ routing: routerReducer, reduxAsyncConnect, auth, form, multireducer: multireducer({ counter1: counter, counter2: counter, counter3: counter }), info, pagination, widgets, // our hero socketexample }); 

We start the server, check and cheer in DevTools we see.


image


If the questions with initialState remain, then try changing them or adding a new variable to it.


Commit: https://github.com/valentinmk/react-redux-universal-hot-example/commit/0c984e3b5bc25056aa578ee57f90895bc6baaf18


Stor


And the line we have already created and the reducer in it is connected. Do not do anything.


If more, then you should remember how we added our reducer to combineReducers above the article. So this combineReducers itself is included in the stor, which is created in the file ./src/redux/create.js .


We connect the detector to react components


We connect all this now to our modules. For a comprehensive demonstration, let’s start with the history module and make it the pure react component (in the sense of redux).


We do not touch the SocketConnectionLog component yet, but go straight to the SocketExamplePage container.


In this container, we will connect and receive data from redux.


We connect the library in the file ./src/containers/SocketExample/SocketExamplePage.js .


 import {connect} from 'react-redux'; 

We take away actions so that we can use them in react.


 import * as socketExampleActions from 'redux/modules/socketexamplemodule'; 

and we will change the string to connect PropTypes


 import React, {Component, PropTypes} from 'react'; 

We write the connector, which will take the data from our reducer.


 @connect( state => ({ loaded: state.socketexample.loaded, message: state.socketexample.message, connected: state.socketexample.connected}), socketExampleActions) 

As you can see state.socketexample.loaded is the reference to redux, in the structure that we see in DevTools.


Now we connect the verification of data obtained from redux, which seems appropriate because Any type data check is a universal good.


  static propTypes = { loaded: PropTypes.bool, message: PropTypes.string, connected: PropTypes.bool } 

We got the data now let's transfer it. Inside the render block, we declare and accept data already from props.


 const {loaded, message, connected} = this.props; 

and calmly and confidently transfer them to our module:


 <SocketConnectionLog loaded={loaded} message={message} connected={connected} /> 

We passed new data (via react) to the component. Now we rewrite our component, which already knows nothing about redu (redux), but only processes the data transferred to it.


In the file ./src/components/SocketExampleComponents/SocketConnectionLog.js act according to the list:


  1. check the received props
  2. assign them inside the render
  3. use in our component

Let's start importing the missing libraries:


 import React, {Component, PropTypes} from 'react'; 

add check:


  static propTypes = { loaded: PropTypes.bool, message: PropTypes.string, connected: PropTypes.bool } 

declare and assign variables passed through props


  const {loaded, message, connected} = this.props; 

use our variables for output


  value={'index =' + 0 + ', loaded = ' + loaded + ', message = ' + message + ', connected = ' + connected}/> {/* value=" index = 2, loaded = true, message = Connected, connected = true index = 1, loaded = false, message = Connecting..., connected = false"/> */} 

We check and see that the initialState arrives directly from redux-> react-> props-> props.


Commit: https://github.com/valentinmk/react-redux-universal-hot-example/commit/60ac05332e35dfdbc11b9415f5bf5c46cd740ba8


SocketExampleMessageLog


Now we are going to the SocketExampleMessageLog component and make it completely independent, in the sense of working with a stor. We will not transfer to him any props, he will receive everything he needs from the pages himself.


Open the file ./src/components/SocketExampleComponents/SocketMessageLog.js


we add the necessary libraries in it


 import React, {Component, PropTypes} from 'react'; import {connect} from 'react-redux'; import * as socketExampleActions from 'redux/modules/socketexamplemodule'; 

add connect , type checking and use received data


 @connect( state => ({ loaded: state.socketexample.loaded, message: state.socketexample.message, connected: state.socketexample.connected}), socketExampleActions) export default class SocketMessageLog extends Component { static propTypes = { loaded: PropTypes.bool, message: PropTypes.string, connected: PropTypes.bool } // ... 

Don't forget to pass the value to the render () method via props


  const {loaded, message, connected} = this.props; 

We will use loaded and connected to determine the readiness for message exchange, and we will display message just for verification.


  <ul> <li key="1" className="unstyled"> <span className="glyphicon glyphicon-arrow-right"> </span> {message} </li> <li key="2" className="unstyled"> <span className="glyphicon glyphicon-arrow-left"> </span> [ECHO] {message} </li> </ul> 

I will check the variables loaded and connected explicitly to be more transparent to (possible) children.


  <form className="form-inline"> <p></p> <div className="form-group"> <input className="form-control input-sm" type="text" ref="message_text" readOnly = {(loaded === true) ? false : true}></input> </div> <button className="btn btn-primary btn-sm" disabled = {(connected === true) ? false : true}> <i className="fa fa-sign-in"/> Send </button> </form> 

Halfway passed.


Commit: https://github.com/valentinmk/react-redux-universal-hot-example/commit/a473d6a86262f2d2b52c590974e77df9454de5a1 .


Part Four Enliven the story


In the previous installments, we all prepared to start using the story.


In this part we will connect events in react and states in the stop. Let's start.


Let's revive the connection history in our component ./src/components/SocketExampleComponents/SocketConnectionLog.js .
But as we remember, he does not know anything about the story. This means that he does not know anything about actions and therefore he needs to pass them through the container ./src/containers/SocketExample/SocketExamplePage.js . Just pass them to the component as if they were simple props.


In general, we connected all the functions of actions via connect . Stop. More details. Recall.


 //.... import * as socketExampleActions from 'redux/modules/socketexamplemodule'; //.... @connect( state => ({ loaded: state.socketexample.loaded, message: state.socketexample.message, connected: state.socketexample.connected}), socketExampleActions) 

Therefore, we simply include them in the check in the ./src/containers/SocketExample/SocketExamplePage.js file:


 static propTypes = { loaded: PropTypes.bool, message: PropTypes.string, connected: PropTypes.bool, socketsConnecting: PropTypes.func, socketsDisconnecting: PropTypes.func } 

and pass to our component


  render() { const {loaded, message, connected, socketsConnecting, socketsDisconnecting} = this.props; return ( <div className="container"> <h1>Socket Exapmle Page</h1> <Helmet title="Socket Exapmle Page"/> <SocketConnectionLog loaded={loaded} message={message} connected={connected} connectAction={socketsConnecting} disconnectAction={socketsDisconnecting}/> <SocketMessageLog/> </div> ); } 

Now let's ensure that devotees are accepted into the action component in the ./src/components/SocketExampleComponents/SocketConnectionLog.js file.


We will add them (actions) to the check and use them in our handlers on the form. We will make two handlers: by clicking the "Connect" and "Disconnect" buttons.


  static propTypes = { loaded: PropTypes.bool, message: PropTypes.string, connected: PropTypes.bool, connectAction: PropTypes.func, disconnectAction: PropTypes.func } handleConnectButton = (event) => { event.preventDefault(); this.props.connectAction(); } handleDisconnectButton = (event) => { event.preventDefault(); this.props.disconnectAction(); } 

We register the call of function handlers by pressing the corresponding buttons.


  render() { const {loaded, message, connected} = this.props; return ( <div> <h3>Socket connection log</h3> <textarea className="form-control" rows="1" readOnly placeholder="Waiting ..." value={'index =' + 0 + ', loaded = ' + loaded + ', message = ' + message + ', connected = ' + connected}/> {/* value=" index = 2, loaded = true, message = Connected, connected = true index = 1, loaded = false, message = Connecting..., connected = false"/> */} <button className="btn btn-primary btn-sm" onClick={this.handleDisconnectButton}> <i className="fa fa-sign-in"/> Connect </button> <button className="btn btn-danger btn-sm" onClick={this.handleConnectButton}> <i className="fa fa-sign-out"/> Disconnect </button> </div> ); 

We start. We are checking. Hurray, it is alive! You can see in DevTools that events are created in the stack.


If you carefully follow how the states change, you can see that the message history component does not work as it should (although it is written correctly). The fact is that when the connection button is clicked, our state is connected = false, and when the connection is disconnected, our state is connected = true. Let's fix those.


To do this, in the file ./src/redux/modules/socketexamplemodule.js correct strange lines


  case SOCKETS_CONNECTING: return Object.assign({}, state, { loaded: true, message: 'Connecting...', connected: true }); case SOCKETS_DISCONNECTING: return Object.assign({}, state, { loaded: true, message: 'Disconnecting...', connected: false }); 

Well, now everything works correctly.


But then we will change these values ​​to the original, this is an important point. The connection attempt event is not identical to the connected state (yes, I'm a cap).

We realize connection history. The main limitation is the principle of operation of the store itself. We can change the state itself, but we can recreate it entirely and assign it. Therefore, in order to accumulate history, we will copy it, add the current state to the copy, and assign this value to the original (from which the copy was taken).


  case SOCKETS_CONNECTING: return Object.assign({}, state, { loaded: true, message: 'Connecting...', connected: true, history: [ ...state.history, { loaded: true, message: 'Connecting...', connected: true } ] }); case SOCKETS_DISCONNECTING: return Object.assign({}, state, { loaded: true, message: 'Disconnecting...', connected: false, history: [ ...state.history, { loaded: true, message: 'Disconnecting...', connected: false } ] }); 

We do the mapping in the same element. First of all, we pass the history variable through props in the file ./src/containers/SocketExample/SocketExamplePage.js . Next, in the ./src/components/SocketExampleComponents/SocketConnectionLog.js file, ./src/components/SocketExampleComponents/SocketConnectionLog.js accepts the passed variable.


Let's ./src/containers/SocketExample/SocketExamplePage.js in the ./src/containers/SocketExample/SocketExamplePage.js file from the store:


 @connect( state => ({ loaded: state.socketexample.loaded, message: state.socketexample.message, connected: state.socketexample.connected, history: state.socketexample.history }), socketExampleActions) 

check for type


  static propTypes = { loaded: PropTypes.bool, message: PropTypes.string, connected: PropTypes.bool, history: PropTypes.array, socketsConnecting: PropTypes.func, socketsDisconnecting: PropTypes.func } 

assign and pass


  render() { const {loaded, message, connected, socketsConnecting, socketsDisconnecting, history} = this.props; return ( <div className="container"> <h1>Socket Exapmle Page</h1> <Helmet title="Socket Exapmle Page"/> <SocketConnectionLog loaded={loaded} message={message} connected={connected} connectAction={socketsConnecting} disconnectAction={socketsDisconnecting} history={history}/> <SocketMessageLog/> </div> ); 

We already accept in the file ./src/components/SocketExampleComponents/SocketConnectionLog.js .


  static propTypes = { loaded: PropTypes.bool, message: PropTypes.string, connected: PropTypes.bool, history: PropTypes.array, connectAction: PropTypes.func, disconnectAction: PropTypes.func } 

To output the history to the log, we no longer actually need the current values ​​of loaded , message , connected .


Let's put in history in reverse chronology, so that the actual state is always on top.


  render() { const {history} = this.props; return ( <div> <h3>Socket connection log</h3> <textarea className="form-control" rows="1" readOnly placeholder="Waiting ..." value={ history.map((historyElement, index) => 'index = ' + index + ' loaded = ' + historyElement.loaded.toString() + ' message = ' + historyElement.message.toString() + ' connected = ' + historyElement.connected.toString() + ' \n').reverse() }/> <button className="btn btn-primary btn-sm" onClick={this.handleConnectButton}> <i className="fa fa-sign-in"/> Connect </button> <button className="btn btn-danger btn-sm" onClick={this.handleDisconnectButton}> <i className="fa fa-sign-out"/> Disconnect </button> </div> ); 

The main thing is to not forget to add history when initializing the reducer, otherwise our checks will not work.


In the ./src/redux/modules/socketexamplemodule.js file.


 const initialState = { loaded: false, message: 'Just created', connected: false, history: [] }; 

We are checking. And we get our record in the history of connection, but for some reason with commas. Javascript wtf? Well, okay, if we add .join ("") after the map and reverse, then this all solves.


".join ('') decides everything.", Karl!

What is our result? We read and write to the store! You can praise yourself! But this is clearly not enough, because we do it only within our own page and do not communicate with the outside world.


Commit: https://github.com/valentinmk/react-redux-universal-hot-example/commit/24144226ea4c08ec1af5db3a5e9b37461be2dbdd


Part Five. Designing chat


We have a blank for connecting / disconnecting to the socket. Now we have to make a chat shell, it will become our working model (we already have a prototype).


With chat, we will perform the same actions as with the log (history) of connections - we will add chat history and teach it to display.


The full cycle will look like this:




./src/redux/modules/socketexamplemodule.js
.


 const initialState = { loaded: false, message: 'Just created', connected: false, history: [], message_history: [] }; 

SOCKETS_MESSAGE_SENDING SOCKETS_MESSAGE_RECEIVING . .


, .


 case SOCKETS_MESSAGE_SENDING: return Object.assign({}, state, { loaded: true, message: 'Send message', connected: true, message_history: [ ...state.message_history, { direction: '->', message: action.message_send } ] }); case SOCKETS_MESSAGE_RECEIVING: return Object.assign({}, state, { loaded: true, message: 'Message receive', connected: true, message_history: [ ...state.message_history, { direction: '<-', message: action.message_receive } ] }); 

action.message_receive action.message_send . . .


.


 export function socketsMessageSending(sendMessage) { return {type: SOCKETS_MESSAGE_SENDING, message_send: sendMessage}; } export function socketsMessageReceiving(sendMessage) { return {type: SOCKETS_MESSAGE_RECEIVING, message_receive: sendMessage}; } 

. - sendMessage sendMessage . , . .


.



, react . .


, ./src/components/SocketExampleComponents/SocketMessageLog.js .


 @connect( state => ({ loaded: state.socketexample.loaded, message: state.socketexample.message, connected: state.socketexample.connected, message_history: state.socketexample.message_history }), socketExampleActions) export default class SocketMessageLog extends Component { static propTypes = { loaded: PropTypes.bool, message: PropTypes.string, connected: PropTypes.bool, message_history: PropTypes.array, socketsMessageSending: PropTypes.func } 

, .


 handleSendButton = (event) => { event.preventDefault(); this.props.socketsMessageSending(this.refs.message_text.value); this.refs.message_text.value = ''; } 

, message_text . message_text . .


props.


 const {loaded, connected, message_history} = this.props; 

,


  <ul> { message_history.map((messageHistoryElement, index) => <li key={index} className={'unstyled'}> <span className={(messageHistoryElement.direction === '->') ? 'glyphicon glyphicon-arrow-right' : 'glyphicon glyphicon-arrow-left'}></span> {messageHistoryElement.message} </li> )} </ul> 

— . Those. ' '?' ':' '. . — . .


  <form className="form-inline" onSubmit={this.handleSendButton}> <p></p> <div className="form-group"> <input className="form-control input-sm" type="text" ref="message_text" readOnly = {(loaded && connected === true) ? false : true}> </input> </div> <button className="btn btn-primary btn-sm" onClick={this.handleSendButton} disabled = {(connected === true) ? false : true}> <i className="fa fa-sign-in"/> Send </button> </form> 

.


. .


 handleSendButton = (event) => { event.preventDefault(); this.props.socketsMessageSending(this.refs.message_text.value); this.props.socketsMessageReceiving(this.refs.message_text.value); this.refs.message_text.value = ''; } 

, this.refs.message_text.value .


!


 static propTypes = { loaded: PropTypes.bool, message: PropTypes.string, connected: PropTypes.bool, message_history: PropTypes.array, socketsMessageSending: PropTypes.func, socketsMessageReceiving: PropTypes.func } 

, !


: https://github.com/valentinmk/react-redux-universal-hot-example/commit/5158391cdd53545408637fd732c981f17852e84b .



http://redux.js.org/docs/advanced/Middleware.html .


https://exec64.co.uk/blog/websockets_with_redux/ .


, , websockets.



./src/redux/middleware/socketExampleMiddleware.js


, . , .


, , .


 import * as socketExampleActions from 'redux/modules/socketexamplemodule'; export default function createSocketExampleMiddleware() { let socketExample = null; socketExample = true; socketExampleActions(); return store => next => action => { switch (action.type) { default: console.log(store, socketExample, action); return next(action); } }; } 

. . return store => next => action => switch (action.type) .


, store, socketExample, action . ( socketExampleActions(); , , ).


, , . .


./src/redux/create.js .


 import createSocketExampleMiddleware from './middleware/socketExampleMiddleware'; //... const middleware = [ createMiddleware(client), reduxRouterMiddleware, thunk, createSocketExampleMiddleware() ]; 

. , !


: https://github.com/valentinmk/react-redux-universal-hot-example/commit/7833a405be3445e58e8e672e9db03f8cfbfde022


. .


. websockets.


, . , , .

./src/redux/middleware/socketExampleMiddleware.js , .


  const onOpen = (token) => evt => { console.log('WS is onOpen'); console.log('token ' + token); console.log('evt ' + evt.data); }; const onClose = () => evt => { console.log('WS is onClose'); console.log('evt ' + evt.data); }; 

( )


  socketExample = true; socketExampleActions(); 

.


  case 'SOCKETS_CONNECT': if (socketExample !== null) { console.log('SOCKETS_DISCONNECTING'); store.dispatch(socketExampleActions.socketsDisconnecting()); socket.close(); } console.log('SOCKETS_CONNECTING'); socketExample = new WebSocket('ws://echo.websocket.org/'); store.dispatch(socketExampleActions.socketsConnecting()); socketExample.onclose = onClose(); socketExample.onopen = onOpen(action.token); break; default: return next(action); 

. . SOCKETS_CONNECT , , , onClose() onOpen(action.token) . , . SOCKETS_CONNECT , . SOCKETS_CONNECTING , — .


  case 'SOCKETS_CONNECTING': if (socketExample !== null) { console.log('SOCKETS_DISCONNECTING'); store.dispatch(SocketExampleActions.socketsDisconnecting()); socket.close(); } console.log('SOCKETS_CONNECTING'); socketExample = new WebSocket('ws://echo.websocket.org/'); store.dispatch(SocketExampleActions.socketsConnecting()); socketExample.onclose = onClose(); socketExample.onopen = onOpen(action.token); break; default: return next(action); 

!!! — .

, . SOCKETS_CONNECTING SOCKETS_DISCONNECTING . .


. "" store => next => action => , SOCKETS_CONNECTING . store.dispatch(SocketExampleActions.socketsConnecting()); , SOCKETS_CONNECTING , ..


— , .


.


( ) :





.


-, .


2 src\redux\modules\socketexamplemodule.js .


 export const SOCKETS_CONNECTING = 'SOCKETS_CONNECTING'; export const SOCKETS_CONNECT = 'SOCKETS_CONNECT'; export const SOCKETS_DISCONNECTING = 'SOCKETS_DISCONNECTING'; export const SOCKETS_DISCONNECT = 'SOCKETS_DISCONNECT'; 

.


 export function socketsConnecting() { return {type: SOCKETS_CONNECTING}; } export function socketsConnect() { return {type: SOCKETS_CONNECT}; } export function socketsDisconnecting() { return {type: SOCKETS_DISCONNECTING}; } export function socketsDisconnect() { return {type: SOCKETS_DISCONNECT}; } 

. ./src/components/SocketExampleComponents/SocketConnectionLog.js , react. ./src/containers/SocketExample/SocketExamplePage.js .


 static propTypes = { loaded: PropTypes.bool, message: PropTypes.string, connected: PropTypes.bool, history: PropTypes.array, socketsConnecting: PropTypes.func, socketsDisconnecting: PropTypes.func, //HERE socketsConnect: PropTypes.func, socketsDisconnect: PropTypes.func } render() { //HERE const {loaded, message, connected, socketsConnecting, socketsDisconnecting, history, socketsConnect, socketsDisconnect} = this.props; return ( <div className="container"> <h1>Socket Exapmle Page</h1> <Helmet title="Socket Exapmle Page"/> <SocketConnectionLog loaded={loaded} message={message} connected={connected} connectAction={socketsConnecting} disconnectAction={socketsDisconnecting} history={history} //HERE connectAction={socketsConnect} disconnectAction={socketsDisconnect} /> <SocketMessageLog/> </div> ); } 

./src/redux/middleware/SocketExampleMiddleware.js .



  case 'SOCKETS_CONNECT': 

:


  case 'SOCKETS_DISCONNECT': if (socketExample !== null) { console.log('SOCKETS_DISCONNECTING'); store.dispatch(socketExampleActions.socketsDisconnecting()); socketExample.close(); } socketExample = null; break; 

, disconnect .


  socketExample.onclose = onClose(store); 


  const onClose = (store) => evt => { console.log('WS is onClose'); console.log('evt ' + evt.data); store.dispatch(socketExampleActions.socketsDisconnect()); }; 

— . Network , .


, .


  socketExample = new WebSocket('ws://echo.websocket.org123/'); 

. , . — , . .


store.dispatch(socketExampleActions.socketsDisconnect()); onClose .


: https://github.com/valentinmk/react-redux-universal-hot-example/commit/7569536048df83f7e720b000243ed9798308df20


.


.
./src/redux/modules/socketexamplemodule.js


 export const SOCKETS_MESSAGE_SENDING = 'SOCKETS_MESSAGE_SENDING'; export const SOCKETS_MESSAGE_SEND = 'SOCKETS_MESSAGE_SEND'; export const SOCKETS_MESSAGE_RECEIVING = 'SOCKETS_MESSAGE_RECEIVING'; export const SOCKETS_MESSAGE_RECEIVE = 'SOCKETS_MESSAGE_RECEIVE'; 


 export function socketsMessageSending(sendMessage) { return {type: SOCKETS_MESSAGE_SENDING, message_send: sendMessage}; } export function socketsMessageSend(sendMessage) { return {type: SOCKETS_MESSAGE_SEND, message_send: sendMessage}; } export function socketsMessageReceiving(receiveMessage) { return {type: SOCKETS_MESSAGE_RECEIVING, message_receive: receiveMessage}; } 

Stop. 4 ? . , , socketsMessageReceive, . , .. "" ( ).



./src/redux/middleware/socketExampleMiddleware.js .


, .


  const onMessage = (ws, store) => evt => { // Parse the JSON message received on the websocket const msg = evt.data; store.dispatch(SocketExampleActions.socketsMessageReceiving(msg)); }; 

  case 'SOCKETS_CONNECT': if (socketExample !== null) { console.log('SOCKETS_DISCONNECTING'); store.dispatch(SocketExampleActions.socketsDisconnecting()); socket.close(); } console.log('SOCKETS_CONNECTING'); socketExample = new WebSocket('wss://echo.websocket.org/'); store.dispatch(SocketExampleActions.socketsConnecting()); socketExample.onmessage = onMessage(socketExample, store); socketExample.onclose = onClose(store); socketExample.onopen = onOpen(action.token); break; 


.


  case 'SOCKETS_MESSAGE_SEND': socketExample.send(action.message_send); store.dispatch(SocketExampleActions.socketsMessageSending(action.message_send)); break; 

. action.message_send — ? , store => next => action => . , .


.


./src/components/SocketExampleComponents/SocketMessageLog.js , .


  static propTypes = { loaded: PropTypes.bool, message: PropTypes.string, connected: PropTypes.bool, message_history: PropTypes.array, socketsMessageSend: PropTypes.func } 

, , .


  handleSendButton = (event) => { event.preventDefault(); this.props.socketsMessageSend(this.refs.message_text.value); this.refs.message_text.value = ''; } 

. message_history react . , this.props.socketsMessageSend(this.refs.message_text.value) , action , SOCKETS_MESSAGE_SEND , SOCKETS_MESSAGE_SENDING , .


We start. We are checking.


!


[ ] , . . , — .

[ ] .

: https://github.com/valentinmk/react-redux-universal-hot-example/commit/40fd0b38f7a4b4acad141997e1ad9e7d978aa3b3


PS


.

Source: https://habr.com/ru/post/318148/


All Articles