-
-
Notifications
You must be signed in to change notification settings - Fork 328
-
Description:
State retains in inputs, when switching sites
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"))
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 2 comments 6 replies
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
Here's the issue: #684
Beta Was this translation helpful? Give feedback.
All reactions
-
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
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
I'm a little unsure of the fix so I put out an alpha release: #695
Beta Was this translation helpful? Give feedback.
All reactions
-
I don't think that last release fixed anything about onChange. I'll try to figure out how to fix it this evening.
Beta Was this translation helpful? Give feedback.
All reactions
-
I've noticed more state retaining issues, I'll add them to this conversation
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)
Beta Was this translation helpful? Give feedback.
All reactions
-
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> ); }
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 2
-
Ok, thanks for clarification!
Beta Was this translation helpful? Give feedback.