DevExtreme React - Component Configuration Syntax
To help you configure DevExtreme UI components in a React-style code structure, we implemented configuration components. These are elements nested in JSX code as if they represented a UI component's sub-elements, but from the technical point of view, they only carry the configuration data. This means that UI component properties can be supplied declaratively and selectively, with default fallbacks in place.
Static Property Value
import Button from 'devextreme-react/button'; export default function App() { return ( <Button disabled={false} width={50} text="Click me" /> ); }
import Button from 'devextreme-react/button'; class App extends React.Component { render() { return ( <Button disabled={false} width={50} text="Click me" /> ); } }
Properties of the Object Type
Use nested configuration components. In the following example, we configure the Chart UI component's tooltip property:
import Chart, { Tooltip } from 'devextreme-react/chart'; export default function App() { return ( <Chart> <Tooltip enabled={true} format="thousands" /> </Chart> ); }
import Chart, { Tooltip } from 'devextreme-react/chart'; class App extends React.Component { render() { return ( <Chart> <Tooltip enabled={true} format="thousands" /> </Chart> ); } }
Object type properties that depend on other properties' values are not implemented as nested configuration components because they cannot be typed (columns[].editorOptions in the DataGrid, item's editorOptions in the Form, items[].options in the Toolbar). These properties should be specified with an object.
import DataGrid, { Column } from 'devextreme-react/data-grid'; const columnEditorOptions = { width: 100 }; export default function App() { return ( <DataGrid> <Column editorOptions={columnEditorOptions} /> </DataGrid> ); }
import DataGrid, { Column } from 'devextreme-react/data-grid'; class App extends React.Component { columnEditorOptions = { width: 100 }; render() { return ( <DataGrid> <Column editorOptions={this.columnEditorOptions} /> </DataGrid> ); } }
If you use React Hooks and need to define a configuration object inside a function component, wrap this object in the useMemo hook to preserve the object's reference between state changes:
import React, { useState, useMemo } from 'react'; import Form, { Label, SimpleItem } from 'devextreme-react/form'; const data = { isAddressRequired: false, Address: '' }; export default function App() { const [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's columns property:
import DataGrid, { Column } from 'devextreme-react/data-grid'; export default function App() { return ( <DataGrid> <Column dataField="firstName" caption="First Name" /> <Column dataField="lastName" caption="Last Name" defaultVisible={true} /> </DataGrid> ); }
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 UI components also support the Item
element. It allows you to declare collection items in the UI component markup. An Item
element can contain custom markup and have properties that control parts of item appearance, such as badge
in the following code. The properties are described in the items section of each collection UI component.
import List, { Item } from 'devextreme-react/list'; export default function App() { return ( <List> <Item>Orange</Item> <Item badge="New">White</Item> <Item>Black</Item> </List> ); }
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
import { useCallback } from 'react'; import Button from 'devextreme-react/button'; export default function App() { const handleButtonClick = useCallback((e) => { alert("The button was clicked") }, []); return ( <Button onClick={handleButtonClick} /> ); }
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") } }
useCallback
React Hook to prevent possible issues caused by unnecessary re-rendering.Callback Functions
import { useCallback } from 'react'; import VectorMap, { Layer } from 'devextreme-react/vector-map'; export default function App() { const customizeLayers = useCallback((elements) => { // ... }, []); return ( <VectorMap> <Layer customize={customizeLayers} /> </VectorMap> ); }
import VectorMap, { Layer } from 'devextreme-react/vector-map'; class App extends React.Component { render() { return ( <VectorMap> <Layer customize={this.customizeLayers} /> </VectorMap> ); } customizeLayers(elements) { // ... } }
useCallback
React Hook to prevent possible issues caused by unnecessary re-rendering.In class components, 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.
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 UI components allow you to declare their content directly in the markup:
The following is an example with ScrollView:
import ScrollView from 'devextreme-react/scroll-view'; export default function App() { return ( <ScrollView> <div>Some scrollable content</div> </ScrollView> ); }
import ScrollView from 'devextreme-react/scroll-view'; class App extends React.Component { render() { return ( <ScrollView> <div>Some scrollable content</div> </ScrollView> ); } }
These UI components do not support dynamically or conditionally rendered content in their root element. For example, the following code does not work:
<Drawer ... > { someCondition && <div> ... </div> // when the condition changes in runtime, the UI component may not render content correctly } </Drawer>
Wrap the content in a static element:
<Drawer ... > <div> { someCondition && <div> ... </div> } </div> </Drawer>
Note that React.Fragment
is a dynamically rendered element and doesn't fit in this case. Use static elements such as div
or span
instead.
Markup Customization
Templates allow you to customize UI components. In the DevExtreme API, template properties 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 a component does not reinitiate a template when the component rerenders, you should use the itemComponent
property. Otherwise, we recommend that you use the itemRender
property.
If the property is called template
, without any prefix, (in the Button, Drawer, and other UI components), use the render
or component
property instead.
Using a Rendering Function
In the following code, rendering functions are used to specify the List's itemTemplate and the Button's template:
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>; } export default function App() { return ( <React.Fragment> <List itemRender={renderListItem} /> <Button render={renderButton} /> </React.Fragment> ); }
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.
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 properties are not implemented as nested configuration components (columns[].editorOptions in the DataGrid, item's editorOptions in the Form, items[].options in the Toolbar). These properties do not have the render
or component
property to which you would pass your rendering function or custom component. However, you can still customize the markup — using the Template
element.
The Template
element declares a named template. Its name
property should be assigned to a ...Template
property of the UI component that uses the Template
. The template's markup can be specified as follows:
Rendering function
Pass the rendering function to theTemplate
'srender
property:Function componentClass componentdata.jsimport 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>; } const employee = service.getEmployee(); const positions = service.getPositions(); const positionEditorOptions = { items: positions, value: '', itemTemplate: 'selectBoxItem' }; export default function App() { return ( <Form formData={employee}> <Item dataField="Position" editorType="dxSelectBox" editorOptions={positionEditorOptions} /> <Template name="selectBoxItem" render={renderSelectBoxItem} /> </Form> ); }
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 theTemplate
'scomponent
property:Class componentdata.jsimport 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 UI component methods, you need the UI component instance. Create a ref and attach it to the target component via the ref
property. In the following code, this approach is used to get a TextBox
instance:
import React, { useRef, useCallback } from 'react'; import Button from 'devextreme-react/button'; import TextBox from 'devextreme-react/text-box'; export default function App() { const textBox = useRef(null); const focusTextBox = useCallback(() => { // `current.instance` points to the UI component instance textBox.current.instance.focus(); }, []); return ( <div> <TextBox ref={textBox} /> <Button text="Focus TextBox" onClick={focusTextBox} /> </div> ); }
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() { // `current.instance` points to the UI component instance return this.textBoxRef.current.instance; } render() { return ( <div> <TextBox ref={this.textBoxRef} /> <Button text="Focus TextBox" onClick={this.focusTextBox} /> </div> ); } }
Alternatively, you can assign the UI component instance to a variable and use it to call the methods. This approach is not compatible with React Hooks.
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> ); } }
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:
import React, { useEffect } from 'react'; import DataSource from 'devextreme/data/data_source'; import List from 'devextreme-react/list'; const items = [ { text: '123' }, { text: '234' }, { text: '567' } ]; const dataSource = new DataSource({ store: { type: "array", data: items }, sort: { getter: "text", desc: true } }); export default function App() { useEffect(() => { // A DataSource instance created outside a UI component should be disposed of manually. return () => { dataSource.dispose(); } }); return ( <List dataSource={dataSource} /> ); }
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 UI component should be disposed of manually. this.dataSource.dispose(); } }
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.
import React, { useState, useCallback } from 'react'; import TextBox from 'devextreme-react/text-box'; import Validator, { RequiredRule, EmailRule } from 'devextreme-react/validator'; import ValidationGroup from 'devextreme-react/validation-group'; import Button from 'devextreme-react/button'; export default function App() { const [email, setEmail] = useState(null); const [password, setPassword] = useState(null); const validate = useCallback((params) => { const result = params.validationGroup.validate(); if (result.isValid) { // The values are valid // Submit them... // ... // ... and then reset // params.validationGroup.reset(); } }, []); const handleEmailChange = useCallback((e) => { setEmail(e.value); }, []); const handlePasswordChange = useCallback((e) => { setPassword(e.value); }, []); return ( <ValidationGroup> <TextBox value={email} onValueChanged={handleEmailChange}> <Validator> <RequiredRule message="Email is required" /> <EmailRule message="Email is invalid" /> </Validator> </TextBox> <TextBox value={password} mode="password" onValueChanged={handlePasswordChange}> <Validator> <RequiredRule message="Password is required" /> </Validator> </TextBox> <Button onClick={validate} text="Submit" /> </ValidationGroup> ); }
import TextBox from 'devextreme-react/text-box'; import Validator, { RequiredRule, EmailRule } 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.state = { email: null, password: null }; this.handleEmailChange = (e) => { this.setState({ email: e.value }); }; this.handlePasswordChange = (e) => { this.setState({ password: e.value }); }; this.validate = this.validate.bind(this); } render() { return ( <ValidationGroup> <TextBox value={this.state.email} onValueChanged={this.handleEmailChange}> <Validator> <RequiredRule message="Email is required" /> <EmailRule message="Email is invalid" /> </Validator> </TextBox> <TextBox value={this.state.password} mode="password" onValueChanged={this.handlePasswordChange}> <Validator> <RequiredRule message="Password is required" /> </Validator> </TextBox> <Button onClick={this.validate} text="Submit" /> </ValidationGroup> ); } validate(params) { const 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.
If you have technical questions, please create a support ticket in the DevExpress Support Center.