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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compatible with React Native #12

Closed
yordis opened this issue Nov 26, 2017 · 23 comments
Closed

Compatible with React Native #12

yordis opened this issue Nov 26, 2017 · 23 comments

Comments

@yordis
Copy link

yordis commented Nov 26, 2017

Lines like this https://github.com/uNmAnNeR/imaskjs/blob/gh-pages/src/imask.js#L29 wouldn't work.

@uNmAnNeR
Copy link
Owner

Yes, this is not the only one, factory.js also seems to be incompatible because of circular dependencies.
I could investigate it later to do something.

@uNmAnNeR
Copy link
Owner

uNmAnNeR commented Dec 3, 2017

@yordis I fixed this.
But probably control/input.js should be reimplemented for React Native in places where view element fires events and interacts with masked.
I am not very familiar with React Native, help needed here.

@yordis
Copy link
Author

yordis commented Dec 3, 2017

@uNmAnNeR I can help you out with the RN but I need to know from you which events would you need from the inputs for iMask to work. Make a checklist on the comment and I will dig in

@uNmAnNeR
Copy link
Owner

uNmAnNeR commented Dec 4, 2017

@yordis ok, here it is:

el.addEventListener('keydown', ...)
el.addEventListener('input', ...)
el.addEventListener('drop', ...)  // just disables
el.addEventListener('click', ...)
el.addEventListener('change' ...)
// ...and remove counterparts

el.selectionStart
el.selectionEnd
el.setSelectionRange(...);
get/set el.value
document.activeElement

@yordis
Copy link
Author

yordis commented Dec 4, 2017

@uNmAnNeR I will port my iMask to the React Native code today so I will have to deal with this.

@mjsisley
Copy link

@yordis How did this end up going?

@uNmAnNeR
Copy link
Owner

uNmAnNeR commented Jan 18, 2018

@yordis @mjsisley
Some thoughts on this. I have not used RN at all, but looking on RN API superficially i think it is possible to use React mixin from imask React plugin and inside it wrap RN element to fit the UIElement API.
But is it possible to bind multiple listeners to events in RN?

@yordis
Copy link
Author

yordis commented Jan 18, 2018

@mjsisley sorry for the delay.

I didn't end up using it for the Mobile app so I don't face the issues.

@uNmAnNeR I think that in RN everything is about callbacks, it doesn't seem that the Native elements use EventEmitter for those things.

@uNmAnNeR
Copy link
Owner

@yordis Sure, but i think it is possible to do like this:

// RN -> React Plugin Mixin wraps RNTextInput to DOMlikeInput -> pass to IMask
// e.g
class TextInputAdapter {
  constructor (nativeElement) {
    this.nativeElement = nativeElement;
  }
  addEventListener(ev, handler) {
    if (ev === 'input') this.nativeElement.onChangeText(handler);
  }
  // ...
}

@yordis
Copy link
Author

yordis commented Jan 19, 2018

@uNmAnNeR

class Input extends React.Component {
  // all the states happen here as well
  // unless specific Adapter needs to handle
  // some internals before letting this component 
  // to know about it
  state = {}
  
  render() {
   const InputAdapter = this.props.adapter

   const inputProps = {
    // this is a well defined protocol for any Input Adapter
    // for example
    onChange(x: string): void
    // every Adapter HAVE to call the same set of data
    // on the web normally the events give you `event` object
    // and in Native it will give you the actual `text`
    // both have to send back the same data structure
   }

   return (
    <InputAdapter  {...inputProps}/>
   )
  }

  // here is where all the callbacks management happens
  // no data management happens inside the adapters
  // example

  onChange(alwaysExpectTheText) {
    // do what you need to do
  }
}

// package name: imask-react-dom

class IMaskDOMInput extends Component {
  // do whatever the DOM API requires you to do

  // the onChange needs to clean up the data before
  // sending to the callback because the DOM event
  handleOnChange(event) {
    const text = event.target.value
    this.props.onChange(text)
  }
}

class IMaskInput extends Component {
  render() {
    <Input
      {...props }
      // notice this
      adapter={IMaskDOMInput}
    >
  }
}

// package name: imask-react-native

class IMaskNativeInput extends Component {
  // do whatever the DOM API requires you to do

  // the onChange needs to clean up the data before
  // sending to the callback because the DOM event
  handleOnChange(text) {
    this.props.onChange(text)
  }
}

class IMaskInput extends Component {
  render() {
    <Input
      {...props }
      // notice this
      adapter={IMaskNativeInput}
    >
  }
}

@yordis
Copy link
Author

yordis commented Jan 19, 2018

@uNmAnNeR something around that API design.

Normally you will have 2 packages, each package the ONLY thing it defines is the Adapter you will be using and both of them will have the same HOC so you could use the same component name in no matter which platform.

Each adapter do whatever it needs to be able to communicate with the Internal HOC that actually manage the iMask interactions needs.

I dont like this

addEventListener(ev, handler) {
    if (ev === 'input') this.nativeElement.onChangeText(handler);
  }

because the component where you are doing that have to know about both platforms, using the Adapter approach your HOC Input do not care about event registration or callbacks that is a detail managed by the adapter so this makes the code more clear.

@yordis
Copy link
Author

yordis commented Jan 19, 2018

Probably the Input that I am refererring is this one https://github.com/uNmAnNeR/imaskjs/blob/gh-pages/src/controls/input.js

just make it agnostic of the low level components and just manage the iMask interactions with React I guess

@uNmAnNeR
Copy link
Owner

@yordis But your Input extends React.Component.
As i understand to be trully agnostic on a low level adapter should be defined in terms of element, and current controls/input.js will use it.
So I need to rewrite UIElement interface and make 2 adapters for HTMLInput and RN TextInput.
Something like this:

interface UIElement {
  value (string) // ok
  selectionStart (): number  // ok
  selectionEnd (): number   // ok
  setSelectionRange (function (number, number): void)  // ok
  // addEventListener (function (string, Function): void) // not ok, will be split to:
  onInput ()
  onKeyDown ()
  onDrop ()
  onClick ()
  onChange ()
  
  // removeEventListener (function (string, Function): void) // not ok, will be replaced with passing null to methods above
}

@yordis
Copy link
Author

yordis commented Jan 19, 2018

Well, I think that you can't use that class on React but you could make it work as long as you let the React component to know about the data changes but even that the way you would pass down the callbacks it is another concern as well, normally you would use HOC Higher Order Component for do these type of practices.

Definitely you could use that class under InputDOM because it is how the web works but I dont see that working for the RN that easy but probably is because I dont know the full internals.

This is how I see it

Web

Vanilla: UIElement (this does DOM stuff in a Web way)  ---> uses ---> iMask
React: Input (HOC) ---> uses ---> iMaskDOMInput (HOC) --- uses ---> UIElement
React Native: Input (HOC) ---> uses ---> iMaskNativeInput (HOC) ---> uses ---> iMask

As you notice React DOM Input could use the UIElement because it is just DOM stuff but I dont see that working for RN.

I thikn you can't do this.nativeElement.onChangeText(handler) and things like mutating the Component directly, React have specific way to do things.

In the case of the Web maybe is different because you can always just ask for the native element and register callbacks and stuff like that directly but I dont think that exists on RN but please dig in more.

@uNmAnNeR
Copy link
Owner

uNmAnNeR commented Feb 15, 2018

@yordis hi again! :)

I hacked a little on RN. I supposed to end up with something like:

import React from 'react';
import { StyleSheet, Text, View, TextInput } from 'react-native';
import { IMaskMixin } from 'react-imask';


class Adapter {
  constructor (el) {
    this.el = el;
  }

  addEventListener (event, handler) {
    // switch (event) {
    //   case 'keydown': return this.el.onKeyPress(handler);
    //   case 'input': return this.el.onChange(handler);
    // }
  }

  removeEventListener (event) {
    // TODO
  }

  selectionStart () {
    return 0;
    // return this.el.selection.start;
  }

  selectionEnd () {
    return 0;
    // return this.el.selection.end;
  }

  setSelectionRange (start, end) {
    // this.el.selection = {start, end};
  }

  get value () {
    return 'asd';
    // return this.el.value;
  }

  set value (value) {
    // this.el.value = value;
  }
}

const MaskedTextInput = IMaskMixin(({inputRef, ...props}) => (
  <TextInput
    {...props}
    ref={el => inputRef(new Adapter(el))}
  />
));

export default class App extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <MaskedTextInput
          style={{
            height: 40,
            width: 100,
            borderColor: 'gray',
            borderWidth: 1
          }}
          mask='000'
          editable = {true}
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

But RN element in ref is not editable, thanks to FLUX. Every change or event should be handled inside component render. Because of this the only way to change something is to update internal state. So I have not percieved yet how do it right and does HOC make sense with RN or it should be complete component like MaskedInput.

@uNmAnNeR
Copy link
Owner

@yordis Hello!
If you are still interested in please take a look.
I've just released react-native-imask and added an example.
Cursor is flickering... events are not always fired... I dont know how to do it better, if it's possible at all.
May be after resolving this RN-issue I will try to remake plugin.
I also tried uncontrolled behavior using setNativeProps, but other issues appeared.

@uNmAnNeR
Copy link
Owner

uNmAnNeR commented Jun 1, 2018

Merged in master. Plugin was released with version 0.0.x and then made private to prevent version updating.
On iOS it works not so bad, despite flickering with lazy=false. On Android cursor is jumping back on input because of fireing onSelectionChange event during rerendering. Probably this is a bug in RN.
Currently development is frozen and waiting for fixing:
facebook/react-native#19505
facebook/react-native#18874
facebook/react-native#18221

@yordis
Copy link
Author

yordis commented Jun 2, 2018

@uNmAnNeR Is it ready then?

@uNmAnNeR
Copy link
Owner

uNmAnNeR commented Jun 3, 2018

@yordis it's not production ready yet.

@ezequiel1988
Copy link

@yordis @uNmAnNeR Is it ready then?

@uNmAnNeR
Copy link
Owner

will be released as is, does not make sense to wait one more year for bug fixing in RN

@TNAJanssen
Copy link

@uNmAnNeR did you ever found a fix for the rerender bug for react native?

@uNmAnNeR
Copy link
Owner

uNmAnNeR commented Jun 2, 2023

@TNAJanssen I tried to make it work a long time ago, but with no luck. Too many things did not work as expected on different systems, so I gave up. If someone can restore my faith with some good PR, I'd be happy to continue supporting it.

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

No branches or pull requests

5 participants