ionic-team / stencil

A toolchain for building scalable, enterprise-ready component systems on top of TypeScript and Web Component standards. Stencil components can be distributed natively to React, Angular, Vue, and traditional web developers from a single, framework-agnostic codebase.
https://stenciljs.com
Other
12.55k stars 784 forks source link

prop that accepts array or object returns string #134

Closed psyrendust closed 7 years ago

psyrendust commented 7 years ago

Issue

If I have a stencil custom element that has a prop that is typed as an array of numbers number[], the value of the prop from within the custom element is a string.

Expected

I expect the prop would be an array of numbers.

HTML Example

import {Component, Prop} from '@stencil/core';

@Component({
  tag: 'my-map',
})
export class MyMap {
  @Prop() name: string;
  @Prop() coords: number[] = [-122.40655, 37.78437];

  componentDidLoad() {
    console.log(this.name, typeof this.coords, this.coords); // => object (2) [-122.40655, 37.78437]
                                                             // or
                                                             // => string -122.40655,37.78437
  }
}
<!DOCTYPE html>
<html dir="ltr" lang="en">
<head>
  <meta charset="utf-8" />
  <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
  <title>Stencil Starter App</title>
  <script src="build/app.js"></script>
</head>
<body>

  <my-map name='no prop'></my-map> <!-- => "no prop array" //-->
  <my-map name='with prop' coords='-122.4,37.7'></my-map> <!-- => "with prop string" //-->

</body>
</html>

I'm noticing this because I'm integrating stencil custom elements into a react application. When I pass in an array to a prop it get's converted by react to a string. So when I'm trying to read the value of the prop it's a string instead of an array of numbers.

The same holds true when trying to pass in an object to a prop.

React Example

import {Component, Prop} from '@stencil/core';

@Component({
  tag: 'my-map',
})
export class MyMap {
  @Prop() coords: number[] = [-122.40655, 37.78437];
  @Prop() foo: any;

  componentDidLoad() {
    console.log(typeof this.coords, this.coords); // => string -122.40655,37.78437
    console.log(typeof this.foo, this.foo); // => string [object Object]
  }
}
import React, { Component } from 'react';

export default class MyView extends Component {
  render() {
    const coords = [-122.40655, 37.78437];
    const foo = {
      bar: 'woot',
    };
    return (
      <my-map coords={coords} foo={foo}></my-map>
    );
  }
}
kylecordes commented 7 years ago

@psyrendust I think this is a React issue. React in its current version passing in attributes (strings) to custom elements, Whereas many of them (like yours and mine...) need JavaScript data via properties. You can read a lot about it in this open issue:

https://github.com/facebook/react/issues/7249

I believe the typical workaround is to pass this form of data using a bit of JavaScript in the current version of React.

psyrendust commented 7 years ago

@kylecordes So it sounds like I'd have to do something like this:

import React, { Component } from 'react';

export default class MyView extends Component {
  render() {
    const coords = [-122.40655, 37.78437];
    const foo = {
      bar: 'woot',
    };

    const handleRef = (myMap) => {
      myMap.coords = coords;
      myMap.foo = foo;
    };

    return (
      <my-map ref={handleRef}></my-map>
    );
  }
}

If so does this mean that my CE would also have to implement the upgradeProperty hack? https://jsbin.com/zicewal/6/edit?html,output

kylecordes commented 7 years ago

@psyrendust I don't use React as often as some of its competitors :-) so I can't tell you the exact most concise invocation. But if you watch this Rob Dodson presentation starting at the time linked below, you should get a full feel for the situation.

https://youtu.be/sK1ODp0nDbM?t=24m40s

He launched a great informational site:

https://custom-elements-everywhere.com/

... Which documents the numerous bits a framework must do for maximal custom element / web component compatibility, and then tests a bunch of the current contenders. React currently is in the unfortunate position of being quite popular yet scoring pretty poorly. I expect that will be addressed in an upcoming React version to get it back on the top of the heap with the other major contenders which already support custom elements very well.

All of this is equally relevant to Stencil or any other way of building web components.

jthoms1 commented 7 years ago

@psyrendust I have ran into this issue with React and created a simple utility function to work around the problem. I have talked with the React team and this is something they are talking about fixing in React 17. Until then we have a workaround.

This utility should be used for custom events or for objects/arrays. Strings, numbers and booleans should work fine.

This utility fixes the issue. https://github.com/ionic-team/ionic-react-conference-app/blob/master/src/utils/stencil.js

Usage is below:

import React, { Component } from 'react';
import { wc } from '../utils/stencil';

export default class MyView extends Component {
  render() {
    const coords = [-122.40655, 37.78437];
    const foo = {
      bar: 'woot',
    };
    return (
      <my-map
        ref={wc({
          coords: coords
        })}
        foo={foo}></my-map>
    );
  }
}
psyrendust commented 7 years ago

@jthoms1 Thanks for the util! Considering that foo is also an object I'm assuming it would look like this (using object shorthand):

import React, { Component } from 'react';
import { wc } from '../utils/stencil';

export default class MyView extends Component {
  render() {
    const coords = [-122.40655, 37.78437];
    const foo = {
      bar: 'woot',
    };
    return (
      <my-map
        ref={wc({ coords })}
        foo={wc({ foo })} />
    );
  }
}
jthoms1 commented 7 years ago

wc is a function specific to ref so all values would be passed through that.

import React, { Component } from 'react';
import { wc } from '../utils/stencil';

export default class MyView extends Component {
  render() {
    const coords = [-122.40655, 37.78437];
    const foo = {
      bar: 'woot',
    };
    return (
      <my-map
        ref={wc({ coords, foo })} />
    );
  }
}
marisaoliva commented 4 years ago

please, Can you put the utility again? :D

I've problem with the link and I've the same issue

https://github.com/ionic-team/ionic-react-conference-app/blob/master/src/utils/stencil.js

marisaoliva commented 4 years ago

Hello

I've found this https://www.npmjs.com/package/reactify-wc ;)