How to prevent multiple clicks on a butoon in react native? - react-native

I'm working on a project that involves signing in users and it it is working fine (I'm using firebase for authentication). However for signing in to occur, it needs some time depending also on the internet connection so I need to disable the button once it is clicked and re-enable it again. Here is my code:
class Login extends Component {
handlelogin = () => {
this.setState.errorMessage = '';
const {email, password} = this.state;
auth
.signInWithEmailAndPassword(email, password)
.then(() => this.props.navigation.navigate('Home'))
.catch((error) => this.setState({errorMessage: error.message}));
};
render() {
return (
// Some code to handle input
<TouchableOpacity
style={
!this.state.email || !this.state.password
? styles.disabled
: styles.button
}
disabled={!this.state.password || !this.state.email}
onPress={this.handlelogin}>
<Text style={{color: '#fff', fontWeight: '500'}}>Sign in</Text>
</TouchableOpacity>
)}
I have tried this answer for my code to go like this but this didn't seem to work:
class Login extends Component {
loginProcess = false;
handlelogin = () => {
this.setState.errorMessage = '';
const {email, password} = this.state;
auth
.signInWithEmailAndPassword(email, password)
.then(() => this.props.navigation.navigate('Home'))
.catch((error) => {
this.setState({errorMessage: error.message})
this.loginProcess = false;
});
};
render() {
return (
// Some code to handle input
<TouchableOpacity
style={
!this.state.email || !this.state.password
? styles.disabled
: styles.button
}
disabled={!this.state.password || !this.state.email}
onPress={()=> {
if (!loginProcess) {
this.handleLogin;
this.loginProcess = true;
}
}>
<Text style={{color: '#fff', fontWeight: '500'}}>Sign in</Text>
</TouchableOpacity>
)}
P.S This is my first question on stackoverflow, any hint for writing better questions is appreciated

You could implement a Spinner that shows instead of the button when it is first clicked and shows the button after the call finishes.
I had the same problem not long ago - here a small example:
const MyComponent = ({makePutCall}) => {
const [showSpinner,setShowSpinner] = useState(false);
const handlePress = async () => {
setShowSpinner(true);
await makePutCall();
setShowSpinner(false);
}
return showSpinner ? <Spinner/> : <Button onPress={handlePress}/>
}
as a "Spinner" just use ActivityIndicator from react-native.

Related

How to use asyncStorage inside useEffect

I'm building a mobile game using react native and I'm trying to retrieve the best value storage on it to display on the screen. The problem is that it seems that react native is rendering the screen before it retrieves the value and then it doesn't re-render when the value is updated using setBest(), so no value is displayed.
Here is the code:
const navigation = useNavigation()
const [result, setResult] = useState('')
const [best, setBest] = useState('')
useEffect(() => {
const Storage = async (key,value) => {
await AsyncStorage.setItem(key,value)
}
const Retrieve = async (key) => {
const value = await AsyncStorage.getItem(key)
setBest(()=>value)
}
Retrieve('1').catch(console.error)
setResult(route.params.paramKey)
if(route.params.paramKey>best){
var aux = result.toString()
Storage('1',aux)
console.log(best)
}
}, [])
return (
<View style={styles.container}>
<View style={styles.textView}>
<Text style={styles.tituloText}>Melhor pontuação</Text>
<Text style={styles.tituloText}>{best}</Text>
<Text style={styles.tituloText}>Sua pontuação</Text>
<Text style={styles.resultText}>{result}</Text>
<View style={styles.viewBtn}>
<TouchableOpacity style={styles.viewBack} onPress={() => navigation.navigate('Modo1')}>
<Icon style={styles.iconBack} name="backward" />
</TouchableOpacity>
<TouchableOpacity style={styles.viewHome} onPress={() => navigation.dispatch(StackActions.popToTop)}>
<Icon style={styles.iconBack} name="home" />
</TouchableOpacity>
</View>
</View>
</View>
);
}
Thanks for the help guys! I've been struggling with this for days and any help will be appreciated!
This is how you retrieve the value..
useEffect(() => {
AsyncStorage.getItem('key').then(value => {
if (value != null) {
console.log(value);
setBest(value);
}
});
}, []);
also don't forget to add the import statement..
To set the value you must use
AsyncStorage.setItem('key', value);
You can use Async Functions inside of ~useEffect()` like this:
useEffect(() => {
(async () => {
async function getData() {
try {
const value = await AsyncStorage.getItem('myKey');
if (value !== null) {
setData(value);
}
} catch (error) {
console.log(error);
}
}
getData();
})();
}, []);
}

Using socket.io and useEffect causing delays the more I add to an array?

The code below works but if I click the button more and more there the app freezes up and the update occurs after a delay. That delay is increased significantly after each button press.
How can I avoid this delay/freeze up?
const ContactChatScreen = ({navigation}) => {
const mySocket = useContext(SocketContext);
const [chatMsgs, setChatMsgs] = useState([]);
const sendMsg = () => {
mySocket.emit('chat msg', 'test');
};
useEffect(() => {
mySocket.on('chat msg', (msg) => {
setChatMsgs([...chatMsgs, msg]);
});
});
return (
<View style={styles.container}>
<StatusBar
backgroundColor={Constants.SECONDN_BACKGROUNDCOLOR}
barStyle="light-content"
/>
{chatMsgs.length > 0
? chatMsgs.map((e, k) => <Text key={k}>{e}</Text>)
: null}
<Text style={styles.text}>Contact Chat Screen</Text>
<MyButton title="test" onPress={() => sendMsg()} />
</View>
);
I was able to fix this by changing this:
useEffect(() => {
mySocket.on('chat msg', (msg) => {
setChatMsgs([...chatMsgs, msg]);
});
return () => {
// before the component is destroyed
// unbind all event handlers used in this component
mySocket.off('chat msg');
};
}, [chatMsgs]);
adding the mySocket.off was the key.

How to get text input data and pass it to email via linking - react native expo

I am trying to pass whatever the user types into the TextInput, to send to email using linking.
I am using Expo and haven't come across anything that might do this.
export default class Completed extends Component {
state = {
email: '',
}
handleEmail = (text) => {
this.setState({ email: text })
}
render() {
return (
<View>
<TextInput style={[styles.input, styles.pushUp]}
multiline
placeholder={"Description"}>
onChangeText = {this.handleEmail}
</TextInput>
<TouchableOpacity style={styles.button} onPress={() => Linking.openURL('mailto:support#example.com?subject=SendMail&body=Description')}>
<Text style={{color: "#FFF", fontWeight: "500"}}>NEXT</Text>
</TouchableOpacity>
</View>
)
}
}
Please try this:
const MyTouchableOpacity = ({ url, children }) => {
const handlePress = useCallback(async () => {
// Checking if the link is supported for links with custom URL scheme.
const supported = await Linking.canOpenURL(url);
if (supported) {
// Opening the link with some app, if the URL scheme is "http" the web link should be opened
// by some browser in the mobile
await Linking.openURL(url);
} else {
Alert.alert(`Don't know how to open this URL: ${url}`);
}
}, [url]);
return <TouchableOpacity style={styles.button} onPress={handlePress} />
};
and use it like this
<MyTouchableOpacity url={`mailto:${this.state.email}?subject=SendMail&body=Description`}>
<Text style={{color: "#FFF", fontWeight: "500"}}>NEXT</Text>
</MyTouchableOpacity>
Based on doc: https://reactnative.dev/docs/linking

Application wide Modal in React Native

I'm currently using react native modal and it serves the purpose of showing modals.
My problem currently is that I want to show the modal application wide. For example when a push notification received I want to invoke the modal regardless of which screen user is in. The current design of the modals bind it to a single screen.
How can this be overcome?
first of all make a context of your modal
const BottomModal = React.createContext();
then provide your modal using reactcontext provider
export const BottomModalProvider = ({children}) => {
const panelRef = useRef();
const _show = useCallback((data, type) => {
panelRef.current.show();
}, []);
const _hide = useCallback(() => {
panelRef.current.hide();
}, []);
const value = useMemo(() => {
return {
_show,
_hide,
};
}, [_hide, _show]);
return (
<BottomPanelContext.Provider value={value}>
{children}
<BottomPanel fixed ref={panelRef} />
</BottomPanelContext.Provider>
);
};
here is code for bottom panel
function BottomPanel(props, ref) {
const {fixed} = props;
const [visible, setVisibility] = useState(false);
const _hide = () => {
!fixed && hideModal();
};
const hideModal = () => {
setVisibility(false);
};
useImperativeHandle(ref, () => ({
show: () => {
setVisibility(true);
},
hide: () => {
hideModal();
},
}));
return (
<Modal
// swipeDirection={["down"]}
hideModalContentWhileAnimating
isVisible={visible}
avoidKeyboard={true}
swipeThreshold={100}
onSwipeComplete={() => _hide()}
onBackButtonPress={() => _hide()}
useNativeDriver={true}
style={{
justifyContent: 'flex-end',
margin: 0,
}}>
<Container style={[{flex: 0.9}]}>
{!fixed ? (
<View style={{flexDirection: 'row', justifyContent: 'flex-end'}}>
<Button
style={{marginBottom: 10}}
color={'white'}
onPress={() => setVisibility(false)}>
OK
</Button>
</View>
) : null}
{props.renderContent && props.renderContent()}
</Container>
</Modal>
);
}
BottomPanel = forwardRef(BottomPanel);
export default BottomPanel;
then wrap your app using the provider
...
<BottomModalProvider>
<NavigationContainer screenProps={screenProps} theme={theme} />
</BottomModalProvider>
...
lastly how to show or hide modal
provide a custom hook
const useBottomPanel = props => {
return useContext(BottomPanelContext);
};
use it anywhere in app like
const {_show, _hide} = useBottomModal();
//....
openModal=()=> {
_show();
}
//...
If you are not using hooks or using class components
you can easily convert hooks with class context
https://reactjs.org/docs/context.html#reactcreatecontext
this way you can achieve only showing the modal from within components
another way is store the panel reference globally anywhere and use that reference to show hide from non-component files like redux or notification cases.

React native Unable to render the page with force update or set state

class Wait extends Component{
constructor(props) {
super(props);
this.state = { fetchingData: true, data: [], check: ''}
this.forceUpdateHandler.bind(this);
}
getData = async() => {
try {
data = await AsyncStorage.getItem('restaurants');
if (data != null) {
this.setState({fetchingData: false , data: JSON.parse(data)})
}
} catch(error){
console.log(error)
}
}
forceUpdateHandler(){
this.forceUpdate();
};
componentDidMount(){
this.getData();
}
renderRestaurant(){
return this.state.data.map((item) => {
return (
<View style ={{marginTop: 20, backgroundColor: 'red', marginTop: 20 }}>
<Text> {item.name} </Text>
<Text> {item.time} </Text>
<Text> {item.wait} </Text>
<Text> {item.people} </Text>
<Button title = 'cancel' onPress = { async () => {
let data = await AsyncStorage.getItem('restaurants');
let temp = JSON.parse(data)
let i = -1
temp.map((value, index) => {
if (value.name == item.name){
i = index;
}
})
if (i > -1){
temp.splice(i, 1)
await AsyncStorage.setItem('restaurants', JSON.stringify(temp))
}
this.forceUpdateHandler() // First way
this.forceUpdate() // Second way
this.setState({check: 'checked'}) // Third way
}
}
/>
</View>
)
})
}
render(){
const { navigate } = this.props.navigation;
const { navigation } = this.props;
return (
<View style={{width:200, height:200, justifyContent:'center', alignItems:'center', }}>
{this.state.fetchingData ? null : this.renderRestaurant()}
</View>
)
}
}
I am trying to make the page re-render each time after I click the button. Once click the button, it access the AsyncStorage and delete the corresponding element in the array, then it update the AsyncStorage with the new array and re-render the page.
I have tried the following:
1) call forUpdate directly after the update of the AsyncStorage
2) define the forceUpdateHandler function and bind it with this
3) call this.setState after the update of the AsyncStorage
But none of the above options re-renders the page. Can someone help to fix it? An example would be great! Thanks in advance.
The answer is simple. It doesn't re-render because it has nothing to re-render. It calls the render, check each component in the render if the data used to render it has changed and render them if needed. If you look at your code, you see that on the button press, you save in the async storage the new data. However, your rendering uses this.state.data to render the item. The problem is that you never update the state of your component with the new data.
Sure, you do this.setState({check: 'checked'}), but nothing in the render is using it. So there's no point in updating the UI.
An easy way to fix it would be to call this.getData() at the end of the button onPress. That way, you would update the data state which would update your UI.
Get the updated list of restaurants { removing the selected restaurant}
Stored the updated list to Asyncstorage.
fetch the updated list from asyncStorage and set the state.
storeData = async (restaurants) => {
try {
await AsyncStorage.setItem('restaurants', JSON.stringify(restaurants))
} catch (error) {
console.log("Error", error);
}
}
renderRestaurant(){
return this.state.data.map((item, index, restaurants) => {
return (
<View key={index}>
<Text> {item.name} </Text>
<Text> {item.time} </Text>
<Button
title = 'cancel'
onPress = {() => {
let restaurantListWithoutCurrentRestaurant = restaurants.filter((restaurant)=> restaurant.name !== item.name);
this.storeData(restaurantListWithoutCurrentRestaurant);
this.getData();
}}/>
</View>
)
})
}
I think this will solve your problem.