Best practice for specifiying component interface in react.js? - reactjs

I wrote a component Foo in React.js (its parameter "interface" consists of a single parameter text; there are more parameters in my real code):
import React from 'react';
export default class Foo extends React.Component {
constructor(props){
super(props);
}
render(){
return <div>{this.props.text}</div>;
}
}
and I thought I could improve the code by modifying the property access as:
import React from 'react';
export default class Foo extends React.Component {
constructor(props){
super(props)
this._text = props.text
}
render(){
return <div>{this._text}</div>;
}
}
This would give me the benefits that
I can immediately see what properties are supported by having a look at the constructor.
The code that applies the properties gets shorter/better to read.
However, this destroys the update workflow for the property. In a parent component I use Foo like
<Foo text={this.state.parentText}/>
and
this.setState({parentText: "new text"})
does not trigger an update of the text in Foo any more. The constructor of Foo is only called once and therefore, the private variable this._text is not updated on property changes.
=> Using extra private properties to modify the parameter access turned out to be a bad idea.
=> What would you recommend to have a clear interface for the component without breaking the update workflow?
Some ideas:
a) List all used properties at the start of render (and componentDidUpdate)
render(){
const text = this.props.text;
return <div>{text}</div>;
}
b) Create a getter for each property and put them directly under the constructor, for example
get _text(){
return this.props.text;
}
c) (Only for shorter access.) Try to avoid class components. With function components there is direct access with props.text instead of this.props.text. Or, as a workaround, inherit from a custom component class that passes props argument to render:
render_props(props){
...
}
(Why doesn't react pass this.props as an argument to render by default?)
d) Document the supported properties in a doc string
=> If you know a better option / some standard / best practice, please let me know.
Also tried but failed:
I tried to use state in the child component, with the hope that it would be automatically updated on updates of the parent state:
import React from 'react';
export default class Foo extends React.Component {
constructor(props){
super(props)
this.state = {
text: props.text
}
}
render(){
return <div>{this.state.text}</div>;
}
}
However, this also breaks the update workflow. Using this.state only seems to make sense in the parent component.
Related:
Can I update a component's props in React.js?
https://github.com/vasanthk/react-bits/blob/master/anti-patterns/01.props-in-initial-state.md
Related topic:
How to interact with third party libraries using function components?
https://reactjs.org/docs/integrating-with-other-libraries.html
How do I use/include third party libraries in react?
Integrating React with Other Libraries

Use function components for React >= 16.8, also see recommendation at
https://www.w3schools.com/react/react_class.asp
Use useState hooks instead of setState. This is the modern way to write React, and gives you a simpler way to access state (foo.text, foo.setText). https://reactjs.org/docs/hooks-state.html
Typescript would help with docs (type props = { text: string }), but I also would like the answer for d) (your question is several questions I think).
Use props.text directly, instead of using extra shortcut variable const text = props.text suggested by option a). This way, you don't have a list of all available properties on top of the component function. However, using a consistent props. prefix makes it easier to spot the injected variables in the react code. If there is a huge number of properties and its hard to identify them, try to improve modularization.
JavaScript example code:
Child component Foo:
import React from 'react';
export default function Foo(props){
return <div>{props.text}</div>;
}
}
Parent component:
import React, { useState } from 'react';
import Froo from './foo';
export default function Parent(){
const [parentText, setParentText] = useState('Hello world');
return <Foo text={parentText}/>;
}

Related

How to wrap ALL components of react in a HOC?

ReactJS is a great library, However, it misses some features which I found in Vue and Angular. These features can be implemented of course in React, however, they require extra code to be written.
Every react component, or every JSX element I should say has the following properties shared, which are given by React to us to consume:
ref
key
I wanted to add extra props:
renderIf
fallback
These props help in a way I can't describe when it comes to conditional rendering and filtering the views based on the logged-in user permissions and roles (and other conditional rendering use cases, of course).
In react, if we wanted to apply these props to our components, we would use a HOC as follows:
// 🍎 Disclaimer: you don't have to understand any of the code written bellow, the general idea is that this is a HOC.
import React from 'react'
import getVal from './getVal'
export default function EnhancedComponent(OriginalComponent) {
return ({ renderIf: renderIf_ = true, override: override_, fallback: fallback_ = undefined, ...props }) => {
const renderIf = getVal(renderIf_)
const override = getVal(override_)
const fallback = getVal(fallback_)
const consumersComponent = <OriginalComponent {...props} />
let render = fallback
if (renderIf) render = consumersComponent
if (override_ !== undefined) render = override
return render
}
}
Where every time you want to apply these props to your components, you would have to wrap every new component you create with EnhancedComponent as follows:
export default EnhancedComponent(function Sidenav(){
return <div> side nav </div>
})
Now, you can use your Sidenav component within your App component as follows:
import Sidenav from './Sidenav'
export default function App(){
return (
<div>
<Sidenav renderIf={(5 + 5 === 10)}/>
<div>etc</div>
</div>
)
}
This API is great, but it has a drawback, which is, every time you want to apply these cool props (renderIf and fallback) you'll have to repeat these steps:
import Enhanced component to your file.
wrap your export with Enhanced component.
What I am looking for, is a method, or a way to inherit, or to add some props to the original react component class, somehow?
In react class components, I can imagine doing this on the React.Component class which we used to extend from in the past
class Car extends React.Component{
constructor(){}
render(){
return <div>I miss you 🌹</div>
}
}
But in react functional component, how can we do that?
I want to apply these props by default everytime I create a new component, without wrapping my components in a HOC everytime.
Does React have a way to do that? To change its defaults ?

Is it possible to create a component property from within a useEffect or Lifecycle method?

For example I could have a component
class HomeDefault extends PureComponent {
...
}
and I could add a property after the instance like so before the export
HomeDefault.someProperty = {test: 'test'}
Now if I were to use something like a higher order component over my HomeDefault component, I could console log out this "someProperty" object that I just set.
export default withHOC(HomeDefault)
// withHOC.js
import React from 'react';
const withHOC = Component => {
const WithHOC = props => {
console.log("the property: ", Component.someProperty) // this will work with the example above
return <Component />;
};
return WithHOC;
};
export default withNavigation;
So THAT works, but I need a solution that can set this property from within a useEffect (for functional components) or a componentDidMount (for class)
Is this possible??
The actual use case is that I'm upgrading react navigation and the .navigationOptions property has been done away with but I'm trying to not completely redo our code for the time being. So it would be awesome t run a function in a useEffect/componentDidMount and set this property that way since navigationOptions doesn't make the navigation state available outside of the component.
I hope this all makes sense! thank you all

React-Redux-Class extends value #<Object> is not a constructor or null

Class extends value # is not a constructor or null
Error shows up when tried Inheriting a Parent Class which is binded with react-redux "connect" ->
Redux
const mapStateToProps=(state)=>({
.....
});
const mapDispatchToProps=(dispatch)=>{
return {
paymentOver:()=>dispatch(paymentClose()),
}
}
export {
mapStateToProps,mapDispatchToProps
}
Parent Class
import { connect } from "react-redux";
import { mapStateToProps,mapDispatchToProps } from "../../State Management/MappingStates";
Class MainContainer extends Component{
componentDidMount(){
this.props.paymentOver(); //redux action
}
}
export default connect( mapStateToProps,mapDispatchToProps )( MainContainer )
Sub Class
import MainContainer from './MainContainer';
Class Sub extends MainContainer{ //Error showing at this line- Class extends value #<Object> is not a
//constructor or null
render(){
return ......
}
}
export default Sub
Afaik, connected components are function components, so you cannot extends them.
Either way, even if it were a class component, it would be a new Component that just rendered your original class component. So neither way, you could extend this.
In general: in React, you should never extend components - even class components, but use other patterns like higher order components or composition.
Also, the whole ecosystem is shifting to function components for two years now. Unless you have a very good reason to (like maintaining a very legacy code base), you probably should not write class components any more.
That is also the recommendation of the redux style guide: use hooks (useSelector and useDispatch) and function components over connect and class components.
If you are just learning react & redux, you are probably following very outdated sources. For Redux, pleasee look at the official tutorials at https://redux.js.org/tutorials/index

Typescript extend React.Component

#tl;dr
I want to extend a React Component without using HOC / Providers in Typescript
OK, so here's the deal...
At my work place we used to work with Vue and plain JS... then we decided to migrate to React with Typescript...
Tecnologies we use:
- React
- Typescript
- Redux
- Redux-Saga
The thing is, back in Vue, we could declare something like:
Vue.use(Auth)
and on every .vue file, inside the script tag we could call something like:
this.$auth
and have access to authorizatin methods.
What I want to do is... create an extension of ReactComponent where I already created some methods that most of my Component will use... something like:
auth // Check if user is authenticated, and if so, get the User Info
route // Give me the current route, with query params, redirects, etc...
Those were the only two I could think off here now.
I want to have in my .ts file something like this:
interface MyProps {
route: any // don't remember the correct Type
}
class MyComponent<T,S = {},SS = {}> extends React.Component<T,S,SS> {
$route = () => {
this.props.route
}
}
export default withRouter(MyComponent)
and have it being called in my application like this:
inteface AnotherProps {
}
class AnotherComponent extends MyComponent<AnotherProps> {
render() {
if(this.$route().location.pathname == "/") {
return <div>Root</div>
} else {
return <div>Not Root</div>
}
}
}
What I have tried so far
HOC (High Order Components)
I could achieve what I want using HOC, but the thing is... if possible, I would like 2 things.
To have this new properties being store at this and not this.props, and if that's possible using HOC, i don't know how
With HOC, I would also need to import the base Props, something like this:
import BaseProps from Outterspace;
inteface AnotherProps extends BaseProps{
}
and I want the logic inside the MyComponent and AnotherComponent to be as independent to each other as possible...
Providers
Same as HOC, I would need to pass the properties I want as props, and would need to extend my props interface.
[EDIT]
Decorators
Someone said in the comments that I could try using Decoratos, and while I did read the docs and it sounded promising... the last line of the Docs kinda worries me..
NOTE Decorator metadata is an experimental feature and may introduce breaking changes in future releases.
Thank you so much for reading this far ^^
If you are using typescript then you can create decorators.
You can call the decorator on top of your class and add the property.
#YourAuthDecorator({
'propertiesForconfiguration': 'value'
})
export class ReactClass extends ReactComponent {}
Example:
function abc() {
return {};
}
export function Auth() {
console.log("-- decorator function invoked --");
return function (constructor: Function){
console.log(constructor, 'called');
constructor.prototype.$auth = abc();
}
}
class Sample {
public prop = 'sample'
}
#Auth()
export class Content extends Sample {
}
export const a = new Content();
It will be the decorator's functionality to append various properties the to this instance providing you access to various functions/properties like auth
You can read more about decorators here.
https://www.typescriptlang.org/docs/handbook/decorators.html

using mixins of a react component in es6 style

I am using a component:- https://github.com/christianalfoni/formsy-react, for creating forms. I am trying to create one of my own components of the input. So as mentioned I need to use it for my's mixin. But unfortunately, there is no support for it in es6 style. So any work around anyone knows of.
here is my component code:-
import Formsy from 'formsy-react';
class DropDownAutoComplete extends React.Component {
constructor(props, context) {
super(props, context);
this.mixins = [Formsy.Mixin];
}
changeValue(event) {
this.mixins[0].setValue(event.currentTarget.value);
}
handleValue1Change(value) {
this.setState({value: value});
}
render() {
const className = this.mixins[0].showRequired() ? 'required' : this.mixins[0].showError() ? 'error' : null;
// An error message is returned ONLY if the component is invalid
// or the server has returned an error message
const errorMessage = this.mixins[0].getErrorMessage();
return <DropdownList
data={this.props.dropDownConfigs.value}
onChange={this.changeValue.bind(this)}
textField={this.props.dropDownConfigs.name}
caseSensitive={false}
filter='contains'>
</DropdownList>
}
}
It's throwing an error where the show required function is called. Apparently, its implementation uses some state variables like required.
By design, mixins do not work with ES6 classes - trying to hack something together is just going to cause you headaches!
One solution is to use what's called a higher-order component - a function that takes in a component, and returns a new component that wraps around it. These wrapper components can have lifecycle hooks of their own, and can pass props down to the wrapped components, effectively providing you with the same functionality mixins would give you, but arguably in a cleaner way!
formsy-react allows you to take this approach, providing its own HOC:
import {HOC} from 'formsy-react';
class MyInput extends React.Component {
render() {
return (
<div>
<input value={this.props.getValue()} onChange={(e) => this.props.setValue(e.target.value)}/>
</div>
);
}
};
export default HOC(MyInput);
You can use react-mixin-decorator.
Quoting from README:
If you're creating React components using ES6 classes and you'd like
to use existing mixins to add some nice functionality to your
component, you probably don't want to take the time to convert the
mixins to something that your ES6 React component class could use.

Resources