Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add setupListeners example for react-native #1931

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

lindskogen
Copy link

@lindskogen lindskogen commented Jan 17, 2022

I recently had to create this for myself when experimenting with rtk-query in a react-native project.

I figured this could be useful for somebody else.

@codesandbox-ci
Copy link

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit d818363:

Sandbox Source
Vanilla Configuration
Vanilla Typescript Configuration
rsk-github-issues-example Configuration
@examples-query-react/basic Configuration
reduxjs/redux-toolkit Configuration
@examples-action-listener/counter Configuration

Copy link
Member

@phryneas phryneas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea documenting that!

But I think instead of defining a new setupListenersReactNative, we should probably show calling setupListeners with the callback instead.

Could you please test the following code and if it works update the PR accordingly?

import NetInfo from '@react-native-community/netinfo'
import { AppState } from 'react-native'

let initialized = false;
setupListeners(
  store.dispatch,
  (dispatch, { onFocus, onFocusLost, onOnline, onOffline }) => {
    let unsubscribeOnChange: NativeEventSubscription | undefined
    let unsubscribeOnNetworkStatusChange: NetInfoSubscription | undefined

    if (!initialized) {
      // Handle focus events
      unsubscribeOnChange = AppState.addEventListener('change', (state) => {
        if (state === 'active') {
          dispatch(onFocus())
        } else if (state === 'background') {
          dispatch(onFocusLost())
        }
      })

      // Handle connection events
      unsubscribeOnNetworkStatusChange = NetInfo.addEventListener((state) => {
        if (state.isConnected) {
          dispatch(onOnline())
        } else {
          dispatch(onOffline())
        }
      })
      initialized = true
    }

    const unsubscribe = () => {
      unsubscribeOnChange?.remove()
      unsubscribeOnNetworkStatusChange?.()
      initialized = false
    }
    return unsubscribe
  }
)

@lindskogen
Copy link
Author

I don't quite love that this signature requires the entirety of this function to either be defined in the same file as your store setup or called as a side-effect of importing it.

This is my current solution to those concerns, but feel free to give more feedback; you have more experience with teaching best practices.

export const setupListenersReactNative = (
  dispatch: ThunkDispatch<any, any, any>
) => {
  let initialized = false;

  return setupListeners(
    dispatch,
    (dispatch, { onFocus, onFocusLost, onOnline, onOffline }) => {
      let unsubscribeOnChange: NativeEventSubscription | undefined;
      let unsubscribeOnNetworkStatusChange: NetInfoSubscription | undefined;

      if (!initialized) {
        // Handle focus events
        unsubscribeOnChange = AppState.addEventListener("change", (state) => {
          if (state === "active") {
            dispatch(onFocus());
          } else if (state === "background") {
            dispatch(onFocusLost());
          }
        });

        // Handle connection events
        unsubscribeOnNetworkStatusChange = NetInfo.addEventListener((state) => {
          if (state.isConnected) {
            dispatch(onOnline());
          } else {
            dispatch(onOffline());
          }
        });
        initialized = true;
      }

      const unsubscribe = () => {
        unsubscribeOnChange?.remove();
        unsubscribeOnNetworkStatusChange?.();
        initialized = false;
      };
      return unsubscribe;
    }
  );
};

@phryneas
Copy link
Member

Well, that is how the existing setupListeners is meant to be called.

Of course people can wrap that all they want in the end to accomodate for their code style/architecture, but the most basic thing here is that you should always call the existing setupListeners function with a callback that works together with your use case.
I'm not sure if we gain a lot by dictating people on how exactly to wrap it 🤔

@phryneas
Copy link
Member

We could split it up another way a bit, but I'm not sure from the back of my head how that would play with TS:

import NetInfo from '@react-native-community/netinfo'
import { AppState } from 'react-native'

let initialized = false;

export const RNListeners = (dispatch, { onFocus, onFocusLost, onOnline, onOffline }) => {
    let unsubscribeOnChange: NativeEventSubscription | undefined
    let unsubscribeOnNetworkStatusChange: NetInfoSubscription | undefined

    if (!initialized) {
      // Handle focus events
      unsubscribeOnChange = AppState.addEventListener('change', (state) => {
        if (state === 'active') {
          dispatch(onFocus())
        } else if (state === 'background') {
          dispatch(onFocusLost())
        }
      })

      // Handle connection events
      unsubscribeOnNetworkStatusChange = NetInfo.addEventListener((state) => {
        if (state.isConnected) {
          dispatch(onOnline())
        } else {
          dispatch(onOffline())
        }
      })
      initialized = true
    }

    const unsubscribe = () => {
      unsubscribeOnChange?.remove()
      unsubscribeOnNetworkStatusChange?.()
      initialized = false
    }
    return unsubscribe
  }


// potentially in another file

setupListeners(
  store.dispatch,
  RNListeners
)

@Pjexaf
Copy link

Pjexaf commented Jul 11, 2022

Seems it's cause infinity query loop on Android.
I also tried 'focus' event instead of 'change' but it also calls multiple times.
Were you able to handle this?

I don't quite love that this signature requires the entirety of this function to either be defined in the same file as your store setup or called as a side-effect of importing it.

This is my current solution to those concerns, but feel free to give more feedback; you have more experience with teaching best practices.

export const setupListenersReactNative = (
  dispatch: ThunkDispatch<any, any, any>
) => {
  let initialized = false;

  return setupListeners(
    dispatch,
    (dispatch, { onFocus, onFocusLost, onOnline, onOffline }) => {
      let unsubscribeOnChange: NativeEventSubscription | undefined;
      let unsubscribeOnNetworkStatusChange: NetInfoSubscription | undefined;

      if (!initialized) {
        // Handle focus events
        unsubscribeOnChange = AppState.addEventListener("change", (state) => {
          if (state === "active") {
            dispatch(onFocus());
          } else if (state === "background") {
            dispatch(onFocusLost());
          }
        });

        // Handle connection events
        unsubscribeOnNetworkStatusChange = NetInfo.addEventListener((state) => {
          if (state.isConnected) {
            dispatch(onOnline());
          } else {
            dispatch(onOffline());
          }
        });
        initialized = true;
      }

      const unsubscribe = () => {
        unsubscribeOnChange?.remove();
        unsubscribeOnNetworkStatusChange?.();
        initialized = false;
      };
      return unsubscribe;
    }
  );
};

@phryneas
Copy link
Member

Are you using ApiProvider? That one had a bug in previous RTK versions that could subscribe to these multiple times.

@Pjexaf
Copy link

Pjexaf commented Jul 11, 2022

Are you using ApiProvider? That one had a bug in previous RTK versions that could subscribe to these multiple times.

Hello,
I use Provider from 'react-redux' (7.2.8).
Tried different approaches but still no luck with refetching queries on Android when app state changes.

I found a similar issue in the "facebook/react-native" repo but it's still open without a solution
facebook/react-native#30206

Any advice will be highly appreciated

@phryneas
Copy link
Member

I'm sorry but if this is a RN thing I don't think we can really help with that.

@andordavoti
Copy link

Hi @msutkowski, is there any update on this? Would be nice to get this into the official docs:)

@YohayBar
Copy link

YohayBar commented May 28, 2024

Thanks worked for me🙏
with typescript:

import { ThunkDispatch } from '@reduxjs/toolkit';
import { api } from ...; // import your api file
import { AppState, NativeEventSubscription } from 'react-native';

type Dispatch = ThunkDispatch<any, any, any>;
type OnFocus = typeof api.internalActions.onFocus;
type OnFocusLost = typeof api.internalActions.onFocusLost;
type OnOnline = typeof api.internalActions.onOnline;
type OnOffline = typeof api.internalActions.onOffline;

let initialized = false;

export const RNListeners = (
dispatch: Dispatch,
{
onFocus,
onFocusLost,
}: {
onFocus: OnFocus;
onFocusLost: OnFocusLost;
onOnline: OnOnline;
onOffline: OnOffline;
},
) => {
let unsubscribeOnChange: NativeEventSubscription | undefined;
if (!initialized) {
// Handle focus events
unsubscribeOnChange = AppState.addEventListener('change', (state) => {
if (state === 'active') {
dispatch(onFocus());
} else if (state === 'background') {
dispatch(onFocusLost());
}
});
//Handle connection events
initialized = true;
}
const unsubscribe = () => {
unsubscribeOnChange?.remove();
initialized = false;
};
return unsubscribe;
};

//called from another file:

import { setupListeners } from '@reduxjs/toolkit/query';

setupListeners(store.dispatch, RNListeners);

@waqas19921
Copy link

This worked for me in react-native with typescript

import NetInfo, { NetInfoSubscription } from '@react-native-community/netinfo';
import { AppState, NativeEventSubscription } from 'react-native';
import { setupListeners } from '@reduxjs/toolkit/query';

let initialized = false;

export const rtkAppListeners: Parameters<typeof setupListeners>['1'] = (
	dispatch,
	{ onFocus, onFocusLost, onOnline, onOffline },
) => {
	let unsubscribeOnChange: NativeEventSubscription | undefined;
	let unsubscribeOnNetworkStatusChange: NetInfoSubscription;

	if (!initialized) {
		// Handle focus events
		unsubscribeOnChange = AppState.addEventListener('change', state => {
			if (state === 'active') {
				dispatch(onFocus());
			} else if (state === 'background') {
				dispatch(onFocusLost());
			}
		});

		// Handle connection events
		unsubscribeOnNetworkStatusChange = NetInfo.addEventListener(state => {
			if (state.isConnected) {
				dispatch(onOnline());
			} else {
				dispatch(onOffline());
			}
		});
		initialized = true;
	}

	return () => {
		unsubscribeOnChange?.remove();
		unsubscribeOnNetworkStatusChange?.();
		initialized = false;
	};
};

and then use this with setupListeners

setupListeners(store.dispatch, rtkAppListeners);

@RobertOstermann
Copy link

Does this work with react-navigation in react-native? Or is there a different setup that is required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants