Use ref in Higher Order Components - javascript

I have a Table component that I want ref to be attached to.
Use: Table.js
class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: 1,
dataLength: props.dataLength,
}
this.tableRef = React.createRef();
}
componentDidUpdate() {
//using ref
this.tableRef.current ..... //logic using ref
this.state.rows ..... //some logic
}
render() {
<TableContainer ref={this.tableRef} />
<CustomPagination />
}
}
This works fine, but now my requirement has changed, and I want to reuse the Table component with pagination applied to all the Tables in my App. I have decided to make a HOC withCustomPagination.
Use: withCustomPagination.js HOC
import CustomPagination from 'path/to/file';
const withCustomPagination = tableRef => Component => {
return class WithCustomPagination extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: 1,
dataLength: props.dataLength,
}
}
componentDidUpdate() {
tableRef.current.state ..... //logic using ref, Error for this line
this.state.rows ..... //some logic
}
render() {
return (
<Component {...state} />
<CustomPagination />
)
}
}
}
export default withCustomPagination;
New Table.js:
import withCustomPagination from '/path/to/file';
const ref = React.createRef();
const Table = props => (
<TableContainer ref={ref} />
);
const WrappedTable = withCustomPagination(ref)(Table);
HOC withCustomPagination returns a class WithCustomPagination that has a componentDidUpdate lifecycle method that uses Table ref in the logic. So I try to pass ref created in Table.js as argument to withCustomPagination, i.e curried with ref and Table stateless component.
This use of ref is wrong and I get error: TypeError: Cannot read property 'state' of null.
I tried using Forwarding Refs, but was unable to implement it.
How do I pass the Table ref to withCustomPagination and be able to use it in HOC?

In this case you can use useImperativeHandle
It means you have to forward ref and specify which function or object or,...
you want to share with ref inside your functional component.
Here is my Hoc example :
import React from 'react';
import { View } from 'react-native';
export function CommonHoc(WrappedComponent) {
const component = class extends React.Component {
componentDidMount() {
this.refs.myComponent.showAlert();
}
render() {
return (
<>
<WrappedComponent
ref='myComponent'
{...this.state}
{...this.props}
/>
</>
);
}
};
return component;
}
and it's my stateless component
const HomeController=(props,ref)=> {
useImperativeHandle(ref, () => ({
showAlert() {
alert("called");
},
}));
return (
<Text>home</Text>
);
};
export default CommonHoc(forwardRef(HomeController));

Either restructure your code to not use a HOC for this or try using React.forwardRef:
Refs Aren’t Passed Through
While the convention for higher-order components is to pass through
all props to the wrapped component, this does not work for refs.
That’s because ref is not really a prop — like key, it’s handled
specially by React. If you add a ref to an element whose component is
the result of a HOC, the ref refers to an instance of the outermost
container component, not the wrapped component.
The solution for this problem is to use the React.forwardRef API
(introduced with React 16.3). Learn more about it in the forwarding
refs section.
via Higher-Order Components: Refs Aren’t Passed Through
In the forwarding refs section there are code examples you could use to pass refs down, but trying to yank them up will fail in your case with:
Warning: Stateless function components cannot be given refs. Attempts to access this ref will fail.
In a project we took a different approach. There's an EnhancedTable component that handles all of the pagination logic and in itself has the dumb table component and the pagination component. It works pretty well but this means you would have to drill props (or use a store lib like Redux or Mobx) and add new ones that will handle pagination options. This will result in some refactoring of Table uses and you'll have to be more explicit but I would take it as a boon rather than a hindrance.

I was able to solve a simmilar issue that brought me to this thread without using forwardRef or useImperativeHandle.
By creating the ref at a higher level, and passign it down into the component and sub components that I needed to act on with the ref.
/** Parent Component has access to ref and functions that act on ref **/
import { useRef } from 'react';
const formRef = useRef(); // ref will have dom elements need accessing
const onClickFunction=()=>{ //sample function acts on ref
var inputs = formRef.current.querySelectorAll('input')
/* Act on ref here via onClick function, etc has access to dom elements
in child component and childs child components */
};
return(
<ComponentGetsAttachedRef formRef={formRef} />
//^ref sent down to component and its children
<ComponentNeedingRef onClickFunction={onClickFunction}/>
//^function with access to ref sent down to component
)
/** Child component needs to act on ref**/
export const ComponentNeedingRef = ({ onClickFunction}) =>{
return(
<button onClick={onClickFunction}>
)
}
/* Child component recieves ref and passes it down */
export const ComponentGetsAttachedRef = ({ formRef}) =>{
//ref comes in as prop gets attached to props or utilized internally
return (
<ChildsChildComponent formRef={formRef}/> //sub component passed ref down
)
}

Related

How to pass data in two way data binding in class base component in ReactJS?

My problem that is in a child component, I have a state that updated via post method and I must show this state in my parent component both these component are the class base component
The ReactJS is a library with One directional data binding. So that is not possible to pass data like Angular or VueJS. you should pass a handler function to the child-component and then after the Axios answer update the local and also the parent component.
And there is a little hint here, there is no different for your situation between class components and functional components. pay attention to the sample code:
class ParentComponent extends React.Component {
state = {
data: undefined,
};
handleGetData = data => {
this.setState({
data,
});
};
render() {
return (
<ChildComponent onGetData={this.handleGetData} />
);
}
}
And now inside the ChildComponent you can access to the handler function:
class ChildComponent extends React.Component {
componentDidMound() {
const { onGetData } = this.props;
Axios
.get('someSampleUrl')
.then( (data) => {
onGetData(data); // running this update your parent state
});
}
render() {
return (
<div />
);
}
}
React's practice is one-directional. For best practice, you must 'Raise' the states into the parent component.
But if you were looking specifically to pass data up, you need to pass a callback down as a prop. use that callback function to pass the data up:
Parent:
<Parent>
<Child callback={console.log} />
</Parent>
Child:
const Child = (props) => {
const { callback } = props;
const postData = (...) => {
...
callback(result);
}
...
}

How to consider defining a functional component inside a class component and outside the class in React?

Considering the following three places of defining a functional component in React -
Inside a class (outside the render method)
Inside a class (inside the render method)
Outside the class
In the sample code below, funcComponent1, funcComponent2 and funcComponent3 are defined in the three different locations. How do I consider when to define a functional component in any of these 3 places?
import React, { Component } from 'react';
const FuncComponent1 = (props) => {
return (
<p>{props.name}</p>
)
}
class TestComponent extends Component {
constructor(props){
super(props);
this.state = {
name: "JavaScript"
}
}
FuncComponent2 = (text) => {
return (
<p>{text}, {this.state.name}</p>
)
}
render(){
const FuncComponent3 = (props) => {
return (
<p>{props.text}, {this.state.name}</p>
)
}
return (
<div>
<FuncComponent1 name={'Abrar'} text={'Hello World'}/>
<FuncComponent3 text={"HEllo World"}/>
</div>
)
}
}
export default TestComponent;
You must avoid using functional component inside of render since they will be recreated on every render.
As far as using functions that return JSX inside Class component but outside render` is considered, you can do that when you want to make use of the state or props of the class in order to render JSX content but that which is very specific to the particular class
A functional component outside of React component is most advantageous when the same component can be used at multiple places and hence it makes sense to pass props to it and render it.

What is memo in react?

I am learning latest react features. As per docks memo works like shouldComponentUpdate or PureComponent in functional component but how do I use this memo concept in functional component.
Say I have below component using class
import React, { Component } from 'react';
class Test extends Component {
shouldComponentUpdate(nextProps, nextState) {
return this.props.text != nextProps.text;
}
render(){
const { text } = this.props;
return(
<div>
<h1>{text}</h1>
</div>
)
}
}
Functional component
function Test = props => {
const { text } = props;
return(
<div>
<h1>{text}</h1>
</div>
)
}
How can I write class component using memo in functional component
Memo works as a higher order component, and you can simply just wrap your functional component export with it. Every time your application updates, memo will automatically perform a shallow comparison of props to determine if they've changed, and if the component needs to re-render.
export default React.memo(Test);
React.memo() is a HOC that takes a functional component and returns a component that behaves the same as a PureComponent.
const MyComponent = React.memo(function MyComponent(props) {
/* only rerenders if props change */
});
Update:
React.memo also accepts a compare function as second argument. By using this function, we can compare props not in a shallow way but whichever way we want to. This gives more control over preventing update of component.
Use this function when your props contain complex Objects and you want to compare fields of these Objects while determining if your component should update.
E.g.
const MyComponent = React.memo(function MyComponent(props) {
/* only rerenders if props change */
}, (props1, props2) => {
prop1.my_property_to_check === prop2.my_property_to_check
});

Understanding React Higher-Order Components

Can someone please explain Higher-order components in React. I have read and re-read the documentation but cannot seem to get a better understanding. According to the documentation, HOCs help remove duplication by creating a primary function that returns a react component, by passing arguments to that function.
I have a few questions on that.
If HOCs create a new enhanced component, can it be possible not to pass in any component as argument at all?
In an example such as this, which is the higher order component, the Button or the EnhancedButton.
I tried creating one HOC like this:
// createSetup.js
import React from 'react';
export default function createSetup(options) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.testFunction = this.testFunction.bind(this);
}
testFunction() {
console.log("This is a test function");
}
render() {
return <p>{options.name}</p>
}
}
}
// main.js
import React from 'react';
import {render} from 'react-dom';
import createSetup from './createSetup';
render((<div>{() => createSetup({name: 'name'})}</div>),
document.getElementById('root'););
Running this does not show the HOC, only the div
Can anyone help out with a better example than the ones given?
A HOC is a function that takes a Component as one of its parameters and enhances that component in some way.
If HOCs create a new enhanced component, can it be possible not to pass in any component as argument at all?
Nope, then it wouldn't be a HOC, because one of the conditions is that they take a component as one of the arguments and they return a new Component that has some added functionality.
In an example such as this, which is the higher order component, the Button or the EnhancedButton.
EnhanceButton is the HOC and FinalButton is the enhanced component.
I tried creating one HOC like this: ... Running this does not show the HOC, only the div
That's because your createSetup function is not a HOC... It's a function that returns a component, yes, but it does not take a component as an argument in order to enhance it.
Let's see an example of a basic HOC:
const renderWhen = (condition, Component) =>
props => condition(props)
? <Component {...props} />
: null
);
And you could use it like this:
const EnhancedLink = renderWhen(({invisible}) => !invisible, 'a');
Now your EnhancedLink will be like a a component but if you pass the property invisible set to true it won't render... So we have enhanced the default behaviour of the a component and you could do that with any other component.
In many cases HOC functions are curried and the Component arg goes last... Like this:
const renderWhen = condition => Component =>
props => condition(props)
? <Component {...props} />
: null
);
Like the connect function of react-redux... That makes composition easier. Have a look at recompose.
In short, If you assume functions are analogues to Components, Closure is analogous to HOC.
Try your createSetup.js with:
const createSetup = options => <p>{options.name}</p>;
and your main.js
const comp = createSetup({ name: 'name' });
render((<div>{comp}</div>),
document.getElementById('root'));
A higher-order component (HOC) is an advanced technique in React for reusing component logic. Concretely, a higher-order component is a function that takes a component and returns a new component.
A HOC is a pure function with zero side-effects.
Example: CONDITIONALLY RENDER COMPONENTS
Suppose we have a component that needs to be rendered only when a user is authenticated — it is a protected component. We can create a HOC named WithAuth() to wrap that protected component, and then do a check in the HOC that will render only that particular component if the user has been authenticated.
A basic withAuth() HOC, according to the example above, can be written as follows:
// withAuth.js
import React from "react";
export function withAuth(Component) {
return class AuthenticatedComponent extends React.Component {
isAuthenticated() {
return this.props.isAuthenticated;
}
/**
* Render
*/
render() {
const loginErrorMessage = (
<div>
Please login in order to view this part of the application.
</div>
);
return (
<div>
{ this.isAuthenticated === true ? <Component {...this.props} /> : loginErrorMessage }
</div>
);
}
};
}
export default withAuth;
The code above is a HOC named withAuth. It basically takes a component and returns a new component, named AuthenticatedComponent, that checks whether the user is authenticated. If the user is not authenticated, it returns the loginErrorMessage component; if the user is authenticated, it returns the wrapped component.
Note: this.props.isAuthenticated has to be set from your application’s
logic. (Or else use react-redux to retrieve it from the global state.)
To make use of our HOC in a protected component, we’d use it like so:
// MyProtectedComponent.js
import React from "react";
import {withAuth} from "./withAuth.js";
export class MyProectedComponent extends React.Component {
/**
* Render
*/
render() {
return (
<div>
This is only viewable by authenticated users.
</div>
);
}
}
// Now wrap MyPrivateComponent with the requireAuthentication function
export default withAuth(MyPrivateComponent);
Here, we create a component that is viewable only by users who are authenticated. We wrap that component in our withAuth HOC to protect the component from users who are not authenticated.
Source
// HIGHER ORDER COMPOENTS IN REACT
// Higher order components are JavaScript functions used for adding
// additional functionalities to the existing component.
// file 1: hoc.js (will write our higher order component logic) -- code start -->
const messageCheckHOC = (OriginalComponent) => {
// OriginalComponent is component passed to HOC
const NewComponent = (props) => {
// business logic of HOC
if (!props.isAllowedToView) {
return <b> Not Allowed To View The MSG </b>;
}
// here we can pass the props to component
return <OriginalComponent {...props} />;
};
// returning new Component with updated Props and UI
return NewComponent;
};
export default messageCheckHOC;
// file 1: hoc.js -- code end -->
// file 2: message.js -- code start -->
// this is the basic component we are wrapping with HOC
// to check the permission isAllowedToView msg if not display fallback UI
import messageCheckHOC from "./hoc";
const MSG = ({ name, msg }) => {
return (
<h3>
{name} - {msg}
</h3>
);
};
export default messageCheckHOC(MSG);
// file 2: message.js -- code end -->
// file 3 : App.js -- code start --->
import MSG from "./message.js";
export default function App() {
return (
<div className="App">
<h3>HOC COMPONENTS </h3>
<MSG name="Mac" msg="Heyy !!! " isAllowedToView={true} />
<MSG name="Robin" msg="Hello ! " isAllowedToView={true} />
<MSG name="Eyann" msg="How are you" isAllowedToView={false} />
</div>
);
}
// file 3 : App.js -- code end --->

React: componentDidMount + setState not re-rendering the component

I'm fairly new to react and struggle to update a custom component using componentDidMount and setState, which seems to be the recommended way of doing it. Below an example (includes an axios API call to get the data):
import React from 'react';
import {MyComponent} from 'my_component';
import axios from 'axios';
export default class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
GetData() {
return axios.get('http://localhost:5000/<route>');
}
componentDidMount() {
this.GetData().then(
(resp) => {
this.setState(
{data: resp.data}
)
}
)
}
render() {
return (
<MyComponent data={this.state.data} />
);
}
}
Doing console.log(this.state.data) just below render() shows that this.state.data does indeed get updated (from [] to whatever the API returns). However, the problem appears to be that MyComponent isn't rendered afresh by componentDidMount. From the Facebook react docs:
Setting state in this method will trigger a re-rendering.
This does not seem to be the case here: The constructor of MyComponent only gets called once (where this.props.data = []) and the component does not get rendered again. I'd be great if someone could explain why this is and whether there's a solution or a different way altogether to get the updating done.
UPDATE
I've added the code for MyComponent (minus some irrelevant features, as indicated by ...). console.log(data_array) prints an empty array.
import React from 'react';
class DataWrapper {
constructor(data) {
this._data = data;
}
getSize() {
return this._data.length;
}
...
}
export class MyComponent extends React.Component {
constructor(props) {
super(props);
this._dataWrapper = new DataWrapper(this.props.data);
this.state = {
data_array: this._dataWrapper,
};
}
render() {
var {data_array} = this.state;
console.log(data_array);
return (
...
);
}
}
You are falling victim to this antipattern.
In MyComponent constructor, which only gets called the first time it mounts, passed your empty array through new DataWrapper and now you have some local state which will never be updated no matter what your parent does.
It's always better to have one source of truth, just one state object anywhere (especially for things like ajax responses), and pass those around via props. In fact this way, you can even write MyComponent as a simple function, instead of a class.
class Example extends Component {
state = { data: [] }
GetData() { .. }
componentDidMount() {
this.GetData().then(res =>
this.setState({data: new DataWrapper(res.data)})
)
}
render() { return <MyComponent data={this.state.data} /> }
}
...
function MyComponent (props) {
// props.data will update when your parent calls setState
// you can also call DataWrapper here if you need MyComponent specific wrapper
return (
<div>..</div>
)
}
In other words what azium is saying, is that you need to turn your receiving component into a controlled one. Meaning, it shouldn't have state at all. Use the props directly.
Yes, even turn it into a functional component. This helps you maintain in your mind that functional components generally don't have state (it's possible to put state in them but ... seperation of concerns).
If you need to edit state from that controlled component, provide the functions through props and define the functions in the "master" component. So the master component simply lends control to the children. They want anything they talk to the parent.
I'm not posting code here since the ammendment you need to make is negligible. Where you have this.state in the controlled component, change to this.props.

Categories

Resources