Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Input onChange does not expose native event #3247

Open
joepuzzo opened this issue Jun 21, 2022 · 11 comments
Open

Input onChange does not expose native event #3247

joepuzzo opened this issue Jun 21, 2022 · 11 comments

Comments

@joepuzzo
Copy link

馃悰 Bug Report

Input onChange does not expose native event. This is bad as it does not enable access to the native event which ultimately allows for complex cursor tracing when formatting text fields.

馃 Expected Behavior

https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/textfield/src/useTextField.ts#L141

This ^^^ should be passing e instead of e.target.value. As this would be huge breaking change. I suggest you simply pass e as a second parameter so users can use it.

馃槸 Current Behavior

Native e event is not passed

馃拋 Possible Solution

I suggest you simply pass e as a second parameter so users can use it.

        onChange: (e: ChangeEvent<HTMLInputElement>) => onChange(e.target.value, e),

馃敠 Context

I am unable to do cursor tracking to format user input.

https://teslamotors.github.io/informed/?path=/story/formatting--number-formatter

Above is example of where compex cursor tracking is used when formatting input fields.

@LFDanLu
Copy link
Member

LFDanLu commented Jun 21, 2022

Mind expanding on what cursor tracking is being done in the example you linked? Could you use onInput or onBeforeInput instead?

EDIT: ah is it to calculate where to place the cursor when a comma is added/removed?

@devongovett
Copy link
Member

This has been discussed before: #1860.

TL;DR: There are two reasons:

a) Consistency. All of our components expose a value as part of the onChange event, not a native event. In some components, there is no native event that would make sense.
b) Cross platform support. Our stately hooks are designed to work across platforms with the same API, e.g. web and react native.

But, that's not to say it's impossible to do what you want. With React Aria, you control the DOM structure, so it's easy to override props we return from our hooks where needed. In this example, we chain React Aria's handler with our own.

import {useTextField} from '@react-aria/textfield';
import {chain} from '@react-aria/utils';

function MyInput(props) {
  let {inputProps} = useTextField(props);
  let myOnChange = e => { /* ... */ };
  return <input {...inputProps} onChange={chain(inputProps.onChange, myOnChange)} />
}

@joepuzzo
Copy link
Author

@LFDanLu Yes it is for setting the position of cursor when the commas are added. @devongovett Hmm so the hook basically turns a basic DOM input into a react spectrum input ? It seems to me I would be missing out on all the goodies in here tho .. https://github.com/adobe/react-spectrum/blob/main/packages/%40react-spectrum/textfield/src/TextField.tsx

@joepuzzo
Copy link
Author

Specifically the TextFieldBase... that has a bunch of things I want to keep. going the custom route just so I can get the native e event seems to throw half of the benefits away ? correct me if im wrong.

@devongovett
Copy link
Member

Ah, didn't realize you were using React Spectrum - assumed you were using the React Aria hooks with your own design.

In that case, you might be able to do what you want with a ref? TextField's ref exposes a getInputElement() function.

let ref = useRef();
let onChange = () => {
  let input = ref.current.getInputElement();
  // do stuff.
};
<TextField onChange={onChange} />

@joepuzzo
Copy link
Author

Ahh a hack but might work non the less! Thanks!

PS: I might make a wrapper library or even use react-spectrum as the main design system for my form docs. Its one of the better design systems I have ever used!

@joepuzzo
Copy link
Author

Ok so now im having issues because I think you guys are doing some fancyness with the ref here https://github.com/adobe/react-spectrum/blob/main/packages/%40react-spectrum/textfield/src/TextFieldBase.tsx

Im getting

inputRef.current.setSelectionRange is not a function

@joepuzzo
Copy link
Author

Internally to my form library i check if they user has passed a DOM ref and call setSelectionRange .. problem is I cant go changing my library to call a getInputElement as that is custom logic to react-spectrum. I wish I could just access the real ref 馃 any ideas?

@joepuzzo
Copy link
Author

joepuzzo commented Jun 21, 2022

A really easy solve for this is to allow the user to pass their own inputRef

   let inputRef = useRef<HTMLInputElement>();
   inputRef = userInputRef ?? inputRef;

https://github.com/adobe/react-spectrum/blob/main/packages/%40react-spectrum/textfield/src/TextField.tsx#L22

I do same thing here

https://github.com/teslamotors/informed/blob/master/src/hooks/useField.js#L161

@joepuzzo
Copy link
Author

Following up with this @devongovett as its still a blocker for advanced cursor positioning when formatting fields

@mjr
Copy link

mjr commented Apr 18, 2024

Is there a way to do this check in the onChange of the confirmPassword field using react-aria-components components?

'use client'

import { useFormState } from 'react-dom'
import { Button, FieldError, Form, Input, Label, TextField } from 'react-aria-components'
import { createUser } from './actions'

export function AddForm() {
  let [{ nonFieldErrors, errors }, formAction] = useFormState(createUser, { nonFieldErrors: null, errors: {} })

  return (
    <Form action={formAction} validationErrors={errors}>
      {nonFieldErrors &&
        <div role="alert" tabIndex={-1} ref={e => e?.focus()}>
          <h3>Unable to submit</h3>
          <p>{nonFieldErrors}</p>
        </div>
      }
      <TextField name="name" isRequired>
        <Label>Name</Label>
        <Input />
        <FieldError />
      </TextField>
      <TextField name="email" type="email" isRequired>
        <Label>Email</Label>
        <Input />
        <FieldError />
      </TextField>
      <TextField
        name="phone"
        type="tel"
        isRequired
        pattern="\((\d{2})\) (\d?)(\d{4})-(\d{4})"
      >
        <Label>Phone</Label>
        <Input />
        <FieldError />
      </TextField>
      <div>
        <label htmlFor="id-password">Password</label>
        <input type="password" id="id-password" name="password" required />
      </div>
      <div>
        <label htmlFor="id-confirm-password">Confirm password</label>
        <input
          type="password"
          id="id-confirm-password"
          name="confirmPassword"
          required
          onChange={(e) => {
            const field = e.currentTarget
            const password = field.form.password
            if (field.value !== password.value) {
              field.setCustomValidity('Passwords do not match')
            } else {
              field.setCustomValidity('')
            }
          }}
        />
      </div>
      <Button type="submit">Add</Button>
    </Form>
  )
}

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

No branches or pull requests

4 participants