betula / use-between Goto Github PK
View Code? Open in Web Editor NEWSharing state between React components
License: MIT License
Sharing state between React components
License: MIT License
Wrap your app to Ssr
component:
<Ssr server={true} data={{ counter: 1 }}>
<App />
</Ssr>
And after It, each shared hook started with one parameter
function useSharedCounter(data) {
const [counter, setCounter] = useState(data.counter);
}
[] Make next.js ssr example
[] Make an article
Server autodetection from react repo code, react native is worked too.
export const isClient: boolean = !!(
typeof window !== 'undefined' &&
typeof window.document !== 'undefined' &&
typeof window.document.createElement !== 'undefined'
);
I can simplify that to:
<Initial data={{ counter: 1 }} server?>
<App />
</Initial>
Yahhh! It so amazing)
Don't have a simple example to share at the moment, but curious if this library supports calling the cleanup of the useEffect
hook when there are no more instances of the hook in the React DOM.
import React, { useState, useCallback } from 'react';
import { useBetween } from 'use-between';
const useCounter = () => {
const [count, setCount] = useState(0);
const inc = useCallback(() => setCount(c => c + 1), []);
const dec = useCallback(() => setCount(c => c - 1), []);
return {
count,
inc,
dec
};
};
const useSharedCounter = () => useBetween(useCounter);
const Count = () => {
const { count } = useSharedCounter();
return <p>{count}</p>;
};
const Buttons = () => {
const { inc, dec } = useSharedCounter();
return (
<>
<button onClick={inc}>+</button>
<button onClick={dec}>-</button>
</>
);
};
const App = () => (
<>
<Count />
<Buttons />
<Count />
<Buttons />
</>
);
export default App;
Does usebetween work properly if i have a hook that recieves props?
Eslinter complains i cant use a hook inside a callback and there are no examples in the documentation.
I tried using it like (props) => useBetween((props) => useLayoutModalsBase(props))
and it doesnt work, i believe it might be because that creates a new hook instance everytime i call it
This is probably unlikely due to how this library uses internal React features, but I was wondering if it would be possible to add Preact support?
Currently it doesn't seem to work :( https://codesandbox.io/s/relaxed-sunset-u2omw
Code Sandbox: https://codesandbox.io/s/react-router-forked-mnnxb
In the above sandbox I wanted to use a useEffect
hook inside my shared hook. For example I want to write a shared hook that listens for location
changes and updated the document title like this from React Router...
import React, { useState, useEffect } from "react";
import { useBetween } from "use-between";
import { useLocation } from "react-router-dom";
const useShared = () => {
const location = useLocation();
const [value, setValue] = useState(false);
useEffect(() => {
document.title = `Location: ${location}`;
setValue(true);
}, [location]); // Only re-run the effect page location changes
return {
value,
setValue
};
};
export const useSharedHook = () => useBetween(useShared);
Is my use case wrong because I get this error:
TypeError
Invalid attempt to destructure non-iterable instance.
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.
basically am in an enquiry of whether we could introduce a helper to facilitate storing / getting dictionary values as per the guy that introduced a reducer pattern here
https://stackoverflow.com/questions/54150783/react-hooks-usestate-with-object
_If you are looking for the same functionality as this.setState ( came from the class components ) in functional components then this is the answer that helps you a lot.
For Example
You have a state like below and want to update the specific field only from the whole state then you need to use the object destructing every time and sometimes it will be irritating.
const [state, setState] = useState({first: 1, second: 2});
// results will be state = {first: 3} instead of {first: 3, second: 2}
setState({first: 3})
// To resolve that you need to use object destructing every time
// results will be state = {first: 3, second: 2}
setState(prev => ({...prev, first: 3}))
To solve that I came up with the useReducer approach. Please check useReducer.
const stateReducer = (state, action) => ({
...state,
...(typeof action === 'function' ? action(state) : action),
});
const [state, setState] = useReducer(stateReducer, {first: 1, second: 2});
// results will be state = {first: 3, second: 2}
setState({first: 3})
// you can also access the previous state callback if you want
// results will remain same, state = {first: 3, second: 2}
setState(prev => ({...prev, first: 3}))
You can store that stateReducer in utils files and import it in every file if you want.
Here is the custom hook if you want.
import React from 'react';
export const stateReducer = (state, action) => ({
...state,
...(typeof action === 'function' ? action(state) : action),
});
const useReducer = (initial, lazyInitializer = null) => {
const [state, setState] = React.useReducer(stateReducer, initial, init =>
lazyInitializer ? lazyInitializer(init) : init
);
return [state, setState];
};
export default useReducer;_
Then you want separate your React hooks state betweeen several components its can be
very difficult, because all context data stored in React component function area.
If you want share some of control functions to another component your need pass
It thought component props. But If your want share It to sibling one level component or
some of set differents components, you will be frustrated.
useBetween its a decision of your problem)
function useUserStore() {
const [ user, setUser ] = useState({});
const [ loading, fetch ] = useFetch('/api/user');
return {
user,
setUser,
load: async () => setUser(await fetch()),
loading
}
}
Its very strange idea, need little bit more think about. But I think Its will be work!
How I can fix hooks? No need fix, another library. I think Its ok!
But what I thinking about custom user hooks. I need try to found way, may be Its
possible for example only for preact. Its will be already ok too.
Or I can make babel plugin who change all { useState } from 'react', but not all cases,
only for es not for commonJS.
Or preact/hooks or anybody else.
Profit?
I think I can make It only for preact, because I need api between them.
I can try It, great idea.
function UserLoading() {
const { loading } = useBetween(useUserStore);
return loading
? <Loading />
: null;
}
Very simple use!
react or preact (no need babel-plugin)
https://codesandbox.io/s/objective-wiles-vmhv7?file=/src/App.js:51-121
console.log(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED);
ITS POSSIBLE FOR REACT.
Ahahaha))
Okey, I will make It.
Cool!!! Yoooo!
Only one function, and after It make article.
If you want use It only for one component or each instance for each component in set
function UserLoading() {
const { loading } = useUserStore();
return loading
? <Loading />
: null;
}
It simple!
For root you'll need use
<ZoneBetween>
<App />
</ZoneBetween>
If you need make separated zones you can use React component.
After Zone unmount all sores will be unmount. useEffect api working same as for React component.
use-between
function UserLoading() {
const { loading } = useBetween(useUserStore);
return loading
? <Loading />
: null;
}
[] make an article
Make answer for this thread:
https://stackoverflow.com/questions/38901106/how-to-make-a-shared-state-between-two-react-components
It is both: synchronization and synchronous.
import { useLayoutEffect } from "./use_layout_effect";
export const useEffect = (effect, values) => {
useLayoutEffect(
() => new Promise(resolve => requestAnimationFrame(_ => resolve(effect()))),
values
);
};
I can make something the same. (from hookuspocus)
Think about more transparency for supported hooks
To make useEffect
asynchronous (nextTick algorithm) for better compatibility.
And make useImperativeHandle
and maybe useLayoutEffect
on that algorithm for compatibility with exists hook's code. To remove synchronous updating.
Or stay only synchronous useEffect
(on the end of tick) with a diamond problem solved.
warn Package use-between has been ignored because it contains invalid configuration. Reason: Cannot find module 'use-between/package.json'
Hi Betula,
Thank you for the great idea, just trying your library and I can't figure out how to pass parameters to my useBetween hook...
For example:
`
const useStreamingData = ({symbol, market = 'stocks', shouldUpdate = true}) => {
...
export const useLiveData = () => useBetween(useStreamingData());
`
Doesn't seem to work...
Thanks again,
Théo
To make my code cleaner I'm exporting my Component into their separate file.
By doing so I'm having an issue as I can't access useSharedCounter
Should I try to pass
Rather than
const useSharedCounter = () => useBetween(useCounter);
const Count = () => {
const { count } = useSharedCounter();
return <p>{count}</p>;
};
const Buttons = () => {
const { inc, dec } = useSharedCounter();
return (
<>
<button onClick={inc}>+</button>
<button onClick={dec}>-</button>
</>
);
};
const App = () => (
<>
<Count />
<Buttons />
<Count />
<Buttons />
</>
);
export default App;
I am trying to do the following
But I can't see to retrieve the useSharedCounter={useSharedCounter} function in either Button.js or Count.js
Is this possibel to perform such refactoring with this library?
import Button from "./Button.js'
import Count from "./Count.js'
const useSharedCounter = () => useBetween(useCounter);
const App = () => (
<>
<Count useSharedCounter={useSharedCounter} />
<Buttons useSharedCounter={useSharedCounter} />
<Count useSharedCounter={useSharedCounter} />
<Buttons useSharedCounter={useSharedCounter} />
</>
);
export default App;
Hey there! I'm making a simple testing scenario and I can't get my Header
component (code below):
export default () => {
const { user, loading } = useBetween(useUser);
console.log('header', {user, loading});
/**
* Navigation callbacks for the right part of the header
*/
const goToDashboard = React.useCallback(() => history.push('/dashboard'), []);
const goToLogin = React.useCallback(() => history.push('/'), []);
const goToProfile = React.useCallback(() => history.push('/profile'), []);
return (
// ....
to grab updates from useUser
, following code:
const useUser = () => {
const [user, setUser] = React.useState(null as any);
const [loading, setLoading] = React.useState(true);
const fetchUser = React.useCallback(() => {
console.log('fetch user');
backend.session.get().then((user) => {
console.log('set user', user);
setUser(user);
}).catch(() => {
// nope here, just keep user null meaning
// we are not logged in
}).then(() => {
setLoading(false);
})
}, [setUser, setLoading])
React.useEffect(() => {
fetchUser();
}, []);
console.log('user', user);
return { user, loading };
};
here's what my console output looks like on startup:
user.ts:24 fetch user
index.tsx:58 header {user: null, loading: true}
user.ts:26 set user {id: 1, email: '[email protected]', name: 'Alex Swensson', role: 'admin', bio: '# Me\n\n...', …}
user.ts:38 user {id: 1, email: '[email protected]', name: 'Alex Swensson', role: 'admin', bio: '# Me\n\n...', …}
user.ts:38 user {id: 1, email: '[email protected]', name: 'Alex Swensson', role: 'admin', bio: '# Me\n\n...', …}
index.js:573 [webpack-dev-server] Hot Module Replacement enabled.
index.js:573 [webpack-dev-server] Live Reloading enabled.
as you can see the update in useUser
is triggered, but it didn't appear in Header
components. What can I be possibly doing wrong?
The release will contain changes to the lifecycle of the shared states.
According to:
The default lifecycle will be exactly what users obviously expect.
As long as the shared state has connections it is mounted, when owners are not, it is unmounted.
And for situations where you want to have an infinitely live shred state, there is a new way to call the useBetween
hook
const useDarkModeState = () => {
const [isDark, setDark] = useState(
localStorage.getItem("darkMode")
);
useEffect(
() => localStorage.setItem("darkMode", isDark ? "true" : ""),
[isDark]
);
const toggle = useCallback(
() => setDark((state) => !state),
[setDark]
);
return {
isDark,
toggle
};
};
If you want to have an infinitely live shred state you should use useBetween.forever
instead of regular useBetween
.
export const useDarkMode = () => useBetween.forever(useDarkModeState);
Going back to the default behavior where a shread state only exists when someone needs it is a serious performance optimization.
I believe that such behavior would be correct and logical. That this is exactly what users expect!
Have a Great Code!
Update index
file code.
import { StrictMode } from "react";
import * as ReactDOMClient from "react-dom/client";
import App from "./App";
const rootElement = document.getElementById("root");
const root = ReactDOMClient.createRoot(rootElement);
root.render(
<StrictMode>
<App />
</StrictMode>
);
import React, { useState, useCallback, useEffect } from "react";
import { useBetween } from "use-between";
const useDarkModeState = () => {
const [isDark, setDark] = useState(
localStorage.getItem("darkMode")
);
useEffect(
() => localStorage.setItem("darkMode", isDark ? "true" : ""),
[isDark]
);
const toggle = useCallback(
() => setDark((state) => !state),
[setDark]
);
return {
isDark,
toggle
};
};
export const useDarkMode = () => useBetween(useDarkModeState);
const Button = () => {
const { isDark, toggle } = useDarkMode();
return (
<button
style={{
...styles.button,
...(isDark ? styles.buttonDark : null)
}}
onClick={toggle}
>
Switch
</button>
);
};
const App = () => {
const { isDark } = useDarkMode();
return (
<div
style={{
...styles.app,
...(isDark ? styles.appDark : null)
}}
>
{/* ... */}
</div>
);
};
@betula have you tested with React 18 yet? One of my devs is reporting its not working to update values in React 18.
I haven't drilled into it yet but wanted to see if you have tested on 18?
it may looks like this:
const useCounter = (defaultValue1, defaultValue2) => {
const [count1, setCount1] = useState(defaultValue1);
const [count2, setCount2] = useState(defaultValue2);
const inc1 = useCallback(() => setCount1(c => c + 1), []);
const inc2 = useCallback(() => setCount2(c => c + 1), []);
return {
count1,
count2,
inc1,
inc2
};
};
const useSharedCounter = (defaultValue1, defaultValue2) => useBetween(useCounter, defaultValue1, defaultValue2);
Hey @betula , just wanted to say thank you for this awesome library! ✋
Switching from React Context + useReducer to this library reduced soooo much boilerplate code.
It's much more nice, clean and simple now, plus the bonus of using "useEffect" incapsulated within the state hook is just awesome.
I don't get why this library doesn't have more stars and more popularity. People using useContext+useReducer are really missing out 😃
Thanks!
/ Jesper
Anyone Jest testing with use-between hooks? Let me give you a shortened example code...
export const MyDialog = () => {
// shared use between hook set in another component
const { displayAddAnalysis, setDisplayAddAnalysis } = useMyHook();
return (
<Dialog id="dlg-new-planned-analysis"
header={`New Planned Analysis`}
visible={displayAddAnalysis}
onHide={onHide}>
)
}
I need to set the displayAddAnalysis
use-between hook value to true
for my Jest test.
Here is the only way we could figure out to set it because use-between
must be used in a Component is to create a fake <testComponent>
but some on our dev team think this feels wrong. Any thoughts on the best way to test use-between hooks in Jest?
function getDialog() {
//Test component exists so that the setMyHook can be switched to true.
//Otherwise, the Dialog component will not exist to test
function TestComponent() {
const { setDisplayAddAnalysis } = usePlannedAnalysisHook();
setDisplayAddAnalysis(true);
return null;
}
render(<TestComponent />);
return render(<MyDialog />);
}
test("Making sure error messages do not show on component loading", async () => {
//Arrange
const Dialog = getDialog();
// Act
const study = await Dialog.findByTestId("studyError");
//Assert
expect(study.textContent).toMatch("");
});
}
I've implemented a custom hook that I can call via useBetween()
in a normal JS component just fine but when I try calling it in a .tsx
typescript component, like this:
const { foo, bar } = useBetween(useCustomHook())
I get an error:
Property 'foo' does not exist on type 'unknown'.
I've tried to add types but it seems the useBetween()
code is defining its own return type of (initialData?: any): unknown
.
Edit: my customHook uses TS too with a defined return type (Object) - likely the issue?
Edit 2: Removing the return type declaration makes no difference to the error message.
I'm a TS noob so I might've missed something obvious - Is there a trick to getting it working in TS?
Make an article about shortify:
const useLoader = () => {
const [loading, setLoading] = useState(false);
return { loading, setLoading };
}
const useSharedLoader = () => useBetween(useLoader);
Hi,
I might be misunderstanding this tool or how it works (although so far I love it, thanks so much!) but is it possible to extend it to be able to work with hooks that accept arguments? In all the examples I've seen they're just no-parameter hooks and when I've tried myself they don't seem to work properly.
Cheers
I really appreciate this utility. It would be great if it could support all hooks
This is my first time using use-between
and I've hit a problem which is very similar to #43. I'm using Next.js and Next Router.
Basically I've created a custom hook which uses useEffect
and Next.js useRouter
to read the query params on first render and listen to changes afterwards:
import { useEffect } from 'react';
import { useRouter } from 'next/router';
const useQueryParams = () => {
const router = useRouter();
useEffect(() => {
const { market, beds, pricemin, pricemax, location, agent, sortby, page } = router.query;
// ...set state accordingly
}, [router.query]);
const updateUrl = ({ market, bedrooms, pricemin, pricemax, location, agent, sortby, page } = {}) => {
const { query } = router;
// ...modify query params accordingly
router.push({
pathname: router.pathname,
query: query,
});
};
return { updateUrl };
};
export const useSharedQueryParams = () => useBetween(useQueryParams);
I'm then trying to use this in a Next.js page like so:
import { useRouter } from 'next/router';
import { useBetween } from 'use-between';
import useSharedQueryParams from '@/hooks/useQueryParams';
const BuyPage = () => {
const { updateUrl } = useBetween(useSharedQueryParams);
// ...
}
But I'm receiving the error:
Error: Hook "useContext" no possible to using inside useBetween scope.
Can you provide any guidance on how to implement the fix suggested in #43 with Next Router?
I was doing some logic that depends on search query
so I did use useLocation inside a hook that will be shared across other hooks
I got this error :
Sandbox : https://codesandbox.io/s/react-router-basic-forked-c00hm4?file=/example.js
See following simple example code:
'use client';
import { useRef } from 'react';
import { Toast } from 'primereact/toast';
import { useBetween } from 'use-between';
export const useSharedToast = () => useBetween(useRef<Toast>);
export default function RootLayout({ children }: { children: React.ReactNode }) {
const toast = useSharedToast();
return (
<html lang='de'>
<body>
{children}
<Toast ref={toast} />
</body>
</html>
);
}
This gives me the error: Type 'MutableRefObject<Toast | undefined>' is not assignable to type 'LegacyRef<Toast> | undefined'.
I know I should initialize useRef
with null
, but I don't know how to do that with useBetween
. I didn't find any examples with useRef, though it's listed as a supported hook.
How can I do this?
Here is the codesandbox to reproduce the problem.
value
from false to true.value
to be still true. But it's false.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.