diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 6f9bc1e17..37c34044c 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -383,12 +383,26 @@ export const preventDefault = (event) => { * @returns The value. */ export const getRefValue = (ref) => { - if (!ref || !ref.current){ + if (!ref || !ref.current) { return; } if (ref.current.type == "checkbox") { return ref.current.checked; } else { - return ref.current.value; + //querySelector(":checked") is needed to get value from radio_group + return ref.current.value || (ref.current.querySelector(':checked') && ref.current.querySelector(':checked').value); } } + +/** + * Get the values from a ref array. + * @param refs The refs to get the values from. + * @returns The values array. + */ +export const getRefValues = (refs) => { + if (!refs) { + return; + } + // getAttribute is used by RangeSlider because it doesn't assign value + return refs.map((ref) => ref.current.value || ref.current.getAttribute("aria-valuenow")); +} diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index 04a4ef3a5..561f2dc4f 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -28,6 +28,7 @@ DEFAULT_IMPORTS: imports.ImportDict = { ImportVar(tag="preventDefault"), ImportVar(tag="refs"), ImportVar(tag="getRefValue"), + ImportVar(tag="getRefValues"), ImportVar(tag="getAllLocalStorageItems"), }, "": {ImportVar(tag="focus-visible/dist/focus-visible")}, diff --git a/reflex/components/forms/form.py b/reflex/components/forms/form.py index bba4dd483..7fbf574e9 100644 --- a/reflex/components/forms/form.py +++ b/reflex/components/forms/form.py @@ -22,12 +22,18 @@ class Form(ChakraComponent): A dict mapping the event trigger to the var that is passed to the handler. """ # Send all the input refs to the handler. - return { - "on_submit": { - ref[4:]: Var.create(f"getRefValue({ref})", is_local=False) - for ref in self.get_refs() - } - } + form_refs = {} + for ref in self.get_refs(): + # when ref start with refs_ it's an array of refs, so we need different method + # to collect data + if ref.startswith("refs_"): + form_refs[ref[5:-3]] = Var.create( + f"getRefValues({ref[:-3]})", is_local=False + ) + else: + form_refs[ref[4:]] = Var.create(f"getRefValue({ref})", is_local=False) + + return {"on_submit": form_refs} class FormControl(ChakraComponent): diff --git a/reflex/components/forms/numberinput.py b/reflex/components/forms/numberinput.py index cc316bdcf..57c43cf54 100644 --- a/reflex/components/forms/numberinput.py +++ b/reflex/components/forms/numberinput.py @@ -88,8 +88,9 @@ class NumberInput(ChakraComponent): The component. """ if len(children) == 0: + _id = props.pop("id", None) children = [ - NumberInputField.create(), + NumberInputField.create(id=_id) if _id else NumberInputField.create(), NumberInputStepper.create( NumberIncrementStepper.create(), NumberDecrementStepper.create(), diff --git a/reflex/components/forms/pininput.py b/reflex/components/forms/pininput.py index b15471c73..81ec13996 100644 --- a/reflex/components/forms/pininput.py +++ b/reflex/components/forms/pininput.py @@ -1,10 +1,12 @@ """A pin input component.""" -from typing import Dict +from typing import Dict, Optional from reflex.components.component import Component +from reflex.components.layout import Foreach from reflex.components.libs.chakra import ChakraComponent from reflex.event import EVENT_ARG +from reflex.utils import format from reflex.vars import Var @@ -66,6 +68,26 @@ class PinInput(ChakraComponent): "on_complete": EVENT_ARG, } + def get_ref(self): + """Return a reference because we actually attached the ref to the PinInputFields. + + Returns: + None. + """ + return None + + def _get_hooks(self) -> Optional[str]: + """Override the base get_hooks to handle array refs. + + Returns: + The overrided hooks. + """ + if self.id: + ref = format.format_array_ref(self.id, None) + if ref: + return f"const {ref} = Array.from({{length:{self.length}}}, () => useRef(null));" + return super()._get_hooks() + @classmethod def create(cls, *children, **props) -> Component: """Create a pin input component. @@ -81,7 +103,21 @@ class PinInput(ChakraComponent): The pin input component. """ if not children and "length" in props: - children = [PinInputField()] * props["length"] + _id = props.get("id", None) + length = props["length"] + if _id: + children = [ + Foreach.create( + list(range(length)), # type: ignore + lambda ref, i: PinInputField.create( + key=i, + id=_id, + index=i, + ), + ) + ] + else: + children = [PinInputField()] * length return super().create(*children, **props) @@ -89,3 +125,19 @@ class PinInputField(ChakraComponent): """The text field that user types in - must be a direct child of PinInput.""" tag = "PinInputField" + + # the position of the PinInputField inside the PinInput. + # Default to None because it is assigned by PinInput when created. + index: Optional[Var[int]] = None + + def _get_hooks(self) -> Optional[str]: + return None + + def get_ref(self): + """Get the array ref for the pin input. + + Returns: + The array ref. + """ + if self.id: + return format.format_array_ref(self.id, self.index) diff --git a/reflex/components/forms/rangeslider.py b/reflex/components/forms/rangeslider.py index 40e319986..b27e33205 100644 --- a/reflex/components/forms/rangeslider.py +++ b/reflex/components/forms/rangeslider.py @@ -1,10 +1,11 @@ """A range slider component.""" -from typing import Dict, List +from typing import Dict, List, Optional from reflex.components.component import Component from reflex.components.libs.chakra import ChakraComponent from reflex.event import EVENT_ARG +from reflex.utils import format from reflex.vars import Var @@ -55,6 +56,26 @@ class RangeSlider(ChakraComponent): "on_change_start": EVENT_ARG, } + def get_ref(self): + """Get the ref of the component. + + Returns: + The ref of the component. + """ + return None + + def _get_hooks(self) -> Optional[str]: + """Override the base get_hooks to handle array refs. + + Returns: + The overrided hooks. + """ + if self.id: + ref = format.format_array_ref(self.id, None) + if ref: + return f"const {ref} = Array.from({{length:2}}, () => useRef(null));" + return super()._get_hooks() + @classmethod def create(cls, *children, **props) -> Component: """Create a RangeSlider component. @@ -69,13 +90,23 @@ class RangeSlider(ChakraComponent): The RangeSlider component. """ if len(children) == 0: - children = [ - RangeSliderTrack.create( - RangeSliderFilledTrack.create(), - ), - RangeSliderThumb.create(index=0), - RangeSliderThumb.create(index=1), - ] + _id = props.get("id", None) + if _id: + children = [ + RangeSliderTrack.create( + RangeSliderFilledTrack.create(), + ), + RangeSliderThumb.create(index=0, id=_id), + RangeSliderThumb.create(index=1, id=_id), + ] + else: + children = [ + RangeSliderTrack.create( + RangeSliderFilledTrack.create(), + ), + RangeSliderThumb.create(index=0), + RangeSliderThumb.create(index=1), + ] return super().create(*children, **props) @@ -98,3 +129,16 @@ class RangeSliderThumb(ChakraComponent): # The position of the thumb. index: Var[int] + + def _get_hooks(self) -> Optional[str]: + # hook is None because RangeSlider is handling it. + return None + + def get_ref(self): + """Get an array ref for the range slider thumb. + + Returns: + The array ref. + """ + if self.id: + return format.format_array_ref(self.id, self.index) diff --git a/reflex/utils/format.py b/reflex/utils/format.py index dfcc55768..25e5ec4e7 100644 --- a/reflex/utils/format.py +++ b/reflex/utils/format.py @@ -434,6 +434,24 @@ def format_ref(ref: str) -> str: return f"ref_{clean_ref}" +def format_array_ref(refs: str, idx) -> str: + """Format a ref accessed by array. + + Args: + refs : The ref array to access. + idx : The index of the ref in the array. + + Returns: + The formatted ref. + """ + clean_ref = re.sub(r"[^\w]+", "_", refs) + if idx: + idx.is_local = True + return f"refs_{clean_ref}[{idx}]" + else: + return f"refs_{clean_ref}" + + def format_dict(prop: ComponentStyle) -> str: """Format a dict with vars potentially as values.