How to get HOC in react to override props? - javascript

My props on an HOC do not seem to be overriding. I feel as though it might be the notation I'm using. Here is what I have right now.
export const HOCComponent = ({ someProp }) => (
<ContainerComponent
propOne={someValueOne}
propTwo={someValueTwo}
propThree={someValueThree)
/>
);
export const wrapperComponent = props =>(
<HOCComponent {...props} propThree={someValueFour}/>
);
someValueFour does not seem to override someValueThree. Any suggestions would be super helpful! Thank you.

Swap the order of the passed props such that anything you pass later overrides anything passed previously.
export const wrapperComponent = props =>(
<HOCComponent propThree={someValueFour} {...props} />
);
The HOCComponent wrapper component needs to also pass along all props to the component it's wrapping.
export const HOCComponent = (props) => (
<ContainerComponent
propOne={someValueOne}
propTwo={someValueTwo}
propThree={someValueThree}
{...props}
/>
);
Just a minor point about terminology, nothing in your code snippet is a Higher Order Component. HOCs consume a React component and return a new React Component.
An Example:
const withMyHOC = WrappedComponent => props => {
// any HOC logic
return (
<Wrapper
// <-- any wrapper props
>
<WrappedComponent
{...props} // <-- pass props through
// <-- override any props
/>
</Wrapper>
);
};
usage:
export default withMyHOC(SomeComponent);

I saw you don't pass props from HOCComponent to ContainerComponent so propThree is not override. You need to pass someProp to ContainerComponent:
<ContainerComponent propOne propTwo propThree {...someProp} />

Related

HOC for JSX element - rendering jsx with wrapped element

I want to call a ReactJS HOC to wrap a tooltip around JSX.
The call should be able like this:
withTooltip(JSX, "very nice")
Therefor I have created this function:
import React from "react";
import MUITooltip from "#material-ui/core/Tooltip";
import useStyles from "./index.styles";
const withTooltip = (Component, text: string) => (props) => {
const classes = useStyles();
return (
<MUITooltip className={classes.root} title={text}>
<Component {...props} />
</MUITooltip>
);
};
export default withTooltip;
The call:
import withTooltip from "commons/withTooltip/withTooltip";
const dialogBtn =
isOk &&
withTooltip(
<div className={classes.buttonWithLoader}>
<OpenDialogButton
variant={BaseButtonVariant.Icon}
openDialogAttributes={areas.button.openDialogAttributes}
/>
</div>,
"Very nice",
);
return (
<Fragment>
{dialogBtn}
</Fragment>
);
It says:
Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it
How to solve it ?
Your HOC accepts a Component argument while you are passing in JSX. Try wrapping the JSX with a function or pass in a component which renders the Button.
However, in your case, you probably want to have control over the toolTip text in your component. If this is the case, I would not use a HOC for this, but rather a wrapping Component.
function WithTooltip({ classes, text, children }) {
return (
<MUITooltip className={classes.root} title={text}>
{children}
</MUITooltip>
);
}
export default WithTooltip;
const dialogBtn = isOk && (
<WithTooltip text="Very nice">
<div className={classes.buttonWithLoader}>
<OpenDialogButton
variant={BaseButtonVariant.Icon}
openDialogAttributes={areas.button.openDialogAttributes}
/>
</div>
</WithTooltip>
);

How to access ref that was set in render

Hi I have some sort of the following code:
class First extends Component {
constructor(props){super(props)}
myfunction = () => { this.card //do stuff}
render() {
return(
<Component ref={ref => (this.card = ref)} />
)}
}
Why is it not possible for me to access the card in myfunction. Its telling me that it is undefined. I tried it with setting a this.card = React.createRef(); in the constructor but that didn't work either.
You are almost there, it is very likely that your child Component is not using a forwardRef, hence the error (from the React docs). ref (in a similar manner to key) is not directly accesible by default:
const MyComponent = React.forwardRef((props, ref) => (
<button ref={ref}>
{props.children}
</button>
));
// ☝️ now you can do <MyComponent ref={this.card} />
ref is, in the end, a DOMNode and should be treated as such, it can only reference an HTML node that will be rendered. You will see it as innerRef in some older libraries, which also works without the need for forwardRef in case it confuses you:
const MyComponent = ({ innerRef, children }) => (
<button ref={innerRef}>
{children}
</button>
));
// ☝️ now you can do <MyComponent innerRef={this.card} />
Lastly, if it's a component created by you, you will need to make sure you are passing the ref through forwardRef (or the innerRef) equivalent. If you are using a third-party component, you can test if it uses either ref or innerRef. If it doesn't, wrapping it around a div, although not ideal, may suffice (but it will not always work):
render() {
return (
<div ref={this.card}>
<MyComponent />
</div>
);
}
Now, a bit of explanation on refs and the lifecycle methods, which may help you understand the context better.
Render does not guarantee that refs have been set:
This is kind of a chicken-and-egg problem: you want the component to do something with the ref that points to a node, but React hasn't created the node itself. So what can we do?
There are two options:
1) If you need to pass the ref to render something else, check first if it's valid:
render() {
return (
<>
<MyComponent ref={this.card} />
{ this.card.current && <OtherComponent target={this.card.current} />
</>
);
}
2) If you are looking to do some sort of side-effect, componentDidMount will guarantee that the ref is set:
componentDidMount() {
if (this.card.current) {
console.log(this.card.current.classList);
}
}
Hope this makes it more clear!
Try this <Component ref={this.card} />

React/ES6: curly braces in function signature? [duplicate]

I know you can pass all a react components props to it's child component like this:
const ParentComponent = () => (
<div>
<h1>Parent Component</h1>
<ChildComponent {...this.props} />
</div>
)
But how do you then retrieve those props if the child component is stateless? I know if it is a class component you can just access them as this.prop.whatever, but what do you pass as the argument into the stateless component?
const ChildComponent = ({ *what goes here?* }) => (
<div>
<h1>Child Component</h1>
</div>
)
When you write
const ChildComponent = ({ someProp }) => (
<div>
<h1>Child Component {someProp}</h1>
</div>
)
From all the props that you are passing to the childComponent you are just destructuring to get only someProp. If the number of props that you want to use in ChildComponents are countable(few) amongst the total number of props that are available, destructuring is a good option as it provides better readability.
Suppose you want to access all the props in the child component then you need not use {} around the argument and then you can use it like props.someProp
const ChildComponent = (props) => (
<div>
<h1>Child Component {props.someProp}</h1>
</div>
)
Are you looking for the ES6 named argument syntax (which is merely destructuring) ?
const ChildComponent = ({ propName }) => (
<div>
<h1>Child Component</h1>
</div>
)
const ChildComponent = (props) => ( // without named arguments
<div>
<h1>Child Component</h1>
</div>
)
Optionally there is a second argument to your function depending of whether you specified a context for your component or not.
Perhaps it would be more helpful wityh a links to the docs. As stated in the first article about functional components. Whatever props passed on to the component is represented as an object passed as first argument to your functional component.
To go a little further, about the spread notation within jsx.
When you write in a component :
<Child prop1={value1} prop2={value2} />
What your component will receive is an plain object which looks like this :
{ prop1: value1, prop2: value2 }
(Note that it's not a Map, but an object with only strings as keys).
So when you're using the spread syntax with a JS object it is effectively a shortcut to this
const object = { key1: value1, key2: value2 }
<Component {...object}/>
Is equivalent to
<Component key1={value1} key2={value2} />
And actually compiles to
return React.createElement(Component, object); // second arg is props
And you can of course have the second syntax, but be careful of the order. The more specific syntax (prop=value) must come last : the more specific instruction comes last.
If you do :
<Component key={value} {...props} />
It compiles to
React.createElement(Component, _extends({ key: value }, props));
If you do (what you probably should)
<Component {...props} key={value} />
It compiles to
React.createElement(Component, _extends(props, { key: value }));
Where extends is *Object.assign (or a polyfill if not present).
To go further I would really recommend taking some time to observe the output of Babel with their online editor. This is very interesting to understand how jsx works, and more generally how you can implement es6 syntax with ES5 tools.
const ParentComponent = (props) => (
<div>
<h1>Parent Component</h1>
<ChildComponent {...props} />
</div>
);
const ChildComponent = ({prop1, ...rest}) =>{
<div>
<h1>Child Component with prop1={prop1}</h1>
<GrandChildComponent {...rest} />
</div>
}
const GrandChildComponent = ({prop2, prop3})=> {
<div>
<h1>Grand Child Component with prop2={prop1} and prop3={prop3}</h1>
</div>
}
You can use Spread Attributes reducing code bloat. This comes in the form of {'somearg':123, ...props} or {...this.props}, with the former allowing you to set some fields, while the latter is a complete copy. Here's an example with ParentClass.js :
import React from 'react';
import SomeComponent from '../components/SomeComponent.js';
export default class ParentClass extends React.Component {
render() {
<SomeComponent
{...this.props}
/>
}
}
If I do, <ParentClass getCallBackFunc={() => this.getCallBackFunc()} />, or if I do <ParentClass date={todaysdatevar} />, the props getCallBackFunc or date will be available to the SomeComponent class. This saves me an incredible amount of typing and/or copying/pasting.
Source: ReactJS.org: JSX In Depth, Specifying the React Element Type, Spread Attributes. Official POD:
If you already have props as an object, and you want to pass it in JSX, you can use ... as a “spread” operator to pass the whole props object. These two components are equivalent:
return <Greeting firstName="Ben" lastName="Hector" />;
}
function App2() {
const props = {firstName: 'Ben', lastName: 'Hector'};
return <Greeting {...props} />;
}```
Now, let's apply this to your code sample!
const ParentComponent = (props) => (
<div>
<h1>Parent Component</h1>
<ChildComponent {...props} />
</div>
);
I thought I would add a simple ES2015, destructuring syntax I use to pass all props from a functional parent to a functional child component.
const ParentComponent = (props) => (
<div>
<ChildComponent {...props}/>
</div>
);
Or if I have multiple objects (props of parent, plus anything else), I want passed to the child as props:
const ParentComponent = ({...props, ...objectToBeAddedToChildAsProps}) => (
<div>
<ChildComponent {...props}/>
</div>
);
This destructuring syntax is similar to the above answers, but it is how I pass props along from functional components, and I think it is really clean. I hope it helps!
But how do you then retrieve those props if the child component is stateless?
const ChildComponent = ({ *what goes here?* }) => (
<div>
<h1>Child Component</h1>
</div>
)
ChildComponent holds the name and the props will be the argument in the arrow function syntax just as you need:
const ChildComponent = props => (
<div>
<p>{props.value ? props.value : "No value."}</p>
</div>
);
If you Babel-it it will create something like this:
var ChildComponent = function ChildComponent(props) {
return React.createElement(
"div",
null,
React.createElement(
"p",
null,
props.value ? props.value : "No value."
)
);
};
For some reason, what seems to work for me is a variation on Shubham's answer above:
const ChildComponent = props => (
<div>
<h1>Child Component {props[0].someProp}</h1>
</div>
)
Using this
const ParentComponent = ({ prop1, prop2, prop3 }) => (
<div>
<h1>Parent Component</h1>
<ChildComponent {...{ prop1, prop2, prop3 }} />
</div>
);
const ChildComponent = ({ prop1, prop2, prop3 }) =>{
<div>
<h1>Child Component with prop1={prop1}</h1>
<h1>Child Component with prop2={prop2}</h1>
<h1>Child Component with prop2={prop3}</h1>
</div>
}

How to use React.forwardRef in a class based component?

I'm trying to use React.forwardRef, but tripping over how to get it to work in a class based component (not HOC).
The docs examples use elements and functional components, even wrapping classes in functions for higher order components.
So, starting with something like this in their ref.js file:
const TextInput = React.forwardRef(
(props, ref) => (<input type="text" placeholder="Hello World" ref={ref} />)
);
and instead defining it as something like this:
class TextInput extends React.Component {
render() {
let { props, ref } = React.forwardRef((props, ref) => ({ props, ref }));
return <input type="text" placeholder="Hello World" ref={ref} />;
}
}
or
class TextInput extends React.Component {
render() {
return (
React.forwardRef((props, ref) => (<input type="text" placeholder="Hello World" ref={ref} />))
);
}
}
only working :/
Also, I know I know, ref's aren't the react way. I'm trying to use a third party canvas library, and would like to add some of their tools in separate components, so I need event listeners, so I need lifecycle methods. It may go a different route later, but I want to try this.
The docs say it's possible!
Ref forwarding is not limited to DOM components. You can forward refs to class component instances, too.
from the note in this section.
But then they go on to use HOCs instead of just classes.
The idea to always use the same prop for the ref can be achieved by proxying class export with a helper.
class ElemComponent extends Component {
render() {
return (
<div ref={this.props.innerRef}>
Div has a ref
</div>
)
}
}
export default React.forwardRef((props, ref) => <ElemComponent
innerRef={ref} {...props}
/>);
So basically, we are forced to have a different prop to forward ref, but it can be done under the hub. It's important that the public use it as a normal ref.
class BeautifulInput extends React.Component {
const { innerRef, ...props } = this.props;
render() (
return (
<div style={{backgroundColor: "blue"}}>
<input ref={innerRef} {...props} />
</div>
)
)
}
const BeautifulInputForwardingRef = React.forwardRef((props, ref) => (
<BeautifulInput {...props} innerRef={ref}/>
));
const App = () => (
<BeautifulInputForwardingRef ref={ref => ref && ref.focus()} />
)
You need to use a different prop name to forward the ref to a class. innerRef is commonly used in many libraries.
Basically, this is just a HOC function. If you wanted to use it with class, you can do this by yourself and use regular props.
class TextInput extends React.Component {
render() {
<input ref={this.props.forwardRef} />
}
}
const ref = React.createRef();
<TextInput forwardRef={ref} />
This pattern is used for example in styled-components and it's called innerRef there.
This can be accomplished with a higher-order component, if you like:
import React, { forwardRef } from 'react'
const withForwardedRef = Comp => {
const handle = (props, ref) =>
<Comp {...props} forwardedRef={ref} />
const name = Comp.displayName || Comp.name
handle.displayName = `withForwardedRef(${name})`
return forwardRef(handle)
}
export default withForwardedRef
And then in your component file:
class Boop extends React.Component {
render() {
const { forwardedRef } = this.props
return (
<div ref={forwardedRef} />
)
}
}
export default withForwardedRef(Boop)
I did the work upfront with tests & published a package for this, react-with-forwarded-ref: https://www.npmjs.com/package/react-with-forwarded-ref
Incase you need to reuse this in many difference components, you can export this ability to something like withForwardingRef
const withForwardingRef = <Props extends {[_: string]: any}>(BaseComponent: React.ReactType<Props>) =>
React.forwardRef((props, ref) => <BaseComponent {...props} forwardedRef={ref} />);
export default withForwardingRef;
usage:
const Comp = ({forwardedRef}) => (
<input ref={forwardedRef} />
)
const EnhanceComponent = withForwardingRef<Props>(Comp); // Now Comp has a prop called forwardedRef
In case anyone is still having trouble combining React.forwardRef() with a HOC like react-redux connect() then this is how I got it to work:
export default connect(mapStateToProps, mapDispatchToProps)(
React.forwardRef((props, ref) => <MyComponent innerRef={ref} {...props} />)
);

How to write a wrapper around a material UI component using React JS?

I am using Material UI next library and currently I am using List component. Since the library is in beta, lot of its parameter names get changed. To solve this I am planning to write a wrapper around the required components so that things wont break. My list component :
<List dense>
<List className={classes.myListStyles}>
<ListItem disableGutters/>
</List>
</List>
How should I write the wrapper for the List(say myListWrapper) and ListItem so that the wrapper component can handle props and pass them to the actual MUI list component inside?
I had worked on MUI wrappers, writing my own library for a project. The implementation we are focusing, is to pass the props to inner/actual-MUI component from the our wrapper component. with manipulation. In case of wrapping props for abstraction.
Following is my approach to the solution:
import { List as MaterialList } from 'material-ui/List';
import { React } from 'react';
import { ListItem as MaterialListI } from 'material-ui/ListItem';
class List extends MaterialList {
constructor(props){
const propsToPass = {
prop1 : change(props.prop1),
...props
}
super(propsToPass);
}
};
class ListItem extends MaterialListItem {
const propsToPass = {
prop1 : change(props.prop1),
prop2 : change(props.prop2),
...props
}
super(propsToPass);
}
};
class App extends React.Component {
render () {
return (
<List prop='value' >
<ListItem prop1={somevalue1} prop2={somevalue2} />
<ListItem prop1={somevalue1} prop2={somevalue2} />
<ListItem prop1={somevalue1} prop2={somevalue2} />
</List>
)
}
};
Above code will allow following things to do with your component:
You can use the props with exact names, as used in Material UI.
You can manipulate/change/transform/reshape you props passed from outside.
If props to you wrapper components are passed with exactly same names as MUI is using, they will directly be sent to the inner component. (... operator.)
You can use Component with exact same name as material is using to avoid confusion.
Code is written according to advance JSX and JavaScript ES6 standards.
You have a space to manipulate your props to pass into the MUI Components.
You can also implement type checking using proptypes.
You can ask for any confusion/query.
You can write it like this:
const MyList = props => (
<List
{/*mention props values here*/}
propA={props.A}
propB={props.B}
>
{props.children}
</List>
)
const MyListItem = props => (
<ListItem
{/*mention props values here*/}
propA={props.A}
propB={props.B}
>
{props.children}
</ListItem>
)
Now you need to use MyList and MyListItem, decide the prop names for these component (as per your convenient), and inside these component map those values to actual Material-UI component properties.
Note:
If you are using the same prop names (same name as material-ui component expect) for your component then you can write like this also:
const MyList = ({children, ...rest}) => <div {...rest}>{children}</div>
const MyListItem = ({children, ...rest}) => <p {...rest}>{children}</p>
Check this example:
const A = props => <div>{props.children}</div>
const B = props => <p>{props.children}</p>
ReactDOM.render(
<A>
<A>
<B>Hello</B>
</A>
</A>,
document.getElementById('app')
)
<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='app' />

Categories

Resources