Add default properties when inheriting reactjs class in ES6 - javascript

I am trying to add a default property that should refer to an instance function when I am inheriting a component class in ReactJS, and ES6. In detail, I have the datepicker from npm (react-day-picker) and want to make sure that two properties are always sent to the base class:
export default class DayPicker extends BaseDayPicker {
constructor(props) {
var { ...newProps } = props;
newProps.onMouseDown = this.onDayPickerMouseDown;
newProps.onMouseUp = this.onDayPickerMouseUp;
super(newProps);
}
componentDidMount() {
super.componentDidMount && super.componentDidMount();
window.addEventListener('mousedown', this.onPageClick, false);
}
componentWillUnmount() {
super.componentWillUnmount && super.componentWillUnmount();
window.addEventListener('mousedown', this.onPageClick, false);
}
onPageClick = (e) => {
if (!this.isDayPickerMouseDown) {
this.props.onPageClick && this.props.onPageClick();
}
};
onDayPickerMouseDown = (e) => {
this.isDayPickerMouseDown = true;
};
onDayPickerMouseUp = (e) => {
this.isDayPickerMouseDown = false;
};
render() {
return super.render();
}
}
The problem with the code above is that I get 'this' is not allowed before super().
I cannot find a way to solve this. If it is not possible to add default properties that must use this, is it possible to solve it in the render method?

Referencing my comment on another answer
You should lean away from inheritance, it is an anti-pattern.
React was designed for composition. What does that mean? If you have some functionality to share, then put it in a component and make it use props in different ways.
TL;DR You want to use Higher-Order Components for this type of situation.
Example:
BaseDayPicker = (RenderedComponent) => React.Component {
// just a function that takes a component, and returns a component.
onMouseDown() {
this.props.onMouseDown(doSomething());
}
onMouseUp() {
this.props.onMouseUp();
}
//...
// notice it renders the component that came in as a parameter.
render(){
return (<RenderedComponent
onMouseUp={this.onMouseUp}
onMouseDown={this.onMouseDown}
/>) // it also adds some props! super cool
}
}
class DayPicker extends React.Comnponent {
//...
onMouseDown() {
this.isDayPickerMouseDown = true;
this.props.onMouseDown();
}
onMouseUp() {
this.isDayPickerMouseDown = false;
this.props..onMouseUp();
}
//....
}
// NOTICE, WRAPPING ONE IN ANOTHER
export BaseDayPicker(DayPicker)
If you want to know WHY, here is a blog post explaining why react mixins are dead.

One of the possible solutions is to specify two empty functions in parent class and then you can override them in child class.
class BaseDayPicker extends React.Comnponent {
//...
onMouseDown() {
this.props.onMouseDown();
}
onMouseUp() {
this.props.onMouseUp();
}
//....
}
class DayPicker extends React.Comnponent {
//...
onMouseDown() {
this.isDayPickerMouseDown = true;
super.onMouseDown();
}
onMouseUp() {
this.isDayPickerMouseDown = false;
super.onMouseUp();
}
//....
}
So in this case you can call class method and function passed within props.
In your case it would be better to wrap BaseDayPicker into another component that would extend it functionality instead of trying to extend component itself.

Related

React method return value won't show in render()

I was playing around in React, and I was trying to have a method return a value, which I would then use to display in the render() method, however when I try to nothing is displayed.
import React, { Component } from "react";
class Test extends Component {
constructor(props) {
super(props);
this.DisplayTest = this.DisplayTest.bind(this);
}
DisplayTest() {
return <h1>Test</h1>;
}
render() {
return <div>{this.DisplayTest}</div>;
}
}
export default Test;
DisplayTest is a method therefore in order to return a value, you will have to execute it.
render() {
return <div>{this.DisplayTest()}</div>;
}
Another way is to make use of class getter
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
So, you will have to declare a getter method like
get DisplayTest() {
return <h1>Test</h1>;
}
render() {
return <div>{this.DisplayTest}</div>;
}
And then, your current implementation will work.
Please add following { this.DisplayTest() }

Keep my code DRY for `$onInit` and `$onChanges` methods

I have a piece of code that is repeated in almost all of my components:
import {deviceCommands} from "../../../core/data/Commands";
let _generalProperties = {
'deviceName': deviceCommands.NAME.key,
'deviceModel': deviceCommands.MODEL.key
};
export default class GeneralDeviceSettingsCtrl {
constructor($scope) {
let $ctrl = this;
$ctrl.$onChanges = function (changes) {
for(let prop in _generalProperties) {
$ctrl[prop] = $ctrl.test.vm.data[_generalProperties[prop]];
}
};
$ctrl.$onInit = function () {
for(let prop in _generalProperties) {
$scope.$watch(() => $ctrl.test.vm.data[_generalProperties[prop]],
(newValue, oldValue) => {
if (newValue !== oldValue) {
$ctrl[prop] = $ctrl.test.vm.data[_generalProperties[prop]];
}
});
}
};
}
}
The only thing that is different is the _generalProperties variable, which is specific to my view.
How can I do to keep it DRY?
Make a base class? use decorators?
I think there is plenty of different approaches to this but if you stick with classes and inheritance you can supply the _generalProperties to your parent constructor and reuse the whole implementation.
For example:
export class BaseSettingsComponent {
constructor(properties){
this._properties = properties;
}
$onInit(){ /* ... implement base component stuff*/ }
}
Then, on each of your pages that uses the same component logic you can extend the base class, provide the properties to the parent class and let the base class do the job.
import { BaseSettingsComponent } from '../common/base-settings-component.js';
let _generalProperties = {
'deviceName': deviceCommands.NAME.key,
'deviceModel': deviceCommands.MODEL.key
};
export class GeneralDeviceSettingsCtrl extends BaseSettingsComponent {
constructor(){
super(_generalProperties);
}
$onInit(){ super.$onInit(); /* ... implement base component stuff*/ }
}
The one thing important to keep in mind when using this approach is that if you need to implement $onInit or $onChanges on the child classes you have to call the super.$onInit() otherwise you lose the parent behavior due to an override.
Finally, for an even cleaner code, you can also discard the declaration of _generalProperties supplying it directly into the super constructor.
import { BaseSettingsComponent } from '../common/base-settings-component.js';
export class GeneralDeviceSettingsCtrl extends BaseSettingsComponent {
constructor(){
super({
'deviceName': deviceCommands.NAME.key,
'deviceModel': deviceCommands.MODEL.key
});
}
}

React - Setting component state using a function outside of state, is it wrong?

Is it wrong to use setState in a function outside of the React component?
Example:
// myFunction.js
function myFunction() {
...
this.setState({ ... })
}
// App.js
import myFunction from './myFunction
class App extends Component {
constructor() {
super()
this.myFunction = myFunction.bind(this)
}
...
}
I'm not sure the way you're binding will actually work. You could do something like:
export const getName = (klass) => {
klass.setState({ name: 'Colin'})
}
then
class App extends Component {
state = {
name: 'React'
};
handleClick = () => {
getName(this);
}
render() {
return (
<div>
<p>{this.state.name}</p>
<button onClick={this.handleClick}>change name</button>
</div>
);
}
}
Working example here.
So the only reasons to do this is if you are reducing repeated code, e.g. two components use the same logic before calling this.setState, or if you want to make testing easier by having a separate pure function to test. For this reason I recommend not calling this.setState in your outside function, but rather returning the object you need so it can you can call this.setState on it.
function calculateSomeState(data) {
// ...
return { updated: data };
}
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = calculateSomeState(props.data);
}
handleChange = (e) => {
const value = e.target.value;
this.setState(calculateSomeState({ ...props.data, value }));
}
}
It looks like a bug waiting to happen... If you want to use an external function to set state, you can use the alternative syntax provided by React:
this.setState((prevState, props) => {
return updatedState; //can be a partial state, like in the regular setState
});
That callback can easily be extracted to an external function and it's guaranteed to work
It is not wrong, the function is never called outside the component. This is a mix-in technique. bind isn't needed, as long as the function isn't used as a callback. In this case myFunction is same among all instances, a more efficient way would be:
class App extends Component {}
App.prototype.myFunction = myFunction;

With React, is it possible to have a propType to specify the DOM element type?

I would like to create a React UI-Compoment for Typography. Where the component can be passed a propType to specify which element type should be rendered or
Perhaps a propType used like: as="span" or as="p"
Is this type of dynamic element type definition possible with React or do I need to do something less elegant and have a Switch statement with two types or return statements?
example: `if (this.props.p) {return <p>....</p>}
Example using this proposed component:
<MyC as="span">Hello World</MyC>
<MyC as="p">Hello World</MyC>
You could use React.createElement() as such:
class MyC extends Component {
state = {
ready: false,
};
componentDidMount() {
this.el = React.createElement(this.props.as, {}, this.props.children);
this.setState({ ready: true });
}
render() {
if (!this.state.ready) {
return null;
}
return this.el;
}
}
Usage:
class App extends Component {
render() {
return <MyC as="span">Test</MyC>;
}
}
Working example: https://codesandbox.io/s/xozpn8ol9o

Default function props in functional component

So I'm looking through several SO questions and each example pertains to a Class based component not a functional component.
So I have a Container ListContainer that renders List and I'm passing toggleDrawer(). I'm unable to define toggleDrawer() in the List.defaultProps
ListContainer.jsx
import React, { Component } from 'react';
import List from './List';
class ListContainer extends Component{
constructor(props){...}
const toggleDrawer = () => {...}
render(){
return(
<List data={data} toggleDrawer={toggleDrawer}/>
)
}
}
...export statement...
List.jsx
import React from 'react';
import PropTypes from 'prop-types';
function List(props){
const { data, toggleDrawer } = props;
const openDrawer = () => toggleDrawer();
const renderListItems = (items) => {//renders a list};
return(
<ul>{renderListItems(data)}</ul>
)
}
List.propTypes = {
toggleDrawer: PropTypes.func,
};
List.defaultProps = {
toggleDrawer: <<<<<<<<<<<<<<<< Not sure how to define toggleDrawer() here
}
...export statement...
The problem I'm running into is I've tried using getDefaultProps() and maybe it's not being implemented correctly but that hasn't worked. All the examples I've looked at are using State components and so trying to use .bind(this) or this.toggleDrawer.this(bind) or any of the other examples I've tried aren't working either.
What am I missing here? Or would it be better practice to just have toggleDrawer: PropTypes.func.isRequired. That does remove the ESLint error, but I'm not 100% on that function always being required
What you probably want is a noop:
List.defaultProps = { toggleDrawer: () => {} };
The advantage of this is that you can be sure that this.props.toggleDrawer is a function even if no value was specified, which means you can safely call it without checking. Another alternative is to set the default to null, and then check that the prop is a function before calling it:
if(typeof this.props.toggleDrawer === 'function') { /* call function */ }
For the record it doesn't make much sense to define another callback (openDrawer()) which simply calls toggleDrawer. You may as well just call toggleDrawer directly.
Just do:
List.defaultProps = {
toggleDrawer: function () {
// enter code here
}
}
or arrow functions:
List.defaultProps = {
toggleDrawer: () => {
// enter code here
}
}
You can also write like this concise style :
List.defaultProps = {
toggleDrawer() {}
};

Categories

Resources