Why do we even need forwardRef in React? [duplicate] - javascript

Let's say I have a component with a scrollable subcomponent, and I want to expose the ability to scroll:
const MyComponent = (props) => {
return <ScrollView ... />
}
I want to be able to do
<MyComponent ref={myRef} />
...
myRef.scrollTo({x: 0});
So I need a way to forward the ref to the <ScrollView>. Let's try putting the ref on the props:
const MyComponent = (props) => {
return <ScrollView ref={props.scrollRef} ... />
}
...
<MyComponent scrollRef={myRef} />
...
myRef.scrollTo({x: 0});
I just tried that with React Native on iOS, and it indeed works. I see several advantages over React.forwardRef:
Simpler, because I don't need to use another React API.
Works also if there is more than one child who needs ref forwarding.
Seems to me that this approach is
What's the advantage of React.forwardRef? Why was it added in React 16.3?

Note that there is no difference between using another named prop like innerRef FOR FORWARDING, it works the same.
Refactoring class components
Since React moved toward function components (hooks) you might want to refactor the class component code to a function component without breaking the API.
// Refactor class component API to function component using forwardRef
<Component ref={myRef} />
React.forwardRef will be your only option (further explained in details).
Clean API
As a library author you may want a predictable API for ref forwarding.
For example, if you implemented a Component and someone wants to attach a ref to it, he has two options depending on your API:
<Component innerRef={myRef} />
The developer needs to be aware there is a custom prop for forwarding
To which element the innerRef attached? We can't know, should be mentioned in the API or we console.log(myRef.current)
<Component ref={myRef} />
Default behavior similar to ref prop used on HTML elements, commonly attached to the inner wrapper component.
Notice that React.forwardRef can be used for function component and HOC (for class component see alternative below).
Ref forwarding is not limited to DOM components. You can forward refs to class component instances, too.
For function components, forwardRef sometimes comes with useImperativeHandle combo (in class component you just call the class methods on ref instance: ref.current.myAttr().
// Same usage
<Component ref={myRef} />
const Component = React.forwardRef((props, ref) => {
// you can forward ref <div ref={ref} />
// you can add custom attributes to ref instance with `useImperativeHandle`
// like having ref.myAttribute() in addition to ones attached to other component.
});
Important behavior of ref prop without forwardRef.
For the class component, this code alone will attach the ref to CLASS INSTANCE which is not useful by itself and need another ref for forwarding:
// usage, passing a ref instance myRef to class Component
<Component ref={myRef} />
Full example, check the logs:
// We want to forward ref to inner div
class ClassComponent extends React.Component {
innerRef = React.createRef();
render() {
// Notice that you can't just `this.props.ref.current = node`
// You don't have `ref` prop, it always `undefined`.
return <div ref={this.innerRef}>Hello</div>;
}
}
const Component = () => {
const ref = React.useRef();
useEffect(() => {
// The ref attached to class instance
console.log(ref.current);
// Access inner div through another ref
console.log(ref.current.innerRef);
}, []);
return <ClassComponent ref={ref} />;
};
In function components, it won't even work because functions don't have instances.
By default, you may not use the ref attribute on function components because they don’t have instances. [1]
forwardRef.
Refs and the DOM.
Why we need ref forwarding?

Related

React changing text of child on button hover

I want to change the text of a child function component when I hover over a button of my parent class component. I'm having trouble accessing the prop though in the child component and getting null. Any help is appreciated
parent component:
export default class PathfindingVisualizer extends React.Component {
constructor(props) {
super(props)
this.state = {
AlgoDef: null,
};
}
render() {
return (
<React.Fragment>
<div className="button-bar"> //buttons that change state
<button
onClick={() => this.helperDikjstras()}
onMouseOver={() => this.setState({ AlgoDef: "Dikj"})}
>Dikjstras</button>
<button
onClick={() => this.helperAstar()}
onMouseOver={() => this.setState({ AlgoDef: "Astar"})}
>A*</button>
</div>
<div>
<AlgoExplaination algoName={this.AlgoDef} /> //changes its text based on state of parent
</div>
</React.Fragment>
);
}
}
my child component:
export default function AlgoExplaination(props) {
const [text, setText] = useState("default");
useEffect(() => {
switch (props) {
case "Dikj":
setText("Dikjstra");
break;
case "Astar":
setText("Astar");
break;
default:
setText("useEff");
}
//console.log(`text: ${text}`);
//console.log(props);
// console.log(props.algoName);
});
return (
<div>
<p>{text}</p>
</div>
)
}
both console logging props gives me: {algoName: null}. and props.algoName gives me null
As #Rajesh has mentioned in a comment, you are passing props to your AlgoExplaination (sic) component incorrectly like this:
<AlgoExplaination algoName={this.AlgoDef} />
You intended to pass the AlgoDef property of your state, which is this.state.AlgoDef, so change accordingly to this:
<AlgoExplaination algoName={this.state.AlgoDef} />
Furthermore, when you access the algoName property of your props, you currently attempt to access it as if it were the props object itself incorrectly like this:
switch (props) {
The props object for AlgoExplaination will be an object with an algoName property that looks (partially) like this:
{ algoName: "Dikj" }
So, the value you need is actually stored in props.algoName. Therefore, please change accordingly to this:
switch (props.algoName) {
As #Wyck has addressed most of the points, I'll focus this answer on the last point:
Third, why do you have both class component and functional component with hooks? Please use 1 way
Class vs Functional component
In theory, a class component has lifecycle events and state, where as a functional component is just a function that returns JSX.Element. Such components were called stateless.
Because of this, class component had a bit of overhead over functional component, and as a performant option functional component were preferred.
Hooks
Hooks are a way in react to give functional components access to have its own state and few major lifecycle events. This is achieved using closure (not going in full depth) and craftsmanship. This also makes class components obsolete as everything can be achieved in functional component.
Which one to use?
As a preference, its suggested to use functional component with hooks as they are easy to use and are performant as well, in comparison.
Why to use one?
In general programming practice, as a developer, you should use a single way to do things. Benefit of this is, it helps in fast reading.
If I use a for loop in one section and a Array.forEach in next, as a developer, I will be asked to read and understand the purpose. This adds overhead and reduces readability. Having same approach moves the focus of reader to just the logic.
I recomend you to destructuring the props, to be more clear.
In
export default function AlgoExplaination(props)
Can be something like:
export default function AlgoExplaination({algoName})
So you can use it in your switch statment.
switch (algoName) {
case "Dikj":
setText("Dikjstra");
Right now, you are passing all props but you will have to access as props.algoName in the switch statment.

useContext only works in stateless functional component

I'm trying to get to grips with the new useContext function in React. Works great in stateless functionality components. For example:
import React from 'react';
import LocaleContext from '../LocaleContext';
const Link = ({ text, url }) => {
const locale = useContext(LocaleContext);
return (
<a href={`/${locale}/${url}`}>
{text}
</a>
);
};
export default Link;
I also want to use useContext in stateful components, and even non React functions, but when I do so, I get the following error:
Hooks can only be called inside the body of a function component.
The message seems simple enough to understand, but is this really true? I can only use it in a stateless functional component? If so, it seems kind of pointless to me, because it's super easy to use a simple HOC or the traditional method of:
<Locale Consumer>
{locale => (
...
)}
</LocaleConsumer>
So what gives here? I have the latest version of every package in my project. Not sure if it matters but I'm developing a NextJS site here.
If you really want to use classes (i actually came from Angular and i still prefer use classes) you can workaround easily like that:
class ComponentImpl extends React.Component<any> {
constructor(props?) {
super(props);
}
render() {
return (
<div>
CounterButton: <button onClick={() => {this.props.appContext.setCount(this.props.appContext.count + 5)}}>App Counter + 5</button>
</div>
)
}
}
export function Component() {
let appContext = useContext(AppContext);
return <ComponentImpl appContext={appContext}></ComponentImpl>
};
And you just use it: <Component></Component>
The problem is what the error says. React hooks aren't available in class components. Due to differences between class components and function components, hooks cannot be used with the former.
As the documentation says,
Hooks let you use more of React’s features without classes. Conceptually, React components have always been closer to functions. Hooks embrace functions, but without sacrificing the practical spirit of React. Hooks provide access to imperative escape hatches and don’t require you to learn complex functional or reactive programming techniques.
Hooks are supposed to address common use cases that are specific to class components which couldn't be previously implemented with stateless functional components alone. Functional components aren't stateless since React 16.8 and are allowed to have a state and trigger own updates.
As for useContext hook,
When the provider updates, this Hook will trigger a rerender with the latest context value.
It would be messed up in class component due to difference between functional and class components. Component function is called each time the component is rendered:
const Foo = props => {
const context = useContext(Context);
// use context
}
There's no place in class component that would behave the same way except render function. And if lifecycle-specific tasks go to render function, this means that a class was a wrong choice, and class component needs to be refactored to a function. A counterpart to useContext in class components is contextType, which is currently restricted to single context.
For multiple contexts it's still required to receive them through context Consumer inside render, or as props from higher-order component wrapper:
const contextsHOC = (contexts = {}) => Comp => (
props => {
const contextProps = {};
for (const prop in contexts) {
// eslint-disable-next-line react-hooks/exhaustive-deps
contextProps[prop] = React.useContext(contexts[prop]);
}
return <Comp {...props} {...contextProps}/>;
}
);
#contextsHOC({ bar: BarContext, baz: BazContext });
export default class FooComponent extends Component {
// contexts are mapped to this.props.bar and this.props.baz
...
}
// or
class FooComponent extends Component { ... }
export default contextsHOC({ ... })(FooComponent);
Passing contexts as props allows for additional optimization with PureComponent or shouldComponentUpdate.
useContext is a hook that consumes a context and can only be used in functional components.
If you want to consume context in class components, you will need to look at alternative methods such as Consumer Component, official docs for this here

Direct call of a functional component

Stateless functional component is just a function that receives props and returns React element:
const Foo = props => <Bar />;
This way <Foo {...props} /> (i.e. React.createElement(Foo, props)) in parent component could be omitted in favour of calling Foo directly, Foo(props), so React.createElement tiny overhead could be eliminated, yet this isn't necessary.
Is it considered a bad practice to call functional components directly with props argument, and why? What are possible implications of doing this? Can this affect the performance in negative way?
My specific case is that there's some component that is shallow wrapper over DOM element because this was considered a good idea by a third party:
function ThirdPartyThemedInput({style, ...props}) {
return <input style={{color: 'red', ...style}} {...props} />;
}
Here's a demo that shows this case.
This is widely accepted practice but the problem with it is that it's impossible to get ref of wrapped DOM element from stateless function, so the component uses React.forwardRef:
function withRef(SFC) {
return React.forwardRef((props, ref) => SFC({ref, ...props}));
// this won't work
// React.forwardRef((props, ref) => <SFC ref={ref} {...props } />);
}
const ThemedInput = withRef(ThirdPartyThemedInput);
This way it can be used as:
<ThemedInput ref={inputRef} />
...
inputRef.current.focus();
The obvious downside I'm aware of is that withRef requires a developer to be aware of wrapped component implementation, which isn't a usual requirement for HOCs.
Is it considered a proper approach in a situation like described above?
I don't think there's anything wrong with calling Stateless Functional Component directly. As you said it's even one tiny overhead eliminated. As to the possible implications, it would be bold to say that there are none implications and there will be none implications in the future because this is a really rare way of using SFC's. But everything points to conclusion that there shouldn't be any implications (it's just one function call less).
Anyway, below I'd like to present another way of doing this using findDOMNode instead of refs:
I've created Focus component that is really convenient to use but needs to be initialized first (since we need a way to trigger focus outside props since a component may be rerendered with the same props):
// focus.js
import React from "react";
import { findDOMNode } from "react-dom";
export default function createFocus() {
class Focus extends React.Component {
componentDidMount() {
Focus.now = () => {
findDOMNode(this).focus();
}
}
render() {
return this.props.children;
}
}
return Focus;
}
// index.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import createFocus from './focus';
const Focus = createFocus();
import { ThirdPartyThemedInput } from './third-party-lib';
function App() {
return (
<div>
<button onClick={() => Focus.now()}>Proceed with form</button>
<Focus>
<ThirdPartyThemedInput placeholder="Fill me" />
</Focus>
</div>
);
}
render(<App />, document.getElementById('root'));
live at: https://stackblitz.com/edit/react-bpqicw
Functional components are very useful when you don't need to use any of the lifecycle method or don't need to update the component state. As far as you don't need to them, you're good and yet best to go with stateless component.
This will not hit the performance issue but gain the profit regarding its performance because we're just simply using function to render the component and not caring for its update, mounts, receive props, etc. But still there's no 100% gain using stateless component because react internally use class to render them.
It's about 45% improvement.
This post will also guide which one to choose between statefull component and stateless component.
Further, you can not only receive the props but can also receive the ref:
const stateless = (props, ref) => <ReturnComponent {...props} ref={ref} />
Okay, let me refine my statement. Most of the blogs and even the docs states that stateless component don't have ref. Here are a few Q/A prepared regarding this issue:
Do I need to use statefull component just to use ref?
No. I already mentioned that we must require the class based component if we have to work with component state or hook some lifecycle method.
How can I create ref in stateless component?
const stateless = () => {
// we can't do this.myRef = React.createRef()
// so, let's create an object
const RefObj = {}
// now, create ref in {RefObj}
RefObj.myRef = React.createRef()
return <input type="text" ref={myRef} />
}

React context props drilling, what is it that I don't get?

There is a TL;DR at the bottom.
I am probably doing this wrong or using the context in an bad way. I am new to react so I have no clue if this is how we are meant to do things.
My understanding:
Context can be used to pass down props to deeper nested child components without having to pass them through all levels of nesting. A provider is filled with props, and a consumer will look "up the tree" to find the nearest provider and get it's data.
If this is the case, then I can load a provider with a function, such as an onChange handler in order to avoid having to write the handler on every child component when they all do the same thing. This would allow for a "smart form" which govern's its input's handlers by "passing" handlers given to it. Obviously just writing one handler on multiple components is not an issue, but having like 20-30 form fields and writing 4+ handlers on each of them just creates code clutter. So I tried the following:
HTML structure is like this, for example:
<ControlledForm data={some_data} handlers={some_handlers}>
<LabeledControl name="Type your name" rel="Name" meta={{some_meta_object}}></LabeledControl>
<LabeledControl name="Pet name" rel="PetName" meta={{some_meta_object}}></LabeledControl>
<LabeledControl name="Type of pet" rel="PetType" meta={{some_meta_object}}></LabeledControl>
<LabeledControl name="Family" rel="Family" meta={{some_meta_object}}></LabeledControl>
</ControlledForm>
And this is the ControlledForm class code:
const { Provider } = React.createContext(); //Note this
class ControlledForm extends Component {
state = {};
render() {
return (
<Provider value={{ onChange: this.props.onChange }}>
<form>{this.props.children}</form>
</Provider>
);
}
}
Now whatever child I place within this form would want to have a <Consumer> wrapper around it to consume the changeHandler, or at least this is the plan. However when I wrap my LabeledControl in a consumer, it acts as if it has no data.
<LabeledControl> (reduced code):
const { Consumer } = React.createContext();
class LabeledControl extends Component {
state = {};
render() {
return (
<Consumer>
{r => {
console.log("consumer:", r); //Logs undefined
return (
<div className="labeled-control">
{/*Code here*/}
</div>
);
}}
</Consumer>
);
}
}
If I was to guess at what the issue is, I'd say it is because both the ControlledForm and the LabeledControl create it's own context, which is not shared, look at the code above. But I do not understand how would I share this context and still keep the two classes in separate .js files. I cannot pass a reference down to the children, all I get is the {this.props.children} and no way to tell it "Hey use this provider here". All the examples I find online have the two classes that are a provider and a consumer in a same file, being able to reference the same "context" but this seriously impacts the freedom of what I can put inside a form, or rather doesn't let me have customization in terms of "children".
TLDR
How do I pass down a "Context" from a Provider to a Consumer when they are in two different javascript files? Code is above. I essentially need to pass down a handler to every child and have it (maybe, maybe not, depending on a child) use the handler to tell the parent to update it's data. All of this whilst using {this.props.children} in a parent in order to allow "outter code" to "inject" the parent component with any children desired and have them either use or not use the parent's handler.
Edit:
I searched about a bit and found two possible solutions, which I both tested and both seem to be working (with a limited use case). Both render props and React.CloneElement seem to do the trick when there is one level of nesting as we can directly render and add props to children with them, but when we need to prop drill several levels, all the components in between would have to implement the same passing of props which then turns to spaghetti code. Still searching to try and find the way to pass the context down to the children for consumption in different files.
Please view the code below.
Also: here is a sample project I have built:https://codesandbox.io/s/5z62q8qnox
import React from 'react'
import PropTypes from 'prop-types';
import 'bootstrap/dist/css/bootstrap.min.css';
export default class ControllerForm extends React.Component {
static childContextTypes = {
onChange: PropTypes.func.isRequired
}
getChildContext() {
return {
onChange: this.handleOnChange
}
}
handleOnChange = (e) => {
console.log(e.target.value) //here is the place you have to implement
}
render() {
return (
<div class="container">
{this.props.children}
</div>
)
}
}
import React from 'react'
import PropTypes from 'prop-types';
import 'bootstrap/dist/css/bootstrap.min.css';
export default class LabeledControl extends React.Component {
static contextTypes ={
onChange : PropTypes.func.isRequired
}
render() {
return (
<div>
<div className="form-group">
<input className="form-control" type="text" onChange={this.context.onChange} />
</div>
</div>
)
}
}
function App() {
return (
<div className="App">
<ControllerForm>
<LabeledControl />
<LabeledControl />
</ControllerForm>
</div>
);
}
It appears that Context is not what I should be using for this, instead either render props or React.cloneElement() is the proper solution, despite my best efforts to enforce a context.
Parent's render:
{this.props.children.map((child, index) =>
React.cloneElement(child, { key: index, handler: handler })
)}
Child's render:
return (
<div>
<span onClick={this.props.handler}>{passed.foo}</span>
</div>
);
This way the structure remains clean and handlers get passed down. Only issue is every component that needs to pass them down has to implement this, but it would have been the same with context, since it is not exported to a separate file.

React Render with Hoc

So I am following some tutorial and I am confused regarding how things render when using HOC
So firstly, I guess props flow from parent to child and is one directional?
Here we created two HOC, The Aux and withclass
The Aux doesn't do anything special besides passing props.children
const aux = (props) => props.children;
export default aux;
The withClass HOC function takes two parameter App and className..
const withClass = (WrappedComponent, className) => {
return class extends Component {
render () {
return (
<div className={className}>
<WrappedComponent {...this.props} />
</div>
)
}
}
And our App.js which is passed as an argument looks like this
import React, { PureComponent } from 'react';
import classes from './App.css';
import Persons from '../components/Persons/Persons';
import Cockpit from '../components/Cockpit/Cockpit';
import Aux from '../hoc/Aux';
import withClass from '../hoc/withClass';
class App extends PureComponent {
//something
render () {
if ( this.state.showPersons ) {
persons = <Persons
persons={this.state.persons}
clicked={this.deletePersonHandler}
changed={this.nameChangedHandler} />;
}
return (
<Aux>
<button onClick={() => { this.setState( { showPersons: true } ) }}>Show Persons</button>
<Cockpit
appTitle={this.props.title}
showPersons={this.state.showPersons}
persons={this.state.persons}
clicked={this.togglePersonsHandler} />
{persons}
</Aux>
);
}
}
export default withClass( App, classes.App );
[Question] So based on the above code if someone can please explain what exactly happens, the way things execute and render?
Secondly, We used {...this.props} in our withClass HOC because according to the instructor they are wrapped and hence our other component, even though they receive prop they can't pass them. Can someone explain this with an example? Also {...this.props} creates copy of all the props? and shouldn't it be like <WrappedComponent = {...this.props} /> instead of <WrappedComponent {...this.props} />
First of all, what is a HOC?
A HOC is a Higher-order component. This means it is a function that takes as its first argument a component and then returns a component.
From this definition you can immediately see that:
withClass is an HOC
Aux is not an HOC
Aux is a functional component. Classic React components are created by defining a class that inherits from React.Component. A newer, simpler way of defining components is to create functions that simply accept props as the first parameter and return what should be rendered.
So based on the above code if someone can please explain what exactly happens, the way things execute and render?
Well, let's look at App just as a component. We have withClass and App and you're exporting withClass(App, classes.App). What would it look like if, instead of using an HOC we used a functional component? It'd look like this:
const AppWithClass = props =>
<div className={classes.App}>
<App/>
</div>
In this case, no props are passed to App. So with this use-case, there is no need to pass props through by writing {...props}. And you'd then simply export AppWithClass.
But usually you write HOCs to be reusable. In that case, you don't know if the component that will be passed to your HOC will receive props or not. For that reason, you want the component you create to take any props passed to it and to pass them through to the wrapped component.
Let's say you have a Button component that takes a parameter colour. You'd typically use it like this:
<Button colour="red"/>
But you want to wrap it with a div and add a class to that div. So you use withClass as follows:
const ButtonWithClass = withClass(Button, "button-class");
Now you can use Button as follows:
<ButtonWithClass colour="red"/>
And really what you'll get is:
<div className="button-class">
<Button colour="red"/>
</div>
If you did not write {...this.props} when rendering WrappedComponent in your withClass HOC, then colour would not get passed through to Button. In your HOC, WrappedComponent is equal to Button in the above case.
The syntax {...this.props} is a combination of the Object literal spread syntax and JSX's own behaviour. The Object spread syntax used in this way means the keys of the given object will become the prop names and the values will become their respective values. So when you write {...{ colour: 'red' }} you're asking JSX to get props from an object that you define inline.
To continue with the above example:
<ButtonWithClass colour="red"/>
Inside withClass this becomes equivalent to:
const WrappedComponent = Button;
return <WrappedComponent {...this.props}/>;
And here this.props equals { colour: 'red' }. So the above becomes:
const WrappedComponent = Button;
return <WrappedComponent {...{ colour: 'red' }}/>;
Which then becomes:
const WrappedComponent = Button;
return <WrappedComponent colour="red"/>;
I hope that helps!

Categories

Resources