Reflex Cloud - Fast, secure & scalable hosting. One command to deploy.

Tutorial: Data Dashboard

During this tutorial you will build a small data dashboard, where you can input data and it will be rendered in table and a graph. This tutorial does not assume any existing Reflex knowledge, but we do recommend checking out the quick Basics Guide first.

The techniques you’ll learn in the tutorial are fundamental to building any Reflex app, and fully understanding it will give you a deep understanding of Reflex.

This tutorial is divided into several sections:

  • Setup for the Tutorial: A starting point to follow the tutorial
  • Overview: The fundamentals of Reflex UI (components and props)
  • Showing Dynamic Data: How to use State to render data that will change in your app.
  • Add Data to your App: Using a Form to let a user add data to your app and introduce event handlers.
  • Plotting Data in a Graph: How to use Reflex's graphing components.
  • Final Cleanup and Conclusion: How to further customize your app and add some extra styling to it.

What are you building?

In this tutorial, you are building an interactive data dashboard with Reflex.

You can see what the finished app and code will look like here:

NameEmailGender
Danilo Sousa[email protected]Male
Zahra Ambessa[email protected]Female

Don't worry if you don't understand the code above, in this tutorial we are going to walk you through the whole thing step by step.

Setup for the tutorial

Check out the installation docs to get Reflex set up on your machine. Follow these to create a folder called dashboard_tutorial, which you will cd into and pip install reflex.

We will choose template 0 when we run reflex init to get the blank template. Finally run reflex run to start the app and confirm everything is set up correctly.

Overview

Now that you’re set up, let’s get an overview of Reflex!

Inspecting the starter code

Within our dashboard_tutorial folder we just cd'd into, there is a rxconfig.py file that contains the configuration for our Reflex app. (Check out the config docs for more information)

There is also an assets folder where static files such as images and stylesheets can be placed to be referenced within your app. (asset docs for more information)

Most importantly there is a folder also called dashboard_tutorial which contains all the code for your app. Inside of this folder there is a file named dashboard_tutorial.py. To begin this tutorial we will delete all the code in this file so that we can start from scratch and explain every step as we go.

The first thing we need to do is import reflex. Once we have done this we can create a component, which is a reusable piece of user interface code. Components are used to render, manage, and update the UI elements in your application.

Let's look at the example below. Here we have a function called index that returns a text component (an in-built Reflex UI component) that displays the text "Hello World!".

Next we define our app using app = rx.App() and add the component we just defined (index) to a page using app.add_page(index). The function name (in this example index) which defines the component, must be what we pass into the add_page. The definition of the app and adding a component to a page are required for every Reflex app.

This code will render a page with the text "Hello World!" when you run your app like below:

Hello World!

Creating a table

Let's create a new component that will render a table. We will use the table component to do this. The table component has a root, which takes in a header and a body, which in turn take in row components. The row component takes in cell components which are the actual data that will be displayed in the table.

NameEmailGender
Danilo Sousa[email protected]Male
Zahra Ambessa[email protected]Female

Components in Reflex have props, which can be used to customize the component and are passed in as keyword arguments to the component function.

The rx.table.root component has for example the variant and size props, which customize the table as seen below.

NameEmailGender
Danilo Sousa[email protected]Male
Zahra Ambessa[email protected]Female

Showing dynamic data (State)

Up until this point all the data we are showing in the app is static. This is not very useful for a data dashboard. We need to be able to show dynamic data that can be added to and updated.

This is where State comes in. State is a Python class that stores variables that can change when the app is running, as well as the functions that can change those variables.

To define a state class, subclass rx.State and define fields that store the state of your app. The state variables (vars) should have a type annotation, and can be initialized with a default value. Check out the basics section for a simple example of how state works.

In the example below we define a State class called State that has a variable called users that is a list of lists of strings. Each list in the users list represents a user and contains their name, email and gender.

To iterate over a state var that is a list, we use the rx.foreach function to render a list of components. The rx.foreach component takes an iterable (list, tuple or dict) and a function that renders each item in the iterable.

Here the render function is show_user which takes in a single user and returns a table.row component that displays the users name, email and gender.

NameEmailGender
Danilo Sousa[email protected]Male
Zahra Ambessa[email protected]Female

As you can see the output above looks the same as before, except now the data is no longer static and can change with user input to the app.

Using a proper class structure for our data

So far our data has been defined in a list of lists, where the data is accessed by index i.e. user[0], user[1]. This is not very maintainable as our app gets bigger.

A better way to structure our data in Reflex is to use a class to represent a user. This way we can access the data using attributes i.e. user.name, user.email.

In Reflex when we create these classes to showcase our data, the class must inherit from rx.Base.

rx.Base is also necessary if we want to have a state var that is an iterable with different types. For example if we wanted to have age as an int we would have to use rx.base as we could not do this with a state var defined as list[list[str]].

The show_user render function is also updated to access the data by named attributes, instead of indexing.

NameEmailGender
Danilo Sousa[email protected]Male
Zahra Ambessa[email protected]Female

Next let's add a form to the app so we can add new users to the table.

Using a Form to Add Data

We build a form using rx.form, whick takes several components such as rx.input and rx.select, which represent the form fields that allow you to add information to submit with the form. Check out the form docs for more information on form components.

The rx.input component takes in several props. The placeholder prop is the text that is displayed in the input field when it is empty. The name prop is the name of the input field, which gets passed through in the dictionary when the form is submitted. The required prop is a boolean that determines if the input field is required.

The rx.select component takes in a list of options that are displayed in the dropdown. The other props used here are identical to the rx.input component.

This form is all very compact as you can see from the example, so we need to add some styling to make it look better. We can do this by adding a vstack component around the form fields. The vstack component stacks the form fields vertically. Check out the layout docs for more information on how to layout your app.

Now you have probably realised that we have all the form fields, but we have no way to submit the form. We can add a submit button to the form by adding a rx.button component to the vstack component. The rx.button component takes in the text that is displayed on the button and the type prop which is the type of button. The type prop is set to submit so that the form is submitted when the button is clicked.

In addition to this we need a way to update the users state variable when the form is submitted. All state changes are handled through functions in the state class, called event handlers.

Components have special props called event triggers, such as on_submit, that can be used to make components interactive. Event triggers connect components to event handlers, which update the state. Different event triggers expect the event handler that you hook them up to, to take in different arguments (and some do not take in any arguments).

The on_submit event trigger of rx.form is hooked up to the add_user event handler that is defined in the State class. This event trigger expects to pass a dict, containing the form data, to the event handler that it is hooked up to. The add_user event handler takes in the form data as a dictionary and appends it to the users state variable.

Finally we must add the new form() component we have defined to the index() function so that the form is rendered on the page.

Below is the full code for the app so far. If you try this form out you will see that you can add new users to the table by filling out the form and clicking the submit button. The form data will also appear as a toast (a small window in the corner of the page) on the screen when submitted.

NameEmailGender
Danilo Sousa[email protected]Male
Zahra Ambessa[email protected]Female

Putting the Form in an Overlay

In Reflex, we like to make the user interaction as intuitive as possible. Placing the form we just constructed in an overlay creates a focused interaction by dimming the background, and ensures a cleaner layout when you have multiple action points such as editing and deleting as well.

We will place the form inside of a rx.dialog component (also called a modal). The rx.dialog.root contains all the parts of a dialog, and the rx.dialog.trigger wraps the control that will open the dialog. In our case the trigger will be an rx.button that says "Add User" as shown below.

After the trigger we have the rx.dialog.content which contains everything within our dialog, including a title, a description and our form. The first way to close the dialog is without submitting the form and the second way is to close the dialog by submitting the form as shown below. This requires two rx.dialog.close components within the dialog.

The total code for the dialog with the form in it is below.

At this point we have an app that allows you to add users to a table by filling out a form. The form is placed in a dialog that can be opened by clicking the "Add User" button. We change the name of the component from form to add_customer_button and update this in our index component. The full app so far and code are below.

NameEmailGender
Danilo Sousa[email protected]Male
Zahra Ambessa[email protected]Female

Plotting Data in a Graph

The last part of this tutorial is to plot the user data in a graph. We will use Reflex's built-in graphing library recharts to plot the number of users of each gender.

Transforming the data for the graph

The graphing components in Reflex expect to take in a list of dictionaries. Each dictionary represents a data point on the graph and contains the x and y values. We will create a new event handler in the state called transform_data to transform the user data into the format that the graphing components expect. We must also create a new state variable called users_for_graph to store the transformed data, which will be used to render the graph.

As we can see above the transform_data event handler uses the Counter class from the collections module to count the number of users of each gender. We then create a list of dictionaries from this which we set to the state var users_for_graph.

Finally we can see that whenever we add a new user through submitting the form and running the add_user event handler, we call the transform_data event handler to update the users_for_graph state variable.

Rendering the graph

We use the rx.recharts.bar_chart component to render the graph. We pass through the state variable for our graphing data as data=State.users_for_graph. We also pass in a rx.recharts.bar component which represents the bars on the graph. The rx.recharts.bar component takes in the data_key prop which is the key in the data dictionary that represents the y value of the bar. The stroke and fill props are used to set the color of the bars.

The rx.recharts.bar_chart component also takes in rx.recharts.x_axis and rx.recharts.y_axis components which represent the x and y axes of the graph. The data_key prop of the rx.recharts.x_axis component is set to the key in the data dictionary that represents the x value of the bar. Finally we add width and height props to set the size of the graph.

Finally we add this graph() component to our index() component so that the graph is rendered on the page. The code for the full app with the graph included is below. If you try this out you will see that the graph updates whenever you add a new user to the table.

NameEmailGender
Danilo Sousa[email protected]Male
Zahra Ambessa[email protected]Female

One thing you may have noticed about your app is that the graph does not appear initially when you run the app, and that you must add a user to the table for it to first appear. This occurs because the transform_data event handler is only called when a user is added to the table. In the next section we will explore a solution to this.

Final Cleanup

Revisiting app.add_page

At the beginning of this tutorial we mentioned that the app.add_page function is required for every Reflex app. This function is used to add a component to a page.

The app.add_page currently looks like this app.add_page(index). We could change the route that the page renders on by setting the route prop such as route="/custom-route", thos would change the route to http://localhost:3000/custom-route for this page.

We can also set a title to be shown in the browser tab and a description as shown in search results.

To solve the problem we had above about our graph not loading when the page loads, we can use on_load inside of app.add_page to call the transform_data event handler when the page loads. This would look like on_load=State.transform_data. Below see what our app.add_page would look like with some of the changes above added.

NameEmailGender
Danilo Sousa[email protected]Male
Zahra Ambessa[email protected]Female

Revisiting app=rx.App()

At the beginning of the tutorial we also mentioned that we defined our app using app=rx.App(). We can also pass in some props to the rx.App component to customize the app.

The most important one is theme which allows you to customize the look and feel of the app. The theme prop takes in an rx.theme component which has several props that can be set.

The radius prop sets the global radius value for the app that is inherited by all components that have a radius prop. It can be overwritten locally for a specific component by manually setting the radius prop.

The accent_color prop sets the accent color of the app. Check out other options for the accent color here.

To see other props that can be set at the app level check out this documentation

Unfortunately in this tutoial here we cannot actually apply this to the live example on the page, but if you copy and paste the code below into a reflex app locally you can see it in action.

Conclusion

Finally let's make some final styling updates to our app. We will add some hover styling to the table rows and center the table inside the show_user with style={"_hover": \{"bg": rx.color("gray", 3)}}, align="center".

In addition, we will add some width="100%" and align="center" to the index() component to center the items on the page and ensure they stretch the full width of the page.

Check out the full code and interactive app below:

NameEmailGender
Danilo Sousa[email protected]Male
Zahra Ambessa[email protected]Female

And that is it for your first dashboard tutorial. In this tutorial we have created

  • a table to display user data
  • a form to add new users to the table
  • a dialog to showcase the form
  • a graph to visualize the user data

In addition to the above we have we have

  • explored state to allow you to show dynamic data that changes over time
  • explored events to allow you to make your app interactive and respond to user actions
  • added styling to the app to make it look better

Advanced Section (Hooking this up to a Database)

Coming Soon!