Giter Site home page Giter Site logo

use-between's Introduction

use-between

npm version build status npm bundle size code coverage typescript supported 100k+ downloaded

When you want to separate your React hooks between several components it's can be very difficult, because all context data stored in React component function area. If you want to share some of state parts or control functions to another component your need pass It thought React component props. But If you want to share It with sibling one level components or a set of scattered components, you will be frustrated.

useBetween hook is the solution to your problem 😚

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;

Edit Counter with useBetween

useBetween is a way to call any hook. But so that the state will not be stored in the React component. For the same hook, the result of the call will be the same. So we can call one hook in different components and work together on one state. When updating the shared state, each component using it will be updated too.

If you like this idea and would like to use it, please put star in github. It will be your first contribution!

Developers 💖 use-between

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 😃

Jesper, This library should have way more stars! 🥇

@betula as I mentioned before this lib is awesome and it allowed me to simplify an app that was using Redux. I was able to replace everything we were doing with Redux with just use-between and its tiny 2K footprint!

Plus personally I think the code is cleaner because with use-between it just looks like normal hooks and not anything special like Redux code. I personally find it easier to read and understand than Redux!

Melloware, Release discussion

I was about to install Redux until I found this library and it is a live saver. Really awesome job @betula. I don't know if I ever need to use Redux again haha

Ronald Castillo

Supported hooks

+ useState
+ useEffect
+ useReducer
+ useCallback
+ useMemo
+ useRef
+ useImperativeHandle

If you found some bug or want to propose improvement please make an Issue or join to release discussion on Github. I would be happy for your help to make It better! 😉

Install

npm install use-between
# or
yarn add use-between

Enjoy and happy coding!

use-between's People

Contributors

betula avatar loia5tqd001 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

use-between's Issues

Using with Next Router to listen to URL changes

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?

How to make dark mode switcher

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);

Try dark mode with useBetween

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>
  );
};

Brain storm

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;
}

Add ssr support

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)

Simple improve useEffect

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.

How to use with useRef?

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?

can we support params?

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);

React 18 Strict Mode

@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?

Accessing useSharedCounter within Components

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;

Preparing for release 1.4

The release will contain changes to the lifecycle of the shared states.
According to:

Behaviour by default

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.

Infinitely live shred state

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);

Resume

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!

Typescript support or example?

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?

Using hooks with props

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

Make an article about shortify

Make an article about shortify:

const useLoader = () => {
  const [loading, setLoading] = useState(false);
  return { loading, setLoading };
}
const useSharedLoader = () => useBetween(useLoader);

Update readme example to React 18

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>
);

Update not triggered

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?

UseEffect: Failing trying to use another hook

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.

Support hooks that require props

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

useEffect cleanup not being called

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.

Update readme example

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;

Edit Counter with useBetween

This library should have way more stars! 🥇

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

Could / should we introduce a helper to store / get dictionary values?? thoughts

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;_

Jest Testing with use-between hooks

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("");
});

}

How to make parameterized shareds?

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

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.