Why useEffect is triggering without dependency change? - react-native

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;

Related

WatermelonDB function hasUnsyncedChanges

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;

React Native App - swipe right means all componenets unmount....can this be stopped?

With Android...when you swipe right on an app, it will unmount all components and cease all app operations. First component to be unmounted appears to be the parent componenet (normally named app.js).....please correct me if Im wrong on this.
When this happens..I know the componentWillUnmount event fires as I added the code below that logs to the console.
componentWillUnmount() {
console.log('app.js....componentWillUnmount');
}
My question is whether I can add some extra code within componentWillUnmount that could Alert the user "are you sure you want to quit app?"....and give them an option so say "no" and keep the app live
The BackHandler API description on the React-Native official docs has the example for the same use case. You can take a look at the given example code snippet on the official docs and pick the example with functional or class based component as per your choice.
You can place that code at the top level component like App.js or Routes.js.
Another approach with the autohide toast can be:
Using functional component
import React, {useEffect, useRef} from 'react';
import {Text, SafeAreaView, BackHandler, ToastAndroid} from 'react-native';
export default function App() {
const doubleBackToExitPressedOnce = useRef(false);
useEffect(() => {
const backAction = () => {
if (doubleBackToExitPressedOnce.current) {
BackHandler.exitApp();
return true;
}
ToastAndroid.show('Press back again to exit', ToastAndroid.SHORT);
doubleBackToExitPressedOnce.current = true;
setTimeout(() => {
doubleBackToExitPressedOnce.current = false;
}, 2000);
return true;
};
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
backAction,
);
return () => backHandler.remove();
}, []);
return (
<SafeAreaView>
<Text>Hello world!</Text>
</SafeAreaView>
);
}
Using class component:
import React, {Component} from 'react';
import {SafeAreaView, Text, BackHandler, ToastAndroid} from 'react-native';
export default class App extends Component {
backAction = () => {
if (this.doubleBackToExitPressedOnce) {
BackHandler.exitApp();
}
ToastAndroid.show('Press back again to exit', ToastAndroid.SHORT);
this.doubleBackToExitPressedOnce = true;
setTimeout(() => {
this.doubleBackToExitPressedOnce = false;
}, 2000);
return true;
};
componentDidMount() {
this.doubleBackToExitPressedOnce = false;
this.backHandler = BackHandler.addEventListener(
'hardwareBackPress',
this.backAction,
);
}
componentWillUnmount() {
this.backHandler.remove();
}
render() {
return (
<SafeAreaView>
<Text>Hello world!</Text>
</SafeAreaView>
);
}
}

How to use redux component in App.js React Native?

I'm doing a simple counter app. It has one label, and a button that you can increment by + 1 (each time it's pushed).
Using redux, I want to use the count that I store (in my Redux Store) in App.js file. However, I'm getting an error:
Error: could not find react-redux context value; please ensure the component is wrapped in a Provider
Using the useSelector works in other files, just not App.js. Is there a work around?
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Dogs from './components/Dogs';
import { Provider, useSelector } from 'react-redux';
import store from './redux/configureStore'
export default function App() {
const count = useSelector((state) => state.counter.count);
{/*useSelector does not work in this file!*/}
return (
<Provider store={store}>
<View style={styles.container}>
<Text>{`ha ${count}`}</Text>
<Dogs />
</View>
</Provider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Counter.js
import React, { useState, useEffect } from "react";
import { View, Text, StyleSheet, Button} from "react-native";
import { useDispatch, useSelector } from "react-redux";
import { increment } from '../redux/ducks/counter'
const Counter = () => {
const count = useSelector((state) => state.counter.count);
{/*useSelector works in this file!*/}
const dispatch = useDispatch();
const handleIncrement = () => {
dispatch(increment())
};
return (
<div>
{/* <Text>{` COunt: ${count}`}</Text> */}
<Button onPress={handleIncrement}>Increment</Button>
</div>
);
}
const styles = StyleSheet.create({})
export default Counter;
redux/configureStore.js
import { combineReducers, createStore } from 'redux';
import counterReducer from './ducks/counter';
const reducer = combineReducers({
counter: counterReducer
});
const store = createStore(reducer);
export default store;
redux/ducks/counter.js
const INCREMENT = 'increment';
export const increment = () => ({
type: INCREMENT
})
const initialState = {
count: 0
};
export default ( state = initialState, action) => {
switch(action.type) {
case INCREMENT:
return{...state, count: state.count + 1}
default:
return state;
}
};
As error saying, you are using useSelector out side of provider. In your app.js you are using useSelector before the app renders, so it is not able to find store. So, create a component for functionality which you want to use in app.js like this :
Create a file, call it anything like CountView.js, in CountView.js use your redux login :
CountView.js
import React from 'react';
import { Text } from 'react-native';
import { useSelector } from 'react-redux';
const CountView = () => {
const count = useSelector((state) => state.counter.count);
return (
<Text>{`ha ${count}`}</Text>
)
}
export default CountView;
Now, In your app.js use this component :
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Dogs from './components/Dogs';
import { Provider } from 'react-redux';
import store from './redux/configureStore'
import CountView from '../components/CountView'; // import CountView component
export default function App() {
return (
<Provider store={store}>
<View style={styles.container}>
{/* Use component here */}
<CountView />
<Dogs />
</View>
</Provider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Keep other things as it is, and now your functionality will works.
useSelector will work only if you wrap it inside Provider. you can create a wrapper file for App.
const AppWrapper = () => {
return (
<Provider store={store}> // Set context
<App /> // Now App has access to context
</Provider>
)
}
In App.js
const App = () => {
const count = useSelector((state) => state.counter.count); // will Work!
}
Unlike a regular React application, an expo React-Native application is not wrapped using an index.js file. Therefore when we wrap the provider in app.js for a React-Native app, we wrap it in index.js for React application. So the hooks like useSelector or useDispatch run before the provider is initialized. So, I would suggest not using any hooks in the app component, instead, we can create other components in the app.js and use the hooks in a separate component like in the code I have used below.
const Root = () => {
const [appIsReady, setAppIsReady] = useState(false);
const dispatch = useDispatch();
const fetchToken = async () => {
const token = await AsyncStorage.getItem("token");
console.log("Stored Token: ", token);
if (token) {
dispatch(setAuthLogin({ isAuthenticated: true, token }));
}
};
const LoadFonts = async () => {
await useFonts();
};
useEffect(() => {
async function prepare() {
try {
await SplashScreen.preventAutoHideAsync();
await LoadFonts();
await fetchToken();
} catch (e) {
console.warn(e);
} finally {
setAppIsReady(true);
}
}
prepare();
}, []);
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
await SplashScreen.hideAsync();
}
}, [appIsReady]);
if (!appIsReady) {
return null;
}
return (
<NavigationContainer onReady={onLayoutRootView}>
<MainNavigation />
</NavigationContainer>
);
};
export default function App() {
return (
<>
<Provider store={store}>
<ExpoStatusBar style="auto" />
<Root />
</Provider>
</>
);
}

React Native Date Range

import React, {useState} from "react";
import { StyleSheet, View, Text } from "react-native";
import { globalStyles } from "../styles/global";
import {Calendar, CalendarList, Agenda} from 'react-native-calendars';
import {LocaleConfig} from 'react-native-calendars';
import moment from "moment";
import DateRangePicker from "react-native-daterange-picker";
export default function About(){
const [endDate, setendDate] = useState(null)
const [startDate, setstartDate] = useState(null)
const [displayedDate, setdisplayedDate] = useState(moment())
state = {
endDate: null,
startDate: null,
displayedDate: moment()
};
const handleSubmit = (props) => {
console.log(props);
setendDate(props.endDate);
setstartDate(props.startDate);
setdisplayedDate(props.displayedDate);
// console.log(props.startDate);
// console.log(props.displayedDate);
}
return(
<View style={globalStyles.container}>
<DateRangePicker
onChange={ handleSubmit }
endDate={endDate}
startDate={startDate}
displayedDate={displayedDate}
range>
<Text>Click me!</Text>
</DateRangePicker>
</View>
)
}
1.not able to select date range.
2. undefined is not an object (evaluating displayedDate.format)
3. Using function component but most of the solutions are available with class component
You can call handleSubmit as follows
onChang={() => handleSubmit()}
And props param is needless in function prototype
const handleSubmit = (props) => {}
Because props is already declared and you don't need to set it as parameter.
If you want to use it as parameter then you should change like this
onChange={() => handleSubmit(props)}
Hope this helps you.
You can change your handleSubmit() function like this
const handleSubmit = (props) => {
if (props.startDate != undefined) {
setStartDate(props.startDate);
}
if (props.displayedDate != undefined) {
setDisplayedDate(props.displayedDate);
}
if (props.endDate != undefined) {
setEndDate(props.endDate);
}
}
Source: issues#15

can react-native-root-siblings work with react-redux

in a handleClick function, update the rootSiblings like this,
handleClick() { this.progressBar.update( <ProgressBar /> ); }
and in ProgressBar component,
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { View } from 'react-native';
const getFinishedWidth = progress => ({ width: progress * totalWidth });
const getUnfinishedWidth = progress => ({ width: (1 - progress) * totalWidth });
function CustomerReassignProgressBar(props) {
const { progress } = props;
return (
<View style={styles.bar}>
<View style={getFinishedWidth(progress)} />
<View style={getUnfinishedWidth(progress)} />
</View> );
}
CustomerReassignProgressBar.propTypes = { progress: PropTypes.number, };
const mapStateToProps = state => ({ progress: state.batchReassignProgress, });
export default connect(mapStateToProps)(ProgressBar);
then, when calling handleClick(), the app crushed, the error is, 'Could not find "store" in either the context or props of "Connect(ProgressBar)". Either wrap the root component in a , or explicitly pass "store" as a prop to "Connect(ProgressBar)".'
if I don't use connect in component, it works well. So, I guess, maybe rootSiblings can not work with react-redux. But does anyone knows this problem?
Upgrade to react-native-root-siblings#4.x
Then
import { setSiblingWrapper } from 'react-native-root-siblings';
import { Provider } from 'react-redux';
const store = xxx;// get your redux store here
// call this before using any root-siblings related code
setSiblingWrapper(sibling => (
<Provider store={store}>{sibling}</Provider>
));