npm package: @instructure/quiz-number-input
Description: Number input with i18n support
Author: Instructure, Inc. Engineering and Product Design
Latest version: 22.17.0

---
category: packages
---

An internationalized, controlled number input:

```js
---
example: true
render: false
---
class Example extends React.Component {
  state = {
    number: 0.1
  }

  handleChange = (event, number) => this.setState({ number })

  render () {
    return (
      <div>
        <NumberInput
          {...this.props}
          value={this.state.number}
          onChange={this.handleChange}
        />
        <br />
        <Button onClick={() => this.handleChange({}, '5')}>
          Go with 5
        </Button>
      </div>
    )
  }
}

render(<Example label="Battery capacity in Amp-hrs" step={0.1} min={0.1} />)
```

With precision:

```js
---
example: true
render: false
---
class Example extends React.Component {
  state = {
    number: 9.99,
    precisionType: 'decimalPrecision',
    precisionValue: 2,
    precision: 2
  }

  handleNumberChange = (event, number) => this.setState({ number })

  handlePrecisionChange = (event, precisionValue, precision) => {
    this.setState({
      precisionValue,
      precision: precision ? Number(precision) : null
    })
  }

  render () {
    return (
      <div>
        <SimpleSelect label="Precision type" onChange={(e, o) => this.setState({ precisionType: o.value })}>
          <SimpleSelect.Option id="decimal" value="decimalPrecision">Decimal</SimpleSelect.Option>
          <SimpleSelect.Option id="significant-digit" value="significantDigits">Significant digits</SimpleSelect.Option>
        </SimpleSelect>
        <br />
        <NumberInput
          label={this.state.precisionType}
          min={0}
          max={10}
          decimalPrecision={0}
          value={this.state.precisionValue}
          onChange={this.handlePrecisionChange}
        />
        <br />
        <NumberInput
          label="Value"
          decimalPrecision={this.state.precisionType === 'decimalPrecision' ?  this.state.precision : null}
          significantDigits={this.state.precisionType === 'significantDigits' ?  this.state.precision : null}
          value={this.state.number}
          onChange={this.handleNumberChange}
        />
      </div>
    )
  }
}

render(<Example />)
```

The number input accepts and displays values according to the specified locale, which
can be supplied either as a property or with [ApplyLocale](#ApplyLocale). If a locale
is not specified, it will be inferred from the browser.

If the `min`, `max`, or `step` props are given as strings, they will be parsed
according to the rules of the `en-US` locale (with `.` decimal separator). This
is for backward-compatibility only; it is recommended to pass numbers, not
strings, for these props.

The `onChange` prop receives the content of the input as a second argument, and
a normalized string parsed according to the current locale as the third
argument. For example, if the locale is `fr` and the input value is set to
`1,5`, the third argument to `onChange` will be `"1.5"`. Note that this third
argument will be `null` if the input string cannot be parsed.

If the `value` prop is a number, it will be formatted according to the locale on
render; if a string is given, make sure it is already in the correct format for
the locale.

The number input will attempt to format the string on blur. The `onChange`
handler will be called *before* the `onBlur` handler.

```js
---
example: true
render: false
---
class Example extends React.Component {
  state = {
    locale: 'de',
    messages: [],
    normalizedValue: '2.4',
    value: 2.4
  }

  handleChange = (event, value, normalizedValue) => {
    this.setState({ value, normalizedValue, messages: [] })
  }

  handleBlur = () => {
    if (this.state.value && !this.state.normalizedValue) {
      this.setState({ messages: [{ text: 'Invalid number', type: 'error' }] })
    } else {
      this.setState({ messages: [] })
    }
  }

  render () {
    const label = this.state.locale === 'de' ? "Comma separator" : "Period separator"
    return (
      <div>
        <SimpleSelect
          renderLabel="Choose locale"
          onChange={(e, o) => this.setState({ locale: o.value })}
        >
          <SimpleSelect.Option id="de" key="de" value="de">de</SimpleSelect.Option>
          <SimpleSelect.Option id="en" key="en" value="en">en</SimpleSelect.Option>
        </SimpleSelect>
        <View padding="small">
          <NumberInput
            renderLabel={label}
            locale={this.state.locale}
            messages={this.state.messages}
            onBlur={this.handleBlur}
            onChange={this.handleChange}
            step={0.1}
            value={this.state.value}
          />
          <p>Normalized value: <code>{JSON.stringify(this.state.normalizedValue)}</code></p>
        </View>
      </div>
    )
  }
}

render(<Example />)
```

### Using a text input

By default, `NumberInput` will use a numeric input field. This causes mobile
Chrome to use a numeric keypad input instead of a regular keyboard input. This
may not be the desired behavior, for example if you don't want to indicate to
the user that a number is expected, or you want to allow users to type in
numbers in scientific notation (see below), but you still need the
internationalization features described above.

In this case, pass `inputType="text"` to the component to use a plain text input
instead of a numeric input field:

```js
---
example: true
render: false
---
class Example extends React.Component {
  state = {
    value: '1.23',
    normalizedValue: '1.23'
  }

  handleChange = (event, value, normalizedValue) => this.setState({ value, normalizedValue })

  render () {
    return (
      <View padding="small">
        <NumberInput
          inputType="text"
          label="Text input"
          value={this.state.value}
          onChange={this.handleChange}
        />
        <p>Normalized value: <code>{JSON.stringify(this.state.normalizedValue)}</code></p>
      </View>
    )
  }
}

render(<Example />)
```

### Scientific Notation

The `ScientificNumberInput` component wraps `NumberInput`, passing numbers given
in scientific notation as the third argument to the `onChange` handler. The
scientific notation format is `mantissa * 10 ^ exponent`
(whitespace-insensitive), e.g.  `1.25*10^-3`.

Note that numbers in scientific notation are not localized. If you are in a
locale that uses commas as the decimal separator, you will still need to use a
decimal point `.` for scientific notation. Thousands separators are not allowed,
and the exponent must be an integer.

If the `significantDigits` or `decimalPrecision` props are given, they apply to
the mantissa. The `min`, `max` and `step` props are ignored (for now).

```js
---
example: true
render: false
---
class Example extends React.Component {
  state = {
    messages: [],
    normalizedValue: '1.175*10^-3',
    value: '1.175*10^-3'
  }

  handleChange = (event, value, normalizedValue) => {
    this.setState({ value, normalizedValue, messages: [] })
  }

  handleBlur = () => {
    if (this.state.value && !this.state.normalizedValue) {
      this.setState({ messages: [{ text: 'Invalid number', type: 'error' }] })
    } else {
      this.setState({ messages: [] })
    }
  }

  render () {
    return (
      <View padding="small">
        <ScientificNumberInput
          inputType="text"
          renderLabel="Scientific notation allowed"
          messages={this.state.messages}
          onBlur={this.handleBlur}
          onChange={this.handleChange}
          value={this.state.value}
        />
        <p>Normalized value: <code>{JSON.stringify(this.state.normalizedValue)}</code></p>
      </View>
    )
  }
}

render(<Example />)
```

When precision is specified for `ScientificNumberInput`, it applies to the
mantissa only. The exponent must be an integer.

```js
---
example: true
render: false
---
class Example extends React.Component {
  state = {
    value: '1.23*10^2',
    normalizedValue: '1.23*10^2',
    precisionType: 'decimalPrecision',
    precisionValue: 2,
    precision: 2
  }

  handleNumberChange = (event, value, normalizedValue) => this.setState({ value, normalizedValue })

  handlePrecisionChange = (event, precisionValue, precision) => {
    this.setState({
      precisionValue,
      precision: precision ? Number(precision) : null
    })
  }

  render () {
    return (
      <div>
        <SimpleSelect renderLabel="Precision type" onChange={(e, o) => this.setState({ precisionType: o.value })}>
          <SimpleSelect.Option id="decimal-precision" value="decimalPrecision">Decimal</SimpleSelect.Option>
          <SimpleSelect.Option id="significant-digits" value="significantDigits">Significant digits</SimpleSelect.Option>
        </SimpleSelect>
        <br />
        <NumberInput
          label={this.state.precisionType}
          min={0}
          max={10}
          decimalPrecision={0}
          value={this.state.precisionValue}
          onChange={this.handlePrecisionChange}
        />
        <br />
        <ScientificNumberInput
          label="Value"
          inputType="text"
          decimalPrecision={this.state.precisionType === 'decimalPrecision' ?  this.state.precision : null}
          significantDigits={this.state.precisionType === 'significantDigits' ?  this.state.precision : null}
          value={this.state.value}
          onChange={this.handleNumberChange}
        />
        <p>Normalized value: <code>{JSON.stringify(this.state.normalizedValue)}</code></p>
      </div>
    )
  }
}

render(<Example />)
```