react-redux-firebase Firestore not ever loading the data - react-native

I have a react-native app that is using react-redux-firebase, react-native-firebase to connect to a firestore to return some todo records. There are records in the store, however all that is displayed is the "loading" text. It never updates the todos.
I have tried connecting directly within the app and it works fine so I'm assuming that firebase is configured correctly.
this.ref.get().then((querySnapshot) => {
console.log("ListItemFS: started query snapshot", querySnapshot);
this.handleSnapshot(querySnapshot);
});
This is my App.tsx
import React from 'react'
import { Provider } from 'react-redux'
import { ReactReduxFirebaseProvider } from 'react-redux-firebase';
import firebase from 'react-native-firebase'
import { createStore, compose } from 'redux'
import { composeWithDevTools } from "redux-devtools-extension";
import { createFirestoreInstance, firestoreReducer } from 'redux-firestore'
import { combineReducers } from 'redux';
import { firebaseStateReducer } from 'react-redux-firebase'
import Todos from './Todos';
//Configure firebase
const fbConfig = {
userProfile: 'users',
useFirestoreForProfile: true, // Firestore for Profile instead of Realtime DB,
enableLogging: true
}
//firebase.initializeApp(fbConfig) // <- I have tried with and without this with no difference.
//let db = firebase.firestore(); makes no difference
//Configure reducers
const rootReducer = combineReducers({
firebase:firebaseStateReducer,
firestore: firestoreReducer
})
// Configure store
const store = createStore(rootReducer, compose(composeWithDevTools()))
export default () => (
<Provider store={store}>
<ReactReduxFirebaseProvider
firebase={firebase}
config={fbConfig}
dispatch={store.dispatch}
createFirestoreInstance={createFirestoreInstance}
>
<Todos />
</ReactReduxFirebaseProvider>
</Provider>
)
This is my TODO component
import React from 'react'
import { connect } from 'react-redux'
import { compose } from 'redux'
import { isLoaded, isEmpty } from 'react-redux-firebase/lib/helpers'
import { View ,Text} from 'react-native'
import { firestoreConnect } from 'react-redux-firebase'
function Todos({ todos, firebase, state }) {
console.log("ToDos",todos);
console.log("state",state);
if (!isLoaded(todos)) {
return <Text>Loading...</Text>
}
if (isEmpty(todos)) {
return <Text>Todos List Is Empty</Text>
}
return (
<View>
{
Object.keys(todos).map(
(key, id) => (
<Text >First Key:{todos[key]}</Text>
)
)
}
</View>
)
}
export default compose(
firestoreConnect(() => [
{ collection: 'todos' } //'todos' // { path: '/todos' } // neither works
]),
connect(state => ({
todos: state.firestore.data.todos
// profile: state.firebase.profile // load profile
}))
)(Todos)
The following are my dependencies
"dependencies": {
"expo": "^34.0.1",
"fbjs": "^1.0.0",
"react": "16.8.3",
"react-dom": "16.8.3",
"react-native": "0.59.10",
"react-native-firebase": "5.2.0",
"react-native-gesture-handler": "~1.3.0",
"react-native-reanimated": "~1.1.0",
"react-native-router-flux": "^4.1.0-beta.8",
"react-native-screens": "1.0.0-alpha.22",
"react-native-size-matters": "^0.2.1",
"react-native-unimodules": "~0.5.2",
"react-native-web": "^0.11.4",
"react-navigation-deprecated-tab-navigator": "^1.3.0",
"react-redux": "^5.0.6",
"react-redux-firebase": "^3.0.0-beta.2",
"recompose": "^0.30.0",
"redux": "3.x.x",
"redux-devtools-extension": "^2.13.8",
"redux-firestore": "^0.8.0"
}
If you have any suggestions on what I've done wrong I would be really grateful.

Try to add
import 'react-native-firebase/firestore'
in your App.tsx

Related

How to fix two navigationcontainer conflict in react-native

I want to use the ZegoCloudUIkit for voice call so after setting up my project, I imported the ZegoCloudUIKit and applied it to my existing code. I runned it and was getting errors.
first error message:
Error: Looks like you have nested a 'NavigationContainer' inside another. Normally you need only one container at the root of the app, so this was probably an error. If this was intentional, pass 'independent={true}' explicitly. Note that this will make the child navigators disconnected from the parent and you won't be able to navigate between them.
so I commentted the navigationContainer Component that was nested to the ZegoUIkit component and I got a different error but the console did not through the error instead from the mobile app.
secode error message:
this is my App.tsx file
import React, {useEffect, useState} from 'react';
import {StyleSheet, View} from 'react-native';
import RNBootSplash from 'react-native-bootsplash';
import {toast, Toasts} from '#backpackapp-io/react-native-toast';
import {SafeAreaProvider} from 'react-native-safe-area-context';
import axios from 'axios';
import AsyncStorage from '#react-native-async-storage/async-storage';
import {useDispatch, useSelector} from 'react-redux';
import {
ZegoUIKitPrebuiltCallWithInvitation,
ZegoInvitationType,
ONE_ON_ONE_VIDEO_CALL_CONFIG,
ONE_ON_ONE_VOICE_CALL_CONFIG,
GROUP_VIDEO_CALL_CONFIG,
GROUP_VOICE_CALL_CONFIG,
} from '#zegocloud/zego-uikit-prebuilt-call-rn';
import ZegoUIKitSignalingPlugin from '#zegocloud/zego-uikit-signaling-plugin-rn';
import {zego_appid, zego_appkey} from '#env';
import Main from './src/Main';
import {GestureHandlerRootView} from 'react-native-gesture-handler';
import {returnListOfProviders} from './src/redux/provider/listProvider';
import {provider} from './src/apis/endpoints';
import {RootState} from './src/types/redux.type';
import whichSignedUser from './src/utils/whichSignedUser';
import IClientData from './src/interfaces/clientData';
import IProviderData from './src/interfaces/providerData';
import {
NavigationContainer,
useNavigation,
useNavigationContainerRef,
} from '#react-navigation/native';
import {isSigned} from './src/redux/auth/isSigned';
import ActivityLoader from './src/components/widgets/ActivityLoader';
import {INavigateProps} from './src/interfaces';
import {Text} from 'react-native-animatable';
const App = () => {
const dispatch = useDispatch();
React.useEffect(() => {
const init = async () => {
try {
// const response = await listProvider();
const response = await axios.get(provider);
await AsyncStorage.setItem(
'list_provider',
JSON.stringify(response.data)
);
dispatch(
returnListOfProviders(
JSON.parse(await AsyncStorage.getItem('list_provider'))
)
);
} catch (error) {
if (error.message === 'Network Error') {
toast('Network error!');
} else if (error.response) {
// Request made but the server responded with an error
toast('Ouch... Something went wrong.');
} else if (error.request) {
// Request made but no response is received from the server.
toast('Request exception: We are sorry for this!');
} else {
// Error occured while setting up the request
toast('An error occured!');
}
}
};
init().finally(async () => {
await RNBootSplash.hide({fade: true});
});
}, [dispatch]);
return (
<SafeAreaProvider>
<GestureHandlerRootView style={styles.container}>
<ZegoUIKitPrebuiltCallWithInvitation
independent
appSign={zego_appkey}
appID={zego_appid}
userID={'ALi'}
callID={'ALi'}
userName={'ALi'}
// callID={userData?.id}
// userID={userData?.phone_number} // userID can be something like a phone number or the user id on your own user system.
// userName={`${userData?.first_name} ${userData?.last_name}`}
ringtoneConfig={{
incomingCallFileName: 'zego_incoming.mp3',
outgoingCallFileName: 'zego_outgoing.mp3',
}}
requireConfig={(data: any) => {
const config =
data.invitees.length > 1
? ZegoInvitationType.videoCall === data.type
? GROUP_VIDEO_CALL_CONFIG
: GROUP_VOICE_CALL_CONFIG
: ZegoInvitationType.videoCall === data.type
? ONE_ON_ONE_VIDEO_CALL_CONFIG
: ONE_ON_ONE_VOICE_CALL_CONFIG;
return config;
}}
// config={{
// ...ONE_ON_ONE_VOICE_CALL_CONFIG,
// onOnlySelfInRoom: () => {
// navigate('drawer_nav');
// },
// onHangUp: () => {
// navigate('drawer_nav');
// },
// }}
plugins={[ZegoUIKitSignalingPlugin]} // The signaling plug-in used for call invitation must be set here.
>
{/* <NavigationContainer> */}
<Main />
<Toasts />
{/* </NavigationContainer> */}
</ZegoUIKitPrebuiltCallWithInvitation>
</GestureHandlerRootView>
</SafeAreaProvider>
);
};
const styles = StyleSheet.create({
container: {flex: 1},
});
export default App;
while these are the project dependencies:
"dependencies": {
"#backpackapp-io/react-native-toast": "^0.8.0",
"#react-native-async-storage/async-storage": "^1.17.10",
"#react-native-community/picker": "^1.8.1",
"#react-navigation/bottom-tabs": "^6.3.3",
"#react-navigation/drawer": "^6.4.4",
"#react-navigation/material-top-tabs": "^6.2.4",
"#react-navigation/native": "^6.1.2",
"#react-navigation/native-stack": "^6.9.8",
"#reduxjs/toolkit": "^1.8.6",
"#types/react-native-vector-icons": "^6.4.12",
"#zegocloud/zego-uikit-prebuilt-call-rn": "^1.3.1",
"#zegocloud/zego-uikit-rn": "^1.4.10",
"#zegocloud/zego-uikit-signaling-plugin-rn": "^1.0.4",
"axios": "0.26.0",
"react": "18.1.0",
"react-delegate-component": "^1.0.0",
"react-native": "0.70.0",
"react-native-animatable": "^1.3.3",
"react-native-bootsplash": "^4.3.2",
"react-native-calendars": "^1.1289.0",
"react-native-date-picker": "^4.2.5",
"react-native-document-picker": "^8.1.1",
"react-native-dotenv": "^3.4.2",
"react-native-gesture-handler": "^2.8.0",
"react-native-image-picker": "^4.10.1",
"react-native-kommunicate-chat": "^1.6.8",
"react-native-pager-view": "^6.0.2",
"react-native-push-notification-popup": "^1.6.1",
"react-native-quick-base64": "^2.0.5",
"react-native-reanimated": "^2.10.0",
"react-native-safe-area-context": "^4.4.1",
"react-native-screens": "^3.18.2",
"react-native-select-dropdown": "^2.0.4",
"react-native-sound": "^0.11.2",
"react-native-tab-view": "^3.1.1",
"react-native-vector-icons": "^9.2.0",
"react-redux": "^8.0.4",
"zego-express-engine-reactnative": "^3.1.2",
"zego-zim-react-native": "^2.5.0"
},
other info:
info Fetching system and libraries information...
System:
OS: Linux 5.15 Zorin OS 16.2
CPU: (4) x64 Intel(R) Core(TM) i5-7200U CPU # 2.50GHz
Memory: 3.45 GB / 7.53 GB
Shell: 5.8 - /bin/zsh
Binaries:
Node: 16.18.1 - /tmp/yarn--1673425542734-0.8217341778464808/node
Yarn: 1.22.19 - /tmp/yarn--1673425542734-0.8217341778464808/yarn
npm: 9.2.0 - ~/.nvm/versions/node/v16.18.1/bin/npm
Watchman: Not Found
SDKs:
Android SDK: Not Found
IDEs:
Android Studio: Not Found
Languages:
Java: 17.0.5 - /usr/bin/javac
npmPackages:
#react-native-community/cli: Not Found
react: 18.1.0 => 18.1.0
react-native: 0.70.0 => 0.70.0
npmGlobalPackages:
*react-native*: Not Found
I am trying to achieve something similar to the article here.
In my case I want to have a one_on_one_voice_call where a caller can invite the other person on a call.

Why is my navigation ref not ready in React Navigation 6 with Redux?

In React Navigation 6, my research shows that to navigate without a prop I should make a reference and use createNavigationContainerRef. I'm able to pass down the screen name to my dispatch but for some reason when I evaluate the condition with isReady I'm always told it isn't. The code:
App.js:
import React from 'react'
import { NavigationContainer } from '#react-navigation/native'
import 'react-native-gesture-handler'
// Provider
import { Provider as AuthProvider } from './src/context/AuthContext'
// Navigation
import { navigationRef } from './src/navigation/NavRef'
// Screens
import ResolveAuthScreen from './src/screens/ResolveAuthScreen'
const App = () => {
return (
<AuthProvider>
<NavigationContainer ref={navigationRef}>
<ResolveAuthScreen />
</NavigationContainer>
</AuthProvider>
)
}
export default App
ResolveAuthScreen.js:
import React, { useEffect, useContext } from 'react'
// Context
import { Context as AuthContext } from '../context/AuthContext'
const ResolveAuthScreen = () => {
const { tryLocalSignIn } = useContext(AuthContext)
useEffect(() => {
tryLocalSignIn()
}, [])
return null
}
export default ResolveAuthScreen
AuthContext.js (stripped down):
import AsyncStorage from '#react-native-async-storage/async-storage'
// Context
import createContext from './createContext'
// Nav
import * as NavRef from '../navigation/NavRef'
const authReducer = (state, action) => {
switch (action.type) {
case 'signin':
return { errorMessage: '', token: action.payload }
case 'clear_error':
return { ...state, errorMessage: '' }
default:
return state
}
}
const tryLocalSignIn = dispatch => async () => {
const token = await AsyncStorage.getItem('token')
console.log({ token }) // renders token
if (token) {
dispatch({ type: 'signin', payload: token })
NavRef.navigate('TrackListScreen')
} else {
NavRef.navigate('SignUp')
}
}
export const { Provider, Context } = createContext(
authReducer,
{ tryLocalSignIn },
{ token: null, errorMessage: '' },
)
NavRef.js:
import { createNavigationContainerRef } from '#react-navigation/native'
export const navigationRef = createNavigationContainerRef()
export function navigate(name, params) {
console.log({ name, params })
if (navigationRef.isReady()) {
console.log('ready')
console.log({ name, params })
navigationRef.navigate('TrackDetailScreen', { name, params })
} else {
console.log('not ready')
}
}
When I log the token from dispatch I get back the token. When I log the screen I get back TrackListScreen from navigate but whenever it's fired it always returns the console log of not ready.
Docs:
Navigating without the navigation prop
Navigating to a screen in a nested navigator
"dependencies": {
"#react-native-async-storage/async-storage": "~1.15.0",
"#react-navigation/bottom-tabs": "^6.0.9",
"#react-navigation/native": "^6.0.6",
"#react-navigation/native-stack": "^6.2.5",
"axios": "^0.24.0",
"expo": "~43.0.0",
"expo-status-bar": "~1.1.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-native": "0.64.2",
"react-native-elements": "^3.4.2",
"react-native-gesture-handler": "~1.10.2",
"react-native-reanimated": "~2.2.0",
"react-native-safe-area-context": "3.3.2",
"react-native-screens": "~3.8.0",
"react-native-web": "0.17.1"
},
Why is my navigate not working after my dispatch or why does the isReady false?
I'm having the same issue. When trying to access the exported navigationRef.isReady() from a redux-saga file, it always returns false. I'm not sure this is a safe approach, nor have I properly tested this, but the following workaround seems to work for me:
App.js
import {setNavigationRef, navigationIsReady} from './NavigationService';
const navigationRef = useNavigationContainerRef();
return (
<NavigationContainer
ref={navigationRef}
onReady={() => {
setNavigationRef(navigationRef);
}}>
...
</NavigationContainer>
);
NavigationService.js
export let navigationRefCopy = undefined;
export function setNavigationRef(navigationRef) {
navigationRefCopy = navigationRef;
}
export function navigationIsReady() {
return navigationRefCopy?.isReady(); // returns true when called in a redux saga file.
}

Invariant Violation: Element type is invalid: expected a string using connect of react-redux

Supposedly, I've got a small problem, but can't tackle it.
I've got a small React-Native app, one screen only.
Redux is used as a store. It's being built via Expo. While using connect of react-redux, I've got the following error:
Invariant Violation: Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
Check the render method of Home.
The app works if the component rendered by Home component isn't wrapped by connect().
Attach some code below.
App.js
import React from "react";
import { createStackNavigator, createAppContainer } from 'react-navigation';
import Home from './src/pages/Home';
import { Provider } from 'react-redux';
import configureStore from './src/stores/store';
const { store } = configureStore();
function App() {
return (
<Provider store={store}>
<AppContainer />
</Provider>
)
}
const MainNavigator = createStackNavigator({
Home: { screen: Home }
},
{
headerMode: 'none',
navigationOptions: {
headerVisible: false,
}
}
);
const AppContainer = createAppContainer(MainNavigator);
export default App;
Home.js
import React from "react";
import { StyleSheet, View, Text, Dimensions } from "react-native";
import SwitchEventTypes from "../components/SwitchEventTypes";
class Home extends React.Component {
constructor() {
super();
}
render() {
return (
<View>
<SwitchEventTypes />
</View>
)
}
}
SwitchEventTypes.js
import React, { Component } from "react";
import { connect } from 'react-redux';
import { StyleSheet, View, Text, Dimensions, TouchableOpacity } from "react-native";
import { updateEventType } from '../actions/actions';
const mapDispatchToProps = (dispatch) => {
return {
updateEventType: (newEventType) => {
dispatch(updateEventType(newEventType));
}
};
};
const mapStateToProps = (state) => {
return {
eventType: state.filter.eventType,
};
};
class SwitchEventTypes extends React.Component {
constructor() {
super();
this.state = {
isSwitchEventTypeOn: true
}
this.handleEventTypeChange = this.handleEventTypeChange.bind(this);
}
handleEventTypeChange(newEventType) {
this.props.updateEventType(newEventType);
}
render() {
return (
<View style={styles.switchTypesContainer}>
{this.props.eventType === 'active' ? <Text>123</Text> :
<Text>456</Text>
}
</View>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SwitchEventTypes);
store.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
const middleware = [thunk];
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const composedEnhancers = composeEnhancers(
applyMiddleware(...middleware),
)
export default () => {
const store = createStore(rootReducer, composedEnhancers);
return { store };
};
package.json
"dependencies": {
"expo": "^32.0.6",
"expo-react-native-shadow": "^1.0.3",
"expo-svg-uri": "^1.0.1",
"prop-types": "^15.7.2",
"react": "16.8.6",
"react-native": "https://github.com/expo/react-native/archive/sdk-32.0.1.tar.gz",
"react-native-calendars": "^1.32.0",
"react-native-svg": "^9.4.0",
"react-navigation": "^3.9.1",
"react-redux": "^7.0.1",
"redux": "^4.0.1",
"redux-thunk": "^2.3.0",
"react-native-switch": "^1.5.0"
},
"devDependencies": {
"babel-preset-expo": "^5.0.0",
"redux-devtools-extension": "^2.13.8",
"schedule": "^0.4.0"
},
What may be the matter? Please, help. Thanks.
Your mapDispatchToProps is returning an object. You also don't need the extra returns as fat arrow functions already have an implicit return. Will make your code a little more readable.
const mapDispatchToProps = dispatch => ({
updateEventType: (newEventType) => dispatch(updateEventType(newEventType));
});
const mapStateToProps = state => {
eventType: state.filter.eventType, // might be worth your time to investigate selectors down the road
};

ReactNavigation Error - Cannot read property 'bind' of undefined

"react-native": "^0.57.0",
"react-navigation": "^3.0.0",
"react-navigation-redux-helpers": "^2.0.7",
"react-redux": "^5.0.6",
"redux": "^4.0.0",
"redux-thunk": "^2.3.0",
"reduxsauce": "0.7.0",
"react-native-gesture-handler": "^1.0.9",
Updating react-navigation to 3.0.0 in my React-Native app. I have followed the official docs here React Navigation and installed all the dependencies.
However cannot resolve this issue.
AppNavigation.js
const PrimaryNav = createStackNavigator({
HomeScreen: {
screen: MainTabNav,
}, {
mode: 'modal',
headerMode: 'none',
initialRouteName: 'HomeScreen',
navigationOptions: {
headerStyle: styles.header,
},
});
export default createAppContainer(PrimaryNav);
ReduxNavigation.js
import AppNavigation from './AppNavigation';
import { reduxifyNavigator, createReactNavigationReduxMiddleware } from
'react-navigation-redux-helpers';
createReactNavigationReduxMiddleware(
'root',
state => state.nav,
);
const ReduxAppNavigator = reduxifyNavigator(AppNavigation, 'root');
render() {
const { dispatch, nav } = this.props;
<ReduxAppNavigator state={nav} dispatch={dispatch} />
}
const mapStateToProps = state => ({ nav: state.nav });
export default connect(mapStateToProps)(ReduxNavigation);
navigation.js
import { Keyboard } from 'react-native';
import AppNavigation from '../navigation/AppNavigation';
export default (state, action) => {
Keyboard.dismiss();
const newState = AppNavigation.router.getStateForAction(action, state);
return newState || state;
};
CreateStore.js
import { createStore, applyMiddleware, compose } from 'redux';
import reduxThunkMiddleware from 'redux-thunk';
import { createReactNavigationReduxMiddleware } from 'react-navigation-redux-
helpers';
import screenTrackingMiddleware from './screenTrackingMiddleware';
export default (rootReducer) => {
const middleware = [];
const enhancers = [];
const navigationMiddleware = createReactNavigationReduxMiddleware(
'root',
state => state.nav,
);
middleware.push(screenTrackingMiddleware);
middleware.push(navigationMiddleware);
middleware.push(reduxThunkMiddleware);
enhancers.push(applyMiddleware(...middleware));
const store = createStore(rootReducer, compose(...enhancers));
return {
store,
};
};
RootContainer.js
import ReduxNavigation from 'navigation/ReduxNavigation';
export default class RootContainer extends Component {
render() {
return (
<View style={styles.applicationView}>
<StatusBar barStyle="light-content" />
<ReduxNavigation />
</View>
);
}
}
This happens because you didn't link react-native-gesture-handler
Just type react-native link in your project dir.
And run again react-native run-android
Solved the issue by adding this plugin to .babelrc file.
[
"#babel/plugin-transform-flow-strip-types",
]

How to authorize users across the react-navigation app?

I have 2 groups of screens in my app.
1) AuthorizedScreens
2) NotAuthorizedScreens
As soon as the app loads I want to check if the user is logged in or not? If the user is logged in, the app loads AuthorizedScreens and if not, It loads NotAuthorizedScreens. How do i achieve this? I have included a sample of not working code but I guess that's how it could be!
App.js
import React from 'react';
import { AsyncStorage } from 'react-native';
import { Provider } from 'react-redux';
import { DrawerNavigator, StackNavigator } from 'react-navigation';
import store from './store';
export default class App extends React.Component {
async componentWillMount() {
const token = await AsyncStorage.getItem('facebook_token');
}
render() {
const AuthorizedScreens = DrawerNavigator(...
const NotAuthorizedScreens = DrawerNavigator(...
return (
<Provider store={store}>
{ (this.token) ? <AuthorizedScreens /> : <NotAuthorizedScreens /> }
</Provider>
);
}
}
Note!
I have an action creator that checks if the user is logged in. But I couldn't connect it to the App component, therefore I decided to use AsyncStorage to store a facebook_token, and if the token exist means user is logged in and if not the user is not...
"dependencies": {
"expo": "^20.0.0",
"react": "16.0.0-alpha.12",
"react-native": "https://github.com/expo/react-native/archive/sdk-20.0.0.tar.gz",
"react-navigation": "^1.0.0-beta.11",
"react-redux": "^5.0.6",
"redux": "^3.7.2",
"redux-thunk": "^2.2.0"
}
I have solved this issue by setting a state in componentWillMount and calling it from return.
import React from 'react';
import { AsyncStorage } from 'react-native';
import { Provider } from 'react-redux';
import { DrawerNavigator, StackNavigator } from 'react-navigation';
import store from './store';
export default class App extends React.Component {
state = {
login_token: '',
};
async componentWillMount() {
const token = await AsyncStorage.getItem('facebook_token');
this.setState({ login_token: token });
}
render() {
const AuthorizedScreens = DrawerNavigator(...
const NotAuthorizedScreens = DrawerNavigator(...
return (
<Provider store={store}>
{ (this.state.login_token) ? <AuthorizedScreens /> : <NotAuthorizedScreens /> }
</Provider>
);
}
}
I have solved this problem, but I am not sure if I did it the right way! Because, Redux is in charge of my state management, but I manually used the react state management by setting state.
I will appreciate your inputs if you know a better way.