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

Add Ref Feature #2805

Closed
wants to merge 1 commit into from
Closed

Add Ref Feature #2805

wants to merge 1 commit into from

Conversation

clysto
Copy link

@clysto clysto commented Oct 26, 2022

Description

Add the ref attribute. When vnode contains the ref attribute, vnode assigns its refrence to ref.current when it is created.
This is similar to ref in react.

Motivation and Context

This makes it easy for developers to get a specific vnode refrence when using chain method to create the vnode tree.
For example:

function HomePage() {
  const inputRef = m.ref();
  const handleClick = () => {
    console.log(inputRef.current.dom.value);
  };
  return {
    view: () =>
      m('div', [m('input', { placeholder: 'name', ref: inputRef }), m('button', { onclick: handleClick }, 'OK')]),
  };
}
m.mount(document.body, HomePage);

How Has This Been Tested?

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation change

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • I have updated docs/changelog.md

Add the ref attribute. When vnode contains the ref attribute, vnode assigns
its refrence to `ref.current` when it is created.
This is similar to ref in react.
This makes it easy for developers to get a specific vnode refrence when using chain
method to create the vnode tree.
@barneycarroll
Copy link
Member

Hi @clysto, you can get this functionality in current Mithril without any extra APIs.

Mithril function components pre-date hooks-aware React components and behave differently: React function components execute on each draw and as a result need special APIs to retain references. Mithril function components execute only the first time each component instance is created, and the returned {…lifecycleMethods?, view} functions then execute as required for that particular component lifecycle.

The advantage of the Mithril method is that everything in the component function scope is persistent for the lifetime of that component, which means we can declare references and initialisation code at setup, and mutate or reassign those references later on. The way to achieve the same functionality as your code sample is to simply declare an unassigned let for the input reference (1), assign the reference directly to the vnode in the view (2), and then refer back to it in the handler (3).

function HomePage() {
  let input // 1
  const handleClick = () => {
    console.log(input.dom.value); // 3
  };
  return {
    view: () =>
      m('div', [
        input = m('input', { placeholder: 'name' }), // 2
        m('button', { onclick: handleClick }, 'OK')
      ]),
  };
}
m.mount(document.body, HomePage);

Here's a sandbox for the sample above

  1. The internal lingo for this (preferred!) shape of component in Mithril is 'closure component': the mechanics are explained in more detail here. The superficial similarity to React function components is pretty confusing but I think the logic is easier to follow from JavaScript first principles.
  2. Every hyperscript expression (m(...)) produces a virtual node (vnode in internal lingo). These need to be created fresh on every view (views, like React function components, run on every render)
  3. Those vnodes are typically accessed through lifecycle method signatures but can be queried just the same through the assignment mechanism used here. The API is documented here.

@clysto
Copy link
Author

clysto commented Oct 26, 2022

Wow, it is amazing. But how to do the same thing in JSX?

@barneycarroll
Copy link
Member

barneycarroll commented Oct 26, 2022

Like this:

function HomePage() {
  let input // 1
  const handleClick = () => {
    console.log(input.dom.value);
  };
  return {
    view: () =>
      <div>
        {[
          input = <input placeholder="name" />,
          <button onclick={handleClick}>
            OK
          </button>
        ]}
      </div>
  };
}
m.mount(document.body, HomePage);

@clysto
Copy link
Author

clysto commented Oct 26, 2022

Yes, it seems that we don't need any extra api to implement this feature. But it doesn't look so elegant in JSX. As you said: mithril function components execute only the first time each component instance is created, it can be implemented through simple assignment statements.

Maybe I can use Babel to implement the same thing at compile time?

@dead-claudia
Copy link
Member

Maybe I can use Babel to implement the same thing at compile time?

@clysto I don't believe there's a pre-made transform, but it should be pretty easy to roll a custom Babel plugin to do it. (Tip: start with this handbook if you aren't familiar.) All you really need to do is find JSXElement nodes with ref attributes and replace them with t.assignmentExpression("=", refValue, jsxElementWithoutRefAttribute).

Do note that JSX elements are expressions. They aren't magical syntax constructs. So don't be afraid to treat them as such.

@clysto clysto closed this Mar 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants