I've been research Higher Order Components in react. My requirement is that I have a set components which I need to extend to give them more functionality without rewriting the entire component. In this case, I found out the concept HOC in react where one could extend the component using a pure function. My question is, can I export the extended component as a normal component. For an example
Component which needs to be extended
class foo extends React.Component {
render(){
//something
}
}
export default foo;
HOC component
function bar(foo) {
render() {
return <foo {...this.props} {...this.state} />;
}
}
export default bar;
Am I able to use the component that way? or am I doing it wrong?
A HOC would take a component, add some more functionality and return a new component and not just return the component instance,
What you would do is
function bar(Foo) {
return class NewComponent extend React.Component {
//some added functionalities here
render() {
return <Foo {...this.props} {...otherAttributes} />
}
}
}
export default bar;
Now when you want to add some functionality to a component you would create a instance of the component like
const NewFoo = bar(Foo);
which you could now use like
return (
<NewFoo {...somePropsHere} />
)
Additionally you could allow the HOC to take a default component and export that as a default component and use it elsewhere like
function bar(Foo = MyComponent) {
and then create an export like
const wrapMyComponent = Foo();
export { wrapMyComponent as MyComponent };
A typical use-case of an HOC could be a HandleClickOutside functionality whereby you would pass a component that needs to take an action based on handleClickOutside functionality
Another way could be like this:
Make a Foo Component
class Foo extends React.Component {
render() {
return ( < h1 > hello I am in Foo < /h1>)
}
}
Make a HOC component.
class Main extends React.Component {
constructor(props) {
super(props);
}
render() {
const {
component, props
} = this.props;
//extract the dynamic component passed via props.
var Component = component;
return ( < div >
< h1 > I am in main < /h1>
< Component {...props} > < /Component>
</div > );
}
}
ReactDOM.render( < Main component = {
Foo
} > < /Main>,
document.getElementById('example')
);
Working code here
Yes you can
const bar = (Foo) => {
return class MyComponent extend Component {
render() {
return <Foo {...this.props} />
}
}
}
//Our Foo Component Code Here
export default bar(Foo)
But again it depends on the functionality. Eg: suppose you're using react router and want to check if user is present before rendering the component don't pass the HOC. eg:
<Route path="/baz" component={auth(Foo)} />
Instead use an new component.
Note: NewComponent is connected to redux and user (state) is passed as props
class NewRoute extends Component{
render(){
const {component:Component, ...otherProps} = this.props;
return(
<Route render={props => (
this.props.user? (
<Component {...otherProps} />
):(
<Redirect to="/" />
)
)}
/>
);
}
}
Then on the routes
<NewRoute path='/foo' component={Foo} />
Related
say my HOC is:
import React, { Component } from "react";
let validateURL = WrappedComponent =>
class extends Component{
render() {
if( wrappedcomponentnameis === 'xyz')
return ...
elseif(wrappedcomponentnameis === 'abc')
return ...
and so on....
}
};
export default validateURL;
how do I get the name of wrapped component inside this HOC?
You can access it via WrappedComponent.name:
const HOC = WrappedComponent => class Wrapper extends React.Component{
render() {
if (WrappedComponent.name === 'Hello') {
return <WrappedComponent name='World' />
}
return <WrappedComponent/>
}
}
class Hello extends React.Component {
render() {
return <div>Hello {this.props.name}</div>
}
}
const App = HOC(Hello)
ReactDOM.render(
<App />,
document.getElementById('container')
);
<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="container">
<!-- This element's contents will be replaced with your component. -->
</div>
However, I will prefer to pass optional props to the HOC, in order to control its behavior, because it's much safer, rather than relying on WrappedComponent.name.
For example: there are many libraries (as redux, react-router, and etc) which provide some functionality to your components through HOC mechanism. When this libraries wraps your component, then WrappedComponent.name will point to the library HOC name and will break your logic silently.
Here's how you can pass custom props:
const HOC = (WrappedComponent, props) => class Wrapper extends React.Component{
render() {
const { shouldPassName } = props
if (shouldPassName) {
return <WrappedComponent name='World' />
}
return <WrappedComponent/>
}
}
const App = HOC(Hello, { shouldPassName: true })
I am having a child component a parent component. I am having a function in child component which returns some jsx what i want to do is use that function to return the same jsx in parent component but iam unable to figure out a way to do that. I am giving my minimal code:
parent component:
class App extends Component {
render() {
return (
<div className="App">
<Player ref={instance=>{this.player = instance}} />
{this.player.func('aaa.com','bbb')}
</div>
);
}
}
export default App;
Child component:
import React, { Component } from "react";
class Player extends Component {
func = (url, label) => {
return (
<button onClick={() => this.func(url)}>
{label}
</button>
)
}
render() {
return <div>1</div>;
}
}
export default Player;
Error: Cannot read property 'func' of undefined
//
Note: i know i can use the jsx in parent component by copy-pasting but iam trying to figure out a way of doing like this. I am having doubt that is it even possible
You can create a Player object and access the function using that object.
new Player().func('aaa.com','bbb')
I don't quite understand what you need exactly but I think that you're looking to pass some jsx element from the Child component to the parent component. What we can do is declare a propType callback on the child component and then implement it on the parent component like so.
import React from 'react';
class Hello extends React.Component {
constructor() {
super();
this.state = {
// this state will keep the element returned by the parent
returnElements: null
}
this.onReturn = this.onReturn.bind(this);
}
// this method will be fired when the Child component returns callback for onSomethingReturned
onReturn(element) {
this.setState({
returnElements: element
})
}
render () {
return (
<div>
<h1>Hello, React!</h1>
<Child onSomethingReturned={this.onReturn} />
{/* I am going to display the state here */}
{this.state.returnElements}
</div>
)
}
}
class Child extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
const element = <h3>this is child element</h3>;
// will call the propType callback function with a element I want to return
this.props.onSomethingReturned(element);
}
render() {
return (null);
}
}
export default Hello;
I have a dumb component, List, that has some methods defined like this:
class List extends React.Component {
...
scrollTo() {
...
}
clear() {
...
}
}
I then use it in a Parent Component, let's say UsersList:
class UsersList extends React.Component {
render() {
return <List {...this.props} {...} />;
}
}
Then I have as a Parent I have FriendsPage:
class FriendsPage extends React.Component {
render() {
return (
...
<UsersList ref={(ref) => { this.usersListRef = ref; }} {...} />
);
}
}
I'd like to be able to call this.usersListRef.scrollTo() for example in FriendsPage, without having to define the methods of List in UsersList.
I can pass a prop called listRef and use it as ref={this.props.listRef} but I was wondering if another solution exists.
You can't call functions of a child and that would also be against the idea of react. Ideally your <UserList> component accepts a prop that makes it know where to scroll to. Something like:
class UserList extends React.Component {
componentDidUpdate() {
const {activeItem} = this.props;
this.scrollTo(activeItem);
}
scrollTo = activeItem => {
// calculate position of active item to scroll to
// and scroll to it
}
}
And then your <FriendsPage> could look something like this:
class FriendsPage extends React.Component {
handleSelectionChange = selected => {
// triggered when the selected element in the list changes
this.setState({selected});
}
render() {
const {selected} = this.state;
return <UserList activeItem={selected} {...this.props} />;
}
}
It's hard to tell if this is 100% the approach you need as you did not provide many details about the conditions that lead to scrolling.
Mmmm, I'm not sure if I'm getting It right, but you should read this: https://reactjs.org/docs/thinking-in-react.html
In React, the idea is to go top-down. As you need the UsersList component to do something when user interacts with List component, then you should define the function in UsersList and pass that function as a prop to the List Component.
For example:
class List extends React.Component {
<div onClick={this.props.scrollTo}
}
I then use it in a Parent Component, let's say UsersList:
class UsersList extends React.Component {
scrollTo(){
do something...
}
render() {
return <List scrollTo={() => this.scrollTo()} {...this.props} {...} />;
}
}
Then I have as a Parent I have FriendsPage:
class FriendsPage extends React.Component {
render() {
return (
...
<UsersList {...} />
);
}
}
I forgot to check the documentation on this one, and there is a paragraph about it here: https://reactjs.org/docs/refs-and-the-dom.html#exposing-dom-refs-to-parent-components.
Basically, it is the solution I envisaged in my question, using a listRef and passing it down to wherever my List Component is.
Thanks, everyone!
I've got a parent component with react-router, setup like this :
constructor(props){
super(props);
this.state = {
diner: false
};
this.updateFromInvite = this.updateFromInvite.bind(this);
}
updateFromInvite(Souper) {
this.setState({diner: Souper});
}
I can't figure out how to setup the route to have both URL parameters and be able to pass a function to update the parent's state from the children component...
<Route path="/Invitation/:NomParam1?/:NomParam2?"
component = {() => (<Invitation updateApp = {this.updateFromInvite} />)} />
I think it's the closest I got...
From children's component :
class Invite extends Component {
constructor(props){
super(props);
this.state = {
diner: this.props.match.params.NomParam1 ,
JSONInfo: this.props.match.params.NomParam2
};
}
componentDidMount() {
const { diner } = this.state;
const { JSONInfo } = this.state;
const { updateApp } = this.props;
updateApp(diner);
}
render() {
return (
<div className="Invite">
<div className="col-centered">
<VidPlay/>
</div>
</div>
);
}
}
export default Invite;
The component property of the route takes a component Class, not an instance of the component. I believe you are looking to use the render property, which takes a rendered component. Your visual component shouldn't be concerned with the routing details, so you can pass that in in the Route configuration like so:
<Route path="/Invitation/:NomParam1?/:NomParam2?"
render={({match}) => (
<Invitation
updateApp={this.updateFromInvite}
diner={match.params.NomParam1}
JSONInfo={match.params.NomParam2}
/>
)}
/>
Then, in the component, don't utilize state, as that's not really what it is for:
class Invite extends Component {
componentDidMount() {
const { diner, JSONInfo, updateApp } = this.props;
// Not exactly sure what is going on here... how you
// will use JSONInfo, etc
updateApp(diner);
}
render() {
return (
<div className="Invite">
<div className="col-centered">
<VidPlay/>
</div>
</div>
);
}
}
Also, I'm not exactly sure what the parent component is doing, and why it is passing both the route params and the function down to the child, only to have the child call it back... but that is probably out of the scope of the question.
Enjoy!
If finally got it (thanks to that answer and the official documentation):
I needed to add props as parameter of my render and
use it with {...props} inside the children element!
<Route path="/Invitation/:NomParam1?/:NomParam2?"
render={ (props) =>
(<Invitation updateApp = {this.updateFromInvite} {...props} />)
}
/>
With that, I have access to BOTH :
my custom props
generic props (match, location and history)
I'm a real beginner in javascript / React...but I'm trying to set-up a tag based on a string value. Why does widget1 fail to get instantiated? (I get an uncaught ReferenceError: FooA is not defined error) What difference does importing the react component make, versus defining it in the same file?
import React, {Component} from "react";
import ReactDOM from "react-dom";
// Assume FooA and FooB have identical definitions
import FooA from './fooa.jsx'
class FooB extends Component {
render() {
return(<p>Hello A</p>);
}
};
class Splash extends Component {
render() {
var widget1 = eval('new ' + "FooA")
var widget2 = eval('new ' + "FooB")
return (
<div>
{(widget1.render())}
{(widget2.render())}
</div>
)
};
}
ReactDOM.render(<Splash/>, container);
I am passing this through webpack to get a single .js file.
Is there a better way to achieve this?
You dont have to instantiate Components, React does that for you.
Your Splash component should look like this:
class Splash extends Component {
render() {
return (
<div>
<FooA />
<FooB />
</div>
)
};
}
Now lets supouse you want to have some logic to determine which component must be rendered:
class Splash extends Component {
let comp = (<FooA />);
if(some condition)
comp = (<FooB />);
render() {
return (
<div>
{comp}
</div>
)
};
}
Now let supouse you want just to parametrize the text:
class FooA extends Component {
render() {
return(<p>this.props.textToShow</p>);
}
};
class Splash extends Component {
let text = 'whatever text you want to show';
render() {
return (
<div>
<FooA textToShow={text}/>
</div>
)
};
}
You can pass as a prop other components as well:
class FooA extends Component {
render() {
return(
<p>Some text</p>
{this.props.child}
);
}
};
class FooAChild extends Component {
render() {
return(
<p>I am a child</p>
);
}
};
class Splash extends Component {
let child = (<FooAChild />);
render() {
return (
<div>
<FooA child={child}/>
</div>
)
};
}
You're coming at this problem from the wrong angle. If you want to make a component able to render other components in a generic, reusable way, there's three approaches you can take:
Pass the component class as a prop
class Splash extends Component {
render() {
let heading = this.props.heading;
// These have to start with a capital letter, otherwise
// JSX assumes 'widget1' is a normal HTML element.
let Widget1 = this.props.widget1;
let Widget2 = this.props.widget2;
return (
<div>
<h1>{heading}</h1>
<Widget1 />
<Widget2 />
</div>
)
};
}
// This can then be used like so:
<Splash heading="My Generic Splash" widget1={FooA} widget2={FooB} />
Pass the component instance as a prop:
class Splash extends Component {
render() {
let heading = this.props.heading;
let widget1 = this.props.widget1;
let widget2 = this.props.widget2;
return (
<div>
<h1>{heading}</h1>
{widget1}
{widget2}
</div>
)
};
}
// This can then be used like so:
let fooA = <FooA />;
<Splash heading="My Generic Splash" widget1={fooA} widget2={<FooB />} />
Pass the components as children:
class Splash extends Component {
render() {
let heading = this.props.heading;
return (
<div>
<h1>{heading}</h1>
{this.props.children}
</div>
)
};
}
// This can then be used like so:
<Splash heading="My Generic Splash">
<FooA />
<FooB />
</Splash>