diff --git a/docs/examples/css/managing_state/conditional_form_component.css b/docs/examples/css/managing_state/conditional_form_component.css new file mode 100644 index 000000000..2cc8b7afe --- /dev/null +++ b/docs/examples/css/managing_state/conditional_form_component.css @@ -0,0 +1,3 @@ +.Error { + color: red; +} diff --git a/docs/examples/css/managing_state/multiple_form_components.css b/docs/examples/css/managing_state/multiple_form_components.css new file mode 100644 index 000000000..b24e106e8 --- /dev/null +++ b/docs/examples/css/managing_state/multiple_form_components.css @@ -0,0 +1,13 @@ +section { + border-bottom: 1px solid #aaa; + padding: 20px; +} +h4 { + color: #222; +} +body { + margin: 0; +} +.Error { + color: red; +} diff --git a/docs/examples/css/managing_state/picture_component.css b/docs/examples/css/managing_state/picture_component.css new file mode 100644 index 000000000..85827067c --- /dev/null +++ b/docs/examples/css/managing_state/picture_component.css @@ -0,0 +1,28 @@ +body { + margin: 0; + padding: 0; + height: 250px; +} + +.background { + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background: #eee; +} + +.background--active { + background: #a6b5ff; +} + +.picture { + width: 200px; + height: 200px; + border-radius: 10px; +} + +.picture--active { + border: 5px solid #a6b5ff; +} diff --git a/docs/examples/python/managing_state/all_possible_states.py b/docs/examples/python/managing_state/all_possible_states.py new file mode 100644 index 000000000..a71f9b54a --- /dev/null +++ b/docs/examples/python/managing_state/all_possible_states.py @@ -0,0 +1,8 @@ +from reactpy import hooks + +# start +is_empty, set_is_empty = hooks.use_state(True) +is_typing, set_is_typing = hooks.use_state(False) +is_submitting, set_is_submitting = hooks.use_state(False) +is_success, set_is_success = hooks.use_state(False) +is_error, set_is_error = hooks.use_state(False) diff --git a/docs/examples/python/managing_state/alt_stateful_picture_component.py b/docs/examples/python/managing_state/alt_stateful_picture_component.py new file mode 100644 index 000000000..1fdd9c1ad --- /dev/null +++ b/docs/examples/python/managing_state/alt_stateful_picture_component.py @@ -0,0 +1,37 @@ +from reactpy import component, event, hooks, html + + +# start +@component +def picture(): + is_active, set_is_active = hooks.use_state(False) + + if is_active: + return html.div( + { + "class_name": "background", + "on_click": lambda event: set_is_active(False), + }, + html.img( + { + "on_click": event(stop_propagation=True), + "class_name": "picture picture--active", + "alt": "Rainbow houses in Kampung Pelangi, Indonesia", + "src": "https://i.imgur.com/5qwVYb1.jpeg", + } + ), + ) + else: + return html.div( + {"class_name": "background background--active"}, + html.img( + { + "on_click": event( + lambda event: set_is_active(True), stop_propagation=True + ), + "class_name": "picture", + "alt": "Rainbow houses in Kampung Pelangi, Indonesia", + "src": "https://i.imgur.com/5qwVYb1.jpeg", + } + ), + ) diff --git a/docs/examples/python/managing_state/basic_form_component.py b/docs/examples/python/managing_state/basic_form_component.py new file mode 100644 index 000000000..07328bff9 --- /dev/null +++ b/docs/examples/python/managing_state/basic_form_component.py @@ -0,0 +1,16 @@ +from reactpy import component, html + + +# start +@component +def form(status="empty"): + if status == "success": + return html.h1("That's right!") + else: + return html._( + html.h2("City quiz"), + html.p( + "In which city is there a billboard that turns air into drinkable water?" + ), + html.form(html.textarea(), html.br(), html.button("Submit")), + ) diff --git a/docs/examples/python/managing_state/conditional_form_component.py b/docs/examples/python/managing_state/conditional_form_component.py new file mode 100644 index 000000000..44f4f565d --- /dev/null +++ b/docs/examples/python/managing_state/conditional_form_component.py @@ -0,0 +1,42 @@ +from reactpy import component, html + + +# start +@component +def error(status): + if status == "error": + return html.p( + {"class_name": "error"}, "Good guess but a wrong answer. Try again!" + ) + else: + return "" + + +@component +def form(status="empty"): + # Try status="submitting", "error", "success" + if status == "success": + return html.h1("That's right!") + else: + return html._( + html.h2("City quiz"), + html.p( + "In which city is there a billboard that turns air into \ + drinkable water?" + ), + html.form( + html.textarea( + {"disabled": "True" if status == "submitting" else "False"} + ), + html.br(), + html.button( + { + "disabled": ( + True if status in ["empty", "submitting"] else "False" + ) + }, + "Submit", + ), + error(status), + ), + ) diff --git a/docs/examples/python/managing_state/multiple_form_components.py b/docs/examples/python/managing_state/multiple_form_components.py new file mode 100644 index 000000000..4d50bd643 --- /dev/null +++ b/docs/examples/python/managing_state/multiple_form_components.py @@ -0,0 +1,16 @@ +from conditional_form_component import form + +from reactpy import component, html + + +# start +@component +def item(status): + return html.section(html.h4("Form", status, ":"), form(status)) + + +@component +def app(): + statuses = ["empty", "typing", "submitting", "success", "error"] + status_list = [item(status) for status in statuses] + return html._(status_list) diff --git a/docs/examples/python/managing_state/necessary_states.py b/docs/examples/python/managing_state/necessary_states.py new file mode 100644 index 000000000..ee70c5686 --- /dev/null +++ b/docs/examples/python/managing_state/necessary_states.py @@ -0,0 +1,5 @@ +from reactpy import hooks + +# start +answer, set_answer = hooks.use_state("") +error, set_error = hooks.use_state(None) diff --git a/docs/examples/python/managing_state/picture_component.py b/docs/examples/python/managing_state/picture_component.py new file mode 100644 index 000000000..bc60a8143 --- /dev/null +++ b/docs/examples/python/managing_state/picture_component.py @@ -0,0 +1,16 @@ +from reactpy import component, html + + +# start +@component +def picture(): + return html.div( + {"class_name": "background background--active"}, + html.img( + { + "class_name": "picture", + "alt": "Rainbow houses in Kampung Pelangi, Indonesia", + "src": "https://i.imgur.com/5qwVYb1.jpeg", + } + ), + ) diff --git a/docs/examples/python/managing_state/refactored_states.py b/docs/examples/python/managing_state/refactored_states.py new file mode 100644 index 000000000..3d080c92c --- /dev/null +++ b/docs/examples/python/managing_state/refactored_states.py @@ -0,0 +1,6 @@ +from reactpy import hooks + +# start +answer, set_answer = hooks.use_state("") +error, set_error = hooks.use_state(None) +status, set_status = hooks.use_state("typing") # 'typing', 'submitting', or 'success' diff --git a/docs/examples/python/managing_state/stateful_form_component.py b/docs/examples/python/managing_state/stateful_form_component.py new file mode 100644 index 000000000..c9d8ffa64 --- /dev/null +++ b/docs/examples/python/managing_state/stateful_form_component.py @@ -0,0 +1,70 @@ +import asyncio + +from reactpy import component, event, hooks, html + + +async def submit_form(*args): + await asyncio.wait(5) + + +# start +@component +def error_msg(error): + if error: + return html.p( + {"class_name": "error"}, "Good guess but a wrong answer. Try again!" + ) + else: + return "" + + +@component +def form(status="empty"): + answer, set_answer = hooks.use_state("") + error, set_error = hooks.use_state(None) + status, set_status = hooks.use_state("typing") + + @event(prevent_default=True) + async def handle_submit(event): + set_status("submitting") + try: + await submit_form(answer) + set_status("success") + except Exception: + set_status("typing") + set_error(Exception) + + @event() + def handle_textarea_change(event): + set_answer(event["target"]["value"]) + + if status == "success": + return html.h1("That's right!") + else: + return html._( + html.h2("City quiz"), + html.p( + "In which city is there a billboard \ + that turns air into drinkable water?" + ), + html.form( + {"on_submit": handle_submit}, + html.textarea( + { + "value": answer, + "on_change": handle_textarea_change, + "disabled": (True if status == "submitting" else "False"), + } + ), + html.br(), + html.button( + { + "disabled": ( + True if status in ["empty", "submitting"] else "False" + ) + }, + "Submit", + ), + error_msg(error), + ), + ) diff --git a/docs/examples/python/managing_state/stateful_picture_component.py b/docs/examples/python/managing_state/stateful_picture_component.py new file mode 100644 index 000000000..38e338cbf --- /dev/null +++ b/docs/examples/python/managing_state/stateful_picture_component.py @@ -0,0 +1,30 @@ +from reactpy import component, event, hooks, html + + +# start +@component +def picture(): + is_active, set_is_active = hooks.use_state(False) + background_class_name = "background" + picture_class_name = "picture" + + if is_active: + picture_class_name += " picture--active" + else: + background_class_name += " background--active" + + @event(stop_propagation=True) + def handle_click(event): + set_is_active(True) + + return html.div( + {"class_name": background_class_name, "on_click": set_is_active(False)}, + html.img( + { + "on_click": handle_click, + "class_name": picture_class_name, + "alt": "Rainbow houses in Kampung Pelangi, Indonesia", + "src": "https://i.imgur.com/5qwVYb1.jpeg", + } + ), + ) diff --git a/docs/src/learn/reacting-to-input-with-state.md b/docs/src/learn/reacting-to-input-with-state.md index ac04c1d98..2e81aaf8f 100644 --- a/docs/src/learn/reacting-to-input-with-state.md +++ b/docs/src/learn/reacting-to-input-with-state.md @@ -141,7 +141,7 @@ You've seen how to implement a form imperatively above. To better understand how 1. **Identify** your component's different visual states 2. **Determine** what triggers those state changes -3. **Represent** the state in memory using `useState` +3. **Represent** the state in memory using `use_state` 4. **Remove** any non-essential state variables 5. **Connect** the event handlers to set the state @@ -159,69 +159,35 @@ First, you need to visualize all the different "states" of the UI the user might Just like a designer, you'll want to "mock up" or create "mocks" for the different states before you add logic. For example, here is a mock for just the visual part of the form. This mock is controlled by a prop called `status` with a default value of `'empty'`: -```js -export default function Form({ status = "empty" }) { - if (status === "success") { - return

That's right!

; - } - return ( - <> -

City quiz

-

- In which city is there a billboard that turns air into drinkable - water? -

-
-