avkonst / hookstate Goto Github PK
View Code? Open in Web Editor NEWThe 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
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
Real application highlighted that plugins are exposed to None values and lots of arguments in onSet/onPreset callbacks. The following improvements are planned:
/* 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 />);
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.
The conversation starting from this comment highlighted some deficiency in the API. The suggested improvement is
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.
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.
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!
Continued discussion from dai-shi/react-tracked#1
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?
@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.
Error: . Hint: use StateLink.set(...) API: replace 'state.T1 = value'
- see missing message
Hi,
I think that the overloads for useStateLinkUnmounted with the transform parameter are missing.
This library looks really promising. I like the api and the flexibility with global, scoped and local states. :)
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?
Just a list of useful features required by the production app. Consider in the future it include to the core.
StateLink<T | null>
to StateLink<T> | null
Object.keys(StateLink.nested)
is frequently usedSuggested 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.
These experimental features proven to be successful. Update docs to expose them.
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
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?
What does Inf
in StateInf
refer to?
Real application of experimental batch method highlighted the following things to improve:
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;
}
I am trying to use the Validation plugin with a transform, but can't get it to work. I've tried it a dozen ways.
Do you have an example of using the two together? Thanks.
Does it support the
store consuming initial data from REST Apis dynamically and in Nested form
This is needed for transactional persistence
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.
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.
TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true')
This is because react rules-of-hooks verification is failed sometimes when useStateLinkUnmounted is invoked in a callback outside of react component.
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 />);
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"}
Let's move the discussion about this topic from #14 (comment)
requires new StateLink.with
method:
with(pluginId: symbol, onAttach: () => Plugin)
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
Complete docs sections:
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.
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
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?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.