GithubHelp home page GithubHelp logo

avkonst / hookstate Goto Github PK

View Code? Open in Web Editor NEW
1.6K 16.0 106.0 9.24 MB

The simple but very powerful and incredibly fast state management for React that is based on hooks

Home Page: https://hookstate.js.org

License: MIT License

JavaScript 4.71% TypeScript 95.29%
react use-state react-hooks react-store react-state-management react-state plugin-architecture redux-alternative mobx-react-alternative typescript

hookstate's People

Contributors

addono avatar afkcodes avatar akshgpt7 avatar avkonst avatar bennoinbeta avatar ctoth avatar dependabot[bot] avatar dphuang2 avatar ench0 avatar enovision avatar ewh avatar gotexis avatar gustavopch avatar heavenshell avatar ice-cold-ethanol avatar loganarnett avatar magne4000 avatar mskonst avatar nelwincatalogo avatar olliechick avatar parisholley avatar payamrwd avatar ppwfx avatar ryanwillis avatar speigg avatar stefanoeb avatar waleedshkt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hookstate's Issues

Improve plugins integration API for version 2

Real application highlighted that plugins are exposed to None values and lots of arguments in onSet/onPreset callbacks. The following improvements are planned:

  • combine callback arguments into 1 object
  • make newValue/previousValue as optional properties to replace current None notation
  • remove onInit (it is successfully replaced by promised states)

Consider if context propagation and memo gives performance improvement

/* eslint-env browser */

import React from "react";
import ReactDOM from "react-dom";

const bitForA = 1 << 0;
const bitForB = 1 << 1;

const MyContext = React.createContext({ a: 1, b: 1 }, (prev, next) => {
  console.log("prev", JSON.stringify(prev));
  console.log("next", JSON.stringify(next));
  let changedBits = 0;
  if (prev.a !== next.a) changedBits |= bitForA;
  if (prev.b !== next.b) changedBits |= bitForB;
  return changedBits;
});

const ComponentA = () => {
  const context = React.useContext(MyContext, bitForA);
  return (
    <p>
      Value A: {context.a} {Math.random()}
    </p>
  );
};
const ComponentB = () => {
  const context = React.useContext(MyContext, bitForB);
  return (
    <p>
      Value B: {context.b} {Math.random()}
    </p>
  );
};

const Main = React.memo(() => (
  <React.Fragment>
    <ComponentA />
    <ComponentB />
  </React.Fragment>
));

const App = () => {
  const [state, setState] = React.useState({ a: 1, b: 1 });
  return (
    <MyContext.Provider value={state}>
      <Main />
      <button onClick={() => setState({ ...state, a: state.a + 1 })}>
        Increment A
      </button>
      <button onClick={() => setState({ ...state, b: state.b + 1 })}>
        Increment B
      </button>
    </MyContext.Provider>
  );
};

ReactDOM.unstable_createRoot(document.getElementById("app")).render(<App />);

Question: Example Application

Any applications in the wild currently using this that you're aware of? The library seems really interesting and all the demo snippets are fantastic, but coming from a redux background I'm trying to wrap my head around how it'd be implemented in a real-world application. Specifically in terms of architecture and recommended design patterns.

Naming improvements for version 2

The conversation starting from this comment highlighted some deficiency in the API. The suggested improvement is

  • to modify createStateLink to return StateLink but not StateInf. Remove transform argument from createStateLink and move it to StateLink wrap method instead.
  • useStateLink shoud correctly bind and observe state changes if it is initialised from the unmounted StateLink.

setting value to deeply nested property

suppose I have an object with the following structure:

const global = createStateLink({
	user: {
		name: 'some user',
		hobbies: ['building', 'stuff'],
		address: {
			city: 'san jose',
		},
	},
})

in order for me to push into hobbies, I have:
const gUser = useStateLink(global)
and a button with onClick of:
gUser.nested.user.nested?.hobbies.set((hobbies) => [...hobbies, 'stuff'])
which is rather hard to read, due to nested extra path
is there a better way that is still strongly typed to do something along the lines of:
gUser.user.hobbies.set(...

Checking the example of tree state in the docs, it shows how to deal with nested properties but still with one nested level at a time

Also, in the guide for .set() it mentions a plugin called Mutate, but it's not listed in the plugin section nor in the docs

I am not sure how the reference is checked to trigger the re-render, but is it possible to just set the whole object? as in:

gUser.set((user) => {
   return {
      ...user,
      hobbies: [...user.hobbies, 'added'],
   }
})

will hookstate figure out what has changed? because in this case we technically the property name as well.

Missing plugin dependencies

Using the Initial plugin requires lodash.clonedeep and lodash.isequal which are are not included in the artifact neither set as peer dependency. Results in build error.

Recent typescript upgrade resulted in state subfields possibly being undefined

Hi!

So I went back to MoonPiano and upgraded a bunch of dependencies, most importantly:

-    "@hookstate/core": "^1.4.1",
-    "@hookstate/persistence": "^1.2.2",
+    "@hookstate/core": "^1.7.0",
+    "@hookstate/persistence": "^1.5.2",
-    "npm": "^6.13.6",
+    "npm": "^6.14.2",
-    "react": "^16.12.0", 
+    "react": "^16.13.0",
-    "react-dom": "^16.12.0",
+    "react-dom": "^16.13.0",
-    "typescript": "^3.7.4",
-    "webpack": "^4.41.5",
 -   "webpack-cli": "^3.3.10"
+   "typescript": "^3.8.3",
+   "webpack": "^4.42.0",
+   "webpack-cli": "^3.3.11"

And then, whenever I access nested field of any state, typescripts adds the possiblity of it being defined, i.e:

assuming user is a StateLink with field friends: UserFriends[].

The following code no longer works:

const friends = user.nested.friends.get().map(e => e.id)

Typescript now assumes user.nested.friends is of type friends: StateLink<UserFriendInterface[] | undefined>

Thus in order for it to work, I have to manually cast it to:

const friends = (user.nested.friends as StateLink<UserFriendInterface[]>).get().map(e => e.id)

I am not saying the problem is with hookstate, but I could really use some help on how to solve this without major changes, because I use HookState all over the place! The number of times i access the nested field of a state is 267 in a total of 33 files ๐Ÿ˜…

Cheers!

Plugin: React Router Integration idea

Hi there!

I hope you are having a fine new year! ๐Ÿบ

I have been rewriting moonpiano, using hookstate for .. well, everything ๐Ÿ˜‚

I have been thinking that it would be really neat to have a plugin for react-router to navigate using state and get access to current pages.

Do you think this is useful? Or it should not be part of hookstate plugins?

[restored] Question: how to "hydrate" hookstate state on client side with SSR (#52)

@joompit I am not sure why, but the issue 52 is gone. I understand nobody can delete the issues. I am trying to restore it now. Here is what I have got captured in my emails:

from: @joompit
I'm sorry if I didn't explain myself correctly. When I wrote "build store" I mean for example the following situation:

A) Server side we call an API (let's say an authentication API) and fill up the authentication store and use it for certain application logic.
B) Server sends the serialized redux data over the network along with the HTML markup.
C) Client side we wouldn't have to call the API to fill up the client side store once again since it was already done server side. It simply deserializes the redux data.

It's certainly not a problem to rebuild the store client side, but it would be more performant to be able to reuse server side in order to avoid calling multiple times the same API.

Maybe the way to go is doing something such as Next.js does with Redux which is serializing and deserializing the object structure (in this case, the hookstate)?

from @joompit
Exactly, that would be what I'm aiming for. There is a "plugin" for Next.js which does something like this for Redux: https://github.com/kirill-konshin/next-redux-wrapper (specific code for the plugin is on https://github.com/kirill-konshin/next-redux-wrapper/blob/master/packages/wrapper/src/index.tsx).

If I understand correctly, I believe that what I need to do is something along the lines of what you do with the Persistence plugin (https://github.com/avkonst/hookstate/blob/master/plugins/Persistence/src/Persistence.ts). On that plugin, you do both serialization/deserialization and rebuild the state from another source (in this case, localStorage).

I would only need to make sure to send these states serialized, and then on client-side make sure to set the state to that specific value.

I believe Hookstate is more versatile and way simpler than Redux, so I'll try to find a way to mimic this same functionality that is available for Redux to Hookstate in order to be able to continue using SSR for my apps and start using Hookstate as well! It might take me a while since I'm not that experienced, but I will try to do it.

Devtools: relies on localStorage

I'm using hookstate in a react-native project and would like to be able to use the devtools.
However I get:

ReferenceError: localStorage is not defined

as localStorage doesn't exist in the react-native environment. Any chance to get devtools working with RN?

New API requirements identified

Just a list of useful features required by the production app. Consider in the future it include to the core.

  • fold StateLink<T | null> to StateLink<T> | null
  • Object.keys(StateLink.nested) is frequently used
  • State revision would be good to exposed to use in the conditions for React.useEffect

Plugin: custom label for a state/store

Suggested API as the following:

StateLink.with(Label('custom label')). Label plugin should allow to label nested links too.
Label(stateLink) should return the current label. It should not throw if it has not been attached => this will require change in the plugins retrieval API: StateLink.with(pluginId) should not throw, it will allow to implement auto attach for some plugins.

Add support for state metadata, which could be managed by a user

Need to access quick signature of a state, included for a nested state. This signature is needed as a condition for React.useEffect(() => { use state here } , [state.edition]). This plugin would deprecate Touched as it will provide broader functionality

Is there any api for set state callback function?

i have problem to call setting state A then set state B in sequence(B logic depends on state A) . As my understanding the state is change is aysnc . is there any api to have callback after setting state A as the react have?

Improve `batch()` api

Real application of experimental batch method highlighted the following things to improve:

  • add explicit option to postpone batch execution if state is promised
  • user's batch function should receive statelink as an argument
  • add custom context argument, which should be propagated to onSet/onPreset plugin callbacks

Add generic history/snapshotting plugin

Hi,

is there a way to get the previous state? Can't use usePrevious to get it, need an alternative.
The Initial plugin only gives the initial value, not the previous one.

Thank you

const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}

Use with immutable structures (immutable.js)

Hi, I been trying to implement scoped states with immutable structures, specifically lists from immutable.js. I run into the problem of needing to convert the StateLink of the list, to a list of StateLinks elements while forwarding to the child components.

My approach was to map elements of the list to StateLinks with something like

  const state = useStateLink(
    List([]),
    s => ({
      nested: s.value.map(x => createStateLink(x)),
    })
  );

However, I get the StateLink is used incorrectly error.

What's the deal here, should I stick to Arrays or am I going about this the wrong way.

Thanks.

Nesting stores example

Is there an example to nest stores?
For example, if I need all stores to be nested under one store.
Or when I attach a store dynamically.
It could be needed for SSR or for persistance. With Next.js, this was the reason why I used react-easy-peasy - one global store.

Crash on isExtensible

TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true')

Research if usage based tracking gives render performance improvements

Example using https://github.com/dai-shi/react-tracked:

More details here:
https://codesandbox.io/s/react-tracked-example-trb02
and here:
dai-shi/react-tracked#1

/* eslint-env browser */

import React, { useEffect, useRef, StrictMode } from "react";
import ReactDOM from "react-dom";

import { Provider, useTracked } from "react-tracked";
import { useStateLink } from "react-use-state-x";

const initialState = {
  count1: 0,
  deep: {
    count2: 0
  }
};

const useValue = () => {
  const link = useStateLink(initialState);
  const linkRef = useRef(link);
  useEffect(() => {
    linkRef.current = link;
  });
  return [link.value, linkRef];
};

const Counter1 = () => {
  const [value, linkRef] = useTracked();
  const increment = () => {
    linkRef.current.nested.count1.set(c => c + 1);
  };
  return (
    <div>
      <span>Count1: {value.count1}</span>
      <button type="button" onClick={increment}>
        +1
      </button>
      {Math.random()}
    </div>
  );
};

const Counter2 = () => {
  const [value, linkRef] = useTracked();
  const increment = () => {
    linkRef.current.nested.deep.nested.count2.set(c => c + 1);
  };
  return (
    <div>
      <span>Count2: {value.deep.count2}</span>
      <button type="button" onClick={increment}>
        +1
      </button>
      {Math.random()}
    </div>
  );
};

const App = () => (
  <StrictMode>
    <Provider useValue={useValue}>
      <Counter1 />
      <Counter1 />
      <Counter2 />
      <Counter2 />
    </Provider>
  </StrictMode>
);

ReactDOM.unstable_createRoot(document.getElementById("app")).render(<App />);

Use with React Native / Expo

Attempting to log statelinks yields an error, which is troublesome when trying to debug

console.error: "There was a problem sending log messages to your development environment", {"line":127542,"column":25,"sourceURL":"http://127.0.0.1:19001/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false"}

Add StateLink.merge from Mutate plugin (WAS: Using Mutate for stateRef)

createStateLink({a: 1}).with(Mutate) does not work, throws an error
Is it supposed to work like this ?
Also, I see in the Mutate example: Mutate(state)
But in validation example I see Validation(state) but also .with(Validation)

I would like to do it once with(mutate) and use Mutate functions everywhere

useStateLink(FieldStore).with(Mutate) doesn't work as well

Documentation

This library seems to be a promising one and clean to use but the documentation seem to me still lacking. I have no idea what happen underneath and if ok to use multiple usestatelink ,or any rules to be taken on when using this library like must put usetstatelink on top of the functional component , cannot put another userstatelink object into another usestatelink etc. more details guide will be more helpful.

Add plugin to optimise rerender of arrays/objects when keys are added/removed

Hi,

first of all thanks for this awesome library!

I intend to use this with a pretty big nested array. Updating an existing element only triggers re-renders on this element and it's children. Nice! But when adding a new element all the sibling elements are re-rendering even though they did not change. This can also be seen in the demo for complex tree structure.
I looked into the mutate plugin but it doesn't seem to get me where I want to go either.

Is there a way to only trigger re-renders for the one element that gets added, or for the element that gets removed and it's the following siblings? Can nested.map somehow be memoized?

Edit: Looks like I just found the solution. Using reacts own memo function seems to do the trick. If anybody is interested I can create a small example. If not this question can be removed

Question: Subscribing to nested changes at a parent level

Hi avkonst,

Amazing library you've built here. It looks exactly like the type of thing I was imagining. I'm going through evaluating libraries to try to get to a less hacky way of implementing state.

I had a question on a specific use case that's not obvious to me if hookstate would handle well (or could have a plugin written to handle).

I have a large recursive nested structure, basically a workflow of questions and answers which lead to more questions then more answers and so on. I've built a hacked-together solution with regular react components (well, preact), where the top-level component has the entire tree as a state variable, passing down sections of the tree to its children, and then each nested component has an oninput that mutates its section of the tree directly and then calls forceUpdate(). This works because the tree of Components mirror the tree of the state, so forceUpdate() correctly cascades for the corresponding subtree. I'm sure many would cry at this implementation, but it works well enough.

In hookstate, it looks like nested combined with the usage of scoped states would work well enough as a replacement here. The subtree would get passed in to each component, and it would be able to set on the scoped state oninput, which should then update the state and trigger a render.

Then, I support undo/redo functionality, by changing the top-level state tree into a proxy, so when anything is set or delete, I store a snapshot of the whole tree, and then undo/redo buttons just setState the whole tree with the snapshot, so everything renders as normal.

This is then where I'm not sure how I would implement this snapshot/apply functionality in hookstate. I think what I would want is some way to know at the top-level (or maybe even any nested level) that there is a change down the line, so that I can know to store a snapshot. There is a Touched plugin demo that shows the parent knows when it was touched, but do those just store a one-time flag for the first change? Or is there some way to subscribe to any changes? This also makes me think about things like time travel, though I think maybe your library specifically chose to eschew this functionality.

Maybe I'm thinking about this the wrong way, and it would just be most straightforward to add a transform for saveSnapshot and have the Component oninput function just call that after state set.

Any thoughts/advice?

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.