Components With State#

Components often need to change what’s on the screen as a result of an interaction. For example, typing into the form should update the input field, clicking “next” on an image carousel should change which image is displayed, clicking “buy” should put a product in the shopping cart. Components need to “remember” things like the current input value, the current image, the shopping cart. In ReactPy, this kind of component-specific memory is called state.

When Variables Aren’t Enough#

Below is a gallery of images about sculpture. Clicking the “Next” button should increment the index and, as a result, change what image is displayed. However, this does not work:

 1import json
 2from pathlib import Path
 3
 4from reactpy import component, html, run
 5
 6
 7HERE = Path(__file__)
 8DATA_PATH = HERE.parent / "data.json"
 9sculpture_data = json.loads(DATA_PATH.read_text())
10
11
12@component
13def Gallery():
14    index = 0
15
16    def handle_click(event):
17        index = index + 1
18
19    bounded_index = index % len(sculpture_data)
20    sculpture = sculpture_data[bounded_index]
21    alt = sculpture["alt"]
22    artist = sculpture["artist"]
23    description = sculpture["description"]
24    name = sculpture["name"]
25    url = sculpture["url"]
26
27    return html.div(
28        html.button({"on_click": handle_click}, "Next"),
29        html.h2(name, " by ", artist),
30        html.p(f"({bounded_index + 1} or {len(sculpture_data)})"),
31        html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
32        html.p(description),
33    )
34
35
36run(Gallery)
[
  {
    "name": "Homenaje a la Neurocirugía",
    "artist": "Marta Colvin Andrade",
    "description": "Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Homenaje_a_la_Neurocirug%C3%ADa%2C_Instituto_de_Neurocirug%C3%ADa%2C_Providencia%2C_Santiago_20200106_02.jpg/1024px-Homenaje_a_la_Neurocirug%C3%ADa%2C_Instituto_de_Neurocirug%C3%ADa%2C_Providencia%2C_Santiago_20200106_02.jpg",
    "alt": "A bronze statue of two crossed hands delicately holding a human brain in their fingertips."
  },
  {
    "name": "Eternal Presence",
    "artist": "John Woodrow Wilson",
    "description": "Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as \"a symbolic Black presence infused with a sense of universal humanity.\"",
    "url": "https://upload.wikimedia.org/wikipedia/commons/6/6f/Chicago%2C_Illinois_Eternal_Silence1_crop.jpg",
    "alt": "The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity."
  },
  {
    "name": "Moai",
    "artist": "Unknown Artist",
    "description": "Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/5/50/AhuTongariki.JPG",
    "alt": "Three monumental stone busts with the heads that are disproportionately large with somber faces."
  },
  {
    "name": "Blue Nana",
    "artist": "Niki de Saint Phalle",
    "description": "The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c8/Blue_Nana_-_panoramio.jpg/1024px-Blue_Nana_-_panoramio.jpg",
    "alt": "A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy."
  },
  {
    "name": "Cavaliere",
    "artist": "Lamidi Olonade Fakeye",
    "description": "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/3/34/Nigeria%2C_lamidi_olonade_fakeye%2C_cavaliere%2C_1992.jpg",
    "alt": "An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns."
  },
  {
    "name": "Big Bellies",
    "artist": "Alina Szapocznikow",
    "description": "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/92/KMM_Szapocznikow.JPG/200px-KMM_Szapocznikow.JPG",
    "alt": "The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures."
  },
  {
    "name": "Terracotta Army",
    "artist": "Unknown Artist",
    "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg/1920px-2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg",
    "alt": "12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor."
  },
  {
    "name": "Lunar Landscape",
    "artist": "Louise Nevelson",
    "description": "Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/1999-3-A--J_s.jpg/220px-1999-3-A--J_s.jpg",
    "alt": "A black matte sculpture where the individual elements are initially indistinguishable."
  },
  {
    "name": "Aureole",
    "artist": "Ranjani Shettar",
    "description": "Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a \"fine synthesis of unlikely materials.\"",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/50/Shettar-_5854-sm_%285132866765%29.jpg/399px-Shettar-_5854-sm_%285132866765%29.jpg",
    "alt": "A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light."
  },
  {
    "name": "Hippos",
    "artist": "Taipei Zoo",
    "description": "The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e3/Hippo_sculpture_Taipei_Zoo_20543.jpg/250px-Hippo_sculpture_Taipei_Zoo_20543.jpg",
    "alt": "A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming."
  }
]

Note

Try clicking the button to see that it does not cause a change.

After clicking “Next”, if you check your web server’s logs, you’ll discover an UnboundLocalError error. It turns out that in this case, the index = index + 1 statement is similar to trying to set global variables. Technically there’s a way to fix this error, but even if we did, that still wouldn’t fix the underlying problems:

  1. Local variables do not persist across component renders - when a component is updated, its associated function gets called again. That is, it renders. As a result, all the local state that was created the last time the function was called gets destroyed when it updates.

  2. Changes to local variables do not cause components to re-render - there’s no way for ReactPy to observe when these variables change. Thus ReactPy is not aware that something has changed and that a re-render should take place.

To address these problems, ReactPy provides the use_state() “hook” which provides:

  1. A state variable whose data is retained across renders.

  2. A state setter function that can be used to update that variable and trigger a render.

Adding State to Components#

To create a state variable and state setter with use_state() hook as described above, we’ll begin by importing it:

from reactpy import use_state

Then we’ll make the following changes to our code from before:

-    index = 0
+    index, set_index = use_state

     def handle_click(event):
-        index = index + 1
+        set_index(index + 1)

After making those changes we should get:

14    index, set_index = use_state(0)
15
16    def handle_click(event):
17        set_index(index + 1)

We’ll talk more about what this is doing shortly, but for now let’s just verify that this does in fact fix the problems from before:

import json
from pathlib import Path

from reactpy import component, hooks, html, run

HERE = Path(__file__)
DATA_PATH = HERE.parent / "data.json"
sculpture_data = json.loads(DATA_PATH.read_text())


@component
def Gallery():
    index, set_index = hooks.use_state(0)

    def handle_click(event):
        set_index(index + 1)

    bounded_index = index % len(sculpture_data)
    sculpture = sculpture_data[bounded_index]
    alt = sculpture["alt"]
    artist = sculpture["artist"]
    description = sculpture["description"]
    name = sculpture["name"]
    url = sculpture["url"]

    return html.div(
        html.button({"on_click": handle_click}, "Next"),
        html.h2(name, " by ", artist),
        html.p(f"({bounded_index + 1} of {len(sculpture_data)})"),
        html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
        html.p(description),
    )


run(Gallery)
[
  {
    "name": "Homenaje a la Neurocirugía",
    "artist": "Marta Colvin Andrade",
    "description": "Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Homenaje_a_la_Neurocirug%C3%ADa%2C_Instituto_de_Neurocirug%C3%ADa%2C_Providencia%2C_Santiago_20200106_02.jpg/1024px-Homenaje_a_la_Neurocirug%C3%ADa%2C_Instituto_de_Neurocirug%C3%ADa%2C_Providencia%2C_Santiago_20200106_02.jpg",
    "alt": "A bronze statue of two crossed hands delicately holding a human brain in their fingertips."
  },
  {
    "name": "Eternal Presence",
    "artist": "John Woodrow Wilson",
    "description": "Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as \"a symbolic Black presence infused with a sense of universal humanity.\"",
    "url": "https://upload.wikimedia.org/wikipedia/commons/6/6f/Chicago%2C_Illinois_Eternal_Silence1_crop.jpg",
    "alt": "The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity."
  },
  {
    "name": "Moai",
    "artist": "Unknown Artist",
    "description": "Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/5/50/AhuTongariki.JPG",
    "alt": "Three monumental stone busts with the heads that are disproportionately large with somber faces."
  },
  {
    "name": "Blue Nana",
    "artist": "Niki de Saint Phalle",
    "description": "The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c8/Blue_Nana_-_panoramio.jpg/1024px-Blue_Nana_-_panoramio.jpg",
    "alt": "A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy."
  },
  {
    "name": "Cavaliere",
    "artist": "Lamidi Olonade Fakeye",
    "description": "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/3/34/Nigeria%2C_lamidi_olonade_fakeye%2C_cavaliere%2C_1992.jpg",
    "alt": "An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns."
  },
  {
    "name": "Big Bellies",
    "artist": "Alina Szapocznikow",
    "description": "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/92/KMM_Szapocznikow.JPG/200px-KMM_Szapocznikow.JPG",
    "alt": "The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures."
  },
  {
    "name": "Terracotta Army",
    "artist": "Unknown Artist",
    "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg/1920px-2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg",
    "alt": "12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor."
  },
  {
    "name": "Lunar Landscape",
    "artist": "Louise Nevelson",
    "description": "Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/1999-3-A--J_s.jpg/220px-1999-3-A--J_s.jpg",
    "alt": "A black matte sculpture where the individual elements are initially indistinguishable."
  },
  {
    "name": "Aureole",
    "artist": "Ranjani Shettar",
    "description": "Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a \"fine synthesis of unlikely materials.\"",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/50/Shettar-_5854-sm_%285132866765%29.jpg/399px-Shettar-_5854-sm_%285132866765%29.jpg",
    "alt": "A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light."
  },
  {
    "name": "Hippos",
    "artist": "Taipei Zoo",
    "description": "The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e3/Hippo_sculpture_Taipei_Zoo_20543.jpg/250px-Hippo_sculpture_Taipei_Zoo_20543.jpg",
    "alt": "A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming."
  }
]

Your First Hook#

In ReactPy, use_state, as well as any other function whose name starts with use, is called a “hook”. These are special functions that should only be called while ReactPy is rendering. They let you “hook into” the different capabilities of ReactPy’s components of which use_state is just one (well get into the other later).

While hooks are just normal functions, but it’s helpful to think of them as unconditioned declarations about a component’s needs. In other words, you’ll “use” hooks at the top of your component in the same way you might “import” modules at the top of your Python files.

Introduction to use_state#

When you call use_state() inside the body of a component’s render function, you’re declaring that this component needs to remember something. That “something” which needs to be remembered, is known as state. So when we look at an assignment expression like the one below

index, set_index = use_state(0)

we should read it as saying that index is a piece of state which must be remembered by the component that declared it. The argument to use_state (in this case 0) is then conveying what the initial value for index is.

We should then understand that each time the component which owns this state renders use_state will return a tuple containing two values - the current value of the state (index) and a function to change that value the next time the component is rendered. Thus, in this example:

  • index - is a state variable containing the currently stored value.

  • set_index - is a state setter for changing that value and triggering a re-render of the component.

The convention is that, if you name your state variable thing, your state setter should be named set_thing. While you could name them anything you want, adhering to the convention makes things easier to understand across projects.


To understand how this works in context, let’s break down our example by examining key moments in the execution of the Gallery component. Each numbered tab in the section below highlights a line of code where something of interest occurs:

Hint

Try clicking through the numbered tabs to each highlighted step of execution

Initial render

def Gallery():
    index, set_index = hooks.use_state(0)

    def handle_click(event):
        set_index(index + 1)

    bounded_index = index % len(sculpture_data)
    sculpture = sculpture_data[bounded_index]
    alt = sculpture["alt"]
    artist = sculpture["artist"]
    description = sculpture["description"]
    name = sculpture["name"]
    url = sculpture["url"]

    return html.div(
        html.button({"on_click": handle_click}, "Next"),
        html.h2(name, " by ", artist),
        html.p(f"({bounded_index + 1} of {len(sculpture_data)})"),
        html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
        html.p(description),
    )

At this point, we’ve just begun to render the Gallery component. As yet, ReactPy is not aware that this component has any state or what view it will display. This will change in a moment though when we move to the next line…

Initial state declaration

def Gallery():
    index, set_index = hooks.use_state(0)

    def handle_click(event):
        set_index(index + 1)

    bounded_index = index % len(sculpture_data)
    sculpture = sculpture_data[bounded_index]
    alt = sculpture["alt"]
    artist = sculpture["artist"]
    description = sculpture["description"]
    name = sculpture["name"]
    url = sculpture["url"]

    return html.div(
        html.button({"on_click": handle_click}, "Next"),
        html.h2(name, " by ", artist),
        html.p(f"({bounded_index + 1} of {len(sculpture_data)})"),
        html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
        html.p(description),
    )

The Gallery component has just declared some state. ReactPy now knows that it must remember the index and trigger an update of this component when set_index is called. Currently the value of index is 0 as per the default value given to use_state. Thus, the resulting view will display information about the first item in our sculpture_data list.

Define event handler

def Gallery():
    index, set_index = hooks.use_state(0)

    def handle_click(event):
        set_index(index + 1)

    bounded_index = index % len(sculpture_data)
    sculpture = sculpture_data[bounded_index]
    alt = sculpture["alt"]
    artist = sculpture["artist"]
    description = sculpture["description"]
    name = sculpture["name"]
    url = sculpture["url"]

    return html.div(
        html.button({"on_click": handle_click}, "Next"),
        html.h2(name, " by ", artist),
        html.p(f"({bounded_index + 1} of {len(sculpture_data)})"),
        html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
        html.p(description),
    )

We’ve now defined an event handler that we intend to assign to a button in the view. This will respond once the user clicks that button. The action this handler performs is to update the value of index and schedule our Gallery component to update.

Return the view

def Gallery():
    index, set_index = hooks.use_state(0)

    def handle_click(event):
        set_index(index + 1)

    bounded_index = index % len(sculpture_data)
    sculpture = sculpture_data[bounded_index]
    alt = sculpture["alt"]
    artist = sculpture["artist"]
    description = sculpture["description"]
    name = sculpture["name"]
    url = sculpture["url"]

    return html.div(
        html.button({"on_click": handle_click}, "Next"),
        html.h2(name, " by ", artist),
        html.p(f"({bounded_index + 1} of {len(sculpture_data)})"),
        html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
        html.p(description),
    )

The handle_click function we defined above has now been assigned to a button in the view and we are about to display information about the first item in out sculpture_data list. When the view is ultimately displayed, if a user clicks the “Next” button, the handler we just assigned will be triggered. Until that point though, the application will remain static.

User interaction

def Gallery():
    index, set_index = hooks.use_state(0)

    def handle_click(event):
        set_index(index + 1)

    bounded_index = index % len(sculpture_data)
    sculpture = sculpture_data[bounded_index]
    alt = sculpture["alt"]
    artist = sculpture["artist"]
    description = sculpture["description"]
    name = sculpture["name"]
    url = sculpture["url"]

    return html.div(
        html.button({"on_click": handle_click}, "Next"),
        html.h2(name, " by ", artist),
        html.p(f"({bounded_index + 1} of {len(sculpture_data)})"),
        html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
        html.p(description),
    )

A user has just clicked the button 🖱️! ReactPy has sent information about the event to the handle_click function and it is about to execute. In a moment we will update the state of this component and schedule a re-render.

New state is set

def Gallery():
    index, set_index = hooks.use_state(0)

    def handle_click(event):
        set_index(index + 1)

    bounded_index = index % len(sculpture_data)
    sculpture = sculpture_data[bounded_index]
    alt = sculpture["alt"]
    artist = sculpture["artist"]
    description = sculpture["description"]
    name = sculpture["name"]
    url = sculpture["url"]

    return html.div(
        html.button({"on_click": handle_click}, "Next"),
        html.h2(name, " by ", artist),
        html.p(f"({bounded_index + 1} of {len(sculpture_data)})"),
        html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
        html.p(description),
    )

We’ve just now told ReactPy that we want to update the state of our Gallery and that it needs to be re-rendered. More specifically, we are incrementing its index, and once Gallery re-renders the index will be 1. Importantly, at this point, the value of index is still 0! This will only change once the component begins to re-render.

Next render begins

def Gallery():
    index, set_index = hooks.use_state(0)

    def handle_click(event):
        set_index(index + 1)

    bounded_index = index % len(sculpture_data)
    sculpture = sculpture_data[bounded_index]
    alt = sculpture["alt"]
    artist = sculpture["artist"]
    description = sculpture["description"]
    name = sculpture["name"]
    url = sculpture["url"]

    return html.div(
        html.button({"on_click": handle_click}, "Next"),
        html.h2(name, " by ", artist),
        html.p(f"({bounded_index + 1} of {len(sculpture_data)})"),
        html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
        html.p(description),
    )

The scheduled re-render of Gallery has just begun. ReactPy has now updated its internal state store such that, the next time we call use_state we will get back the updated value of index.

Next state is acquired

def Gallery():
    index, set_index = hooks.use_state(0)

    def handle_click(event):
        set_index(index + 1)

    bounded_index = index % len(sculpture_data)
    sculpture = sculpture_data[bounded_index]
    alt = sculpture["alt"]
    artist = sculpture["artist"]
    description = sculpture["description"]
    name = sculpture["name"]
    url = sculpture["url"]

    return html.div(
        html.button({"on_click": handle_click}, "Next"),
        html.h2(name, " by ", artist),
        html.p(f"({bounded_index + 1} of {len(sculpture_data)})"),
        html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
        html.p(description),
    )

With ReactPy’s state store updated, as we call use_state, instead of returning 0 for the value of index as it did before, ReactPy now returns the value 1. With this change the view we display will be altered - instead of displaying data for the first item in our sculpture_data list we will now display information about the second.

Repeat...

def Gallery():
    index, set_index = hooks.use_state(0)

    def handle_click(event):
        set_index(index + 1)

    bounded_index = index % len(sculpture_data)
    sculpture = sculpture_data[bounded_index]
    alt = sculpture["alt"]
    artist = sculpture["artist"]
    description = sculpture["description"]
    name = sculpture["name"]
    url = sculpture["url"]

    return html.div(
        html.button({"on_click": handle_click}, "Next"),
        html.h2(name, " by ", artist),
        html.p(f"({bounded_index + 1} of {len(sculpture_data)})"),
        html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
        html.p(description),
    )

From this point on, the steps remain the same. The only difference being the progressively incrementing index each time the user clicks the “Next” button and the view which is altered to to reflect the currently indexed item in the sculpture_data list.

Note

Once we reach the end of the sculpture_data list the view will return back to the first item since we create a bounded_index by doing a modulo of the index with the length of the list (index % len(sculpture_data)). Ideally we would do this bounding at the time we call set_index to prevent index from incrementing to infinity, but to keep things simple in this examples, we’ve kept this logic separate.

Multiple State Declarations#

The powerful thing about hooks like use_state() is that you’re not limited to just one state declaration. You can call use_state() as many times as you need to in one component. For example, in the example below we’ve added a show_more state variable along with a few other modifications (e.g. renaming handle_click) to make the description for each sculpture optionally displayed. Only when the user clicks the “Show details” button is this description shown:

import json
from pathlib import Path

from reactpy import component, hooks, html, run

HERE = Path(__file__)
DATA_PATH = HERE.parent / "data.json"
sculpture_data = json.loads(DATA_PATH.read_text())


@component
def Gallery():
    index, set_index = hooks.use_state(0)
    show_more, set_show_more = hooks.use_state(False)

    def handle_next_click(event):
        set_index(index + 1)

    def handle_more_click(event):
        set_show_more(not show_more)

    bounded_index = index % len(sculpture_data)
    sculpture = sculpture_data[bounded_index]
    alt = sculpture["alt"]
    artist = sculpture["artist"]
    description = sculpture["description"]
    name = sculpture["name"]
    url = sculpture["url"]

    return html.div(
        html.button({"on_click": handle_next_click}, "Next"),
        html.h2(name, " by ", artist),
        html.p(f"({bounded_index + 1} or {len(sculpture_data)})"),
        html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
        html.div(
            html.button(
                {"on_click": handle_more_click},
                f"{('Show' if show_more else 'Hide')} details",
            ),
            (html.p(description) if show_more else ""),
        ),
    )


run(Gallery)
[
  {
    "name": "Homenaje a la Neurocirugía",
    "artist": "Marta Colvin Andrade",
    "description": "Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Homenaje_a_la_Neurocirug%C3%ADa%2C_Instituto_de_Neurocirug%C3%ADa%2C_Providencia%2C_Santiago_20200106_02.jpg/1024px-Homenaje_a_la_Neurocirug%C3%ADa%2C_Instituto_de_Neurocirug%C3%ADa%2C_Providencia%2C_Santiago_20200106_02.jpg",
    "alt": "A bronze statue of two crossed hands delicately holding a human brain in their fingertips."
  },
  {
    "name": "Eternal Presence",
    "artist": "John Woodrow Wilson",
    "description": "Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as \"a symbolic Black presence infused with a sense of universal humanity.\"",
    "url": "https://upload.wikimedia.org/wikipedia/commons/6/6f/Chicago%2C_Illinois_Eternal_Silence1_crop.jpg",
    "alt": "The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity."
  },
  {
    "name": "Moai",
    "artist": "Unknown Artist",
    "description": "Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/5/50/AhuTongariki.JPG",
    "alt": "Three monumental stone busts with the heads that are disproportionately large with somber faces."
  },
  {
    "name": "Blue Nana",
    "artist": "Niki de Saint Phalle",
    "description": "The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c8/Blue_Nana_-_panoramio.jpg/1024px-Blue_Nana_-_panoramio.jpg",
    "alt": "A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy."
  },
  {
    "name": "Cavaliere",
    "artist": "Lamidi Olonade Fakeye",
    "description": "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/3/34/Nigeria%2C_lamidi_olonade_fakeye%2C_cavaliere%2C_1992.jpg",
    "alt": "An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns."
  },
  {
    "name": "Big Bellies",
    "artist": "Alina Szapocznikow",
    "description": "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/92/KMM_Szapocznikow.JPG/200px-KMM_Szapocznikow.JPG",
    "alt": "The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures."
  },
  {
    "name": "Terracotta Army",
    "artist": "Unknown Artist",
    "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg/1920px-2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg",
    "alt": "12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor."
  },
  {
    "name": "Lunar Landscape",
    "artist": "Louise Nevelson",
    "description": "Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/1999-3-A--J_s.jpg/220px-1999-3-A--J_s.jpg",
    "alt": "A black matte sculpture where the individual elements are initially indistinguishable."
  },
  {
    "name": "Aureole",
    "artist": "Ranjani Shettar",
    "description": "Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a \"fine synthesis of unlikely materials.\"",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/50/Shettar-_5854-sm_%285132866765%29.jpg/399px-Shettar-_5854-sm_%285132866765%29.jpg",
    "alt": "A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light."
  },
  {
    "name": "Hippos",
    "artist": "Taipei Zoo",
    "description": "The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e3/Hippo_sculpture_Taipei_Zoo_20543.jpg/250px-Hippo_sculpture_Taipei_Zoo_20543.jpg",
    "alt": "A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming."
  }
]

It’s generally a good idea to define separate state variables if the data they represent is unrelated. In this case, index corresponds to what sculpture information is being displayed and show_more is solely concerned with whether the description for a given sculpture is shown. Put other way index is concerned with what information is displayed while show_more is concerned with how it is displayed. Conversely though, if you have a form with many fields, it probably makes sense to have a single object that holds the data for all the fields rather than an object per-field.

Note

This topic is discussed more in the How to Structure State 🚧 section.

State is Isolated and Private#

State is local to a component instance on the screen. In other words, if you render the same component twice, each copy will have completely isolated state! Changing one of them will not affect the other.

In this example, the Gallery component from earlier is rendered twice with no changes to its logic. Try clicking the buttons inside each of the galleries. Notice that their state is independent:

import json
from pathlib import Path

from reactpy import component, hooks, html, run

HERE = Path(__file__)
DATA_PATH = HERE.parent / "data.json"
sculpture_data = json.loads(DATA_PATH.read_text())


@component
def Gallery():
    index, set_index = hooks.use_state(0)
    show_more, set_show_more = hooks.use_state(False)

    def handle_next_click(event):
        set_index(index + 1)

    def handle_more_click(event):
        set_show_more(not show_more)

    bounded_index = index % len(sculpture_data)
    sculpture = sculpture_data[bounded_index]
    alt = sculpture["alt"]
    artist = sculpture["artist"]
    description = sculpture["description"]
    name = sculpture["name"]
    url = sculpture["url"]

    return html.div(
        html.button({"on_click": handle_next_click}, "Next"),
        html.h2(name, " by ", artist),
        html.p(f"({bounded_index + 1} or {len(sculpture_data)})"),
        html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
        html.div(
            html.button(
                {"on_click": handle_more_click},
                f"{('Show' if show_more else 'Hide')} details",
            ),
            (html.p(description) if show_more else ""),
        ),
    )


@component
def App():
    return html.div(
        html.section({"style": {"width": "50%", "float": "left"}}, Gallery()),
        html.section({"style": {"width": "50%", "float": "left"}}, Gallery()),
    )


run(App)
[
  {
    "name": "Homenaje a la Neurocirugía",
    "artist": "Marta Colvin Andrade",
    "description": "Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Homenaje_a_la_Neurocirug%C3%ADa%2C_Instituto_de_Neurocirug%C3%ADa%2C_Providencia%2C_Santiago_20200106_02.jpg/1024px-Homenaje_a_la_Neurocirug%C3%ADa%2C_Instituto_de_Neurocirug%C3%ADa%2C_Providencia%2C_Santiago_20200106_02.jpg",
    "alt": "A bronze statue of two crossed hands delicately holding a human brain in their fingertips."
  },
  {
    "name": "Eternal Presence",
    "artist": "John Woodrow Wilson",
    "description": "Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as \"a symbolic Black presence infused with a sense of universal humanity.\"",
    "url": "https://upload.wikimedia.org/wikipedia/commons/6/6f/Chicago%2C_Illinois_Eternal_Silence1_crop.jpg",
    "alt": "The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity."
  },
  {
    "name": "Moai",
    "artist": "Unknown Artist",
    "description": "Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/5/50/AhuTongariki.JPG",
    "alt": "Three monumental stone busts with the heads that are disproportionately large with somber faces."
  },
  {
    "name": "Blue Nana",
    "artist": "Niki de Saint Phalle",
    "description": "The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c8/Blue_Nana_-_panoramio.jpg/1024px-Blue_Nana_-_panoramio.jpg",
    "alt": "A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy."
  },
  {
    "name": "Cavaliere",
    "artist": "Lamidi Olonade Fakeye",
    "description": "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/3/34/Nigeria%2C_lamidi_olonade_fakeye%2C_cavaliere%2C_1992.jpg",
    "alt": "An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns."
  },
  {
    "name": "Big Bellies",
    "artist": "Alina Szapocznikow",
    "description": "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/92/KMM_Szapocznikow.JPG/200px-KMM_Szapocznikow.JPG",
    "alt": "The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures."
  },
  {
    "name": "Terracotta Army",
    "artist": "Unknown Artist",
    "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg/1920px-2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg",
    "alt": "12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor."
  },
  {
    "name": "Lunar Landscape",
    "artist": "Louise Nevelson",
    "description": "Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/1999-3-A--J_s.jpg/220px-1999-3-A--J_s.jpg",
    "alt": "A black matte sculpture where the individual elements are initially indistinguishable."
  },
  {
    "name": "Aureole",
    "artist": "Ranjani Shettar",
    "description": "Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a \"fine synthesis of unlikely materials.\"",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/50/Shettar-_5854-sm_%285132866765%29.jpg/399px-Shettar-_5854-sm_%285132866765%29.jpg",
    "alt": "A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light."
  },
  {
    "name": "Hippos",
    "artist": "Taipei Zoo",
    "description": "The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.",
    "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e3/Hippo_sculpture_Taipei_Zoo_20543.jpg/250px-Hippo_sculpture_Taipei_Zoo_20543.jpg",
    "alt": "A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming."
  }
]

This is what makes state different from regular variables that you might declare at the top of your module. State is not tied to a particular function call or a place in the code, but it’s “local” to the specific place on the screen. You rendered two Gallery components, so their state is stored separately.

Also notice how the Page component doesn’t “know” anything about the Gallery state or even whether it has any. Unlike props, state is fully private to the component declaring it. The parent component can’t change it. This lets you add state to any component or remove it without impacting the rest of the components.

Read More

What if you wanted both galleries to keep their states in sync? The right way to do it in ReactPy is to remove state from child components and add it to their closest shared parent.