3

Which is the best way to declare variables in React functional components (From now on - RFC)? It is not duplicate to Similar question this one.

As for me, there are a few ways of declaring variables `

  1. Declaring it with const inside RFC - If we pass this to the HeavyComponent component, so on each render, it will create a new reference, and HeavyComponent will render too. Not a good option.
  2. Declaring with useRef inside RFC - The reference will be the same within subsequent renders, which is good, but on the other hand, we are using useRef internal method (Function call - which does some job under the hood)
  3. Declaring with const outside of RFC (outer scope) - In this case, the reference will be again the same, as it will be taken from the closure and we don't use useRef method. But, in this particular case, we declare that variable in the outer scope and it is not being garbage collected and will cause memory leaks (if we use that cases very often).

I know, each case has its pros and cons. But when do we stick with a certain option?

UPDATED Here is the example

export const UnemployedEmployeesReportPaper = React.memo((props: IProps) => {
    const [filterText, setFilterText] = useState('');
    const [tableData, setTableData] = useState([]);

    const headerColumns = useRef([
        { children: 'First Name', width: 240, flexGrow: 1, sortValue: item => item.FirstName },
        { children: 'Last Name', width: 240, flexGrow: 1, sortValue: item => item.LastName },
        { children: 'Last day of employment', width: 240, flexGrow: 1, sortValue: item => moment(item.Since).format('L') },
    ]).current;

    const filterEmployee = (event: SyntheticEvent): void => {
        const { value } = event.target;
        const { payload } = props.unemployedEmployees;

        const newTableData = payload.filter((row: ISimpleEmployeeRowData): boolean =>
            (row.FirstName).toLowerCase().includes(value.toLowerCase()));

        setTableData(newTableData);
        setFilterText(value);
    };

    const rows = useMemo(() => {
        return tableData.map(entry => {
            return {
                data: [
                    { children: entry.FirstName },
                    { children: entry.LastName },
                    { children: entry.Since },

                ],

                onDoubleClick: (): void => props.goToEmployees(entry.ID),
                // Empty onClick will turn the hovering of table on
                onClick: () => {}
            };
        });
    }, [tableData]);

    useEffect(() => {
        if (props.unemployedEmployees.payload) {
            setTableData(props.unemployedEmployees.payload);
        }

        setFilterText('');
    }, [props.unemployedEmployees]);



    return (
        <VTable
           sortable
           striped
           rowHeight={36}
           headerColumns={headerColumns}
           rows={rows}
        />);
});

Here is used useRef, but I am not sure if it's better than just declaring it outside of RFC.

Norayr Ghukasyan
  • 1,298
  • 1
  • 14
  • 28
  • What kind of answer you are looking for? What are the use cases for each? As if you know the props/cons it becomes opinioned question – Dennis Vash Jun 07 '20 at 11:57
  • 1
    If you pay attention to my title and also to the bottom of the question , there I mentioned what I need . What is the best way to do it and I am sure , there are cases , that one if them is preferable – Norayr Ghukasyan Jun 07 '20 at 11:59
  • So what is the use case? Do you have any particular one and you don't know what you should do? – Dennis Vash Jun 07 '20 at 12:01
  • I have used all of them, but don't know which one is better in certain cases – Norayr Ghukasyan Jun 07 '20 at 12:02
  • Thats what I'm trying to say, show us the **certain cases**, in Stackoverflow, as you know, that's kind of question that should be asked. I can come up with cases, but they will be arbitrary. See [How do I ask a good question?](https://stackoverflow.com/help/how-to-ask) – Dennis Vash Jun 07 '20 at 12:03

2 Answers2

4

The best way to store variables in a functional component depends on your useCase.

In most cases you can make use of useRef hook since it returns you the same instance of the variable on each render of the function.

You can however define a variable and assign its value using useMemo hook too.

like

const val = useMemo(() => {
   return some calculation based value or in general a normal value
},[]) // dependency array to recalculate value

You must note that useRef helps you get around closure issues and comes in handy when you want to use variables that are affected by closure. For instance using a value from closure inside a setInterval function defined within useEffect with empty dependency.

On the other hand useMemo will help you prevent references change for variables no each re-render. A common useCase of this would be to provide a memoized value for ContextProvider

UPDATE:

For your usecase, there are two ways to define headerColumns.

  • Outside of the component as a constant. Declaring it as a constantoutside of the functional component makes sense when the value is not expected to change, nor is it expected to use any value from the closure

  • As a memozied value within the function

 const headerColumns = useMemo( () => [
        { children: 'First Name', width: 240, flexGrow: 1, sortValue: item => item.FirstName },
        { children: 'Last Name', width: 240, flexGrow: 1, sortValue: item => item.LastName },
        { children: 'Last day of employment', width: 240, flexGrow: 1, sortValue: item => moment(item.Since).format('L') },
    ], []);

You must note the using useMemo to assign value to headerColumns makes sense when you use value from closure.

Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • 1
    Why use useRef, if you can declare it outside of the component and get the variable through closure? what's the difference? And when to choose one over another? – Norayr Ghukasyan Jun 07 '20 at 12:18
  • 1
    It is because if you declare it outside of the component you can't create multiple instances of the component each having a separate reference of the variable in a different parent component – Shubham Khatri Jun 07 '20 at 12:23
2

In your use-case, headerColumns should be in outer scope:

const headerColumns = [
  {
    children: "First Name",
    width: 240,
    flexGrow: 1,
    sortValue: (item) => item.FirstName,
  },
  // more
];

export const UnemployedEmployeesReportPaper = React.memo((props) => {});

If its a "read-only" object, it should be in the outer scope.

See Why need useRef to contain mutable variable but not define variable outside the component function?.

In your current situation, if you have N UnemployedEmployeesReportPaper components, you will have N headerColumns references, instead on outer scope all components will share the same immutable object (immutable because in your use case, it acts as read-only object).

Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
  • I know , that variables in outer scope are global , and not being garbage collected . What about it ? – Norayr Ghukasyan Jun 07 '20 at 12:23
  • Are you sure it won't garbage collected? Did you check it? It is not a **real** global variable, it is in scope of the component's file. – Dennis Vash Jun 07 '20 at 12:23
  • Also, do you prefer N copies of the same object over all your components? So basically you claim that it is better to have N copies of the same object than one object that "doesn't" gets garbage collected – Dennis Vash Jun 07 '20 at 12:24
  • To be honest, I am not sure about garbage collecting, but your argument is good and can be a good point to choose one over another – Norayr Ghukasyan Jun 07 '20 at 14:49
  • @DennisVash So just to be sure, the variable kept outside the component will get garbage collected/destroyed just like any other variable kept inside component when component unmounts? I want to keep a class instance variable which I need to initialize only once. Would keeping it outside be a good idea? – coder Apr 16 '22 at 14:17
  • Yes, but for more context just ask a question – Dennis Vash Apr 17 '22 at 08:26