DevExtreme React - Component Configuration Syntax

To help you configure DevExtreme widgets in a React-style code structure, we implemented configuration components. These are elements nested in JSX code as if they represented a widget's sub-elements, but from the technical point of view, they only carry the configuration data. This means that widget options can be supplied declaratively and selectively, with default fallbacks in place.

Static Option Value

App.js
import Button from 'devextreme-react/button';

class App extends React.Component {
    render() {
        return (
            <Button
                disabled={false}
                width={50}
                text="Click me"
            />
        );
    }
}

Options of the Object Type

Use nested configuration components. In the following example, we configure the Chart widget's tooltip option:

App.js
import Chart, {
    Tooltip
} from 'devextreme-react/chart';

class App extends React.Component {
    render() {
        return (
            <Chart>
                <Tooltip
                    enabled={true}
                    format="thousands"
                />
            </Chart>
        );
    }
}

Object type options that depend on other options' values are not implemented as nested configuration components because they cannot be typed (editorOptions in the DataGrid, editorOptions in the Form, widget options in the Toolbar). These options should be specified with an object.

App.js
import DataGrid, {
    Column
} from 'devextreme-react/data-grid';

class App extends React.Component {
    columnEditorOptions = { width: 100 };

    render() {
        return (
            <DataGrid>
                <Column
                    editorOptions={this.columnEditorOptions}
                />
            </DataGrid>
        );
    }
}
IMPORTANT
We recommend that you declare the object outside the configuration component to prevent possible issues caused by unnecessary re-rendering.

If you use React Hooks and need to define a configuration object inside a function, wrap this object in the useMemo hook to preserve the object's reference between state changes:

App.js
import React, { useState, useMemo } from 'react';
import Form, { Label, SimpleItem } from 'devextreme-react/form';

const data = { isAddressRequired: false, Address: '' };

export default function App() {

    let [visible, setVisible] = useState(false);
    const checkBoxOptions = useMemo(() => {
        return {
            text: "Display Address",
            onValueChanged: (e) => {
                setVisible(e.value);
            }
        }
    }, []);
    const addressOptions = useMemo(() => {
        return {
            placeholder: 'Enter your addresss',
            maxLength: 50
        }
    }, []);

    return (
        <Form formData={data}
            width={400}>
            <SimpleItem
                dataField="isAddressRequired"
                editorType="dxCheckBox"
                editorOptions={checkBoxOptions}>
                <Label visible={false} />
            </SimpleItem>
            <SimpleItem
                dataField="Address"
                editorType="dxTextBox"
                editorOptions={addressOptions}
                visible={visible}
            />
        </Form>
    );
}

Collections

Use nested configuration components. The following example shows how to configure the DataGrid widget's columns option:

App.js
import DataGrid, {
    Column
} from 'devextreme-react/data-grid'; 

class App extends React.Component {
    render() {
        return (
            <DataGrid>
                <Column
                    dataField="firstName"
                    caption="First Name"
                />
                <Column
                    dataField="lastName"
                    caption="Last Name"
                    defaultVisible={true}
                />
            </DataGrid>
        );
    }
}

DevExtreme collection widgets also support the Item component. It allows you to declare collection items in the widget markup. An Item element can contain custom markup and have attributes that control parts of item appearance, such as badge in the following code. The attributes are described in the items section of each collection widget.

App.js
import List, {
    Item
} from 'devextreme-react/list';

class App extends React.Component {
    render() {
        return (
            <List>
                <Item>Orange</Item>
                <Item badge="New">White</Item>
                <Item>Black</Item>
            </List>
        );
    }
}

Event Handling

App.js
import Button from 'devextreme-react/button';

class App extends React.Component {
    constructor(props) {
        super(props);
        // Uncomment the line below to bind the handler to the React component's context, for example, to call this.setState()
        // this.handleButtonClick = this.handleButtonClick.bind(this);
    }

    render() {
        return (
            <Button
                onClick={this.handleButtonClick}
            />
        );
    }

    handleButtonClick(e) {
        alert("The button was clicked")
    }
}

Callback Functions

App.js
import VectorMap, { Layer } from 'devextreme-react/vector-map';

class App extends React.Component {
    render() {
        return (
            <VectorMap>
                <Layer
                    customize={this.customizeLayers}
                />
            </VectorMap>
        );
    }

    customizeLayers(elements) {
        // ...
    }
}

Callback functions are executed outside the React component's context. If the context is important, explicitly bind the callback function to it in the constructor.

App.js
class App extends React.Component {
    myCountry: string = 'USA'; // we need to access this context variable in the callback function

    constructor(props) {
        super(props);
        this.customizeLayers = this.customizeLayers.bind(this);
    }

    customizeLayers(elements) {
        let country = this.myCountry;
        // ...
    }
    // ...        
}

Declare Content in the Markup

The following widgets allow you to declare their content directly in the markup:

The following is an example with ScrollView:

App.js
import ScrollView from 'devextreme-react/scroll-view';

class App extends React.Component {
    render() {
        return (
            <ScrollView>
                <div>Some scrollable content</div>
            </ScrollView>
        );
    }
}

Markup Customization

Templates allow you to customize widget elements. In the DevExtreme API, template options end with Template: itemTemplate, groupTemplate, contentTemplate. When you specify them in React, replace Template in the name with Render or Component, depending on whether the template is a rendering function or custom component. For example, instead of itemTemplate, use itemRender or itemComponent.

If the option is called template, without any prefix, (in the Button, Drawer, and other widgets), use the render or component attribute instead.

Using a Rendering Function

In the following code, rendering functions are used to specify the List's itemTemplate and the Button's template:

App.js
import List from 'devextreme-react/list';
import Button from 'devextreme-react/button';

const renderListItem = (itemData) => {
    return <p>{itemData.itemProperty}</p>;
}
const renderButton = (button) => {
    return <div style={{ padding: 20 }}><p>{button.text}</p></div>;
}
class App extends React.Component {
    render() {
        return (
            <React.Fragment>
                <List
                    itemRender={renderListItem}
                />
                <Button
                    render={renderButton}
                />
            </React.Fragment>
        );
    }
}

Using a Custom Component

You can define the template markup in a separate component. We recommend using React.PureComponent because React.Component can be re-rendered unnecessarily. Alternatively, you can implement the shouldComponentUpdate() method.

In the following code, custom components are used to specify the List's itemTemplate and the Button's template. Template variables are passed to the components as props.

App.js
import List from 'devextreme-react/list';
import Button from 'devextreme-react/button';

class ListItemTmpl extends React.PureComponent {
    render() {
        return (
            <p>{this.props.data.itemProperty}</p>
        );
    }
}

class ButtonTmpl extends React.PureComponent {
    render() {
        return (
            <div style={{ padding: 20 }}>
                <p>{this.props.data.text}</p>
            </div>
        );
    }
}

class App extends React.Component {
    render() {
        return (
            <React.Fragment>
                <List itemComponent={ListItemTmpl} />
                <Button component={ButtonTmpl} />
            </React.Fragment>
        );
    }
}

Using the Template Component

Several options are not implemented as nested configuration components (editorOptions in the DataGrid, editorOptions in the Form, widget options in the Toolbar). These options do not have the render or component attribute to which you would pass your rendering function or custom component. However, you can still customize the markup — using the Template component.

The Template component declares a named template. Its name property should be assigned to a ...Template option of the widget that uses the Template. The template's markup can be specified as follows:

  • Rendering function
    Pass the rendering function to the Template's render property:

    App.js
    data.js
    import Form, { Item } from 'devextreme-react/form';
    import { Template } from 'devextreme-react/core/template';
    
    import service from './data.js';
    
    const renderSelectBoxItem = item => {
        return <div>{item.toUpperCase()}</div>;
    }
    
    class App extends React.Component {
        constructor(props) {
            super(props);
            this.employee = service.getEmployee();
            this.positions = service.getPositions();
            this.positionEditorOptions = {
                items: this.positions,
                value: '',
                itemTemplate: 'selectBoxItem'
            };
        }
        render() {
            return (
                <Form formData={this.employee}>
                    <Item
                        dataField="Position"
                        editorType="dxSelectBox"
                        editorOptions={this.positionEditorOptions}
                    />
                    <Template name="selectBoxItem" render={renderSelectBoxItem} />
                </Form>
            );
        }
    }
    export default App;
    const employee = {
        ID: 1,
        FirstName: 'John',
        LastName: 'Heart',
        Position: 'CEO',
        BirthDate: '1964/03/16',
        HireDate: '1995/01/15',
        Address: '351 S Hill St., Los Angeles, CA',
        Phone: '360-684-1334',
        Email: 'jheart@dx-email.com'
    };
    
    const positions = [
        'HR Manager',
        'IT Manager',
        'CEO',
        'Controller',
        'Sales Manager',
        'Support Manager',
        'Shipping Manager'
    ];
    
    export default {
        getEmployee() {
            return employee;
        },
        getPositions() {
            return positions;
        }
    }
  • Custom component
    Assign the custom component to the Template's component property:

    App.js
    data.js
    import Form, { Item } from 'devextreme-react/form';
    import { Template } from 'devextreme-react/core/template';
    
    import service from './data.js';
    
    class SelectBoxItemTmpl extends React.PureComponent {
        render() {
            return (
                <div>{this.props.data.toUpperCase()}</div>
            );
        }
    }
    
    class App extends React.Component {
        constructor(props) {
            super(props);
            this.employee = service.getEmployee();
            this.positions = service.getPositions();
            this.positionEditorOptions = {
                items: this.positions,
                value: '',
                itemTemplate: 'selectBoxItem'
            };
        }
        render() {
            return (
                <Form formData={this.employee}>
                    <Item
                        dataField="Position"
                        editorType="dxSelectBox"
                        editorOptions={this.positionEditorOptions}
                    />
                    <Template name="selectBoxItem" component={SelectBoxItemTmpl} />
                </Form>
            );
        }
    }
    export default App;
    const employee = {
        ID: 1,
        FirstName: 'John',
        LastName: 'Heart',
        Position: 'CEO',
        BirthDate: '1964/03/16',
        HireDate: '1995/01/15',
        Address: '351 S Hill St., Los Angeles, CA',
        Phone: '360-684-1334',
        Email: 'jheart@dx-email.com'
    };
    
    const positions = [
        'HR Manager',
        'IT Manager',
        'CEO',
        'Controller',
        'Sales Manager',
        'Support Manager',
        'Shipping Manager'
    ];
    
    export default {
        getEmployee() {
            return employee;
        },
        getPositions() {
            return positions;
        }
    }

Call Methods

To call widget methods, you need the widget instance. Create a ref and attach it to the target component via the ref attribute. Implement a getter that returns the instance taken from the ref. In the following code, this approach is used to get a TextBox instance:

App.js
import Button from 'devextreme-react/button';
import TextBox from 'devextreme-react/text-box';

class App extends React.Component {
    constructor(props) {
        super(props);

        this.textBoxRef = React.createRef();

        this.focusTextBox = () => {
            this.textBox.focus()
        };
    }

    get textBox() {
        return this.textBoxRef.current.instance;
    }

    render() {
        return (
            <div>
                <TextBox ref={this.textBoxRef} />
                <Button text="Focus TextBox" onClick={this.focusTextBox} />
            </div>
        );
    }
}

Alternatively, you can save the widget instance in a component property once the widget is initialized. This approach is not compatible with React Hooks.

App.js
import Button from 'devextreme-react/button';
import TextBox from 'devextreme-react/text-box';

class App extends React.Component {
    constructor(props) {
        super(props);

        this.saveTextBoxInstance = this.saveTextBoxInstance.bind(this);
        this.focusTextBox = this.focusTextBox.bind(this);
    }

    saveTextBoxInstance(e) {
        this.textBoxInstance = e.component;
    }

    focusTextBox() {
        this.textBoxInstance.focus();
    }

    render() {
        return (
            <div>
                <TextBox onInitialized={this.saveTextBoxInstance} />
                <Button text="Focus TextBox" onClick={this.focusTextBox} />
            </div>
        );
    }
}

If you use React Hooks, implement the useRef hook and attach it to the target component via the ref attribute:

App.js
import Button from 'devextreme-react/button';
import TextBox from 'devextreme-react/text-box';
import React, { useRef } from 'react';

export default function App() {      
    const textBox = useRef(null);
    const focusTextBox = () => {
        textBox.current.instance.focus();
    };
    return (
        <div>
            <TextBox ref={textBoxRef} />
            <Button text="Focus TextBox" onClick={focusTextBox} />
        </div>
    );       
}

Get a Widget Instance

For information on this matter, refer to Call Methods.

Data Layer

DevExtreme Data Layer is a set of components for working with data. The following example shows how to use the DataSource component with the List UI component:

App.js
import DataSource from 'devextreme/data/data_source';
import List from 'devextreme-react/list';

const items = [
    { text: '123' },
    { text: '234' },
    { text: '567' }
];

class Example extends React.Component {
    constructor(props) {
        super(props);

        this.dataSource = new DataSource({
            store: {
                type: 'array',
                data: items
            },
            sort: { getter: 'text', desc: true }
        });
    }

    render() {
        return (
            <List dataSource={this.dataSource} />
        );
    }

    componentWillUnmount() {
        // A DataSource instance created outside a widget should be disposed of manually
        this.dataSource.dispose();
    }
}

If you use React Hooks and need to define the DataSource component inside a function, wrap its definition in the useMemo hook to preserve the object's reference between state changes.

App.js
import React, { useMemo, useEffect } from 'react';
import DataSource from 'devextreme/data/data_source';
import List from 'devextreme-react/list';

const items = [
    { text: '123' },
    { text: '234' },
    { text: '567' }
];

export default function Example() {
    const dataSource = useMemo(() => {
        return new DataSource({
            store: {
                type: "array",
                data: items
            },
            sort: { getter: "text", desc: true }
        });
    }, []);
    useEffect(() => {
        // A DataSource instance created outside a widget should be disposed of manually
        return () => { dataSource.dispose(); }
    }, []);
    return (
        <List dataSource={dataSource} />
    ); 
}

In the code above, the useEffect hook is used to dispose of the DataSource instance after the UI component is removed from the DOM tree.

NOTE
When a data layer component's properties are modified, the bound UI component is not re-rendered.

DevExtreme Validation Features

In the following example, two textboxes are placed in a validation group that is validated on a button click. Each textbox has a set of validation rules. The validation result is displayed under the textboxes in a validation summary.

App.js
import TextBox from 'devextreme-react/text-box';
import Validator, { ValidationRule } from 'devextreme-react/validator';
import ValidationGroup from 'devextreme-react/validation-group';
import Button from 'devextreme-react/button';

class App extends React.Component {
    constructor(props) {
        super(props);
        this.email = null;
        this.password = null;
    }

    render() {
        return (
            <ValidationGroup>
                <TextBox value={this.email}>
                    <Validator>
                        <ValidationRule type="required" message="Email is required" />
                        <ValidationRule type="email" message="Email is invalid" />
                    </Validator>
                </TextBox>

                <TextBox value={this.password} mode="password">
                    <Validator>
                        <ValidationRule type="required" message="Password is required" />
                    </Validator>
                </TextBox>
                <Button onClick={this.validate} text="Submit" />
            </ValidationGroup>
        );
    }

    validate(params) {
        let result = params.validationGroup.validate();
        if (result.isValid) {
            // The values are valid
            // Submit them...
            // ...
            // ... and then reset
            // params.validationGroup.reset();
        }
    }
}

Refer to the Data Validation article for more information.