My application uses HoCs for its modals and I use withStyles to style them, when I apply multiple HoCs to one component however the classes prop of the first HoC is passed to the next in the compose of the component.
Example HoC1:
const styles = themes => ({
HoCOneStyle: {
margin: 'auto'
}
})
const withHoCOne = (WrappedComponent) => {
class HoCOne extends React.Component {
<HoC Stuff className={classes.HoCOneStyle} />
<WrappedComponent
{...this.props}
/>
}
return withStyles(styles, {withTheme: true})(HoCOne);
}
export default withHoCOne;
Example HoC2:
const styles = themes => ({
HoCTwoStyle: {
margin: 'auto'
}
})
const withHoCTwo = (WrappedComponent) => {
class HoCTwo extends React.Component {
<HoC Stuff className={classes.HoCTwoStyle} />
<WrappedComponent
{...this.props}
/>
}
return withStyles(styles, {withTheme: true})(HoCTwo);
}
export default withHoCTwo;
Example component:
class DemoComponent extends React.Component {
render() {
return (
<Component Stuff />
)
}
}
export default compose(
withHoCOne,
withHoCTwo
)(DemoComponent)
If run this code would throw an error in the console saying:
Warning: Material-UI: the key 'HoCOneStyle' provided to the classes
property is not implemented in HoCTwo. You can only override one of
the following: HoCTwoStyle.
How do I avoid throwing this error? It doesn't actually stop anything from working I just don't want errors in my console.
You just need to avoid passing the classes property from HoCOne into HoCTwo. When you include the classes property on something that is also using withStyles it triggers Material-UI's mergeClasses functionality.
You should be able to solve this with something like the following:
render() {
const {classes, ...otherProps} = this.props;
return <><HoC className={classes.HoCOneStyle} /><WrappedComponent
{...otherProps}
/></>;
}
Related
I have this higher order component which recieve a comp of volunteer for ex and an action, and then render a table with the volunteer info:
the volunteer comp code:
class Volenteer extends Component {
render() {
const title = 'רשימת מתנדבים';
const mode = 'work';
return (
<EntityTable
columns = {columns}
title = {title}
mode = {mode}
promiseProps = {this.props}
/>
)
}
}
export default WithEntity(Volenteer, requestVolunteerData() );
and the HOC code is:
import React, {Component} from 'react';
import { connect } from 'react-redux';
const WithEntity = (EntityComponent, action) => {
const mapStateToProps = state => {
return {
isPending: state.requestEntitiesReducer.isPending,
entities: state.requestEntitiesReducer.entities,
error: state.requestEntitiesReducer.error
}
}
const mapDispatchToProps = dispatch => {
return {
onRequestEntities: () => dispatch(action)
}
}
class WithEntity extends Component {
componentDidMount () {
this.props.onRequestEntities();
}
render() {
return (
<EntityComponent {...this.props} />
)
}
}
return connect(mapStateToProps, mapDispatchToProps)(WithEntity);
}
export default WithEntity;
it works fine but i am getting this warning:
There are similiar question about this , but did not find the solution there, also i have tied to implement componentDidUpdate but it fails. is there a problem by using componentDidMount life cycle?
Edit:
the DataProvider, FilterProvider or SortProvider, the components that mentioned in the message, comes from the react-bootstrap-table-2 comp:
const Table = ( {data, columns, mode} ) => {
<div className = 'table-responsive fixed word-wrap scroll mapping_table'>
<BootstrapTable
bootstrap4
keyField={'id'}
data={data}
columns={columns}
responsive = {true}
condensed
hover
pagination={ paginationFactory()}
filter={ filterFactory() }
defaultSortDirection="asc"
/>
</div>
}
export default Table;
here is a picture of the components list:
This is a known problem in react-bootstrap-table-2 component and has nothing to do with the HOC code you've pasted.
Your options are:
ignore the warning and hope nothing breaks
do the work to fix the library for more modern React and maybe put in a PR - wait for someone else to do the work
switch to another library
I am trying to go very in-depth to understand the purpose of spreading props in React HOC
So taking the below example;
const EnhanceComponent = BaseComponent => {
return class EnhancedComponent extends Component {
state = {
name: 'You have been enhanced'
}
render() {
return (
<BaseComponent {...this.props} {...this.state} />
)
}
}
};
export default EnhanceComponent;
Now let's say the usage of BaseComponent is as below;
<BaseComponent className='wrapper-container' onClick={this.handleClick} />
I assume if had not spread the props in the HOC, we would have been unable to access "this.props.className" OR "this.props.onClick" in BaseComponent. Would that be correct understanding ?
class BaseComponent extends React.Component {
render() {
const { className, onClick} = this.props;
...
}
}
Now to use the HOC itself, we would say;
const EnhancedMyComponent = EnhanceComponent(MyComponent);
And render it as
<EnhancedMyComponent className='wrapper-container' onClick={this.handleClick} />
Now, below are my 2 specific questions;
What do we finally render i.e. BaseComponent or EnhancedMyComponent OR using HOC allows us to use either flavor e.g. in some case, if we do not want the enhanced functionality, we just use the base component ?
OR
<EnhancedMyComponent className='wrapper-container' onClick={this.handleClick} />
Would the props access issue i.e. if we do not spread the props be applicable in both the above cases of consumption i.e. <BaseComponent /> AND <EnhancedMyComponent /> ?
1) What do we finally render i.e. BaseComponent or EnhancedMyComponent OR using HOC allows us to use either flavor e.g. in some case, if we do not want the enhanced functionality, we just use the base component ?
/ Using HOC allows us to use either flavor. It totally depends where we are wrapping the Component in HOC i.e while exporting or while using it at someplace.
Now, In the below case one has the option to use it with or without HOC
// BaseComponent.js
class BaseComponent extends React.Component {
render() {
const { className, onClick} = this.props;
...
}
}
export default BaseComponent;
// SomeComponent.js
import BaseComponent from './BaseComponent';
const MyComponent = EnhanceComponent(BaseComponent);
class SomeComponent extends React.Component {
render() {
return (
...
<MyComponent className={...} onClick={...} someExtraPropForHOC={...}/>
<BaseComponent className={...} onClick={...} />
...
)
}
To not allow anyone to directly use the Component, wrap it in HOC and export
// BaseComponent.js
class BaseComponent extends React.Component {
render() {
const { className, onClick} = this.props;
...
}
}
export default EnhanceComponent(BaseComponent);
// SomeComponent.js
import BaseComponent from './BaseComponent';
class SomeComponent extends React.Component {
render() {
return (
...
<BaseComponent className={...} onClick={...}/>
...
)
}
2) Would the props access issue i.e. if we do not spread the props be applicable in both the above cases of consumption i.e. AND ?
/ Spread the props is needed as HOC does not know what props would be needed for the dynamically wrapped component. So pass all the props which are coming is the only possible way.
class BaseComponent extends React.Component {
render() {
const { className, onClick} = this.props;
...
}
}
class CustomTextField extends React.Component {
render() {
const { className, onKeyPress, value} = this.props;
...
}
}
const EnhancedBaseComponent = EnhanceComponent(BaseComponent);
const EnhancedTextComponent = EnhanceComponent(CustomTextField);
Now in this case EnhancedBaseComponent and EnhancedTextComponent both need different props, but since they are wrapped in EnhanceComponent. It won't know which props to pass. So spread it and send all the props coming to it.
Is it possible to have the props of the parent component to be available in child component without passing them down?
I am trying to implement a provider pattern, so that to access all the provider props in its child components.
EX:
Suppose the below provider comp FetchProvider will fetch the data and theme props on its own, and when any child component is enclosed by it, I want to access both props "data" and "theme" in the child component as well. How can we achieve it?
class FetchProvider
{
proptypes= {
data: PropTypes.shape({}),
theme: PropTypes.shape({})
}
render()
{
// do some
}
mapStateToProps()
{
return {data, theme};
}
}
class ChildComponent
{
proptypes= {
name: PropTypes.shape({})
}
render()
{
const{data, them} = this.props; // is this possible here?
// do some
}
}
and if I try to above components as below.
<FetchProvider>
<ChildComponent name="some value"/> //how can we access parent component props here? without passing them down
<FetchProvider/>
This is exactly what react context is all about.
A Consumer can access data the a Provider exposes no matter how deeply nested it is.
// Context lets us pass a value deep into the component tree
// without explicitly threading it through every component.
// Create a context for the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// Use a Provider to pass the current theme to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing "dark" as the current value.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton(props) {
// Use a Consumer to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
return (
<ThemeContext.Consumer>
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);
}
Here is a small running example:
Note This is the react v16 context API.
Your use case can be solved with the usage of React context. With the help of Context, any child that is wrapped by a provided can be a consumer for the data that is provided by the Provider
In your case, you can use it like
context.js
export const FetchContext = React.createContext();
Provider.js
import { FetchContext } from 'path/to/context.js';
class FetchProvider extends React.Component
{
proptypes= {
data: PropTypes.shape({}),
theme: PropTypes.shape({})
}
render()
{
const { data, theme, children } = this.props;
return (
<FetchContext.Provider value={{ data, theme}}>
{children}
</FetchContext.Provider>
)
}
mapStateToProps()
{
return {data, theme};
}
}
ChildComponent.js
class ChildComponent extends React.Component
{
proptypes= {
name: PropTypes.shape({})
}
render()
{
const{data, them} = this.props; // use it from props here
// do some
}
}
export default (props) => (
<FetchContext.Consumer>
{({ data, theme }) => <ChildComponent {...props} data={data} theme={theme} />}
</FetchContext.Consumer>
)
However given the fact that you are already using Redux, which is build on the concept of Context, you might as well use redux and access the values within the child component since they are the same values that are supplied from the Redux store to the child by parent.
class ChildComponent extends React.Component
{
proptypes= {
name: PropTypes.shape({})
}
render()
{
const{data, them} = this.props; // use it from props here
// do some
}
}
const mapStateToProps = (state) => {
return {
data: state.data,
theme: state.theme
}
}
You can use React.Children to iterate over the children and pass whatever props you want to send to the new cloned elements using React.cloneElement.
EX:
class Parent extends React.Component {
constructor(props) {
super(props);
}
render() {
const { children } = this.props;
const newChildren = React.Children.map(children, child =>
React.cloneElement(child, { myProp: 'test' }));
return(
<View>
{newChildren}
</View>
)
}
}
Are you looking for:
class MyParent extends Component {
render() {
return <MyChild {...this.props}>
// child components
</MyChild>
}
}
This would pass all of the props passed into MyParent to the MyChild being rendered.
How can I validate that the supplied prop is a component class (not instance)?
e.g.
export default class TimelineWithPicker extends React.PureComponent {
static propTypes = {
component: PropTypes.any, // <-- how can I validate that this is a component class (or stateless functional component)?
};
render() {
return (
<this.props.component {...this.props} start={this.state.start}/>
);
}
}
For anyone using PropTypes >= 15.7.0 a new PropTypes.elementType was added in this pull request and was released on february 10, 2019.
This prop type supports all components (native components, stateless components, stateful components, forward refs React.forwardRef, context providers/consumers).
And it throws a warning when is not any of those elements, it also throws a warning when the prop passed is an element (PropTypes.element) and not a type.
Finally you can use it like any other prop type:
const propTypes = {
component: PropTypes.elementType,
requiredComponent: PropTypes.elementType.isRequired,
};
EDITED: Added React's FancyButton example to codesandbox as well as a custom prop checking function that works with the new React.forwardRef api in React 16.3. The React.forwardRef api returns an object with a render function. I'm using the following custom prop checker to verify this prop type. - Thanks for Ivan Samovar for noticing this need.
FancyButton: function (props, propName, componentName) {
if(!props[propName] || typeof(props[propName].render) != 'function') {
return new Error(`${propName}.render must be a function!`);
}
}
You'll want to use PropTypes.element. Actually... PropType.func works for both stateless functional components and class components.
I've made a sandbox to prove that this works... Figured this was needed considering I gave you erroneous information at first. Very sorry about that!
Working sandbox example!
Here is the code for the test in case link goes dead:
import React from 'react';
import { render } from 'react-dom';
import PropTypes from "prop-types";
class ClassComponent extends React.Component {
render() {
return <p>I'm a class component</p>
}
}
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
const FSComponent = () => (
<p>I'm a functional stateless component</p>
);
const Test = ({ ClassComponent, FSComponent, FancyButton }) => (
<div>
<ClassComponent />
<FSComponent />
<FancyButton />
</div>
);
Test.propTypes = {
ClassComponent: PropTypes.func.isRequired,
FSComponent: PropTypes.func.isRequired,
FancyButton: function (props, propName, componentName) {
if(!props[propName] || typeof(props[propName].render) != 'function') {
return new Error(`${propName}.render must be a function!`);
}
},
}
render(<Test
ClassComponent={ ClassComponent }
FSComponent={ FSComponent }
FancyButton={ FancyButton } />, document.getElementById('root'));
I'm trying to write a React Higher Order Components to add a custom attribute "test_id" to the view of a wrappedComonent, I need that auto-genrated attribute to do some UI testing later. but I have not find a way to achieve that.
import React, {Component, PropTypes} from "react";
const wrapTestableComponent = (ComponentToWrap) => {
class TestableComponent extends Component {
constructor(props){
super(props);
}
render() {
return <ComponentToWrap {...this.props} test_id={this.props.test_id} />;
}
}
TestableComponent.propTypes = {
test_id: PropTypes.string.isRequired,
}
return TestableComponent
}
export default wrapTestableComponent;
I've also tried the below version but I got that error: Uncaught TypeError: Can't add property test_id, object is not extensible
import React, {Component, PropTypes} from "react";
const wrapTestableComponent = (ComponentToWrap) => {
class TestableComponent extends Component {
constructor(props){
super(props);
}
render() {
var wrappedComponentView = <ComponentToWrap {...this.props} />;
wrappedComponentView.test_id = this.props.test_id;
return <ComponentToWrap {...this.props} />;
}
}
TestableComponent.propTypes = {
test_id: PropTypes.string.isRequired,
}
return TestableComponent
}
export default wrapTestableComponent;
EDIT
According to the comments we discussed below, I misunderstood the question before and I revised my answer.
The way you use in http://pastebin.com/0N9kKF73 should be the best way to do what you want.
I've tried to make a function that returns React.creatElement() to make a copy and assigned the extra props for ComponentToWrap but failed because of two main reasons.
React.creatElement() needs a param type.
ReactJS supported HTML attributes
REF:
Get HTML tag name from React element?
React: Can I add attributes to children's resultant HTML?
The revised version from your pastebin and the internet.
const wrapTestableComponent = (ComponentToWrap) => {
class TestableComponent extends React.Component {
componentDidMount() {
ReactDOM.findDOMNode(this.wrappedRef).setAttribute('test_id', this.props.test_id);
}
render() {
return <ComponentToWrap {...this.props}
ref={() => { this.wrappedRef = this; }}
/>;
}
}
TestableComponent.propTypes = {
test_id: React.PropTypes.string.isRequired,
}
return TestableComponent
}
const TestComp = (props) => (<div>Here is the TestComp</div>)
const NewComponent = wrapTestableComponent(TestComp)
ReactDOM.render(<NewComponent test_id="555" />, document.getElementById('View'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="View"></div>
I suppose this is what are you trying to find. ref may do all magic for you. Actually i dont have any others idea how to add custom component. fiddle
export function SelectWrapper(Select){
return class Wrapper extends Component {
componentDidMount(){
var element = ReactDOM.findDOMNode(this.refs.test);
element.setAttribute('custom-attribute', 'some value');
}
...
render(){
return <Select {...this.props} ref='test'/>
}
}
}