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

[Core] Add uniqueId generator #4253

Closed
wants to merge 1 commit into from
Closed

[Core] Add uniqueId generator #4253

wants to merge 1 commit into from

Conversation

ajihyf
Copy link
Contributor

@ajihyf ajihyf commented May 13, 2016

  • PR has tests / docs demo, and is linted.
  • Commit and PR titles begin with [ComponentName], and are in imperative form: "[Component] Fix leaky abstraction".
  • Description explains the issue / use-case resolved, and auto-closes the related issue(s) (http://tr.im/vFqem).

Fix #3757.

@ajihyf ajihyf changed the title [Core] Fix uniqueId generating [Core] Add uniqueId generating May 13, 2016
@ajihyf ajihyf changed the title [Core] Add uniqueId generating [Core] Add uniqueId generator May 13, 2016
function getUniqueIdGenerator() {
let index = 0;
return () => {
return `mui-id-${index++}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that it would work. Have a look at #3533.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL;DR The index increase faster on the server-side than on the client-side.

Copy link
Contributor Author

@ajihyf ajihyf May 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function returns a generator function instead of a uniqueId directly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oliviertassinari This id generator is created per context, i.e. per request. If the client and server call the getUniqueId function in the same order then this method is deterministic. And since every request will have it's own context no two requests will have reference to the same index variable captured in that function.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry, I have overlooked the context part 👍. I though we were going back to the previous implementation.

@oliviertassinari oliviertassinari added the PR: needs revision The pull request can't be merged. More details is available on the code review or fails in the CI label May 13, 2016

describe('one generator', () => {
it('should generate different uniqueIds each time it is invoked', () => {
assert.notEqual(gen(), gen());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather use notStrictEqual.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oliviertassinari The generated ids are used as html tag attributes in most cases, which may contain an implicit type conversion.

const id1 = 1;
const id2 = '1';
document.head.id = id1;
document.body.id = id2;
console.log(id1 === id2); // false
console.log(document.head.id === document.body.id); // true

id1 and id2 will pass notStrictEqual. But document.head and document.body will have conflicts using same id.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we call toString and return a string to prevent this implicit type conversion?

Copy link
Contributor Author

@ajihyf ajihyf May 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oliviertassinari I agree with your previous opinion. 😲Using notEqual or toString here focus too much on the the generator's use cases, not its own functionality. I should add some tests to TextField.

@oliviertassinari
Copy link
Member

@AJIH Could you have a look at my comment, squash down the commit and use the PR title as the commit messages? We will be good to go 🚀.

@ajihyf
Copy link
Contributor Author

ajihyf commented May 14, 2016

@oliviertassinari Done! 😁

@oliviertassinari oliviertassinari added PR: Review Accepted and removed PR: needs revision The pull request can't be merged. More details is available on the code review or fails in the CI labels May 14, 2016
@mbrookes
Copy link
Member

I'm guessing the name was chosen to be in keeping with getMuiTheme, but could the module just be called uniqueIdGenerator instead of getUniqueIdGenerator?

@oliviertassinari oliviertassinari added PR: needs revision The pull request can't be merged. More details is available on the code review or fails in the CI and removed PR: Review Accepted labels May 16, 2016
@ajihyf
Copy link
Contributor Author

ajihyf commented May 17, 2016

@mbrookes It may be ambiguous to name the module uniqueIdGenerator. The module exports a function of type () => () => String, not () => String. And MuiThemeProvider calls the exported function to get a real uniqueIdGenerator of type () => String as its context.

@alitaheri
Copy link
Member

@mbrookes it's what it does, hence the name. I think it's more clear this way. it dose give you a new uniqueIdGenerator whenever you call it, it's a factory. 🏭 🏭

@oliviertassinari oliviertassinari added PR: Review Accepted and removed PR: needs revision The pull request can't be merged. More details is available on the code review or fails in the CI labels May 17, 2016
@mbrookes
Copy link
Member

Got it, thanks.

@mbrookes
Copy link
Member

Docs site doesn't use MuiThemeProvider, so:

warning.js:44 Warning: Failed Context Types: Required context `uniqueIdGen` was not specified in `TextField`. Check the render method of `SelectField`
TextField.js:223 Uncaught TypeError: this.context.uniqueIdGen is not a function

@mbrookes mbrookes added PR: needs revision The pull request can't be merged. More details is available on the code review or fails in the CI and removed PR: Review Accepted labels May 17, 2016
@alitaheri
Copy link
Member

😱 😱 That's not good. should we put it on the muiTheme or should we patch docs? a context for one single component kinda seems overkill. maybe putting it on muiTheme some how isn't all that bad? But this is the best approach for the issue though. we have to either patch docs or put it on muiTheme.

@nathanmarks
Copy link
Member

nathanmarks commented May 17, 2016

@alitaheri putting it on muiTheme seems like mixing concerns. 😟

@alitaheri
Copy link
Member

Should we just rename muiTheme to everythingMuiNeedsToOperateSuchAsTheme_UniqueIdGenerator_StyleTransformation_UserAgentAndMore? 😆, We won't have conflicts with other though, which is nice 😆 😆

@ajihyf
Copy link
Contributor Author

ajihyf commented May 18, 2016

😲Doc site has a Master component to provide context to its children. Should we add uniqueIdGen to its context to solve the problem?

@ajihyf
Copy link
Contributor Author

ajihyf commented May 18, 2016

@alitaheri 😱I found another bug of current implementation.

Example code(https://jsfiddle.net/aji_h/6o63e7gL/4/):

class MuiThemeProvider extends React.Component {
  ...
  getChildContext() {
    return {
      muiTheme: this.props.muiTheme || getMuiTheme(),
      uniqueIdGen: this.props.uniqueIdGen || getUniqueIdGenerator(),
    };
  }
}

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = {clicked: false};
  }
  onClick = () => {
    this.setState({
      clicked: !this.state.clicked
    });
  };
  render() {
    return (      
      <div>
        <button onClick={this.onClick}>Click Me</button>
        <MuiThemeProvider>{
            this.state.clicked ? 
          <div><TextField /><TextField /></div>
          : <div><TextField /></div>
        }</MuiThemeProvider>
      </div>      
    );
  }
}

After clicking the button, the first TextField keeps its uniqueId of mui-id-0 because it's already mounted. But the second TextField will request a context which contains a new uniqueIdGen, call componentWillMount and get its uniqueId of mui-id-0.

@ajihyf
Copy link
Contributor Author

ajihyf commented May 18, 2016

I have refactored the code to fix the bug above mentioned and added a uniqueIdGen to Master component.😂

@alitaheri
Copy link
Member

Wow this is nice 👍 I think it's ready, @callemall/material-ui Take a look please 😁

@ajihyf
Copy link
Contributor Author

ajihyf commented May 18, 2016

@alitaheri My pleasure.
@mbrookes Extracted "Using context" section to a single page.😋

@mbrookes mbrookes added PR: Needs Review and removed PR: needs revision The pull request can't be merged. More details is available on the code review or fails in the CI labels May 18, 2016
```

Read more details about uniqueId in the [issue](https://github.com/callemall/material-ui/issues/3757) and the [PR](https://github.com/callemall/material-ui/pull/4253).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure the PR thread adds much value - it's mostly comments like this one. 😄

@mbrookes
Copy link
Member

mbrookes commented May 18, 2016

Some comments on using-context.md above. Also, in the Nav menu, 'Using context' should probably follow Themes. The logical order might be:

  • Colors
  • Inline Styles
  • Themes
  • Using Context

Simplest to most advanced, and coincidentally alphabetical order. 😄

};

state = {
uniqueIdGen: this.props.uniqueIdGen || getUniqueIdGenerator(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for using the state here? Why not following the muiTheme pattern?
According to the React docs site:

The getChildContext function will be called when the state or props changes.

I see one potential issue, the uniqueIdGen context won't be updated according to the property.

Copy link
Contributor Author

@ajihyf ajihyf May 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oliviertassinari I tried using the muiTheme pattern and found a bug described in #4253 (comment)

Copy link
Member

@oliviertassinari oliviertassinari May 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks like the expected behavior. What about this version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In your version, components in MuiThemeProvider will invoke all lifecycle methods(including componentWillMount, where TextField get its uniqueId) each time the button is clicked because the virtual DOM trees are totally different.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oliviertassinari You mean MuiThemeProvider should return a new uniqueIdGen whenever its getChildContext is called? I think it should keep uniqueIdGen all its lifecycle.

Copy link
Member

@oliviertassinari oliviertassinari May 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes indeed. getChildContext is called at each rerender. So your point is that getUniqueIdGenerator() should only be called at the mount time. That's sounds good 👍.
But, shouldn't we update the uniqueIdGen state during the componentWillReceiveProps hook?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating the uniqueIdGen may cause some issues. Mounted components will keep their ids mui-id-0, mui-id-1...But if there are components will mount, their uniqueIdGen may start at 0, and their ids would be mui-id-0, mui-id-1...😰

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, let's not update it in this case. It looks like we have reached the limitation of this solution.
I can't see real use cases for this edge case, I think that we are fine.
Should we add a comment?

P.S. I start to feel like using an heuristic for the id generation would have been simpler. 😁

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah.😂 In fact, the feature has been discussed a bit before in facebook/react#1137 and facebook/react#4000. We use many hacky methods in this solution because we need a global counter which should reset itself each time React calls render method. But Material-ui is a component library and should not inject the render process.
If MuiThemeProvider is the root provider, many potential issues would be eliminated. But in doc it's only preferable, not necessary.

Copy link
Contributor Author

@ajihyf ajihyf May 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oliviertassinari How about using a global uniqueIdGen(like your previous implementation) and adding an API to allow manually reset uniqueIdGenerator before rendering?

@oliviertassinari
Copy link
Member

oliviertassinari commented Jun 3, 2016

The more I think about this PR the more I think we are over-engineering the solution.
Is the solution should really make us depend on the context?
Would depending on the id property be enough (as I do in my closed PR)?

@ajihyf
Copy link
Contributor Author

ajihyf commented Jun 4, 2016

@oliviertassinari Manually setting id property is enough. 😁I think your PR is better.

@oliviertassinari
Copy link
Member

Finding an id for every used TextField in an app only to support SSR seems overkill to me.

@alitaheri Do you still feel that way? Both solutions came with their tradeoffs, but I think that the id one is simpler.

@alitaheri
Copy link
Member

@oliviertassinari I kinda feel the same. In my opinion both solutions seem a bit hacky.

I'd say we should think outside the box a little. This id is used for 2 purposes: 1. aria 2. focus

If we drop the mandatory aria support ( users should provide id of their own to enable this ) and solve the focus problem in a different way, then this won't be a problem.

In other words, the only reason we need this is the focus. How about handling this with js? handle the touchTap on label and call focus on textarea? That should solve it right?

@nathanmarks
Copy link
Member

@oliviertassinari

This PR never sat super well with me but everyone else seemed happy so I let sleeping dogs lay. It did seem like we were over-engineering and putting a very specific function on context for something which can be (and usually is) handled in user land.

Do you think we should re-use your last PR?

@oliviertassinari
Copy link
Member

@nathanmarks I like the idea proposed by @alitaheri. We could be using the onTouchTap event for achieving the same behavior. That would allow us to avoid this unique id issue.
Actually, as far as I know, this was the original solution implemented 1 year ago.

@nathanmarks
Copy link
Member

@oliviertassinari I would hold on implementing anything dependent on onTouchTap

@oliviertassinari
Copy link
Member

@AJIH I'm closing this issue based on this comment. Thanks!

@zannager zannager added the core Infrastructure work going on behind the scenes label Mar 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Infrastructure work going on behind the scenes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[TextField] uniqueId breaks server rendering
6 participants