Create react HOC with props children - reactjs

I understand the react higher order component example from the official docs but I want to use it slightly differently if possible, with props.children - ie
<PageHoc> // Higher order component
<Route exact path="/" component={Invite} /> // I want to auto inject props here
</PageHoc>
In my page HOC I can auto render out the child component but how can I attach some new props here?
import React from 'react';
class PageHoc extends React.Component {
constructor(props) {
super(props);
}
render() {
return this.props.children
}
}
export default PageHoc;

Your PageHoc component technically is just a parent component, not a HOC as it isn't wrapping and returning a new component. But you can still inject props into children component via react's Children helper and cloneElement.
import React, { Children, Component, createElement } from 'react';
class PageParent extends Component {
constructor(props) {
super(props);
}
render() {
return Children.map(
this.props.children,
child => cloneElement(child, { injectedProp: injectedPropValue })
);
}
}
export default PageParent;
As HOC
const withInjectedProps = WrappedComponent => {
const injectedProps = {
prop1: value1,
prop2: value2,
<...etc...>
};
return <WrappedComponent {...this.props} {...injectedProps} />
}
export default withInjectedProps;
const InjectedRoute = withInjectedProps(Route);
<InjectedRoute exact path="/" component={Invite} /> // has props injected
<InjectedRoute exact path="/a" component={OtherComponent} /> // has props injected too!
Kind of depends how you need to inject the props, if you have have just a single component, many, etc..
I forgot to mention that react HOCs by convention are named starting with "with", but this isn't the rule, i.e. react-redux's connect.
react HOC docs

One way is to clone the children and override the props like this,
import React from 'react';
class PageHoc extends React.Component {
constructor(props) {
super(props);
}
doSomething = () => {
//your logic
}
render() {
const childrenWithProps = React.Children.map(this.props.children, child =>
React.cloneElement(child, { doSomething: this.doSomething })
);
return <div>{childrenWithProps}</div>
}
}
export default PageHoc;

Related

Call child function in parent with i18next react

I used React.createRef() to call child method, like that
import Child from 'child';
class Parent extends Component {
constructor(props) {
super(props);
this.child = React.createRef();
}
onClick = () => {
this.child.current.getAlert();
};
render() {
return (
<div>
<Child ref={this.child} />
<button onClick={this.onClick}>Click</button>
</div>
);
}
}
Child class like that
export default class Child extends Component {
getAlert() {
alert('getAlert from Child');
}
render() {
return <h1>Hello</h1>;
}
}
It works well. But when I want to use i18next to translate child component, I have to add withTranslation() to use HOC.
import { useTranslation } from 'react-i18next';
class Child extends Component {
getAlert() {
alert('getAlert from Child');
}
render() {
const { t } = this.props;
return <h1>{t('Hello')}</h1>;
}
}
export default withTranslation()(Child);
Then return error: Function components cannot be given refs.
Means cannot use ref in <Child /> tag. Is there any way to call child function after add i18next?
This is a problem since the withTranslation HOC is using a function component. By wrapping your Child component with a HOC you essentially are placing the ref on the withTranslation component (by default).
There are multiple ways to fix this problem, here are the two easiest:
Using withRef: true >= v10.6.0
React-i18n has a built in option to forward the ref to your own component. You can enable this by using the withRef: true option in the HOC definition:
export default withTranslation({ withRef: true })(Child);
Proxy the ref using a named prop
Instead of using <Child ref={this.child} />, choose a different prop to "forward" the ref to the correct component. One problem though, you want the ref to hold the component instance, so you will need to assign the ref manually in the lifecycle methods.
import Child from 'child';
class Parent extends Component {
constructor(props) {
super(props);
this.child = React.createRef();
}
onClick = () => {
this.child.current.getAlert();
};
render() {
return (
<div>
<Child innerRef={this.child} />
<button onClick={this.onClick}>Click</button>
</div>
);
}
}
import { useTranslation } from 'react-i18next';
class Child extends Component {
componentDidMount() {
this.props.innerRef.current = this;
}
componentWillUnmount() {
this.props.innerRef.current = null;
}
getAlert() {
alert('getAlert from Child');
}
render() {
const { t } = this.props;
return <h1>{t('Hello')}</h1>;
}
}
export default withTranslation()(Child);

Update parent component State from child component in react js

I am setting a state into child component on event perform and want to sent this to Parent component. I searched for this on SO. But still didn't found any way to do this.
Let say i have a parent component Home, and have child component User. I am performing some event in User component, and at that time, i want to pass data to Home component. How can i do this?
Below is my code:
/* Parent component */
import React, { Component } from 'react';
import User from './user';
class Home extends React.Component{
constructor(props){
super(props)
this.state = {
isReportSent: false
}
}
render(){
<Switch>
<Route exact path="/" component={User}/>
</Switch>
}
}
/* child component */
class User extends React.Component{
constructor(props){
super(props)
}
render(){
}
}
Note: My parent component is Routing component, in which i am routing my child component on particular path. So can't pass any function to child component.
import React, { Component } from "react";
class Home extends Component {
constructor(props) {
super(props);
this.state = {};
}
onChildAPICall = result => {
console.log(result);
};
render() {
return <User onAPICall={this.onChildAPICall} />;
}
}
class User extends Component {
constructor(props) {
super(props);
this.state = {};
this.API = "https://apicall";
}
makeAnAPICall = async () => {
let result = await fetch(this.API);
this.props.onAPICall(result);
};
render() {
return <button onClick={this.makeAnAPICall}>API Call</button>;
}
}
export default Home;
Something like this would work. I'm not sure if the below is 100% functioning as I just wrote it quickly but the idea is to pass down setState() as a prop from parent to child. So when child calls setState from props it's setting state in the parent component.
class Home extends React.Component {
constructor (props) {
super(props)
this.state = {
data: []
}
}
render () {
<ChilComponent setState={this.setState} />
}
}
const User = async ({ setState }) => {
const receivedData = await getDataHowever(params)
setState({
data: receivedData
})
return (
<p>Got data!</p>
)
}
You can call callback function of parent from child component and in parent you can set the state based on callback response.
import React, { Component } from "react";
class Home extends Component {
constructor(props) {
super(props);
this.state = { }
}
setStateOfParent= result => {
this.setState({result : result});
}
render() {
return <User setStateOfParent={this.setStateOfParent} />;
}
}
class User extends Component {
constructor(props) {
super(props);
this.state = {};
this.API = "https://apicall";
}
makeAnAPICall = async () => {
let result = await fetch(this.API);
this.props.setStateOfParent(result);
};
render() {
return <button onClick={this.makeAnAPICall}>API Call</button>;
}
}
export default Home;
Your explanation is not all clear what you want to acheive but as a simple pattern you can pass the callback prop to the child component using render method of react router
Parent Component
<Route exact path="/" render={(props) => <User {...props} callback={this.callback} />}/>
Child Class
this.props.callback(data)
#user10742206 The best way is to create an independent component and include it as a child in any parent component. Then you can pass a callback function from parent and child can use it to send back any data to parent.

Extend React lifecycle hook (e.g add a print statement on every ComponentDidMount)

I want to add some behaviour on a given lifecycle hook of a React application.
For example, adding a console.log('Component is mounted') on every ComponentDidMount of all the components of an application, without having to define it in every one of them (as a decorator for example), sort of like a global extender of that method that adds some code to it. Like that: Extending Vue Lifecycle Hooks but for React.
Anyone has an idea on how to achieve that? Cheers!
You can use hoc. In the root app, apply the higher order component.
Example:
const withMountHOC = WrappedComponent => {
return class extends Component {
componentDidMount() {
console.log('mounted');
}
render() {
return <WrappedComponent {...this.props} />
}
}
}
export default withMountHOC;
In your app component:
const WrappedApp = withMountHOC(App);
ReactDOM.render(
WrappedApp,
document.getElementById('root')
);
Since the parent componentDidMount hook is called after child componentDidMount hook, the HOC componentDidMount will be applied in any nested level of the component.
You may also be interested to see this blog: Replacing Mixins in React.
create CustomComponent.js
import React, { Component } from 'react'
class CustomComponent extends Component {
constructor(props){
super();
}
componentDidMount(){
console.log('component is mounted');
}
render () {
return (
<div>
{this.props.children}
</div>
)
}
}
export default CustomComponent
Now create MyComponent.js that extends CustomComponent.js
import React, { Component } from 'react'
import CustomComponent from './CustomComponent'
class MyComponent extends CustomComponent {
render () {
return (
<div>
Hello from MyComponent
</div>
)
}
}
export default MyComponent;
now you see console , you have log : "component is mounted"
but if you write componentDidMonunt() inside MyComponent.js , you will get log from MyComponent.js

ShouldComponentUpdate is never called in child components

I've two components. One is parent (smart component that is connected to redux) and another is child component that is rendered in an iteration of array.
Whenever some redux action is dispatched from child component, the state in store is changed and whole list of elements is re-rendered but I want only to render the child that's actual state has been changed. Like in an array of locations, I want to show loader on a particular location and the object in array is updated well but why the shouldComponentUpdate is not available in child so that I can decide whether it should render or not.
Parent Component
import React from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
class Locations extends React.Component {
constructor(props) {
super(props);
}
render() {
return this.props.locations.map((location) =>
<Location
toggleLocationStatusInfo={this.props.toggleLocationStatusInfo}
location={location} />)
}
}
const mapDispatchToProps = (dispatch) => ({
fetchLocations: (data) => dispatch(actions.fetchLocations(data)),
toggleLocationStatusInfo: dispatch(actions.toggleLocationStatusInfo()),
});
const mapStateToProps = createStructuredSelector({
locations: selectors.getLocations(),
});
export default connect(mapStateToProps, mapDispatchToProps)(Locations);
Child Component
import React from 'react';
class Location extends React.Component {
constructor(props) {
super(props);
}
shouldComponentUpdate() {
// THIS METHOD IS NEVER CALLED EVEN THE TOGGLE ACTION IS
// DISPATCHCED AND REDUX STATE IS CHANGED. IT IS CALLED FINE FINE
// PARENT (Locations) COMPONENT BUT NOT HERE
}
render() {
// render this.props.location content here
// One of my anchor calls onClick={this.props.toggleLocationStatusInfo}
}
}
Location.propTypes = {
location: React.PropTypes.object
toggleLocationStatusInfo: React.PropTypes.func,
}
How can I find out why the shouldComponentUpdate is not called in children?
I forgot to add a unique key on each component, so it was just creating new children every time.
After adding key prop on Location, it worked fine.
Same issue occurs if you are using Route from react-router and passing an inline function instead of a component, like so:
ISSUE
<Route
path="/decrement"
component={() => (
<Decrement decrementBy={decrementBy} isLoading={isLoading} />
)}
/>
SOLUTION
<Route
path="/decrement"
render={() => (
<Decrement decrementBy={decrementBy} isLoading={isLoading} />
)}
/>
or
<Route path="/decrement">
<Decrement decrementBy={decrementBy} isLoading={isLoading} />
</Route>

How to include the Match object into a ReactJs component class?

I am trying to use my url as a parameter by passing the Match object into my react component class. However it is not working! What am I doing wrong here?
When I create my component as a JavaScript function it all works fine, but when I try to create my component as a JavaScript class it doesn't work.
Perhaps I am doing something wrong? How do I pass the Match object in to my class component and then use that to set my component's state?
My code:
import React, { Component } from 'react';
import axios from 'axios';
import PropTypes from 'prop-types';
class InstructorProfile extends Component {
constructor(props, {match}) {
super(props, {match});
this.state = {
instructors: [],
instructorID : match.params.instructorID
};
}
componentDidMount(){
axios.get(`/instructors`)
.then(response => {
this.setState({
instructors: response.data
});
})
.catch(error => {
console.log('Error fetching and parsing data', error);
});
}
render(){
return (
<div className="instructor-grid">
<div className="instructor-wrapper">
hi
</div>
</div>
);
}
}
export default InstructorProfile;
React-Router's Route component passes the match object to the component it wraps by default, via props. Try replacing your constructor method with the following:
constructor(props) {
super(props);
this.state = {
instructors: [],
instructorID : props.match.params.instructorID
};
}
Hope this helps.
Your constructor only receives the props object, you have to put match in it...
constructor(props) {
super(props);
let match = props.match;//← here
this.state = {
instructors: [],
instructorID : match.params.instructorID
};
}
you then have to pass that match object via props int a parent component :
// in parent component...
render(){
let match = ...;//however you get your match object upper in the hierarchy
return <InstructorProfile match={match} /*and any other thing you need to pass it*/ />;
}
for me this was not wrapping the component:
export default (withRouter(InstructorProfile))
you need to import withRouter:
import { withRouter } from 'react-router';
and then you can access match params via props:
someFunc = () => {
const { match, someOtherFunc } = this.props;
const { params } = match;
someOtherFunc(params.paramName1, params.paramName2);
};
Using match inside a component class
As stated in the react router documentation. Use this.props.match in a component class. Use ({match}) in a regular function.
Use Case:
import React, {Component} from 'react';
import {Link, Route} from 'react-router-dom';
import DogsComponent from "./DogsComponent";
export default class Pets extends Component{
render(){
return (
<div>
<Link to={this.props.match.url+"/dogs"}>Dogs</Link>
<Route path={this.props.match.path+"/dogs"} component={DogsComponent} />
</div>
)
}
}
or using render
<Route path={this.props.match.path+"/dogs"} render={()=>{
<p>You just clicked dog</p>
}} />
It just worked for me after days of research. Hope this helps.
In a functional component match gets passed in as part of props like so:
export default function MyFunc(props) {
//some code for your component here...
}
In a class component it's already passed in; you just need to refer to it like this:
`export default class YourClass extends Component {
render() {
const {match} = this.props;
console.log(match);
///other component code
}
}`

Resources