GithubHelp home page GithubHelp logo

spicy-hooks's Introduction

spicy-hooks's People

Contributors

goce-cz avatar yuqiora avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spicy-hooks's Issues

Usefulness of `core/useValueVersion`

I'm starting to question the usefulness of useValueVersion hook outside of its only usage within the library - the useInternedValue

Somehow I'm leaning towards the opinion that useInternedValue would simply do better job anywhere the useValueVersion comes in mind.

Example

const MyComponent = ({deepObject}) => {
  const deepObjectVersion = useValueVersion(deepObject, isEqual)
  useEffect(
    () => console.log('the object changed deeply', deepObject),
    [deepObjectVersion] // linter warns about wrong dependency list
  )
  ...
}

can easily be replaced with probably more obvious and way more React friendly

const MyComponent = ({deepObject}) => {
  const internedDeepObject = useInternedValue(deepObject, isEqual)
  useEffect(
    () => console.log('the object changed deeply', internedDeepObject),
    [internedDeepObject ] // all fine here
  )
  ...
}

Both hooks are very simple, so I'd be keen to merge them if we decide not to export the useValueVersion.

What do you think? Should we remove ๐Ÿ‘ or not ๐Ÿ‘Ž ?

Make `core/useStateWithoutRerender` private

As @tomkis pointed out, code of such a dangerous character as the useStateWithoutRerender might not be suitable for exporting from the package.

The hook itself shouldn't definitely be used unless wrapped in some kind of a higher level safe hook, such as our useDependantState. Making it private is starting to make sense...

Any opinions?

Rethink `observables/useSyncObservable`

I think we need to reconsider the name and general usefulness of this hook.

The beauty of BehaviorSubject lays in its ability to provide the latest emitted value through getValue() method. This gives us possibility to initialize React state directly with a value rather than waiting for the subject to emit. Doing useSnapshot() can therefore provide an immediate value directly during the first render, when you pass a BehaviorSubject to it.

Unfortunately, pipeing the subject doesn't pipe the getValue() function, therefore even if the pipeline itself is synchronous we lose the possibility to grab the latest (and transformed) value.

useSyncObservable solves this by subscribing to an observable in a synchronous fashion. If the observable itself is synchronous it will emit the value immediately, thus we can grab it and pass it through a newly created BehaviorSubject. The calling code can then reach it through the standard getValue().

I currently see following issues:

  1. the name is totally confusing
  2. the TSDoc as well
  3. it can trigger "side-effects" during a render
    • first call to the hook will immediately subscribe to the observable
    • if the observable is cold, this will trigger the internal logic and start emitting
    • it is generally considered to be a bad practice to initiate side-effect during the render phase
      • however the concept of resources and suspense is IMO encouraging this as the first call to resource.read() (or similar) will always trigger the side-effect
      • so I'm not sure how much of a big deal this actually is ๐Ÿคท
  4. it is used only by useSnapshot
    • I'm not sure how useful this hook actually is outside of the scope of useSnapshot
    • maybe we could merge these (or better make this one private)
    • a completely different way would be to remove the sage of useSyncObservable from useSnapshot and make it optional - i.e. let the user do useSnapshot(useSyncObservable(source)) manually, but I don't know if I like this

Any opinion more than welcomed...

Make use of `undefined` for `useSnapshot`

Currently the useSnapshot hook returns [null, SnapshotState.WAITING, null] while waiting for the first emit, unless a default value is specified. Using null as a indicator of a missing value feels a bit weird in this case and has some technical implications as well:

  • We cannot assign a default value during destructuring const [value = 5] = useSnapshot(observable) as null will override the default. Because of that we need to accept defaultValue as an argument to useSnapshot.
  • It's hard to differentiate between no emission and null emission. undefined would suffer from the same issue, but who does emit undefined?

Additionally we use null for indication of no error and also for a missing state (whenever the source observable is nullish). I feel that if we convert one, we should probably convert all of them.

So what do we do?

  1. replace null with undefined for value (the first element of the tuple)
  2. replace null with undefined for state in case of a missing source observable (the middle element of the tuple)
  3. replace null with undefined for error (the last element of the tuple)
  4. remove const [value] = useSnapshot(observable, defaultValue) overload in favor of const [value = defaultValue] = useSnapshot(observable)

Note that any of these changes will also affect usePartialSnapshot and useAsyncMemo.

Add `core/useProperty`

Signature

useProperty<O, K extends keyof O> (object: O, setObject: Dispatch<SetStateAction<O>>, property: K): [O[K], Dispatch<SetStateAction<O[K]>>]

Purpose

The purpose of this hooks is to select part of a complex state and work with it as if it is a simple one:

Usage example

const [object, setObject] = useState({ a: 'a', b: 1 })
const [b, setB] = useProperty(object, setObject, 'b')
...
return <button onClick={() => setB(prevB => prevB + 1)}>Increment B (current value: {b})</button>

Note that while in the above case it would be definitely better to allocate two different states, there are situations when you either cannot influence this (the object comes from above) or it is beneficial to use the object as an atomic unit.

Proposed implementation (untested)

export function useProperty<O, K extends keyof O> (object: O, setObject: Dispatch<SetStateAction<O>>, property: K): [O[K], Dispatch<SetStateAction<O[K]>>] {
  const setState = useCallback(
    (value: SetStateAction<O[K]>) =>
      setObject(prevCompositeState => {
        const previousValue = prevCompositeState[property]
        const newValue = isFunction(value)
          ? value(previousValue)
          : value
        if (newValue === previousValue) {
          return prevCompositeState
        }
        return {
          ...prevCompositeState,
          [property]: newValue
        }
      }),
    [setObject, property]
  )
  return [object[property], setState]
}

I'm looking forward to see ๐Ÿ‘ / ๐Ÿ‘Ž and eventual comments.

`utils/isShallowEqual` chokes on null

When one of the arguments passed to isShallowEqual is null and the other one is plain object, the function throws:

TypeError: Cannot use 'in' operator to search for 'width' in null

The reason is that typeof null === 'object' and the function then tries comparing the two property by property.

Export command-line related helpers from `bin`

Currently the bin package doesn't export anything and publishes only the executable binaries.

The package however contains some interesting code related to parsing and handling command line arguments.
We should export these and make them usable for building custom commands with easy and full TS support.

Namely I'm suggesting to export a helper that would create a command from given definitions, make it validate the arguments and automatically generate usage guide (--help).

Rename `core/useInternedValue` to `useDistinct[Value]`

I finally found a better name for this weird piece of code.
It's actually inspired by the distinctUntilChanged() operator of RxJS.

  • useInternedValue -> useDistinctValue

๐Ÿค” Thinking whether we need the Value suffix or not. I'd probably keep it though.

Default equality function of `observables/usePartialSnapshot`

The usePartialSnapshot hook accepts equality function as an optional argument, the default value being isShallowEqual.

While the default value makes the hook usable in most of the scenarios out of the box it has a couple of drawbacks:

  • it severely differs from how react works
    • React uses Object.is basically everywhere and at some places allows custom functions to be provided
    • this adds a cognitive overhead to any new user of our hook
  • doing a isShallowEqual comparison in cases where Object.is would suffice is an overkill
    • while the users can deliberately choose Object.is as the equality function, majority of them won't do it as the default works well
    • this can have a significant negative performance impact as instead of simple inequality detection through Object.is the isShallowEqual has to traverse all the properties

I'm kinda inclined to use Object.is as the default equality function and make the users reach for isShallowEqual manually when needed.

I'd love to hear some opinions here.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.