What is the best way to declare static variables in React functional components?

Viewed 3408

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.

2 Answers

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.

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).

Related