I'm trying to push text input into an array. When I modify the input text and push it, the array is wiped before the push (not showing my previous pushes in the console.log). What am I missing?
const [text, onChangeText] = React.useState('Useless Text');
let chatHistory = [];
function logHistory(text) {
chatHistory.push(text);
console.log(chatHistory);
}
return (
<View style={styles.container}>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<TextInput
style={styles.input}
onChangeText={onChangeText}
value={text}
/>
<Button title={'Ask the computer...'} onPress={() => logHistory(text)} />
</View>
</View>
);
I supposed that because you change state, it rerender this component and redeclare chatHistory as empty array.
use useState for chatHistory instead.
And instead of Array.push i recommend to use chatHistory = [...chatHistory, text],in useState case setChatHistory([...chatHistory,text]);
If you want the changes in chatHistory to be reflected on your component you need to use a state and not just a plain variable. Such variables do not survive through re-renders and anything you store in regular variables will be lost. Try out after making these changes
const [chatHistory, setChatHistory] = React.useState([]);
function logHistory(text) {
setChatHistory((history) => [...history, text]);
}
Also the console.log statement might not give you the result you are expecting. You might also want to display the chat history somewhere maybe in a flat list.
Related
I created a short test to get an explanation of how my code reacts.
Basically I want to call a function only when I press my button.
No problem, the code below works.
// Components/Test.js
import React, { useState } from "react";
import { View, StyleSheet, TextInput, Button } from "react-native";
function Test(props) {
const [text, setText] = useState("");
const myFunction = () => {
console.log("test");
};
return (
<View style={styles.container}>
<TextInput
style={{ borderWidth: 1, width: "70%" }}
onChangeText={(text) => setText(text)}
/>
<Button title="Button" onPress={myFunction} />
</View>
);
}
// Styles
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});
export default Test;
As I'm learning Javascript at the same time, I thought I'd put parentheses after my function. Technically, if I want to pass parameters to my function, I thought I should do it like this.
<Button title="Button" onPress={myFunction()} />
And now with this change, my "myFunction" is called systematically if I modify the text in TextInput.
Can someone explain what is going on? Thank you !
You should use arrow function
<Button title="Button" onPress={() => myFunction('parameter')} />
if you just call like
<Button title="Button" onPress={myFunction()} /> // its calling function directly
Its call the function directly
Arrow Functions:
We all love them, but should be cautious when using them with React. Arrow functions are great, they let us quickly create new javascript function short hand. The consequence of this is exactly that, it creates a new function every time it is executed. For the most part in Javascript this isn’t a real issue. When it comes to the React render method this can quickly become an issue.
render(){
return (
<View>
<TouchableOpacity onPress={() => console.log('Hello!')}>
Click Me
</TouchableOpacity>
</View>
)
}
So what could be the harm here, the button is simply just logging out a string on press. But in fact, when React executes this render method it will always see a new function. The arrow function returns a new function every time. This causes React to think something has changed in our view, when in fact nothing has.
logHello(){
console.log('Hello!');
}
render(){
return (
<View>
<TouchableOpacity onPress={this.logHello}>
Click Me
</TouchableOpacity>
</View>
)
}
Everytime you change the text, the state of the component also changes becuase, you've used onChangeText={(text) => setText(text)}, this causes your component to re-render.
During re-render, the following line is also executed. In this line you are calling myFunction.
<Button title="Button" onPress={myFunction()} />
In short, changing the text causes state update, which in turn causes a re-render, and during that re-render myFunction is called.
I have a Text Input in React Native and I want to display the typed input on real time (two way binding ) in a way that when typing each letter in the input, the text under the input field is automatically updated with the letter typed. I want to achieve this without the use of state but this code doesn't work
export default function App() {
const updateDisplay=(typedLetters)=> {return (<View><Text>typedLetters</Text></View>)}
return(
<TextInput
style={{height: 40,margin:20}}
placeholder="Search"
onChangeText={(text)=>updateDisplay(text)}
/>)
}
First, updateDisplay should be like this:
const updateDisplay = (typedLetters) => {
return (
<View>
// notice argument is in {}
<Text>{typedLetters}</Text>
</View>
);
};
In order to show the text, you have to call the updateDisplay inside the component:
return (
<View>
<TextInput
style={{ height: 40, margin: 20 }}
placeholder="Search"
onChangeText={(text) => updateDisplay(text)}
/>
{/* what parameter you are going to be passing to this function */}
{updateDisplay()}
</View>
The thing is when you defined the updateDisplay, it receives an argument. So somehow you need to extract the input value of TextInput component. That is why we need to use the state.
TextInput is actually a function and you cannot go inside of a function and grab a value. Inside a function, you mutate the state. we use setState because we are not setting the state, we are asking React and it decides when to set it.
export default function App() {
const [text, setText] = useState(null);
const updateDisplay = (typedLetters) => {
return (
<View>
<Text>{typedLetters}</Text>
</View>
);
};
return (
<View>
<TextInput
style={{ height: 40, margin: 20 }}
placeholder="Search"
// now save the input value to state.
onChangeText={(text) => setText(text)}
/>
{/* what parameter you are going to be passing to this function */}
{updateDisplay(text)}
</View>
);
}
It is not possible to do this without state. Using state you provide a hook for the UI to know when to re-render your component. Without it, your UI will not re-render, and you won't see any difference.
Here are my functions. They check the selected image by the user and assign an ID to them.
setCharacter = (props) => {
this.setState({
character:props
})
}
const pressHandler = (character) => {
this.setCharacter(character)
console.log(character);
}
I'm using the functions here to assign the ID.
TouchableOpacity onPress={(pressHandler('1'))}>
<Image style={{height: 120,
width: 120, alignSelf: 'center'}} source={require('../assets/characters/001-superhero.png')}
/>
</TouchableOpacity>
<TouchableOpacity onPress={(pressHandler('2'))}>
<Image style={{height: 120,
width: 120, alignSelf: 'center'}} source={require('../assets/characters/003-superhero.png')}
/>
</TouchableOpacity>
This is my error.
ReferenceError: Can't find variable: setCharacter
Thank you very much in advance! I used another stack overflow link to create this:
How to get value of text in state.when i clicked on TouchableOpacity in react-native?
I hope I'm following all the rules on stackoverflow :)
Try using like
const setCharacter = (props) => {
// .. use state from `useState` for hooks
}
const pressHandler = (character) => {
setCharacter(character)
console.log(character);
}
TouchableOpacity onPress={() => pressHandler('1')}>
...
</TouchableOpacity>
<TouchableOpacity onPress={() => pressHandler('2')}>
...
</TouchableOpacity>
Three things:
What Nooruddin said -- onPress has to point at a function and (pressHandler('1') is not a function. Change it to onPress={()=>pressHandler('1')}
You need to declare setCharacter properly, with const or something similar: const setCharacter = ...
Try replacing this.setCharacter(character) with setCharacter(character) -- I can't tell for sure if that's an issue but it may be.
You can use Bind or an arrow function but the below should work.
In your constructor also init the state with the character property.
constructor(props) {
super(props);
this.state = {
character: '',
};
}
<TouchableOpacity onPress={this.pressHandler.bind(this, '1')}>
Then you can just update your state with whatever value you passed from the button press in your case either a 1 or a 2.
const pressHandler = (c) => {
this.setState({character: c});
console.log(c);
}
To use the character that was passed in you would just access it via this.state.character
<Text>Current Image ID: {this.state.character}</Text>
Until expo lets me use Realm databases ive decided to go with Json and asyncstorage. The source data originates from Json anyway so for this app it makes sense to leave it as json.
I have a flatlist with the json displayed. To the left of each item in the list is a star icon.
When I touch each item in the list the star will go solid to indicate it has been pressed. Press it again, the icon will be an outline of a star to indicate it has been de-selected.
The onpress function looks like this symbols is the name of the JSON data the JSON data has 3 keys... symbol, name and iconName. Symbol is the item in the flatlist that is touched.
onPressListItem = ( symbol ) => {
for (var i = 0; i < this.state.symbols.length; i++){
if (this.state.symbols[i].symbol === symbol){
const copyOfSymbolsList = [...this.state.symbols];
if (copyOfSymbolsList[i].iconName === 'md-star') {
copyOfSymbolsList[i].iconName = 'md-star-outline';
} else {
copyOfSymbolsList[i].iconName = 'md-star';
}
this.setState({ symbols:copyOfSymbolsList });
}
}
}
So as you can see above it basically just scrolls through the entire json array to find the appropriate row and then makes a copy of the list, changes the data and then sets state again.
The app isn't super fast maybe half a second delay before the icon changes on my pixel 2 and there is only 100 records in the list. Im a little worried if the list gets into the thousands of rows itll be really bad.
Is there a better/faster/simpler/more react-y way to solve this problem?
EDIT
This is the renderListItem function which calls the onPress function
renderListItem = ({ item }) => {
return (
<TouchableOpacity
onPress={() => this.onPressListItem(item.symbol)}
>
<View style={{flex: 1, flexDirection: 'row'}}>
<View style={{backgroundColor: 'powderblue'}}>
<Ionicons style={styles.listItemIcon} name={item.iconName} />
</View>
<View style={{backgroundColor: 'skyblue'}}>
<Text style={styles.listItem}>
{item.name.toUpperCase()} {item.symbol}
</Text>
</View>
</View>
</TouchableOpacity>
);
};
EDIT #2
This is the FlatList code.
<View style={styles.mainContainer}>
<FlatList
data={this.state.symbols}
keyExtractor= {(item, index) => item.symbol}
ItemSeparatorComponent={this.renderListSeparator}
renderItem={this.renderListItem}
/>
</View>
i guest your json structure is like this.
the_data = [
{'iconName': 'xxx', 'symbol': '<i class="fa fa-items"></i>'}
]
You can access this structure by index, passing the index to the onPressListItem function.
renderListItem = ({ item }, index) => {
return (
<TouchableOpacity
onPress={() => this.onPressListItem(index)}
>
<View style={{flex: 1, flexDirection: 'row'}}>
<View style={{backgroundColor: 'powderblue'}}>
<Ionicons style={styles.listItemIcon} name={item.iconName} />
</View>
<View style={{backgroundColor: 'skyblue'}}>
<Text style={styles.listItem}>
{item.name.toUpperCase()} {item.symbol}
</Text>
</View>
</View>
</TouchableOpacity>
);
};
With this design you don't have to iterate your json.
onPressListItem = ( index ) => {
the_dataobj = the_data[index];
the_dataobj.iconName = 'md-start';
if (the_dataobj.iconName === 'md-start'){
the_dataobj.iconName = 'md-start_outline';
}
//you don't need a extra else here, is in-necesarry
the_data[index] = the_dataobj;
//set state ... do more
}
BTW: This is have nothing to do with react, the correct design of your workflow should be independent of the framework library.
Happy Codding!!!
For those of you reading, the accepted answer did not update the rendered list properly on screen. So the actual working code for me is below. Im pretty sure its because the accepted answer did not use the this.setState function to write the values back to the array.
Also the if then else needs to have an else in it because when the user taps the same row twice i want it to reverse its changes.
All that being said, the updates are still very slow. About the same speed as it was when using the for loop in the question. Not sure why.
onPressListItem = ( index ) => {
const copyOfSymbolsList = [...this.state.symbols];
thePressedSymbol = copyOfSymbolsList[index];
if (thePressedSymbol.iconName === 'md-star') {
thePressedSymbol.iconName = 'md-star-outline';
}else{
thePressedSymbol.iconName = 'md-star';
}
copyOfSymbolsList[index] = thePressedSymbol;
this.setState({ symbols:copyOfSymbolsList });
}
I'm creating a list of collapsible using the react native flatlist component.
I'm using ref attribute to get the item clicked.
But when i try to access the ref from the click event, it doesn't take effect on the clicked item but on the last item in the flatlist.
export default class Update extends Component {
renderItems (data, index) {
return (
<TouchableNativeFeedback
onPress={() => this.expandView()}
>
<View style={[styles.itemWrapperExpandable]}>
<View style={styles.itemHeader}>
<View style={styles.itemAvatar}>
<Image source={require('../images/logo.png')} style={styles.avatar}></Image>
</View>
<View style={styles.itemContent}>
<Text style={[styles.itemTitle, styles.black]}>{data.name}</Text>
<Text style={[styles.rating, styles.grey]}>
{data.rating}<Icon name="star"></Icon>
</Text>
<Text style={[styles.content, styles.black]}>{data.description}</Text>
</View>
<View style={styles.itemBtn}>
<Icon name="chevron-down" style={{ color: '#000', fontSize: 22 }}></Icon>
</View>
</View>
<View ref={(e) => this._expandableView = e } style={[styles.itemBody]}>
<Text style={styles.itemBodyText}>
some more information about this update will appear here
some more information about this update will appear here
</Text>
</View>
</View>
</TouchableNativeFeedback>
);
}
expandView () {
LayoutAnimation.easeInEaseOut();
if (this._expandableView !== null) {
if (!this.state.isExpanded) {
// alert(this.state.isExpanded)
this._expandableView.setNativeProps({
style: {height: null, paddingTop: 15, paddingBottom: 15,}
})
}
else {
this._expandableView.setNativeProps({
style: {height: 0, paddingTop: 0, paddingBottom: 0,}
});
}
this._expandableView.setState(prevState => ({
isExpanded: !prevState
}));
}
}
render() {
return (
<FlatList
data={this.state.data}
renderItem={({ item, index }) => this.renderItems(item, index)}
/>
)
}
}
I also tried placing using the index of the items, but couldn't make it work.
Any way around this? I think the ref are overwritten by the next one when the items are rendering.
You are right about your assumption. Ref is overwriting with the next item so ref is the last item's ref. You can use something like below to set each items ref separately.
ref={(ref) => this.refs[data.id] = ref}
Of course this solution assumes you have a unique id or sort in your item data.
To paraphrase the React Native docs, direct manipulation (i.e. refs) should be used sparingly; unless you need it for some other reason I'm unaware of, refs aren't necessary in this case. Typically, the best way to keep track of selected items in a FlatList is by utilizing the keyExtractor and extraData props in conjunction with a Javascript Map object in state.
The way React is able to keep track of items being added/removed/modified is by using a unique key prop for each item (preferably an id, or if necessary indexes work if the list order will not change). In a FlatList, this is handled "automagically" if you will using the keyExtractor prop. To keep track of the selected item, we can add/remove items from our Map object whenever we click on one. Map is a type of object like an array that holds key-value pairs. We'll use this in state to store a key item.id and a boolean value true for each item that is selected.
So, we end up with something like this:
export default class Update extends Component {
state = {
data: [],
selected: (new Map(): Map<string, boolean>)
}
renderItems = ({ item }) => {
// note: the double ! operator is to make sure non boolean values are properly converted to boolean
return (
<ExpandableItem
item={item}
selected={!!this.state.selected.get(item.id)}
expandView={() => this.expandView(item)}
/>
);
}
expandView (item) {
LayoutAnimation.easeInEaseOut();
this.setState((state) => {
const selected = new Map(state.selected);
selected.set(item.id, !selected.get(item.id));
return {selected};
});
// the above allows for multiple expanded items at a time; the following will simultaneously close the last item when expanding a new one
// this.setState((state) => {
// const selected = new Map();
// selected.set(item.id, true);
// return {selected};
// });
}
render() {
return (
<FlatList
data={this.state.data}
keyExtractor={(item, index) => `${item.id}`}
renderItem={this.renderItems}
/>
);
}
}
const ExpandableItem = ({ item, selected, expandView }) => {
return (
<TouchableNativeFeedback onPress={expandView}>
<View style={styles.itemWrapperExpandable}>
{/* ...insert other header code */}
<View style={[styles.itemBody, selected && styles.itemBodySelected]}>
<Text style={styles.itemBodyText}>
some more information about this update will appear here
</Text>
</View>
</View>
</TouchableNativeFeedback>
);
}
You'll have to play around with the styles.itemBodySelected to make it look how you want. Note that the separate functional component <ExpandableItem /> for the renderItem isn't required, just how I prefer to structure my code.
Helpful links:
https://facebook.github.io/react-native/docs/flatlist.html
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
https://reactjs.org/docs/lists-and-keys.html#keys