React: Support Symbol keys for props

Created on 24 Aug 2016  路  23Comments  路  Source: facebook/react

Do you want to request a _feature_ or report a _bug_?
I want to report a bug

What is the current behavior?
render() doesn't receive props with Symbol keys (for example, {[Symbol()]: 'lol'}). I guess it is because of hasOwnProperty in ReactElement.createElement

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via jsfiddle or similar.
https://jsfiddle.net/sh2xbm3x/1/

What is the expected behavior?
Symbol-keyed props passed to render()

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
Every single one, as far as I know

Component API Feature Request

Most helpful comment

I'd like to argue in favor of @zemlanin's use case. The very purpose of Symbol is to be a private property identifier that cannot collide with another - it feels like a much more appropriate solution to this sort of problem compared to randomKey.

I think the more useful simplifying guideline would be "react prop keys are just like object prop keys" (rather than just "keys are strings"). For the most part they are exactly object keys, and when dealing with JSX you can use the spread operator the same way you do with objects. There is no slippery slope towards supporting all manner of exotic types as prop keys, any more than there is for object keys. The valid key types are simply: string, Symbol - end of list.

React has tried to stick close to ES6+ in terms of style so far, it would be nice to continue along those lines by treating Symbols the same way ES6 does - i.e. collision-proof private key identifiers, for use by frameworks and extension mechanisms, never iterated over, but usually copied (e.g. by Object.assign, and so in turn perhaps by React.cloneElement).

In terms of JSX syntax implications, again, I don't see anything special that would need to be done here. Props are always strings, so you wouldn't be able to use symbols there, it would either have to be as part of the spread object, or with cloneElement. If in the future you allow prop name expressions using a square bracket syntax (e.g. <Component [propName]={value} /> then that would, of course, also cover symbol prop names, but that's just incidental.

Random prop names or long namespaced names like __myFramework__propName are good enough solutions too, but Symbol just seems like a cleaner fit, much like like the move from createClass to plain ES6 classes.

All 23 comments

Can you please explain your use case for this?

Framework-specific props

For example, brabadu/tanok (internal implementation of elm-architecture) uses convention with tanokStream (or deprecated eventStream) property name to pass around Rx.Subject for sending actions. Instead, it can export Symbol to mark a component as framework-dependent without inventing names to avoid collisions

I guess, Symbols can find place in acdlite/recompose, but I didn't use it that much

Isn't a possibility of receiving Symbol-keyed props enough of a reason to process them in some way:

  • passing it to an element
  • throwing a warning
  • mentioning this behavior in the documentation

?

A framework could also choose a cryptographically random 64-character alphanumeric key, which would effectively eliminate any possibility of a name conflict. Save it to a variable and use it as a symbol, if so desired.

Another workaround is to create a prop called frameworkProps, which takes in a general object containing your framework-specific props. Then you can use symbols and anything else you desire. Usage would be: <YourComponent frameworkProps={{[Symbol()]: 'lol'}} />

The simplicity of "keys are strings" is something that I'm a little reluctant to give up. If component authors start accepting symbols as keys, things become more complicated. For instance, what is the story around JSX?

My intuition is that if we do anything, perhaps it should be to throw a warning if we encounter a symbol as a prop key.

Random string key is a fine alternative, but it has the same "overloaded" look in JSX as a symbol does:

import { randomKey } from 'framework'

<Component {...{[randomKey]: 'lol'}} />

Replaced passing Symbol props to a component with warning about react's behavior in #7554

I'd like to argue in favor of @zemlanin's use case. The very purpose of Symbol is to be a private property identifier that cannot collide with another - it feels like a much more appropriate solution to this sort of problem compared to randomKey.

I think the more useful simplifying guideline would be "react prop keys are just like object prop keys" (rather than just "keys are strings"). For the most part they are exactly object keys, and when dealing with JSX you can use the spread operator the same way you do with objects. There is no slippery slope towards supporting all manner of exotic types as prop keys, any more than there is for object keys. The valid key types are simply: string, Symbol - end of list.

React has tried to stick close to ES6+ in terms of style so far, it would be nice to continue along those lines by treating Symbols the same way ES6 does - i.e. collision-proof private key identifiers, for use by frameworks and extension mechanisms, never iterated over, but usually copied (e.g. by Object.assign, and so in turn perhaps by React.cloneElement).

In terms of JSX syntax implications, again, I don't see anything special that would need to be done here. Props are always strings, so you wouldn't be able to use symbols there, it would either have to be as part of the spread object, or with cloneElement. If in the future you allow prop name expressions using a square bracket syntax (e.g. <Component [propName]={value} /> then that would, of course, also cover symbol prop names, but that's just incidental.

Random prop names or long namespaced names like __myFramework__propName are good enough solutions too, but Symbol just seems like a cleaner fit, much like like the move from createClass to plain ES6 classes.

Another use case:

https://github.com/acdlite/recompose/issues/358

const namespace =  (ns, ...hocs) => compose(
  withProps(props => ({ $parentProps: props })), // TODO $parentProps as symbol
  ...hocs,
  mapProps(props => ({ [ns]: props, ...props.$parentProps }))
)

Yet another use case.

https://reacttraining.com/react-router/web/guides/dealing-with-update-blocking

<UpdateBlocker location={location}>
  <NavLink to='/about'>About</NavLink>
  <NavLink to='/faq'>F.A.Q.</NavLink>
</UpdateBlocker>

In react-router v4, the suggested practice for ensuring that pure components have their impure react-router children update is to pass an otherwise unused location prop into the pure component. Since this prop is only used for the purposes of shouldComponentUpdate(), it would be nice to be able to use a Symbol and guarantee that the name will never, ever conflict with the real props for any given pure component.

const props = {
  ...
  [Symbol('location')]: location
};
<UpdateBlocker {...props}>
  <NavLink to='/about'>About</NavLink>
  <NavLink to='/faq'>F.A.Q.</NavLink>
</UpdateBlocker>

This was one of those things that I tried, only to be mildly surprised that it didn't work. (In my case the syntax was cleaner than above, because the prop was being injected via react-redux connect().)

I think the more useful simplifying guideline would be "react prop keys are just like object prop keys"

...completely nails it, in my opinion.

Another use case is to create non-enumerable props.

Take, for instance, a component that is wrapped in a container that injects additional props in (dispatch from react-redux is a good example).

A common React convention is to use the spread operator to select specific props and pass the rest down, like so:

render() {
  const { backgroundColor, ...restProps } = this.props
  return (
    <div style={{ backgroundColor }}>
      <ChildComponent {...restProps} />
    </div>
  )
}

This would inadvertently pass dispatch down to ChildComponent, something that is rarely deliberate. Symbols, however, are inherently non-enumerable, so if dispatch were a Symbol (not necessarily a default behavior, but certainly one that could be configured by connect(), then all uses of the dispatch prop would have to be explicit.

And it would certainly be much more natural to use this.props[dispatch](action) (where dispatch is a Symbol imported from react-redux) than it would be to use a randomly generated identifier to avoid collisions.

It sounds to me like this is just trying to work around the real problem (HOC pattern pollutes props) with a solution that further obscures it.

We are looking at revamping the context API in a way that doesn鈥檛 present this problem. Would this help you?
https://github.com/reactjs/rfcs/pull/2

Ah, yeah, this is cool. I didn't want to use context because the current API didn't really seem much better than props, since it still deals with naming collisions and such. Nice proposal! :+1:

It's a lovely proposal (I am 馃憤 for the proposal itself), but not all uses of Symbol props translate to using contexts instead. Sometimes you want the locality actual props provide. Most use cases here have something to do with components that are intentionally coupled with each-other and meant to be used together as a coherent set. Sometimes this works best with contexts (e.g. forms and their fields) but other times, the communication really has to happen between parent and _direct_ children only. E.g. the way <select> and <option> work - or more generally, when we use components as a mechanism for passing specific data to specific parent components. Going the other way as well: Sometimes parents need to inject specific data, _privately_, into specific children - e.g. when you have a parent that needs to do facilitate some coordination between its children. This came up for me personally for custom layout systems, but I'm sure there are broader examples.

We have some very rough ideas about solving these use cases too without the notion of private props.

Oh heck yeah, that would be _much_ better.

Between the context proposal and the createReturn one, yeah, I believe that covers every use case I've ran into.

The call return API previously mentioned was never finalised and has since been removed. There are times where library authors may want to attach pass private values through levels of consumer components via props. Is there any serious reason not to support Symbols in props?

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution.

Is there any info available on why call return was scrapped?

This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any additional information, please include with in your comment!

bump

One issue I am having is the Context API doesn't seem to have a way to know the order of components. So that leads to using a parent child set of components where the parent has to tell the child its index via cloning children.

This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!

bumpity bump

Just ran into this today, was a bit surprising that it just got silently stripped 馃

My use case was to pass internal props down to specific children, in this case the background color to the Row in a Table component:

import React, { ReactElement } from 'react'
import { View } from 'react-native'
import { HStack, VStack } from 'react-stacked'

const kBackgroundColor = Symbol('BackgroundColor')

interface CellProps {}

export const Cell: React.FC<CellProps> = ({ children }) => (
  <View style={{ flexBasis: 1, flexGrow: 1, flexShrink: 0 }}>{children}</View>
)

interface RowProps {
  [kBackgroundColor]?: string
  children?: ReadonlyArray<ReactElement<CellProps>> | ReactElement<CellProps> | null
}

export const Row: React.FC<RowProps> = ({ children, ...props }) => (
  <HStack backgroundColor={props[kBackgroundColor]}>{children}</HStack>
)

interface TableProps {
  children?: ReadonlyArray<ReactElement<RowProps>> | ReactElement<RowProps> | null
}

export const Table: React.FC<TableProps> = ({ children }) => (
  <VStack>
    {React.Children.map(children, (child, index) => (
      React.cloneElement(child, { [kBackgroundColor]: ['#fff', '#eee'][index % 2] })
    ))}
  </VStack>
)

I guess I could create a context for each row, but my gut feeling is that performance will suffer 馃槄


edit:

My primary use case for this is that I don't want my internal props to show up in autocompletion, and I'm using TypeScript. With those two prerequisites I found this workaround that I think is acceptable:

const kBackgroundColor: unique symbol = '_backgroundColor' as any

This will treat kBackgroundColor as a unique symbol and thus not display that there is a prop called _backgroundColor on <Row>, yet I can still pass it wherever I have access to kBackgroundColor.

Was this page helpful?
0 / 5 - 0 ratings