I am using WatermelonDB in my RN app. I am trying to display a button if WatermelonDB has unsynced changes. WatermelonDB has a function called hasUnsyncedChanges that returns a boolean. I can get the function to console.log true/false. But unsure how to use this outside of the async function.
async function checkUnsyncedChanges() {
return await hasUnsyncedChanges({database});
}
(async () => {
console.log(await checkUnsyncedChanges());})();
I am new to React Native and have had good luck researching other issues with WatermelonDB but running into a wall in how to get this working.
I figured it out. Code below for anyone having similar difficulties.
import React, {useState, useEffect, useContext} from 'react';
import {View, Text, Button} from 'react-native';
import {hasUnsyncedChanges} from '#nozbe/watermelondb/sync';
import {database} from '../database/db';
import {sync} from '../components/watermelonSync';
import {AuthContext} from '../AuthProvider';
const SyncButton = () => {
const {user} = useContext(AuthContext);
const [showButton, setShowButton] = useState(false);
useEffect(() => {
const checkUnsyncedChanges = async () => {
const unsyncedChanges = await hasUnsyncedChanges({database});
setShowButton(unsyncedChanges);
};
checkUnsyncedChanges();
}, []);
return (
<View>
{showButton && (
<Button title="Sync Changes" onPress={() => sync(user.token)} />
)}
</View>
);
};
export default SyncButton;
Related
I am new to using react native. I am using the code contributed in other topics similar to this one:
I use a hook:
import * as Font from "expo-font";
export default useFonts = async () =>
await Font.loadAsync({
Bebas: require('../assets/fonts/BebasNeue-Regular.ttf'),
Montserrat: require('../assets/fonts/Montserrat-Italic-VariableFont_wght.ttf'),
Inter: require('../assets/fonts/Inter-Black.otf')
});
And then in my app.js:
import * as Font from 'expo-font';
import AppLoading from 'expo-app-loading';
import React, { useState } from 'react';
import useFonts from './hooks/useFonts';
export default function App() {
const [IsReady, SetIsReady] = useState(false);
const LoadFonts = async () => {
await useFonts();
};
if (!IsReady) {
return (
<AppLoading
startAsync={LoadFonts}
onFinish={() => SetIsReady(true)}
onError={() => {}}
/>
);
}
return <View styles={styles.container}>{/* Code Here */}</View>;
}
I then try to use them with the same name in my different screens, and I think they are loaded correctly, (no error), but I can't see the change when I restart my application.
What I need to do is to render the react native routes based on the users auth status. Right now I am doing this the wrong way, by having an interval running to check for auth status change:
import React, { useState, useEffect } from 'react';
import { AppLoading } from 'expo';
import { checkAuth } from './auth';
import { LoggedInRoutes, LoggedOutRoutes } from './router';
export default () => {
const [isReady, setReady] = useState(false);
const [loggedIn, setLoggedIn] = useState(false);
useEffect(() => {
setInterval(() => {
checkAuth()
.then((res) => { setLoggedIn(res); setReady(true); console.log('checked..') })
.catch((err) => alert(err));
}, 1500);
}, [loggedIn]);
if (!isReady) {
return (
<AppLoading
onFinish={() => setReady(true)}
/>
);
}
return (
loggedIn ? <LoggedInRoutes /> : <LoggedOutRoutes />
);
}
But obviously that is quite bad. I am using async storage to save the user when he authenticates and remove him from storage when he clicks the logout button.
Is there a way to check for changes in async storage and re-render the routes? or run a function that changes loggedIn state when user click login/logout button?
I would recommend to use switchNavigator in react navigation
reactnavigation.org/docs/4.x/auth-flow – mr-nobody 40 secs ago Edit Delete
this approach will works like a charm.
import React, {useState, useEffect} from 'react';
import OnBoardingRoutes from './onBoarding.routes';
import AppRoutes from './app.routes';
import checkFirstUsage from "./checkFirstUsage/path";
const Routes: React.FC = () => {
const [loading, setLoading] = useState(true)
const [firstUsage,setFirstUsage] =useState(null);
useEffect(() => {
async function check() {
const fU = await checkFirstUsage()
setFirstUsage(fU)
setLoading(false)
}
check()
},[])
if (loading) return null // or any better component
return firstUsage ? <OnBoardingRoutes /> : <AppRoutes />;
};
export default Routes;
Search file
as you can see i am making get request to google api, but before rendering this search page it already gives me an error undefined is not an object (evaluating 'items.volumeInfo'), as if i remove this {result === undefined ? null : } line and try to run code it give me perfect title of first book in array(as in search() console.log is working perfectly) after making proper search
import React, {useState, useEffect} from 'react';
import { View, Text } from 'react-native';
import SearchBar from '../components/SearchBar';
import axios from 'axios';
import RenderList from '../components/RenderList';
function SearchScreen() {
const [term, setTerm] = useState("");
const [result, setResult] = useState([]);
const [errorMessage, setErrormessage] = useState("")
const search = async () => {
const res = await axios.get(`https://www.googleapis.com/books/v1/volumes?q=${term}:keyes&key=AIzaSyDfYS8u4y8OADwoUIkl0gYOl0SJQ4GLuaA`, {
headers: {
Accept: "application/json",
"Content-Type": "application-json"
}
})
setResult(res.data.items)
console.log(res.data.items[0].volumeInfo.title)
}
return (
<View>
<SearchBar
term={term}
onTermChange={newTerm => setTerm(newTerm)}
onTermSubmit={() => search()}
/>
{result === undefined ? null : <RenderList result={result} />}
</View>
)
}
export default SearchScreen;
Render file
import React from 'react';
import { View, Text, StyleSheet, FlatList } from 'react-native';
function RenderList({result}) {
return (
<FlatList
data='result'
renderItem={({items}) => {
return <Text>{items.volumeInfo.title}</Text>
}} />
)
}
export default RenderList
Try calling the API in useEffect()
useEffect((
apiCall();)=>{},[searchedText])
Can you please show the Code of SearchBar?
I wanted only when I setCart to trigger useEffect. But this is not happening:
import React from 'react'
import { View } from 'react-native'
const CartScreen = () => {
const [cart, setCart] = React.useState([])
React.useEffect(() => {
console.log('test');
}, [cart])
return (
<View>
</View>
)
}
export default CartScreen;
Output: test
it fires without even having touched the cart state
useEffect will always run the first time when your component is rendered. If you only want some code to run after you change the state you can just have an if statement to check that
import React from 'react'
import { View } from 'react-native'
const CartScreen = () => {
const [cart, setCart] = React.useState([])
React.useEffect(() => {
if(cart.length > 0)
console.log('test')
}, [cart])
return (
<View>
</View>
)
}
export default CartScreen;
I understand this kind of question was already asked several times here at StackOverflow. But I tried all the recommended solutions and nothing works for me. I'm running out of ideas.
The problem is with a React Native application for Android. Basically, the app provides a search bar to search an underlying database. The search results should be put into the store.
I use Redux v4.0.5, React-Redux v7.1.3, React v16.12.0 and React Native v0.61.5. For debugging, I use React Native Debugger in the latest version.
Now the simplified code. First, the component with the search bar. Here, mapStateToProps() is called. User makes an input and useEffect() immediately runs the database query, which should result in immediately calling mapStateToProps().
import React, {useEffect, useRef, useState} from 'react';
import {connect} from 'react-redux';
import {RootState} from '../../../rootReducer/rootReducer';
import {setResultValueSearchBar} from '../../../store/searchBar/actions';
imports ...
type Props = {};
const SearchBar: React.FC<Props> = () => {
const [returnValue, setReturnValue] = useState('');
const [inputValue, setInputValue] = useState('');
useEffect(() => {
// get query results
// logic to finally get a result string that should be put into the store
const resultNames: string = resultNamesArray.toString();
// method to set local and Redux state
const sendReturnValueToReduxStore = (resultNames: string) => {
setReturnValue(resultNames);
setResultValueSearchBar({resultValue: resultNames});
console.log('result value sent to store ', resultNames);
};
// call above method
sendReturnValueToReduxStore(resultNames);
}, [inputValue, returnValue]);
return (
<View>
<ScrollView>
<Header searchBar>
<Item>
<Input
placeholder="Search"
onChangeText={text => setInputValue(text)}
value={inputValue}
/>
</Item>
</Header>
</ScrollView>
</View>
);
};
function mapStateToProps(state: RootState) {
console.log("map state to props!", state); // is only called one time, initially
return {
resultValue: state.searchBarResult.resultValue,
};
}
const mapDispatchToProps = {
setResultValueSearchBar,
};
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
Here is the rootReducer:
import {combineReducers} from 'redux';
import searchBarResultReducer from '../store/searchBar/reducers';
import reducer2 from '../store/reducer2example/reducers';
const rootReducer = combineReducers({
searchBarResult: searchBarResultReducer,
reducer2Result: reducer2,
});
export type RootState = ReturnType<typeof rootReducer>;
Here is the searchBarResultReducer in reducers.ts file:
import {
SearchBarResultState,
SET_RESULT_VALUE_SEARCHBAR,
ResultValueType,
} from './types';
const initialState: SearchBarResultState = {
resultValue: 'No results',
};
// take state and action and then return a new state
function searchBarResultReducer(
state = initialState,
action: ResultValueType,
): SearchBarResultState {
console.log('invoked result: ', action.type); // called only initially
if (action.type === 'SET_RESULT_VALUE_SEARCHBAR') {
return {
...state,
...action.payload,
};
} else {
return state;
}
}
export default searchBarResultReducer;
And the corresponding types.ts ...
export const SET_RESULT_VALUE_SEARCHBAR = 'SET_RESULT_VALUE_SEARCHBAR';
export interface SearchBarResultState {
resultValue: string;
}
interface ResultValueAction {
type: typeof SET_RESULT_VALUE_SEARCHBAR;
payload: SearchBarResultState;
}
export type ResultValueType = ResultValueAction
... and the actions.ts:
import {SET_RESULT_VALUE_SEARCHBAR, ResultValueType, SearchBarResultState} from './types'
export const setResultValueSearchBar = (resultValue: SearchBarResultState): ResultValueType => ({
type: SET_RESULT_VALUE_SEARCHBAR,
payload: resultValue,
});
And index.js:
import React from 'react';
import {AppRegistry} from 'react-native';
import {createStore, applyMiddleware, compose} from 'redux';
import {Provider} from 'react-redux';
import App from './App';
import {name as appName} from './app.json';
import rootReducer from './src/rootReducer/rootReducer';
import Realm from 'realm';
import { composeWithDevTools } from 'redux-devtools-extension';
import invariant from 'redux-immutable-state-invariant';
const composeEnhancers = composeWithDevTools({});
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(invariant()))
);
const Root = () => {
Realm.copyBundledRealmFiles();
return (
<Provider store={store}>
<App />
</Provider>
);
};
AppRegistry.registerComponent(appName, () => Root);
To summarize: Whenever the database query succeeds, the result value should be sent to the store. But in the React Native Debugger/Redux Devtools, the reducer/mapStateToProps() is called only once and only, as shown by the console.log s in the code.
What is going on here?
Solved! As stated by Hemant in this Thread, you also have to pass the action that you import as props into the component. Works like a charm now :)