Overriding React Component's prototype method - javascript

Let's say I have the following React Component class:
class SayHello extends React.Component {
constructor(props) {
super(props);
this.handleOnClick = this.handleOnClick.bind(this);
}
render() {
return <div onClick={this.handleOnClick}>Click Me</div>;
}
handleOnClick() {
console.log("clicked");
}
}
What I want to do is create a higher order component that knows about the handleOnClick in SayHello but before calling SayHello's handleOnClick, I want it to execute some code I pass in first (i.e. I want to run code that logs something in my server).
Is there a React pattern for doing something like this?
EDIT:
I want to provide some more context here. I want my higher order component to be dynamic in terms of which methods to call. For example, sometimes it might be handleOnClick but other times it might be handleOnSomethingElse.

A higher-order component is a function that takes a component argument and returns a new component.
This function returns a component with a decorated handleClick method:
// A higher-order component that runs some code before
// the given component's `handleClick` method
function wrapHello(componentClass) {
return class wrapped extends componentClass {
beforeHandleClick() {
console.log("I run first!")
}
handleClick(...args) {
this.beforeHandleClick()
super.handleClick(...args)
}
}
}
This pattern is neat because it isn't specific to React at all; it's just a pure function. That means it's easy to test and reason about.
Here's a test harness that doesn't use React:
function wrapHello(componentClass) {
return class wrapped extends componentClass {
beforeHandleClick() {
console.log("I run first!")
}
handleClick(...args) {
this.beforeHandleClick()
super.handleClick(...args)
}
}
}
class SayHello {
handleClick() {
console.log("handleClick")
}
}
const WrappedHello = wrapHello(SayHello)
new WrappedHello().handleClick()

You need something like a dynamic mixin.
This higher-order component takes a Component class and an Object of decorator methods.
The HOC wraps each method that has a matching decorator. These methods call the decorator then call through to the original component method. Non-decorated methods are unchanged.
// Higher-order component
function decorateMethods(componentClass, decorators) {
class decoratedClass extends componentClass { }
Object.keys(decorators).forEach(decoratorName => {
decoratedClass.prototype[decoratorName] = function(...args) {
decorators[decoratorName].call(this, ...args);
return componentClass.prototype[decoratorName].call(this, ...args)
}
})
return decoratedClass
}
//
// Test
//
class Component {
foo() {
console.log("foo")
}
bar() {
console.log("bar")
}
baz() {
console.log("baz")
}
}
const DecoratedComponent = decorateMethods(Component, {
foo() {
console.log("before foo")
},
bar() {
console.log("before bar")
}
})
const d = new DecoratedComponent()
d.foo()
d.bar()
d.baz()
In this case the decorator methods exactly match the base class method names. If you want the decorator to use, e.g. beforeFoo instead, you could map method names with:
const methodName = decoratorName replace(/before(\w)/, (_, a) => a.toLowerCase())

Related

React setState is not accessible within lambda function

I'm trying to define a global state in a react project using React.useContext & React.useState.
I currently have something like this:
# GlobalState.js
class GlobalState extends AbstractState {
register(current, setCurrent) {
this.current = current
this.setCurrent = setCurrent
}
...
}
const globalState = new GlobalState()
export default globalState
And this is passed from App by something like:
const [current, setCurrent] = React.useState({})
globalState.register_state(current, setCurrent)
const state = {current: current, setCurrent: setCurrent}
return React.createElement(GlobalContext.Provider, {value: state},
...children
)
and it is used at some point as follows:
class WebRequestsTimeChart extends React.Component {
render(){
...
return React.createElement(GlobalContext.Consumer, null, (state) => {
return this.#getChart(state)
})
}
componentDidMount() {
console.log(GlobalState.setCurrent) // actual react dispatch content
}
componentWillUnmount() {
console.log(GlobalState.setCurrent) // actual react dispatch content
}
#getChart(state) {
console.log(GlobalState.setCurrent) // actual react dispatch content
return React.createElement(VictoryChart, {
...
containerComponent: React.createElement(VictoryZoomVoronoiContainer, {
...
events: {
onClick: (event) => {
console.log(state.setCurrent) // actual react dispatch content
console.log(GlobalState.setCurrent) // f(){} <--- ???
}
}
}
}
}
So somehow, lambda passed to onClick cannot access the GlobalState.setCurrent but it has no problem accessing state.setCurrent. If I define another method like GlobalState.someRandomMethod, this is still accessible by onClick. Somehow, setCurrent returned by React.useState seems to be behave differently. I also realized this method cannot be copied (eg. structuredClone doesn't work for that). Another thing I considered is Difference between class object Method declaration React?, but I'm not sure how this would cause GlobalState.setCurrent to behave differently.
adding below also doesn't change this behavior:
constructor(props) {
super(props);
this.getChart = this.getChart.bind(this).bind(GlobalState);
}
Please let me know if you have any idea on what is going on, and how I can use GlobalState.setCurrent inside onClick
GlobalState.setCurrent would be a static method on the class itself, which you haven't declared. It's not the same thing as a setCurrent instance method.
class Foo {
bar () {
return 'bar';
}
}
console.log(Foo.bar?.()); // undefined. "bar" doesn't exist on the class itself.
const foo = new Foo();
console.log(foo.bar()) // 'bar'; exists on instance of Foo.
class Baz {
static boom () {
return "static boom";
}
boom () {
return "instance boom";
}
}
console.log(Baz.boom()); // static boom
const baz = new Baz();
console.log(baz.boom()); // instance boom

Use a parent function inside a child function

I'm trying to call a parent function with arguments from a child component but I'm not sure exactly how to get it working. I specifically need to be able to call the parent function from the child within another function, so I tried passing a reference to the function through props but this is not quite right. The parent class owns a resource that only it should interact with through the specific function call I'm passing. When done in the following way, I am told the function isn't defined.
export class ParentClass extends React.Component {
ParentFunctionWithArguments(a, b) {
alert("a is being used by my private resource");
alert("b is being used by my private resource");
return true; //some result based on a and b
}
render() {
return (
<ChildClass>ParentFunctionWithArguments={() => this.ParentFunctionWithArguments()}</ChildClass>
);
}
}
And
export class ChildClass extends React.Component {
...
handleOk = (e) => {
...
if (condition) {
if (this.props.ParentFunctionWithArguments(a, b)) {}
}
...
};
...
}
<childComponent callFromChild={this.parentMethod}/>
//in child component call like below
this.props.callFromChild()
You simply need to the apss the parent function as props to the Child component and not call it within the children
export class ParentClass extends React.Component {
ParentFunctionWithArguments(a, b) {
alert("a is being used by my private resource");
alert("b is being used by my private resource");
return true; //some result based on a and b
}
render() {
return (
<ChildClass ParentFunctionWithArguments={this.ParentFunctionWithArguments}></ChildClass>
);
}
}
and simply call it in child like
export class ChildClass extends React.Component {
handleOk = (e) => {
...
if (condition) {
if (this.props.ParentFunctionWithArguments(a, b)) {}
}
...
};
...
}
What I was looking for was the "bind" function
something like this in the constructor of the parent:
export class ParentClass extends React.component {
constructor() {
this.ParentFunctionWithArguments = this.ParentFunctionWithArguments.bind(this);
}
... //rest unchanged
}
This will allow the child to use the passed in parent function in vanilla react.

How to pass callback function to litelement from React?

I'm trying to use lit-element component in my React project, and I'd like to pass in the callback function to lit-element component from React but with no luck.
I've tried a couple of different ways, like change property type, and pass function in as a string, but none of them works.
lit-element component code:
import { LitElement, html } from "lit-element";
class MyButton extends LitElement {
static get properties() {
return {
clickHandler: {
type: String
},
bar: {
type: String
}
};
}
render() {
const foo = this.clickHandler; //value is undefined
const bar = this.bar; //value is "it's bar"
return html`
<button #click=${this.clickHandler}>click me</button>
`;
}
}
customElements.define("my-button", MyButton);
react side code:
<my-button clickHandler={() => alert("clicked")} bar="it's bar" />
I put a break point in the render section of the component, and I can see the 'bar' value get passed in correctly, but the value of 'clickHandler' is undefined.
Does anyone have any idea on how to pass function from React to lit-element?
Thanks!
This question probably isn't just valid for react. It's quite general, how do I pass a handler from parent to child component, either it's a lit-element or html element.
According to the property doc, https://lit-element.polymer-project.org/guide/properties
<my-element
mystring="hello world"
mynumber="5"
mybool
myobj='{"stuff":"hi"}'
myarray='[1,2,3,4]'>
</my-element>
Doesn't seem that it supports (callback) function at the moment. So how does Element handle event from parent level ?
According to the event doc, https://lit-element.polymer-project.org/guide/events, you can dispatch any event to the dom tree, including your parent. Dom event system is much broader than React prop system.
class MyElement extends LitElement {
...
let event = new CustomEvent('my-event', {
detail: {
message: 'Something important happened'
}
});
this.dispatchEvent(event);
}
and then in either lit or non-lit context, use the following to handle the event,
const myElement = document.querySelector('my-element');
myElement.addEventListener('my-event', (e) => {console.log(e)});
This way you can allow children to fire implementation for parent, which is exactly the definition for callback.
What I found that works is adding a ref in the react component to the lit element, then literally setting the property on it.
So, for the following JSX:
<some-webcomponent ref={this.myRef}></some-webcomponent>
You can pass a property to ‘some-webcomponent’ in i.e. componentDidMount:
componentDidMount () {
const element = this.myRef.current;
element.someCallback = () => // ...
}
It’s not too pretty, but I wouldn’t consider it a hack either. Requires quite a lot of boilerplate though :/
Here’s a full React component for reference:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <some-webcomponent ref={this.myRef}></some-webcomponent>;
}
componentDidMount() {
const element = this.myRef.current;
element.someCallback = () => console.log(“call!”);
}
}
Where the lit element is:
import { LitElement, html } from "lit-element";
class SomeWebcomponent extends LitElement {
static get properties() {
return {
someCallback: { type: Function }
};
}
render() {
return html`
<button #click=${this.someCallback}>click me</button>
`;
}
}
customElements.define("some-webcomponent", SomeWebcomponent);
Have a look at the menu button click function for the no redux pwa-starter-kit at https://github.com/Polymer/pwa-starter-kit/blob/template-no-redux/src/components/my-app.js. I believe that provides the example that may work for you.

Understanding the use of this inside a class method

I am having tough time understanding use of this keyword in Javascript.
The other questions on stackoverflow I stumbled upon have been more about calling a method or function using this keyword. Like using bind or ES6 arrow function and so on..
So I have this stateful component in React and we are using Axios to intercept request
import React, { Component } from 'react';
import Modal from '../../components/UI/Modal/Modal';
import Aux from '../Aux/Aux';
const withErrorHandler = ( WrappedComponent, axios ) => {
return class extends Component {
state = {
error: null
}
componentWillMount () {
this.reqInterceptor = axios.interceptors.request.use(req => {
this.setState({error: null});
return req;
});
this.resInterceptor = axios.interceptors.response.use(res => res, error => {
this.setState({error: error});
});
}
componentWillUnmount() {
axios.interceptors.request.eject(this.reqInterceptor);
axios.interceptors.response.eject(this.resInterceptor);
}
render () {
return (
<Aux>
<Modal
//Something
</Modal>
<WrappedComponent {...this.props} />
</Aux>
);
}
}
}
export default withErrorHandler;
Something like above code, Here in above code we call interceptors which we want to remove when we want componentWillMount (to avoid memory leaks)
For that the instructor did something like this in componentDidMount followed by
this.reqInterceptor = axios.interceptors.request.use(req => {
this.setState({error: null});
return req;
this in componentWillUnmount
axios.interceptors.request.eject(this.reqInterceptor);
[Question] Can some explain me this.reqInterceptor here? like shouldn't we create a constructor and declare it there and then use it (maybe I am thinking it wrong)?
To answer your question we need a good understanding of structure of React.Component first.
React stateful components are well design to leverage a bit of object-oriented programming (though you may achieve the same pattern in other paradigms.) You have this which refers to the whole component class at your disposal. You can retrieve or assign values to properties or call bounded methods to the component by referring to this within the scope.
In stateful components React executes componentDidMount() when the DOM is ready and mounted then according to your code you assign a value to reqInterceptor property of the component by this.reqInterceptor = value..., this is basically the component that we are returning from our function function withErrorHandler { return class extends Component {...} }.
This is a common pattern to dynamically create components on fly. We can apply same in the following example to demonstrate how this works in the scope of ES6 classes:
class Service {
constructor(x) {
this.x = x;
}
}
function getMyService(extra) {
return class extends Service {
getExtra() {
return extra; // extra value like WrappedComponent or axios
}
getX() {
return this.x;
}
};
}
// result
const MyService = getMyService('some extra value'); // Returns the class
const myServiceInstance = new MyService(1); // This is what React does to instantiate your component
console.log(myServiceInstance.getX()); // -> 1
console.log(myServiceInstance.getExtra()); // -> 'some extra value'
Update:
I updated the above example to be semantically close to React.Component
The constructor will be called with the new keyword, so since the method definition is not in the constructor, you could instantiate multiple objects and you won't register every time a listener.
In this case, he wants to tie the class method to the react lifecycle (componentWillMount and componentWillUnmount).

React Higher Order Component conditional data load

Imagine I have some "page" component, which needs to ask for data from a server. The data it requests will depend on whether or not the current user is authenticated. Further, in the event of a login, the page will want to reload the data. My question is, how can I accomplish something like this using HOCs rather than inheritance?
To illustrate the problem, I'll demonstrate a solution using inheritance. The program will have the following objects. I'll leave out the boilerplate code.
session: an EventEmitter that emits start when the session changes (either a login or a log out).
Page: the superclass that all pages inherit from
MyPage: the subclass of Page in this example
API: will be an API class for retrieving data from the server
Here's the code:
// Page superclass
class Page extends React.Component {
componentWillMount() {
session.on("start", this.loadData);
this.loadData();
}
loadData() {
// this method is overwritten in subclasses
}
}
// MyPage subclass
class MyPage extends Page {
loadData() {
if(session.isAuthenticated()) {
API.loadPrivateData();
} else {
API.loadPublicData();
}
}
}
Here's a solution that uses an HOC, but seems less elegant than inheritance. It still requires that every "subclass" page have a method loadData, and it requires that method to be called in every "subclass's" componentWillMount.
// Page HOC
function Page(WrappedComponent) {
return class EnhancedPage extends React.Component {
componentWillMount() {
session.on("start", this.loadData);
// this._page.loadData() will fail here
// since this._page is undefined until rendering finishes
}
loadData() {
this._page.loadData();
}
render() {
return <WrappedComponent {...props} ref={(e) => { this._page = e; }} />
}
}
}
// MyPage
class MyPage extends React.Component {
componentWillMount() {
this.loadData();
}
loadData() {
if(session.isAuthenticated()) {
API.loadPrivateData();
} else {
API.loadPublicData();
}
}
}
const component = Page(MyPage)
// what would make sense here is to have a method something like
// const component = Page(MyPage, () => MyPage.loadData())
// but then the MyPage.loadData logic would need to be defined
// elsewhere
This pattern will happen often: I'll want to load some data, then reload when the session changes. I'd like to understand the "react" way of accomplishing the same.
EDIT: I am not trying to pass a username or "loggedIn" flag through the HOC. That is to say something like <WrappedComponent isLoggedIn={session.isAuthenticated()} {...props} /> won't cut it here. Tying the API logic to props requires that I check for changes in MyPage.componentWillUpdate().
When using a HOC you shouldn't place the loadData function on the wrapped component. Instead pass the function as a parameter to the HOC constructor.
Something like this might work for you. The sessionHoc function takes a callback function which'll be called every time the session state changes. Its result will be passed to WrappedComponent as a data prop.
function sessionHoc(onSessionChange) {
return function (WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
};
session.on('start', this.handleSessionChange.bind(this));
}
handleSessionChange() {
this.setState({
data: onSessionChange(),
});
}
render() {
return <WrappedComponent data={data} {...this.props} />
}
};
};
}
class MyPage extends React.Component {
render() {
// Just access this.props.data here!
}
}
const EnhancedPage = sessionHoc(function () {
if (session.isAuthenticated()) {
return API.loadPrivateData();
} else {
return API.loadPublicData();
}
})(MyPage);
Hopefully this helped! :)

Categories