Extending component class exported wrapped in a HOC - reactjs

This question had something to do with react-admin until I realized the problem was the was the component I was extending was actually exported.
I'm using a react-admin's TabbedForm and child FormTab to show lots of fields in my admin, and I need to customize a tab's layout.
Basically I want to extend FormTab with a custom layout.
But I'm not getting anywhere.
Here is the problem :
If I use a custom component for my tabs like
import React from 'react';
import {
FormTab,
FormInput,
} from 'react-admin'
const hiddenStyle = { display: 'none' };
export class CustomFormTab extends FormTab {
renderContent = ({ children, hidden, ...inputProps }) => (
<span style={hidden ? hiddenStyle : null}>
{React.Children.map(
children,
child => React.cloneElement(child, {
...inputProps
})
)}
</span>
);
}
export const CustomFormTabPart = ({ children, className, ...inputProps }) => {
return (
<div className={className}>
{React.Children.map(
children,
input =>
input && (
<FormInput
{...inputProps}
input={input}
/>
)
)}
</div>
)
}
This should get me going but I'm extending FormTab which not exported as a simple component.
// in react-admin/packages/ra-ui-materialui/src/form/FormTab.js
...
FormTab.displayName = 'FormTab';
export default translate(FormTab);
translate is a HOC...
I don't know how to extend this. Is that even possible ?
Actually the code in FormTab is not huge and copy-pasting it is a solution. I just hate like it.

It's a good practice to export original class as named export along with default export but this wasn't done in FormTab module.
It's a good practice to expose wrapped component (as can be seen in React Router's withRouter, React Redux's connect and so on) as static property on decorated component but this wasn't done in translate HOC.
Since translate wraps original FormTab component with context component, a quick-and-dirty fix is to deconstruct a hierarchy of React components:
const translateContext = FormTab.prototype.render();
const callback = translateContext.props.children;
const OriginalFormTab = callback({ translate: () => {}, label: '' }).type;
#translate
export class CustomFormTab extends OriginalFormTab { ... }
Since this is a hack that relies on third-party library internals, it may become broken with new library release, so it cannot be recommended, or at least requires to write unit test that tests internals in use.
A more appropriate solution is to fork a library, fix the drawbacks that were described (expose wrapped component in translate or/and export original components from modules) and submit a PR.

Related

Exporting const from a fucntion in React into another component?

I have a file ButtonExample.js that I am exporting a component (ButtonExample) and const (color) from. I am attempting to import them into a file called CustomCreation.js. However, on compiling I am met with the error:
Attempted import error: 'color' is not exported from './ButtonExample'.
This is the code for ButtonExample.js
import React, { useState } from "react";
import { GithubPicker } from "react-color";
function ButtonExample() {
const [showColorPicker, setShowColorPicker] = useState(false);
const [color, setColor] = useState("");
return (
<div>
<button
type="button"
class="btn btn-dark"
onClick={() =>
setShowColorPicker((showColorPicker) => !showColorPicker)
}
>
{showColorPicker ? "Close" : "Choose Color"}
</button>
{showColorPicker && (
<div>
<GithubPicker
color={color}
onChange={(updatedColor) => setColor(updatedColor.hex)}
colors={[
"#131313" /*black*/,
"#575757" /*grey*/,
"#f6f6f6" /*white*/,
"#203e20" /*green*/,
"#423122" /*brown*/,
"#121c4f" /*navy*/,
]}
/>
</div>
)}
</div>
);
}
export default { ButtonExample, color };
This is how I'm attempting to import it in CustomCreation.js
import ButtonExample, { color } from "./ButtonExample";
I've done this in the past and it's worked fine so I'm just unsure if I'm missing something really obvious.
color is declared as state inside the scope of the ButtonExample() functional component, so when you try to access it inside export default { ButtonExample, color }; it'll always be undefined.
Moreover, your export and import syntax seems incorrect. A default export defines a single export per module, so what you're essentially doing here is exporting an object comprised of ButtonExample and an undefined variable and attempting to use both named and default imports to import them.
To better share state between your components, you should either use a state manager like Redux to manage state globally throughout your application or implement a parent component for this component and any other component that relies on the current color. The parent component will then manage the color state and pass that down as a prop to any other components that need it. Additionally, the parent component will need to implement its own setColor function that it passes to ButtonExample as a prop, which can then be substituted for GithubPicker's onChange callback.

making a new react component want to add code not DOM

I'm relearning react, I've done tons and tons of angular I ran the following command :
generate-react component carousel
(I'm using generate-react-cli)
this works well and generates the following .tsx file (plus test, lazy-loading and style files, which I am omitting) :
import React from 'react';
import styles from './carousel.module.scss';
const carousel: React.FC = () => (
<div className={styles.carousel}>
carousel Component
</div>
);
export default carousel;
this loads fine in my app but now I want to add actual code, functions.
I can't seem to do this.
I've broken open this const like so :
const carousel: React.FC = () => {
constructor(props) {
super(props);
}
componentDidMount() {
}
return (
<div className={styles.carousel}>
carousel Component
</div>
);
}
I assume this is where the constructor and so on go,
however in this area the constructor name and ect. is not allowed.
so I tried changing the type to React.Component
however at that point I get an error directly on the const name stating :
TS2740: Type '() => Element' is missing the following properties from
type 'Component{}, {}, any>': context, setState, forceUpdate, render,
and 3 more.
I'm really shooting in the dark here and reacts docs are antithetical to examples. they refuse to help with any of this.
I want this newer, shorthand syntax but I also want to be able to use lifecycles and functions.
As it turns out the correc syntax for this is as follows :
class carousel extends React.Component {
componentDidMount() {
console.log('hello');
}
render() {
return (
<div className={styles.carousel}>
carousel Component
</div>
);
}
}

When should I use a this.context vs Consumer in the New Context API in React?

There are two styles for using the context data in the Child components: this.context and using the <myContext.Consumer>{(contextData) => {return(<div>{myContextData}</div>}}</myContext.Consumer>
when should I use the former vs latter?
There are not two but three ways to use context in v16.8.0 and above. The choice of which one you should use depends upon your use case.
The first one i.e using static contextType is the simplest of the two to use which allows you to use context throughout your class component. You can acchieve the same behaviour using render props pattern but then you have to wrap your component within another functional component.
So the choice, although a matter of personal preference and wouldn't impact performance of debugging much is simple to make.
If you want to make use of just one context within the component, you can make use of static contextType to define it.
However, if you have more than one context which you want your component to use, then you can't achieve that with the first pattern,so you have to make use of render props pattern
An example of the above is say you are using a ThemeProvider and your component just uses that you can write it simply as below using first pattern
class MyComponent extends React.Component {
...
}
MyComponnet.contextType = ThemeContext;
export default MyComponent
with render prop pattern you would need to write it as below given you want to use context outside of render method too
class MyComponent extends React.Component {
...
}
export default (props) => (
<ThemContext.Consumer>
{(context) => <MyComponent {...props} context={context} />
<ThemeContext.Consumer>
)
However say you want to use two context within MyComponent ie. say a ThemeContext and ShortCutContext to listen to keyboard shortcuts you would write it using render props like below
class MyComponent extends React.Component {
...
}
export default (props) => (
<ShortcutContext.Consumer>
{(sCon) => (
<ThemContext.Consumer>
{(tCon) => <MyComponent {...props} themeContext={tCon} shortcutContext={sCon} />
<ThemeContext.Consumer>
)}
</ShortcutContext.Consumer>
)
There is a third way which is useContext, which allows you to use context within functional components using hooks pattern. If you are writing your App with hooks, you can make use of useContext which allows you to have multiple contexts within your App too
You can use multiple contexts using useContext as below
const MyComponent = () => {
const shortcutContext = useContext(ShortcutContext);
const themeContext = useContext(ThemeContext);
...
}

How to use List component "items" prop in React Semantic UI?

I can't understand how to use "items" property of React Semantic UI "List" component. Is there a way to pass it a function or a component it will use to render every item ?
<List items={repos} component={RepoListItem} />;
const RepoListItem = props => {
return (
<List.Item>
<List.Content>{props.full_name}</List.Content>
</List.Item>
);
};
I look for a property like "component" here that could be used to set a rendering function or component.
You should be able to pass React Components an array of Child components if the parent allows it. Looking at the docs, this very much seems possible.
https://react.semantic-ui.com/elements/list/#types-basic
The example below comes straight from the docs
import React from 'react'
import { List } from 'semantic-ui-react'
const ListExampleBasic = () => (
<List>
<List.Item>Apples</List.Item>
<List.Item>Pears</List.Item>
<List.Item>Oranges</List.Item>
</List>
)
export default ListExampleBasic
So what you want to do is generate an array of components for List.Item. based on the items state you provide. This can be achieved using the .map function.
import React from 'react'
import { List } from 'semantic-ui-react'
const RenderList = () =>
<List>
{repos.map((item) => RepoListItem(item))}
</List>
const RenderRepoListItem = item =>
<List.Item>
<List.Content>{item.full_name}</List.Content>
</List.Item>
As JSX has an XML-like syntax for compositional components, any children components are nested inside their parent.
Also, I'm not sure where List.Content is coming from, you could probably omit that and just have {item.full_name} directly inside List.Item

React.js - how to pass event handlers to deeply nested component without props drilling?

I have the structure of components (nested) that seems like this:
Container
ComponentA
ComponentB
ComponentC(want to handle event here with state that lives on container)
Do I need to pass as props all the way from Container, ComponentA, ComponentB and finally ComponentC to have this handler? Or is there another way like using Context API?
I'm finding a bit hard to handle events with react.js vs vue.js/angular.js because of this.
I would recommend using either Context API (as you mentioned) or Higher Order Components (HoC)
Context Api is your data center. You put all the data and click events that your application needs here and then with "Consumer" method you fetch them in any component regardless of how nested it is. Here is a basic example:
context.js //in your src folder.
import React, { Component, createContext } from "react";
import { storeProducts } from "./data"; //imported the data from data.js
const ProductContext = createContext(); //created context object
class ProductProvider extends Component {
state = {
products: storeProducts,
};
render() {
return (
<ProductContext.Provider
//we pass the data via value prop. anything here is accessible
value={{
...this.state,
addToCart: this.addToCart //I wont use this in the example because it would
be very long code, I wanna show you that, we pass data and event handlers here!
}}
>
// allows all the components access the data provided here
{this.props.children},
</ProductContext.Provider>
);
}
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer };
Now we set up our data center with .Consumer and .Provider methods so we can access
here via "ProductConsumer" in our components. Let's say you want to display all your products in your home page.
ProductList.js
import React, { Component } from "react";
import Product from "./Product";
import { ProductConsumer } from "../context";
class ProductList extends Component {
render() {
return (
<React.Fragment>
<div className="container">
<div className="row">
<ProductConsumer>
//we fetch data here, pass the value as an argument of the function
{value => {
return value.products.map(product => {
return <Product key={product.id} />;
});
}}
</ProductConsumer>
</div>
</div>
</React.Fragment>
);
}
}
export default ProductList;
This is the logic behind the Context Api. It sounds scary but if you know the logic it is very simple. Instead of creating your data and events handlers inside of each component and prop drilling which is a big headache, just put data and your event handlers here and orchestrate them.
I hope it helps.

Resources