Gonna try to make this as detailed as I can since I'm new to React Native and maybe this'll help someone else as well ✨.
I'm using the React Native Super Grid library, which internally uses FlatList and SectionList, to render a grid layout. The grid is populated with text via props from a separate const
What I'm trying to do is: When a user taps any item in the FlatList, the item that was tapped is copied to the clipboard and an alert is shown confirming it was copied.
What's happening right now: Each item is tappable & the correct alert is showing onPress confirming that you've copied to your clipboard, BUT nothing is actually being written to the clipboard. Just to make sure that writeToClipboard is working I have a static message in there that just says "WELL AT LEAST THE CLIPBOARD WORKS," so if you tap any item, that static message is successfully copied to the clipboard. I'm just not sure of how to copy the specific item that was tapped to the clipboard.
Here's the code for the grid component:
import React, { Component } from "react";
import {
StyleSheet,
Alert,
View,
Text,
TouchableOpacity,
Clipboard,
Button,
onPress
} from "react-native";
import { FlatGrid } from "react-native-super-grid";
class Emojigrid extends Component {
constructor(props) {
super(props);
this.state = {
text: "WELL AT LEAST THE CLIPBOARD WORKS",
clipboardContent: null
};
}
writeToClipboard = async () => {
await Clipboard.setString(this.state.text);
alert("Boom, Copied");
};
render() {
return (
<FlatGrid
itemDimension={130}
items={items}
style={styles.gridView}
// staticDimension={300}
// fixed
spacing={2}
renderItem={({ item, index }) => (
<View style={[styles.itemContainer, { backgroundColor: "#F7F7F7" }]}>
<TouchableOpacity onPress={this.writeToClipboard}>
<Text style={styles.itemName}>{item.name}</Text>
</TouchableOpacity>
</View>
)}
/>
);
}
}
export default Emojigrid;
const items = [
{ name: "¯_(ツ)_/¯" },
{ name: "ʕ·͡ᴥ·ʔ" },
{ name: "•`_´•" },
];
const styles = StyleSheet.create({
gridView: {
marginTop: 0,
marginBottom: 400,
flex: 1
},
itemContainer: {
justifyContent: "center",
alignItems: "center",
borderRadius: 0,
height: 125
},
itemName: {
fontSize: 18,
color: "black",
fontWeight: "400"
}
});
Thinking the answer is probably quite obvious, but any help is much appreciated!
Your writeToClipboard function needs to accept an argument.
writeToClipboard = async (text) => {
await Clipboard.setString(text);
alert("Boom, Copied");
};
And pass that argument where you call it.
<TouchableOpacity onPress={() => this.writeToClipboard(item.name)}>
Related
I am trying to make a game in react-native. I want to render 200+ views on the Game screen. Each View has a pressable functionality. Whenever I press the View I need to run a function that will change the View background color and update score on the game context. But Whenever I try to press any View it took some time to change the background and update the context.
Note
I am using the expo as a development environment and I am using a real device too.
My View Component
import { useEffect, useState, memo } from "react";
import { useContext } from "react";
import { gameContext } from "./gameContext";
import { Pressable, View } from "react-native";
function CheckBoxCom() {
const [active, setActive] = useState(false);
const { score, setScore } = useContext(gameContext);
useEffect(() => {
let time = setTimeout(() => {
setActive(false);
}, Math.floor(Math.random() * 35000));
return () => clearTimeout(time);
}, [active]);
const handlePress = () => {
if (active) return;
setActive(true);
setScore(score + 1);
};
return (
<View>
<Pressable onPress={handlePress}>
<View
style={{
width: 20,
height: 20,
borderWidth: 2,
borderColor: active ? "green" : "gray",
margin: 3,
borderRadius: 3,
backgroundColor: active ? "green" : null,
}}
></View>
</Pressable>
</View>
);
}
export default memo(CheckBoxCom);
Game Screen Component
import { useContext, useEffect, useState } from "react";
import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View, FlatList } from "react-native";
import CheckBox from "./CheckBox";
import { gameContext } from "./gameContext";
export default function Game({ navigation }) {
const { score, time, setTime, boxList } = useContext(gameContext);
const [intervalId, setIntervalId] = useState("");
useEffect(() => {
const int = setInterval(() => {
setTime((prvTime) => prvTime - 1);
}, 1000);
setIntervalId(int);
return () => clearInterval(int);
}, []);
if (time === 0) {
clearInterval(intervalId);
navigation.navigate("Score", { score });
}
return (
<View style={{ flex: 1 }}>
<StatusBar style="auto" />
<View style={styles.textHeader}>
<Text>Score : {score}</Text>
<Text>Time Left: {time}s</Text>
</View>
<View style={styles.checkBoxContainer}>
<FlatList
style={{ alignSelf: "center" }}
data={boxList}
initialNumToRender={50}
numColumns={12}
renderItem={(i) => <CheckBox />}
keyExtractor={(i) => i.toString()}
scrollEnabled={false}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
textHeader: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
marginTop: 40,
paddingHorizontal: 30,
},
checkBoxContainer: {
margin: 20,
display: "flex",
flexWrap: "wrap",
height: "80%",
overflow: "hidden",
flexDirection: "row",
},
});
How can I run view function immediately whenever I press it?
The reason it is slow is that when you press on a view, all 200+ CheckBoxCom components rerender. If they don't need to, we can improve performance by trying to prevent those unnecessary rerenders.
I believe the major bottleneck here is the gameContext. It groups together a lot of states and if any of these were to change, all components will rerender. It provides score state that you are reading within each CheckBoxCom. Whenever the score changes all CheckBoxCom components will re-render. If you change handlePress() to:
const handlePress = () => {
if (active) return;
setActive(true);
setScore(score => score + 1);
};
Please note the use of callback to update the score in the above handler. In this case, we don't need to read score from context, so we can remove it from the game context provider, only pass setScore. Removing score from the context provider is important because not doing so will rerender all components using the context even if you don't specifically destructure score.
Also, make sure you don't have a lot of state variables within a single context. Split it into multiple contexts if you have different states in there. In this way, you will be able to reduce unnecessary rerenders of the CheckBoxCom components.
Since your CheckBoxCom components have an internal state, using React.memo() will not help to prevent rerenders because it only works for rerenders resulting from changed props.
But if you are able to refactor them to lift the active state up to the parent i.e. something like activeViews or something (which could be a map of indexes which are true i.e. active), then you can pass the active state as a boolean prop to each CheckBoxCom component. And if we also pass setScore via a prop instead of via context, we can benefit from React.memo(). BTW it is not necessary to wrap setState methods with useCallback().
The end result will be: CheckBoxCom components with zero internal states and no reliance on context, in other words, pure components i.e. components which work nicely with React.memo().
Use pagination in flatlist
for ref: Pagination in flatlist
import React, { Component } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
FlatList,
Platform,
ActivityIndicator,
} from 'react-native';
export default class App extends Component {
constructor() {
super();
this.state = {
loading: true,
//Loading state used while loading the data for the first time
serverData: [],
//Data Source for the FlatList
fetching_from_server: false,
//Loading state used while loading more data
};
this.offset = 0;
//Index of the offset to load from web API
}
componentDidMount() {
//fetch('http://aboutreact.com/demo/getpost.php?offset=' + this.offset)
fetch('https://www.doviz.com/api/v1/currencies/all/latest')
.then(response => response.json())
.then(responseJson => {
responseJson = responseJson.slice((this.offset*12),((this.offset+1)*12)-1)
console.log("offset : "+this.offset);
console.log(responseJson.slice((this.offset*12),((this.offset+1)*12)-1));
//Successful response from the API Call
this.offset = this.offset + 1;
//After the response increasing the offset for the next API call.
this.setState({
// serverData: [...this.state.serverData, ...responseJson.results],
serverData: [...this.state.serverData, ...responseJson],
//adding the new data with old one available in Data Source of the List
loading: false,
//updating the loading state to false
});
})
.catch(error => {
console.error(error);
});
}
loadMoreData = () => {
//On click of Load More button We will call the web API again
this.setState({ fetching_from_server: true }, () => {
//fetch('http://aboutreact.com/demo/getpost.php?offset=' + this.offset)
fetch('https://www.doviz.com/api/v1/currencies/all/latest')
.then(response => response.json())
.then(responseJson => {
responseJson = responseJson.slice((this.offset*12),((this.offset+1)*12)-1)
console.log("offset Load : "+this.offset);
console.log(responseJson);
//Successful response from the API Call
this.offset = this.offset + 1;
//After the response increasing the offset for the next API call.
this.setState({
//serverData: [...this.state.serverData, ...responseJson.results],
serverData: [...this.state.serverData, ...responseJson],
fetching_from_server: false,
//updating the loading state to false
});
})
.catch(error => {
console.error(error);
});
});
};
renderFooter() {
return (
//Footer View with Load More button
<View style={styles.footer}>
<TouchableOpacity
activeOpacity={0.9}
onPress={this.loadMoreData}
//On Click of button calling loadMoreData function to load more data
style={styles.loadMoreBtn}>
<Text style={styles.btnText}>Loading</Text>
{this.state.fetching_from_server ? (
<ActivityIndicator color="white" style={{ marginLeft: 8 }} />
) : null}
</TouchableOpacity>
</View>
);
}
render() {
return (
<View style={styles.container}>
{this.state.loading ? (
<ActivityIndicator size="large" />
) : (
<FlatList
style={{ width: '100%' }}
keyExtractor={(item, index) => index}
data={this.state.serverData}
renderItem={({ item, index }) => (
<View style={styles.item}>
<Text style={styles.text}>
{item.currency}
{'.'}
{item.code}
</Text>
</View>
)}
onEndReached={this.loadMoreData}
onEndReachedThreshold ={0.1}
ItemSeparatorComponent={() => <View style={styles.separator} />}
ListFooterComponent={this.renderFooter.bind(this)}
//Adding Load More button as footer component
/>
)}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingTop: 30,
},
item: {
padding: 10,height:80
},
separator: {
height: 0.5,
backgroundColor: 'rgba(0,0,0,0.4)',
},
text: {
fontSize: 15,
color: 'black',
},
footer: {
padding: 10,
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
},
loadMoreBtn: {
padding: 10,
backgroundColor: '#800000',
borderRadius: 4,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
btnText: {
color: 'white',
fontSize: 15,
textAlign: 'center',
},
});
I have referred official documentation of react native(https://reactnavigation.org/docs/params).In that I observed that they are passing the static data between screens. But I want to pass data taken from user.
If someone knows how to share data then please help. I have taken help of context api also but I failed to pass the data. Any source or material will also be helpful.
Your issue may be related to your input. It seems you are not capturing your inputs into a state variable.
Check this example from ReactNative input:
https://reactnative.dev/docs/0.65/textinput
import React from "react";
import { SafeAreaView, StyleSheet, TextInput } from "react-native";
const Screen1 = () => {
const [text, onChangeText] = React.useState("Hello world");
return (
<SafeAreaView>
<TextInput
style={styles.input}
onChangeText={onChangeText}
value={text}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
},
});
export default Screen1;
Then you could use navigate:
navigation.navigate('Details', {
param1: text,
});
In the other screen you could read the param1 like this:
route.params.param1
Don't forget
Don't forget to pass route as a parameter in fuction( {route) }{ ... }
Checkout this code snippet.
React.useEffect(() => {
if (route.params?.post) {
// Post updated, do something with `route.params.post`
// For example, send the post to the server
}
}, [route.params?.post]);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button
title="Create post"
onPress={() => navigation.navigate('CreatePost')}
/>
<Text style={{ margin: 10 }}>Post: {route.params?.post}</Text>
</View>
);
}
function CreatePostScreen({ navigation, route }) {
const [postText, setPostText] = React.useState('');
return (
<>
<TextInput
multiline
placeholder="What's on your mind?"
style={{ height: 200, padding: 10, backgroundColor: 'white' }}
value={postText}
onChangeText={setPostText}
/>
<Button
title="Done"
onPress={() => {
// Pass and merge params back to home screen
navigation.navigate({
name: 'Home',
params: { post: postText },
merge: true,
});
}}
/>
</>
);
}
to pass a param to a screen
navigation.navigate('screenName', {
paramName: 'valueYouwantToPass',
});
because you mentioned context API, try
Async Storage
import {AsyncStorage} from 'react-native';
or
Device Event Emitter
import { DeviceEventEmitter} from 'react-native';
I am trying to familiarize myself with React Native. At the moment I am working on an app but came across an issue when trying to display changes to the individual elements of an array. For example:
function MyApp() {
const [array, setArray] = useState([1,2,3]);
const onPress = () => {
let temp = [3,2,1];
setArray(temp);
}
return(
<View>
<TouchableHighlight onPress={onPress}>
<View>
<Text>{array[0]}</Text>
</View>
</TouchableHighlight>
</View>
);
}
With the above code, I expect '1' to be displayed in the text component and to change to '3' upon being pressed. console.log shows the state being changed but what is being displayed in the text component inside the actual app never updates. I then tried this using individual integer states like so:
const [int, setInt] = useState(0);
const onPress = () => {
setInt(1);
}
Using an integer state such as the one above works totally fine as expected. Can anyone show me what I am doing wrong with my array state? Thank you.
Your code looks perfect and should work without any issue.
Here is the slightly modified example where the first element is generated randomly and is being shown properly in the Text component.
Working Example: Expo Snack
import React, { useState } from 'react';
import { Text, View, StyleSheet, TouchableHighlight } from 'react-native';
import Constants from 'expo-constants';
export default function MyApp() {
const [array, setArray] = useState([1, 2, 3]);
const onPress = () => {
let temp = [Math.floor(Math.random() * 10), 2, 1];
setArray(temp);
};
return (
<View style={styles.container}>
<TouchableHighlight onPress={onPress}>
<View style={styles.btn}>
<Text
style={{ alignSelf: 'center', fontSize: 28, fontWeight: 'bold' }}>
{array[0]}
</Text>
</View>
</TouchableHighlight>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
btn: {
width: 100,
height: 100,
borderColor: 'purple',
borderWidth: 5,
justifyContent: 'center',
},
});
Your code looks to be fine, I tried the below following your code and can see that state and UI getting updated successfully when the button is being clicked.
Could you please check if your event handler function onPress is getting called, and if you are getting any error in the console when you click on it.
function App() {
const [array, setArray] = React.useState([1, 2, 3]);
const handleBtnClick = () => {
const temp = [3, 2, 1];
setArray(temp);
};
return (
<React.Fragment>
<button onClick={handleBtnClick}>Click</button>
{array.map((el) => (
<p>{el}</p>
))}
<hr />
{array[0]}
</React.Fragment>
);
}
I created a component at react-native, but the text of the button is always at uppercase, someone knows why it doesn't take the text that pass, because I want to show 'Login', but it shows 'LOGIN'
import React, { Component } from 'react';
import { View, Button} from 'react-native';
import LabelApp from "../../config/labels.app";
const labelApp = LabelApp.loginView;
export default class Login extends Component {
constructor(props){
super(props);
this.handleClickBtnEnter = this.makeLogin.bind(this);
}
makeLogin() {
}
render() {
return (
<View>
<Button title= {labelApp.textButtonLogin} onPress={this.handleClickBtnEnter}/>
</View>
);
}
}
Label of component
const LabelApp = {
loginView: {
textButtonLogin: 'Ingresar',
},
}
export default LabelApp;
The visualization
For react Native Paper button use uppercase={false} prop:
<Button
mode="outlined"
uppercase={false}
accessibilityLabel="label for screen readers"
style={styles.yourButtonStyle}>Button label</Button>
So, the other two answers are correct that you should use TouchableOpacity, but as someone new to React Native, it took me awhile to understand what was going on here. Hopefully this explanation provides a little more context.
The built-in Button component seems to have some weird compatibility/visibility issues on occasion, one of which is rendering the title prop text all uppercase. When viewing the documentation for the Button component in Chrome, the preview shows all text being capitalized under the "Web" view but not Android or iOS (I was having this issue using Expo and Metro Bundler on an Android device, so not sure what to make of this). I couldn't find anything about capitalization/uppercase in the Button docs, so perhaps this is a bug.
The solution is to use a different component called TouchableOpacity. It also has an onPress event you can use and a built-in touch animation, but it has less out of the box styling than the Button component. Important to note from docs: "Opacity is controlled by wrapping the children in an Animated.View, which is added to the view hierarchy. Be aware that this can affect layout." It doesn't have a title prop, so you just put the button text in a Text component, like so:
<Button
title='text will be capitalized'
onPress={onPress}
/>
becomes
<TouchableOpacity onPress={onPress}>
<Text>text will stay lowercase</Text>
</TouchableOpacity>
I was having the same issue as OP, and this solved it for me.
From the official documentation
A basic button component that should render nicely on any platform. Supports a minimal level of customization.
The recommend use of touchable opacity or touchable native feedback
https://facebook.github.io/react-native/docs/touchableopacity
Below I've added textTransform: 'lowercase', as a style rule for the button to override any inherited text casing.
import React, { Component } from 'react'
import {
StyleSheet,
TouchableOpacity,
Text,
View,
} from 'react-native'
export default class App extends Component {
constructor(props) {
super(props)
this.state = { count: 0 }
}
onPress = () => {
this.setState({
count: this.state.count+1
})
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity
style={styles.button}
onPress={this.onPress}
>
<Text> Touch Here </Text>
</TouchableOpacity>
<View style={[styles.countContainer]}>
<Text style={[styles.countText]}>
{ this.state.count !== 0 ? this.state.count: null}
</Text>
</View>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingHorizontal: 10
},
button: {
alignItems: 'center',
backgroundColor: '#DDDDDD',
padding: 10,
textTransform: 'lowercase', // Notice this updates the default style
},
countContainer: {
alignItems: 'center',
padding: 10
},
countText: {
color: '#FF00FF'
}
})
https://snack.expo.io/Bko_W_gx8
This question is 3 years old and I'm not sure why no one has answered it correctly until now.
Native android buttons are all caps by default starting from android lollipop, which is what react native uses when you use the control Button from react-native in your app. To override the functionality, you just need to add this line in your styles.xml file inside your app theme (not the splash screen style)
<item name="android:textAllCaps">false</item>
You can get more details here: https://stackoverflow.com/a/30464346/11104068
The changes are not going to apply instantly obviously since the change is in the naive xml file and not in a JavaScript file. So you will need to do a npm/yarn run android
I've tried your code and it looks like it's the expected behaviour with Button component from react-native
You can see this at the official documentation
I believe that you need to change Button component, take it from another package to meet your needs.
As an alternative you can create your own button
import React, { Component } from 'react';
import { View, Button, TouchableHighlight, StyleSheet } from 'react-native';
import LabelApp from "../../config/labels.app";
const labelApp = LabelApp.loginView;
export default class Login extends Component {
constructor(props){
super(props);
this.handleClickBtnEnter = this.makeLogin.bind(this);
}
makeLogin() {
}
render() {
return (
<View>
<TouchableHighlight onPress={this.handleClickBtnEnter} underlayColor="white">
<View style={styles.button}>
<Text style={styles.buttonText}>{labelApp.textButtonLogin}</Text>
</View>
</TouchableHighlight>
</View>
);
}
}
const styles = StyleSheet.create({
button: {
marginBottom: 30,
width: 260,
alignItems: 'center',
backgroundColor: '#2196F3'
},
buttonText: {
textAlign: 'center',
padding: 20,
color: 'white'
}
});
<Button
style={{
borderRadius: 10,
backgroundColor: "#000",
width: 200,
height: 50,
}}
>
<Text
uppercase={false}
>
Login
</Text>
</Button>
after searching for hours I am finally completly lost. I tried to build a simple Dictionary app following an outdated tutorial (https://code.tutsplus.com/tutorials/creating-a-dictionary-app-using-react-native-for-android--cms-24969) for react native. The standard app after I run "react-native init" works fine on my phone. However my code just shows a blank screen without any errors. Below I posted the code, which I used to replace everthing in index.adroid.js. I would really appreciate it, if you could help me here. Thanks in advance!
import React, {Component} from 'react';
import {
AppRegistry,
Text,
View,
TextInput,
StyleSheet,
} from 'react-native';
var english_german = require('./english_german.json');
class Dictionary extends Component {
constructor(props) {
super(props);
this.state = {
input: '',
output: ''
};
}
render() {
return(
<View style={styles.parent}>
<Text>
Type something in English:
</Text>
<TextInput
// style= {{height: 40}}
// placeholder="Type here to translate!"
onChangeText={(text) => this._onTextInputChangeText(text)}
value={this.state.input}
onSubmitEditing={ this.showTranslation().bind(this)} />
<Text style = {styles.germanLabel}>
German translation:
</Text>
<Text style = {styles.germanWord}>
{this.state.output}
</Text>
</View>
);
}
_onTextInputChangeText(text) {
//alert(text);
this.setState({
input : text
})
}
showTranslation() {
var translation = this.state.input in english_german ? english_german[this.state.input] : "Not found";
this.setState({
output: translation
});
}
}
const styles = StyleSheet.create({
// For the container View
parent: {
padding: 16
},
// For the Text label
germanLabel: {
marginTop: 20,
fontWeight: 'bold'
},
// For the Text translation
germanWord: {
marginTop: 15,
fontSize: 30,
fontStyle: 'italic'
}
});
AppRegistry.registerComponent('Dictionary', () => Dictionary);
Thank you guys!
I didn't get the error most of the time, but the syntax error at onSubmitEditing was the problem. For some reason it didn't work (show anything) when I uncommented the whole TextInput. Anyway the fix of Michael Cheng to onSubmitEditing={ this.showTranslation.bind(this)} worked.