#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
Related
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}/>;
}
I am migrating my AngularJS website into ReactJS. I am new to React , so I am not much known to the background details of it. I have some classes that extends Injectables. But as React does not have dependency injections, how to treat them in React. I have some lines of code in AngularJS and want to convert it in ReactJS. What will be the strategy to do it?
class myClass extends Injectable {
$fetch(entities, resolveExtraContexts) {
const { $uibResolve, $q } = this.getServices(['$uibResolve', '$q']);
...
});
}
}
const fetcher = new myClass();
...
export default abc;
Here I do not know how to pass promises instead of $q . Also, what is the replacement of classes that extends Injectable. Last, but not the least, this.getServices just queues up all the dependency injection.
You may use componentDidMount and apply your api service logic there:
componentDidMount() {
// ... fetch
this.setState({fetchedData })
Now, strict to your question.
React has no dependency injection concept. And you may find this medium blog helpful.
If you want to apply injection logic in react, then you may use HOC. And you may find this medium blog helpful. The following code extracted from the linked blog:
function withApiService (WrappedComponent) {
class HOC extends React.Component {
render () {
const key = this.props.apiKey;
const apiService = new ApiService({key});
return (
<WrappedComponent
{...this.props}
apiService={apiService}
/>
);
}
}
return HOC;
}
I am learning react and stuck at a particular point. I want to have an utility js class that can be imported to most of the components. This utility class has to read the data from the store and then give the required information from the store.
import { connect } from 'react-redux';
class Test{
constructor(){
}
getData(){
// HOW TO GET THE DATA **HERE** ?
}
}
function mapStateToProps(state){
return { data: state.data};
}
export default connect(mapStateToProps)(Test);
Is it ok to have the Test as a ReactComponent with render() returning a null and using it as a utility function in other components ?
You are adding extra complexity to your code using this class.
import connect in any component you need and add add:
function mapStateToProps(state){
return { data: state.data};
}
export default connect(mapStateToProps)(Test);
you can access the state using props in that component. also you can use selectors for your state object and use that selectors in your component.
you can use this pattern: Redux Selector Pattern
I have a list of functions that use different react-native's built-in features, I want to create a react-native class that uses those features and only use its functions and those functions will not return anything
What I have already done is returned null from my render function.
but in this case I have to create use refs to call my functions
what I'm trying to achieve is this
export default class Wrapper extends Component{}
and in my screens I want to do this
export default class MyScreen extends Wrapper{
}
or may be
export default class MyScreen extends Component{
componentDidMount(){
Wrapper.myFunction()
}
}
Wrapper.myFuntion() works with all the react native's feature but doesn't render anything in view and only used for data manipulation.
Why dont you create a class which just imports the React features you want, then that class can be called with the functions you want?
If you don't want to run through the lifecycle of React, then don't extend off of a React Component.
For example, create a class:
export class ReactFunctions {
constructor (stuff : any) {
//constructor code
}
public reactFunction1 () {
//react stuff....
}
}
And import it when you need it....
import {ReactFunctions} from ....
Following on from that, I create 'micro-services' and helper classes which don't necessarily have React based functions in. You can separate out common functionality into common modules.
I have a redux reducer loaded with several reactjs components.
I want to load these inside other components through this.props
Like: this.props.components.MyReactComponent
class OtherComponent extends Component {
render() {
const Component = this.props.components.MyReactComponent
return (
<div>
<Component />
</div>
)
}
}
Is this possible? If so, how?
EDIT The component is a connected component. I am able to load it but it is broken. In this case, it is a counter, when you click to increment or decrement nothing happens. In the console, there is this error:
Uncaught ReferenceError: _classCallCheck is not defined
if I convert the component into a dumb component (without connecting it), the error is this:
Uncaught ReferenceError: _classCallCheck3 is not defined
EDIT 2
I found out why those errors show up. It is because the react component gets stripped out when stored in the reducer:
A react component would look something like this:
{ function:
{ [Function: Connect]
displayName: 'Connect(Counter)',
WrappedComponent: { [Function: Counter] propTypes: [Object] },
contextTypes: { store: [Object] },
propTypes: { store: [Object] } } }
However, after I store it inside a reducer, it loses its properties and ends up looking something like this:
{ function:
{ [Function: Connect] } }
After reading the comments below, I thought of an alternative. I can store in a reducer the path to each component, then make a new wrapper component that could render those other components from those paths.
I tried it but encoutered a different problem with the funcion require from nodejs that for some weird reason is not letting me user a variable as an argument. For example:
This works:
var SomeContent = require('../extensions/myContent/containers')
This does not:
var testpath = '../extensions/myContent/containers'
var SomeContent = require(testpath)
Giving me the following error:
Uncaught Error: Cannot find module '../extensions/myContent/containers'.
It is adding a period at the end of the path. How can I prevent require to add that period?
If you can think of any other alternative I can implement for what I am trying to do, I would greatly appreciate it.
EDIT 3 Following Thomas advice...
What I am trying to accomplish is this:
I want to be able to render react components inside other react components, I know how to do it the same way most us know how to; however, I want to be able to do it by importing a file that would contain all the components without actually having to import and export each one of them:
OtherComponent.js
import React, { Component } from 'react'
import { SomeComponent } from '../allComponentes/index.js'
export default class OtherComponent extends Component {
render() {
return (
<SomeComponent />
)
}
}
SomeComponent.js
import React, { Component } from 'react'
export default class SomeComponent extends Component {
render() {
return (
<div>
Hello
</div>
)
}
}
allComponents/index.js
import SomeComponent from '../allComponents/SomeComponent/index.js'
export { SomeComponent }
What I am trying to do in allComponents/index.js is to avoid having import/export statements for each component by reading (with fs module) all the components inside the allComponents folder and export them.
allComponents/index.js (pseudocode)
get all folders inside allComponents folder
loop through each folder and require the components
store each component inside an object
export object
When I tried that, I encountered multiple issues, for one, export statements have to be in the top-level, and second, fs would work only on the server side.
So, that is why I thought of loading all the components in a reducer and then pass them as props. But as I found out, they got stripped out when stored them in a reducer.
Then, I thought of only storing the path to those components inside a reducer and have a wrapper component that would use that path to require the needed component. This method almost worked out but the nodejs function require wont allow me to pass a variable as an argument (as shown in EDIT 2)
I think your question is not really to do with redux but rather is (as you say):
What I am trying to do in allComponents/index.js is to avoid having import/export statements for each component by reading (with fs module) all the components inside the allComponents folder and export them.
By way of example, I have all of my (dumb) form components in a folder path components/form-components and the index.js looks something like:
export FieldSet from './FieldSet'
export Input from './Input'
export Label from './Label'
export Submit from './Submit'
export Select from './Select'
export Textarea from './Textarea'
Then when I want to import a component elsewhere, it is import { FieldSet, Label, Input, Submit } from '../../components/form-components/';