React Hooks Best Practices

React Hooks Best Practices with Smart UI

The purpose of this help topic is to explain the best practices for using React Hooks in combination with Smart.Grid to maintain its state between re-renders

A reference to all built-in React Hooks here. Note that hooks are used only in Functional Components, they cannot be used in Class Components!

Row Data

If you set the row data as a normal array variable in the component, a new reference will be passed to the Smart.Grid on each re-render. This means the dataSource is changed and the result is unexpected actions in the Grid, such as loosing the Grid's state.

To overcome this problem, we should pass the same reference to the dataSource property on every re-render.
This can be achieved using one of the hooks: useState, useMemo, useRef

The data for the Grid is often used in other places, not only in the Grid, this means that usage of the useRef is not proper because when changing a ref, a re-render of the component is not triggered. useMemo will store the same reference of the data and change it if some of the dependencies of the array change. But most of the time, this type of data does not have any dependencies, so the best option in this case is to use useState.

Here is an example, how to set the data this way:

App.js

function App() {
    
    const [rowData, setRowData] = useState([
        { product: 'Red Apple', quantity: 23, price: 1.5 },
        { product: 'Blueberry', quantity: 77, price: 3.3 },
        { product: 'Watermelon', quantity: 4, price: 2 }
    ]);
    
    return (
        <div className="App">
            <Grid
                dataSource={rowData}
            ></Grid>
        </div>
    );
}

Setting Columns

To set the columns properly, you should use useState, useMemo, useRef because you must pass the same reference of the columns array to the Grid each time the Component re-renders.

function App() {
    
    //Same reference each re-render
    const [columns, setColumns] = useState([
        { label: "First Name", dataField: 'firstName' },
        { label: "Last Name", dataField: 'lastName' },
        { label: "Product Name", dataField: 'productName' }
    ])
    
    return (
        <div className="App">
        <Grid
            id="grid"
            columns={columns}
        ></Grid>
        </div>
    );
}

If you do not use some of the hooks, a new reference of the columns will be passed to the Grid and unexpected actions will occur.

If the columns are not subject to change, our suggestion is to use useMemo or useRef. If your columns will change, use the useState hook.

Setting other properties

Another properties, which are primitive values can be set normally, without state or ref. The problem comes with the reference type properties. A recommended way of setting them is using useState, useMemo, useRef. If you set these properties without hooks and pass new reference on each render, the Grid may reset its state and have unexpected behaviour.

function App() {
    
    //This is not good
    const header = {
        visible: true,
        template: '<div>This is the best header</div>'
    };
    
    //This is okay, because between re-renders a new reference is NOT created
    const [gridSettings, setGridSettings] = useState({
        header: {
            visible: true,
            template: '<div>This is the best header</div>'
        }
    })
    
    return (
        <div className="App">
            <Grid
                id="grid"
                header={header} //Bad
                header={gridSettings.header} //Good
            ></Grid>
        </div>
    );
}

For properties which are primitive data types, such as strings, numbers, booleans, there is no need to do this, you may set them directly or with a variable. A re-render will not cause any change in the behaviour. A change is detected by a value, not by a reference.

There are only several Grid properties that accept primitive data types

function App() {

    const rightToLeft = true;
    
    return (
        <div className="App">
            <Grid
                id="grid"
                rightToLeft={rightToLeft}
            ></Grid>
        </div>
    );
}

In the example above, no hooks are used to set the property, and the state is maintained. That works because the value is compared, not the reference, as with the reference data types.

Setting Callbacks

For the callback properties, the usage of useCallback is optional. Changing the callback of the Grid does not trigger any state reset or other unexpected actions. When the callback is changed, it will just use the new one the next time.

If you prefer to use useCallback, don't forget to pass the necessary dependencies in the array to avoid stale closures.

function App() {

    const gridRef = useRef();
    
    const [rowData, setRowData] = useState([
        {
            "id": 0,
            "available": true,
            "firstName": "Nancy",
            "lastName": "Burke",
            "name": "Nancy Burke",
            "productName": "Caffe Latte",
            "price": 4.5,
            "quantity": 3,
            "total": 13.5,
        },
        {
            "id": 1,
            "available": true,
            "firstName": "Guylene",
            "lastName": "Peterson",
            "name": "Guylene Peterson",
            "productName": "Cappuccino",
            "price": 5,
            "quantity": 9,
            "total": 45,
        },
        {
            "id": 2,
            "available": false,
            "firstName": "Mayumi",
            "lastName": "Wilson",
            "name": "Mayumi Wilson",
            "productName": "Caffe Americano",
            "price": 2.5,
            "quantity": 9,
            "total": 22.5,
        }
    ]);
    
    const [gridSettings, setGridSettings] = useState({
        sorting: {
        enabled: true
        },
        dataSourceSettings: {
        dataFields: [
            'id: number',
            'firstName: string',
            'lastName: string',
            'productName: string',
            'available: bool',
            'quantity: number',
            'price: number',
            'total: number'
        ]
        }
    })
    
    //This is good
    const onCellRender = (c) => { console.log(c) }
    
    //This is good also
    const onRender = useCallback(() => {
        console.log(rowData);
    }, [rowData])
    
    return (
        <div className="App">
            <Grid
                ref={gridRef}
                id="grid"
                dataSource={rowData}
                sorting={gridSettings.sorting}
                dataSourceSettings={gridSettings.dataSourceSettings}
                onCellRender={onCellRender}
                onRender={onRender}
            ></Grid>
        </div>
    );
}