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

To implement React Hooks API or to be based/inspired by it? #65

Closed
jpray opened this issue Feb 18, 2019 · 14 comments
Closed

To implement React Hooks API or to be based/inspired by it? #65

jpray opened this issue Feb 18, 2019 · 14 comments

Comments

@jpray
Copy link
Collaborator

jpray commented Feb 18, 2019

There are a number of React hooks that aren't implemented in haunted (useRef, useImperativeHandle, useLayoutEffect, useDebugValue). Personally I am ok with this as I think there are substantive differences between web components and React that should influence the hooks patterns for each.

If the goal of haunted is to be "React's Hooks API implemented for web components", when we are kinda stuck needing to implement the full API, and nothing more. If the goal is tweaked to something like "Web Component Hooks, inspired by React", then not implementing everything React has is okay and we have more freedom to innovate. Thoughts?

@jpray
Copy link
Collaborator Author

jpray commented Feb 23, 2019

Along similar lines I'll ask this question... Would it be terrible if setState() returned the value being set instead of nothing? I'm consuming a vanilla router library and it would be nice to do something like

const useRouter = () => {
  let [route, setRoute] = useState("");

  useMemo(() => {
    const router = new Router();
    router.on("*", (_route)=> {
      route = setRoute(_route);
    })
    router.start(); // causes the above callback to run immediately
  }, [true]) // run once

  return route;
}

I'm open to other ideas too. I'd just prefer for the first render to be meaningful instead of a throw away render waiting for something like useEffect() to run.

@matthewp
Copy link
Owner

matthewp commented Feb 24, 2019

Hey, sorry I missed this question earlier. The way I see Haunted is to mimic React's "modern" APIs. So hooks, Context... maybe eventually Suspense. So as far as hooks are concerned, I think we should adopt theirs as closely as possibly, where it makes sense too.

In this particular case, does setState return anything from React?

I agree though, the run once type of setup dance isn't super fun.

@jpray
Copy link
Collaborator Author

jpray commented Feb 24, 2019

setState() in react doesn't return anything. I do have a slight improvement on the above approach that doesn't require setState() to return a value.

Still has smells, but getting better:

const useRouter = () => {
  const [, update] = useState(); // just need a function to call so component gets updated

  const getRoute = useMemo(() => {
    let route = "";
    const router = new Router();
    router.on("*", (_route)=> {
      route = _route;
      update();
    })
    router.start(); // causes the above callback to run immediately
    return () => route;
  }, [true]) // run once

  return getRoute();
}

In order to handle destroying the router when this component is disconnected I would like something where I can return a value like useMemo() but also provide a teardown method like useEffect(). I don't have a great pattern for that yet...

@matthewp
Copy link
Owner

Do we need useRef maybe?

@askbeka
Copy link
Contributor

askbeka commented Feb 24, 2019 via email

@jpray
Copy link
Collaborator Author

jpray commented Feb 25, 2019

@matthewp, not sure if useRef would be helpful. To accomplish the most common use case of assigning an element to a reference, it seems maybe lit-html would need some additional functionality added.

@askbeka, Thanks for the feedback. Injecting the actual router is one way to go, and it seems that most router libraries do want to own the whole URL. Another possibility is to inject the baseRoute as a string into the component and have it use its own router. (more of a micro-frontend approach) If interested here's a naive PoC. It's still using a single router instance in this example, but as long as routers know to ignore routes that are outside of their assigned baseRoute, it should work with separate instances too:

Example: https://jpray.github.io/haunted-tests/dist/router-test.html
Code: https://github.com/jpray/haunted-tests/tree/master/src/router-test
(also includes examples of custom useDisconnected() and useTriggerUpdate() hooks)

Thanks for letting me bounce ideas off of you two. Any feedback is welcome. Eventually we'll need to create a library of recipes for common needs like routing, form handling, etc...

@jessehattabaugh
Copy link

useRef is useful for more than getting a reference to a dom element. You can use it to store a reference to any value, even primitives like a boolean. This is the Hooks answer to instance variables. The docs recommend it for several purposes including getting previous props or state

@jpray
Copy link
Collaborator Author

jpray commented Jun 19, 2019

Here is a PR for useRef: #100. Like useCallback, useRef is just sugar around useMemo.

For using it with a DOM element I've been doing the following:

my-app/src/directives/ref.js

// add a custom "ref" directive for use with lit-html
import {directive, AttributePart} from "lit-html";

const ref = directive((refInstance) => (part) => {
    if (!(part instanceof AttributePart)) {
        throw new Error('ref directive can only be used as an attribute');
      }
      refInstance.current = part.committer.element;
});

export { ref };

my-app/src/app.js

import { ref } from "./directives/ref.js";
import { useRef, useEffect } from "haunted.js";

const inputRef = useRef(null);

useEffect(() => {
  //do something awesome with inputRef.current
})

// TODO: having to use a ref attribute that serves no purpose other than it 
// being needed for lit directives to work on the element is a bummer.
return html`
  <input
    type="text"
    name="foo"
    ?ref=${ref(inputRef)} 
  />
`;

@matthewp
Copy link
Owner

Nice! I'll try to get this merged in tonight.

@jessehattabaugh
Copy link

jessehattabaugh commented Jun 19, 2019 via email

@matthewp
Copy link
Owner

@jpray Should your ref.js be in Haunted? is that something people will usually need?

@matthewp
Copy link
Owner

matthewp commented Jul 9, 2019

bump @jpray, is ./directives/ref.js something we need in Haunted? If not, is there an example of using useRef without needing something like that?

@jpray
Copy link
Collaborator Author

jpray commented Jul 9, 2019

The react docs mention using it without being a reference to an element, but don't have a code example that I could see. I use it whenever I have state that I want to set without triggering a render (not that common a use case). Adding the lit-html ref directive makes sense, especially if we split out haunted-core from haunted and just keep haunted as the lit-html version. ;)

@matthewp
Copy link
Owner

So we have useRef, useContext now. I think useMutationEffect is the only missing one. There's an issue for that: #13

Let's close this and open individual hook issues if any need to be added. And if there are non-React hooks that make sense for Haunted, I think we can definition talk about that.

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

No branches or pull requests

4 participants