Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

State error: retaining state in inputs and dropdowns #693

Unanswered
jkrobicki asked this question in Problem
Discussion options

Description:
State retains in inputs, when switching sites

retaining_state_ab

However if I return one of the pages in different schema (e.g. if I add one html.div() above), problem doesn't occure anymore
input_works

Same issue happens with dropdowns

Code with the issue:

from idom import html, run, use_state, component, event, vdom, EventHandler
from pathlib import Path
from idom.server.sanic import PerClientStateServer
import json
import requests
from sanic import Sanic
from typing import List, Callable
def FlexContainer(*args: html):
 return html.div({"class": "flex w-full"}, args)
@component
def ListPages(current_page, set_current_page, pages: List[str], title: str = ""):
 @event(prevent_default=True)
 def handle_click(event):
 set_current_page(event["target"]["value"])
 anchors = []
 for page in pages:
 anchors.append(
 vdom(
 "button",
 {
 "href": f"#{page}",
 "value": page,
 "onClick": handle_click,
 },
 page,
 )
 )
 return html.div(anchors)
@component
def Sidebar(current_page, set_current_page, pages: List[str], title: str = ""):
 return html.div(
 html.div(
 html.a(
 {
 "href": "#",
 },
 title,
 ),
 ),
 html.nav(
 ListPages(current_page, set_current_page, pages=pages, title=title),
 ),
 )
@component
def Input(
 set_value: Callable,
 type: str = "text",
):
 return html.input(
 {
 "type": type,
 "onChange": lambda event: set_value(event["target"]["value"]),
 }
 )
@component
def users_page():
 a, set_a = use_state("")
 inp_a = Input(set_a)
 return html.div(inp_a, "SiteA")
@component
def timelogs_page():
 b, set_b = use_state("")
 inp_b = Input(set_b)
 return html.div(inp_b, "SiteB")
@component
def page():
 current_page, set_current_page = use_state("SiteA")
 pages = ["SiteA", "SiteB"]
 if current_page == "SiteA":
 current_page_component = users_page(key="site_a")
 elif current_page == "SiteB":
 current_page_component = timelogs_page(key="site_b")
 return html.div(
 {"class": "flex w-full"},
 Sidebar(current_page, set_current_page, pages=pages),
 FlexContainer(current_page_component),
 )
run(page)
```python
Partly solving change:
@component
def users_page():
 print("a")
 a, set_a = use_state("")
 inp_a = Input(set_a)
 return html.div(html.div(inp_a, "SiteA"))
You must be logged in to vote

Replies: 2 comments 6 replies

Comment options

This looks to be related to your solution here: #668

By removing the "value" key from the input, the client doesn't know it should reset the text field.

I think I need to resolve the underlying issue with onChange to make that work.

You must be logged in to vote
4 replies
Comment options

Here's the issue: #684

Comment options

ah, so you are saying that the second render would replace the input field if it had a value of some kind, makes sense, it would replace it

Comment options

I'm a little unsure of the fix so I put out an alpha release: #695

Comment options

I don't think that last release fixed anything about onChange. I'll try to figure out how to fix it this evening.

Comment options

I've noticed more state retaining issues, I'll add them to this conversation

state_error

When first dropdown is selected it populates the second dropdown with data. When another option in first dropdown is selected it auto-chooses one of options in the second dropdown, but only visually in the component, the state doesn't change.
image
For that you have to deselect and again select the option.

Code:

from fastapi import FastAPI
from idom import component, html, use_state
from idom.backend.fastapi import configure
@component
def Select(set_value, data, label):
 options = []
 for row in data:
 option = html.option({"value": row["display_value"]}, row["display_value"])
 options.append(option)
 return html.select(
 {
 "onChange": lambda event: set_value(event["target"]["value"]),
 },
 html.option({"value": "value"}, label),
 options,
 )
@component
def SubmitButton(value1, set_value1, value2, set_value2):
 def handle_event(event):
 set_value1(value1)
 set_value2(value2)
 return html.button({"onclick": handle_event}, "Submit")
@component
def Page():
 title, set_title = use_state("Edibles group")
 subtitle, set_subtitle = use_state("example")
 dropdown_title, set_dropdown_title = use_state("")
 dropdown_subtitle, set_dropdown_subtitle = use_state("")
 edibles = [
 {"display_value": "Fruits"},
 {"display_value": "Vegetables"},
 ]
 def subtitle_dropdown(title_id):
 data = []
 if title_id == "Fruits":
 data = [
 {"display_value": "mango"},
 {"display_value": "kiwi"},
 ]
 elif title_id == "Vegetables":
 data = [
 {"display_value": "carrot"},
 {"display_value": "asparagus"},
 ]
 return data
 select_title = Select(set_dropdown_title, edibles, "select edible")
 select_subtitle = Select(
 set_value=set_dropdown_subtitle,
 data=subtitle_dropdown(dropdown_title),
 label="select example",
 )
 button = SubmitButton(dropdown_title, set_title, dropdown_subtitle, set_subtitle)
 return html.div(
 html.div(
 html.h1(title + ":"),
 html.h2(subtitle),
 ),
 select_title,
 select_subtitle,
 button,
 html.h3(f"dropdown edible state is: {dropdown_title}"),
 html.h3(f"dropdown example state is: {dropdown_subtitle}"),
 )
app = FastAPI()
configure(app, Page)
You must be logged in to vote
2 replies
Comment options

This is expected behavior.

I'm guessing your expectation is that, upon having the options change in html.select, its onChange callback should be triggered thus updating dropdown_subtitle. However, this is not the case. Instead, onChange only responds to user interaction. While it's probably not immediately obvious, it makes sense upon inspection, because you would not want to confuse a user's choice for one made by the application (in this case the changing options).

Thus, you must track whether or not the options have changed in order to forcibly trigger set_value since onChange won't do that for you. One way to handle this might look like the following:

@component
def Select(set_value, data, label):
 options = []
 for row in data:
 option = html.option({"value": row["display_value"]}, row["display_value"])
 options.append(option)
 @use_effect(dependencies=[data])
 def set_value_on_data_change():
 set_value(options[0])
 return html.select(
 {
 "onChange": lambda event: set_value(event["target"]["value"]),
 },
 html.option({"value": "value"}, label),
 options,
 )
@component
def Page():
 ...
 select_subtitle = Select(
 set_value=set_dropdown_subtitle,
 # use_memo will preserve the identity of the options list unless dropdown_title changes 
 data=use_memo(lambda: subtitle_dropdown(dropdown_title)),
 label="select example",
 )
 ...

You can play with the example below to see that the same thing happens in React:

import "./styles.css";
import * as React from "react";
export default function App() {
 const [firstValue, setFirstValue] = React.useState("x");
 const [secondValue, setSecondValue] = React.useState(null);
 const secondOptions = {
 x: [1, 2, 3],
 y: [4, 5, 6]
 }[firstValue];
 return (
 <>
 <Select setValue={setFirstValue} data={["x", "y"]} label="first" />
 <Select setValue={setSecondValue} data={secondOptions} label="second" />
 <p>Second value: {`${secondValue}`}</p>
 </>
 );
}
function Select({ setValue, data, label }) {
 const options = data.map((value) => (
 <option key={value} value={value}>
 {value}
 </option>
 ));
 return (
 <select
 onChange={(event) => {
 setValue(event.target.value);
 }}
 >
 {options}
 </select>
 );
}
Comment options

Ok, thanks for clarification!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet

AltStyle によって変換されたページ (->オリジナル) /