9

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?

Fabio Manzano
  • 2,847
  • 1
  • 11
  • 23
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Yes you're right about the ref; however why do you need the ref in this case? – Chris Cousins Aug 27 '18 at 21:20
  • @ChrisCousins That's for getting native element and calling `focus()` and `blur()`. I had other cases that could possibly benefit from getting native element or calling stateless component, but this one looks quite convincing to me, I see no good alternatives. – Estus Flask Aug 27 '18 at 22:04
  • Have you tried passing the ref explicitly? Like `` – SimpleJ Aug 30 '18 at 23:56
  • 1
    No performance downsides: https://stackoverflow.com/questions/50188213/how-to-wrap-react-component-with-little-performance-penalty/50244738#50244738 – Roy Wang Aug 30 '18 at 23:57
  • @SimpleJ I have no control over , that's why I named the component ThirdPartyThemedInput . – Estus Flask Aug 31 '18 at 00:11
  • @riwu Thanks for the reference to related question. I wonder if this still applies to 16, as it was said in comments https://stackoverflow.com/questions/50188213/how-to-wrap-react-component-with-little-performance-penalty/50244738#comment87636237_50244738 . React 16 was really different IME, some things were slower but some received a good load of optimizations. – Estus Flask Aug 31 '18 at 00:15
  • `The obvious downside I'm aware of is that withRef requires a developer to be aware of wrapped component implementation` what do you mean? – marzelin Sep 06 '18 at 17:46
  • @marzelin I should know for sure that ThirdPartyThemedInput is functional component to call it like `ThirdPartyThemedInput()`. – Estus Flask Sep 06 '18 at 20:16
  • in `withRef` HOC you could check if it is stateless and if not just return the first argument as is. Also even if third party provided Stateful component, passing it a ref would only get the instance of that component not the underlying DOM. I think the best way is for component library to expose `innerRef` prop for attaching refs on its components. – marzelin Sep 06 '18 at 20:24
  • maybe somebody already figured out an abstraction for `blur` and `focus` and all you need is put your component in a `` or `` and everything magically works? – marzelin Sep 06 '18 at 20:29
  • @marzelin *HOC you could check if it is stateless* - it's a bit more complicated because that it's not stateless doesn't necessarily mean it's stateful, but yes, a check should be done. *if not just return the first argument as is* - as is won't work, I guess I will need `findDOMNode`. *the best way is for component library to expose innerRef* - I totally agree, it's a good convention, but I proceed from the fact that it doesn't do that. – Estus Flask Sep 06 '18 at 20:55

2 Answers2

1

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

marzelin
  • 10,790
  • 2
  • 30
  • 49
  • Thanks for the example, yes, I suppose a container like Focus that uses findDOMNode is the only alternative we have here. – Estus Flask Sep 06 '18 at 23:51
  • findDOMNode is deprecated in 16.6 – Sebastian Nov 23 '18 at 15:27
  • @epsilon not exactly, `findDOMNode` is deprecated in *Strict Mode*. There are no plans to remove `findDOMNode` from react although this method is an escape hatch and it should be only used if there's no other option. – marzelin Nov 27 '18 at 20:30
  • @marzelin And the upcoming `ConcurrentMode` (and therefore within `createRoot`) which disqualifies its usage IMO in that mode since it adds to much noise. – Sebastian Nov 27 '18 at 20:40
  • @epsilon well, I hope that new version will bring better tools to solve this kind of problems. – marzelin Nov 27 '18 at 21:09
0

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} />
}
Bhojendra Rauniyar
  • 83,432
  • 35
  • 168
  • 231
  • Thanks for the answer, However, the answer cites the article as the one that has been already mentioned in comments, and the problem with it is that it was written for React 15. If you have information on how stateless components perform in 16, this would be helpful. The question isn't about stateful vs stateless, so the link is not relevant. – Estus Flask Aug 31 '18 at 21:23
  • The problem is that `const stateless = (props, ref) => ` won't work. Stateless components don't have `ref` as second param, that's what `forwardRef` is for. `ReturnComponent` stateless component can't accept `ref` prop because they don't have refs, this is where the problem described in the question originates from (see 'this won't work' part in the question). – Estus Flask Aug 31 '18 at 21:25
  • I have tried to help you but it seems my head is not around clear concept about your exact question. I might need some fresh air for some time. Will be back later if updated post's scenario is not the what you'll want. – Bhojendra Rauniyar Sep 01 '18 at 03:28
  • Thanks for trying to help. I updated the question with workable demo of my current case. The point is that stateless component cannot be modified to get ref from it, I need to get a ref outside of it somehow, that's where calling the component as a function helped. This is not the only point of the question, just an example of supposedly good use of this approach. – Estus Flask Sep 01 '18 at 12:58
  • Sorry, but I'm not able to understand your question too clearly (perhaps, its because I'm going to loose my job), though if it might help: https://stackoverflow.com/a/52211571/2138752 – Bhojendra Rauniyar Sep 06 '18 at 20:25