betula/use-between

attempting to use with react-native - but hit a snag

johndpope opened this issue · 14 comments

warn Package use-between has been ignored because it contains invalid configuration. Reason: Cannot find module 'use-between/package.json'

Hi @johndpope, thanks, I will see.

I've been able to install and use it in my react expo project just fine.

Hi @johndpope
Thanks for waiting!

I haven't found any issues with using "use-between" shared state on "react-native".

https://github.com/betula/use-between-react-native-ok

Perhaps in your case, it makes sense to delete the "node_modules" folder
and install the dependencies again yarn install.

happy coding

been fighting with expo - forgive me. I do love this project. Thanks for your help.

Thanks for your choice! And have a nice code👌

@johndpope Were you able to successfully use useBetween in an Expo React Native project?

@ronster37 Could you please share a minimal example?

@YahyaBagia This is how I used it:

import { useBetween } from "use-between"
import { useState } from "react"

const useCart = () => {
  const [cartItems, setCartItems] = useState<number[]>([])

  const addToCart = (item: number) =>
    setCartItems((prevValue) => [...prevValue, item])

  return {
    addToCart,
    cartItems,
  }
}

export default function useSharedCart() {
  return useBetween(useCart)
}

Now in different screens I could do:

import useSharedCart from "../../hooks/useCart"


export default function LoginScreen({
  navigation,
}: RootStackScreenProps<"PricesAndPackages">) {
  const { cartItems } = useSharedCart()
  ...

 or any other Screen

Now all the screens can share and modify the same cart state.

@ronster37

My use case is a bit more complex because I'm using expo-router along with useLocalSearchParams. I couldn't make it work, so I decided to change my approach to avoid the need to share the controller.

Hi @YahyaBagia

Can you explain more about the error you see?

Maybe some of the useful thoughts you will find in this issue, if it’s fit to you.

#43

Thanks for using use-between👍

@betula

I was about to create an Expo Snack to reproduce the issue, but to my surprise, Expo Snack still doesn’t support expo-router. I don’t have enough time at the moment to set up a local project to recreate the issue. If I manage to recreate it later, I’ll share it here for further investigation.

@betula

Here's my controller that I want to share between different routes (using expo-router). It throws two distinct sets of errors on Web and Native (Android + iOS).

import { useEffect, useState } from "react";
import { useGlobalSearchParams } from "expo-router";
import { useBetween } from "use-between";

import UserAPIs from "@/src/apis/user/UserAPIs";
import { IUser } from "@/src/apis/user/Models/IUser";
import { IUserDetails } from "@/src/apis/user/Models/IUserDetails";

import EventDispatcher from "@/src/common/EventDispatcher";

import {
  useAPIDataState,
  useShallow,
} from "@/src/global-state/APIDataState/APIDataState";

import UIComponentsStateActions from "@/src/global-state/UIComponentsState/UIComponentsStateActions";

const _useUserDataController = () => {
  const { _id } = useGlobalSearchParams<{ _id: string }>();

  const [userDetails, setUserDetails] = useState<IUserDetails>();

  const [getDataItem] = useAPIDataState(
    useShallow((state) => [state.getDataItem])
  );

  useEffect(() => {
    loadUserDetails();

    EventDispatcher.onReloadData(loadUserDetails);
    return () => {
      EventDispatcher.offReloadData(loadUserDetails);
    };
  }, []);

  const user = getDataItem("users", _id!) as IUser;

  const loadUserDetails = async () => {
    try {
      UIComponentsStateActions.showLoaderHud();
      const response = await UserAPIs.getUserDetails(_id);
      const { success, data } = response;
      if (success) {
        setUserDetails(data);
      } else {
        UIComponentsStateActions.showAPIErrorMsg(response);
      }
    } catch (error) {
      UIComponentsStateActions.showAPICallFailedMsg(error);
    } finally {
      UIComponentsStateActions.dismissLoaderHud();
    }
  };

  return { user, userDetails };
};

const useUserDataController = () => useBetween(_useUserDataController);
export default useUserDataController;
Errors Thrown
Web iOS Android
Screenshot 2024-08-28 at 4 35 13 PM Screenshot 2024-08-28 at 4 37 23 PM
Environment:
"expo": "~51.0.31",
"react-native": "0.74.5",
"expo-router": "~3.5.23",
"use-between": "^1.3.5",

Hi @YahyaBagia,

I recommend using useBetween generally for sharing state, because oftenly some hooks inside your logic, can use logic depends on contexts or another state management systems inside libraries.

In your case you should keep and share only IUserDetails state between your app components.

/**
 * Keep your app state here
 */
const useUserDetails = () => {
  const [userDetails, setUserDetails] = useState<IUserDetails>();
  return [userDetails, setUserDetails];
}

/**
 * Share your app state
 */
const useSharedUserDetails = () => {
  return useBetween(useUserDetails);
}

Next step update your useUserDataController for using shared app state.

const useUserDataController = () => {
  const { _id } = useGlobalSearchParams<{ _id: string }>();


  //
  // !ATTENTION HERE
  //
  // Update this line only useSharedUserDetails() instead of useState<IUserDetails>()
  //
  const [userDetails, setUserDetails] = useSharedUserDetails();

  const [getDataItem] = useAPIDataState(
    useShallow((state) => [state.getDataItem])
  );

  useEffect(() => {
    loadUserDetails();

    EventDispatcher.onReloadData(loadUserDetails);
    return () => {
      EventDispatcher.offReloadData(loadUserDetails);
    };
  }, []);

  const user = getDataItem("users", _id!) as IUser;

  const loadUserDetails = async () => {
    try {
      UIComponentsStateActions.showLoaderHud();
      const response = await UserAPIs.getUserDetails(_id);
      const { success, data } = response;
      if (success) {
        setUserDetails(data);
      } else {
        UIComponentsStateActions.showAPIErrorMsg(response);
      }
    } catch (error) {
      UIComponentsStateActions.showAPICallFailedMsg(error);
    } finally {
      UIComponentsStateActions.dismissLoaderHud();
    }
  };

  return { user, userDetails };
};

After that your should use useUserDataController hook in one place of your application, this will be setted shared state of your user details.

But useSharedUserDetails will be accessed everywhere.

Please @YahyaBagia try this decision, and provide feedback about result.

Thank you @betula for guiding me.

It worked once I separated the state and logic between different controllers (custom hooks).