Info
, TrendChart
and DailyChart
.Info
component. Right now it is a simple SVG icon. class Info extends React.Component { render() { return ( <svg className="Icon-svg Icon--hoverable-svg" height={this.props.height} viewBox="0 0 16 16" width="16"> <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" /> </svg> ) } }
onMouseOver
and onMouseOut
. The function that is passed onMouseOver
will be called if the mouse pointer hits the component area, and the function passed to onMouseOut
will be called when the pointer leaves the component. In order to organize all this in the manner that is accepted in React, we add the hovering
property to the component, which is stored in the state, which allows us to re-render the component, showing or hiding the tooltip, if this property changes. class Info extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() { return ( <> {this.state.hovering === true ? <Tooltip id={this.props.id} /> : null} <svg onMouseOver={this.mouseOver} onMouseOut={this.mouseOut} className="Icon-svg Icon--hoverable-svg" height={this.props.height} viewBox="0 0 16 16" width="16"> <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" /> </svg> </> ) } }
TrendChart
and DailyChart
. The above mechanism for the Info
component works fine, it’s not necessary to repair it, so let's recreate the same thing in other components using the same code. TrendChart
component TrendChart
. class TrendChart extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() { return ( <> {this.state.hovering === true ? <Tooltip id={this.props.id}/> : null} <Chart type='trend' onMouseOver={this.mouseOver} onMouseOut={this.mouseOut} /> </> ) } }
DailyChart
. class DailyChart extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() { return ( <> {this.state.hovering === true ? <Tooltip id={this.props.id}/> : null} <Chart type='daily' onMouseOver={this.mouseOver} onMouseOut={this.mouseOut} /> </> ) } }
function add (x, y) { return x + y } function addFive (x, addReference) { return addReference(x, 5) } addFive(10, add) // 15
add
function to the addFive
function as an argument, rename it to addReference
and then call it. function add (x,y) { return x + y } function higherOrderFunction (x, callback) { return callback(x, 5) } higherOrderFunction(10, add)
[1,2,3].map((i) => i + 5) _.filter([1,2,3,4], (n) => n % 2 === 0 ); $('#btn').on('click', () => console.log('Callbacks are everywhere') )
addFive
function, we want to create the addTen
function, and addTwenty
, and other similar functions. Considering how the addFive
function is addFive
, we will have to copy its code and change it to create the above-mentioned functions based on it. function add (x, y) { return x + y } function addFive (x, addReference) { return addReference(x, 5) } function addTen (x, addReference) { return addReference(x, 10) } function addTwenty (x, addReference) { return addReference(x, 20) } addFive(10, add) // 15 addTen(10, add) // 20 addTwenty(10, add) // 30
addFive
, addTen
, addTwenty
, and so on) as we need, while minimizing code duplication. Maybe to achieve this goal we need to create a function makeAdder
? This function can take a certain number and a link to the add
function. Since the purpose of this function is to create a new function that adds the number passed to it to the specified one, we can make the function makeAdder
return a new function, in which a certain number is specified (like the number 5 in makeFive
), and which could accept the numbers for addition with this number. function add (x, y) { return x + y } function makeAdder (x, addReference) { return function (y) { return addReference(x, y) } } const addFive = makeAdder(5, add) const addTen = makeAdder(10, add) const addTwenty = makeAdder(20, add) addFive(10) // 15 addTen(10) // 20 addTwenty(10) // 30
add
functions as needed, and at the same time minimize the amount of duplication of code..bind
method used in JavaScript.makeAdder
higher-order makeAdder
helps us minimize code duplication, what’s called a “higher order component” will help us deal with the same problem in a React application. However, everything will look different here. Namely, instead of a work scheme in which a higher order function returns a new function that calls a callback, a higher order component can implement its own scheme. Namely, it is able to return a new component that renders the component playing the role of a callback. Perhaps we have already spoken a lot of things, so it's time to move on to examples. function higherOrderFunction (callback) { return function () { return callback() } }
function higherOrderComponent (Component) { return class extends React.Component { render() { return <Component /> } } }
state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false })
withHover
) to encapsulate the event-handling code of the mouse, and then pass the hovering
property to the components it renders. This will allow us to prevent duplication of the corresponding code by placing it in the withHover
component.hovering
property, we can transfer this component to a component of a higher order withHover
. That is, we want to work with the components as shown below. const InfoWithHover = withHover(Info) const TrendChartWithHover = withHover(TrendChart) const DailyChartWithHover = withHover(DailyChart)
withHover
is rendered, it will be the source component to which the hovering
property is passed. function Info ({ hovering, height }) { return ( <> {hovering === true ? <Tooltip id={this.props.id} /> : null} <svg className="Icon-svg Icon--hoverable-svg" height={height} viewBox="0 0 16 16" width="16"> <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" /> </svg> </> ) }
withHover
component. From the above it can be understood that he must perform three actions:hovering
property. function withHover (Component) { }
function withHover (Component) { return class WithHover extends React.Component { } }
hovering
property? In fact, we have already written the code for working with this property. We just need to add it to the new component, and then pass the hovering
property to it when rendering the component passed to the higher order component as the Component
argument. function withHover(Component) { return class WithHover extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() { return ( <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}> <Component hovering={this.state.hovering} /> </div> ); } } }
Info
, TrendChart
and DailyChart
components into new components that, thanks to the hovering
property, know whether the mouse pointer is over them.withHover
, you will notice that it has at least one weak point. It implies that the receiving component of the hovering
property will not experience any problems with this property. In most cases, probably, this assumption is justified, but it may happen that this is unacceptable. For example, what if a component already has a hovering
property? In this case, there will be a name conflict. Therefore, a withHover
can be made to the withHover
component, which is to allow the user of this component to indicate what name the hovering
property should pass to the components. Since withHover
is just a function, let's rewrite it so that it withHover
second argument that specifies the name of the property passed to the component. function withHover(Component, propName = 'hovering') { return class WithHover extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() { const props = { [propName]: this.state.hovering } return ( <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}> <Component {...props} /> </div> ); } } }
hovering
, thanks to the default parameter setting mechanism of ES6, but if the user of the withHover
component wants to change this, he can pass, in this second argument, the name he needs. function withHover(Component, propName = 'hovering') { return class WithHover extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() { const props = { [propName]: this.state.hovering } return ( <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}> <Component {...props} /> </div> ); } } } function Info ({ showTooltip, height }) { return ( <> {showTooltip === true ? <Tooltip id={this.props.id} /> : null} <svg className="Icon-svg Icon--hoverable-svg" height={height} viewBox="0 0 16 16" width="16"> <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" /> </svg> </> ) } const InfoWithHover = withHover(Info, 'showTooltip')
withHover
implementation. If we analyze our Info
component, we can see that it, among other things, accepts the height
property. The way everything is arranged now means that the height
will be set to undefined
. The reason for this is that the withHover
component is the component responsible for rendering what is passed to it as the Component
argument. Now we have no properties, except for the hovering
we created, we do not pass the Component
component. const InfoWithHover = withHover(Info) ... return <InfoWithHover height="16px" />
height
property is passed to the InfoWithHover
component. And what is this component? This is the component that we return from withHover
. function withHover(Component, propName = 'hovering') { return class WithHover extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() { console.log(this.props) // { height: "16px" } const props = { [propName]: this.state.hovering } return ( <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}> <Component {...props} /> </div> ); } } }
WithHover
component this.props.height
is 16px
, but we do not do anything with this property in the future. We need to make this property be passed to the Component
argument, which we are rendering. render() { const props = { [propName]: this.state.hovering, ...this.props, } return ( <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}> <Component {...props} /> </div> ); }
withRouter
React Router. In accordance with the documentation, withRouter
will pass the match
, location
and history
properties to the wrapped component when it is rendered. class Game extends React.Component { render() { const { match, location, history } = this.props // From React Router ... } } export default withRouter(Game)
Game
element (i.e., <Game />
). We completely transfer our component React Router and trust this component not only rendering, but also transferring the correct properties to our component. We have already encountered this problem above when we talked about possible name conflicts when passing hovering
properties. In order to fix this, we decided to allow the HOC withHover
use the second argument to set the name of the corresponding property. Using someone else's HOC withRouter
we do not have this opportunity. If the match
, location
or history
properties are already used in the Game
component, then we can say that we are not lucky. Namely, we either have to change these names in our component, or refuse to use HOC withRouter
.Source: https://habr.com/ru/post/428572/
All Articles