I have a list of tag objects in array (tagList), which I am fetching from the server and storing into the state. After a succesfull fetch I am mapping each item inside a ScrollView.
export default class RegisterTags extends Component {
constructor(props) {
super(props)
this.state = {
tagList: [],
selectedTags: [],
}
}
componentWillMount() {
api.getTagsOnRegister()
.then((res) => {
this.setState({
tagList: res
})
})
}
insertTag = (tag) =>{
this.setState({
selectedTags: this.state.selectedTags.push(tag)
})
}
render() {
return(
<View style={styles.container}>
<ScrollView contentContainerStyle={styles.ScrollViewContainer}>
{this.state.tagList.map((tag) => {
return(
<View style={styles.tagStyle} key={tag.id} onPress={this.insertTag(tag)}>
<Text style={styles.tagText}>{tag.name}</Text>
</View>
)
})}
</ScrollView>
</View>
)
}
}
What I want to achieve is, when I press on any of the tag, I would like to add that object into the selectedTags array. But I am getting error:
Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
Possible Unhandled Promise Rejection (id: 0):
_this.state.selectedTags.push is not a function
TypeError: _this.state.selectedTags.push is not a function
How can I add the pressed tag item into the selectedTags array?
onPress={this.insertTag(tag)}
Issue: in your render function you are directly calling a function that changes state. Render function should never call any function that updates state.
So how to call onPress?
Ans: as written below
onPress = {() => {this.insertTag(tag) }}
Will code work now?
Answer is no.
You have to put your views inside TouchableHighlight and move onPress method from View to TouchableHighlight.
Then hopefully your code works. I am assuming everything is setup property.
~~~~~~EDIT 1~~~~~~~~
What't the difference between onPress={this.insertTag(tag)} and onPress = {() => {this.insertTag(tag) }}
Every thing inside curly braces are expressions in react jsx. Now onPress={this.insertTag(tag)} evaluates the expression inside curly braces and assign it to onPress property, and in your case this.insertTag happens to update state.
While onPress = {() => {this.insertTag(tag) }} , on evaluating curly braces returns a function, but doesn't call that function. And when onPress event is triggered than that function is called.
Google "Arrow Function" for more.
Related
Hi Guys I am a newbie in React-native I will like to know How I can called a function of one component when a button in another component is click like below I will like to call the onPlayPress() function from PlayWidget component when the button in AlbumHeader get click on
AlbumHeader.tsx file
export type AlbumHeaderProp = {
album: Album;
}
const AlbumHeader = (props: AlbumHeaderProp) => {
const { album } = props;
const playallSong = () => {
}
return (
<View style={styles.container}>
<TouchableOpacity onPress={playallSong}>
<View style={styles.button}>
<Text style={styles.buttonText} >
<PlayerWidget onPress={playallSong}/>
{Play}
</Text>
</View>
</TouchableOpacity>
</View>
)
}
export default AlbumHeader;
PlayWidget.tsx files
const onPlayPausePress = async () => {
if (!sound) {
return;
}
if (isPlaying) {
await sound.pauseAsync();
} else {
await sound.playAsync();
}
}
You could pass the function as prop to the component with the button. Then when you click the button, you can call that function
Component A - has function
Component B - has button
Assuming Component B is a child of Component A - then <Component B func={compAFunc}/>
Then in Component B button's onClick method
<btn onClick={props.func} />
If B is not a child of A, then you will probably have to lift state up to another component. Otherwise add the function to a global store like redux so it can be shared all over.
This is a good example of a use case for an observer pattern.
In the observer pattern there is a particular event that an "observer" cares about in the "publisher" (There are other names used for these things).
To sum it up, the "observer" (sometimes called "Subscriber") is the one who needs to be called when an event occurs. The "Publisher" or "Observable" is the one who will do the calling of all subscribers when a particular event occurs.
The "publisher" will have an "on" or "subscribe" function that registers the observer to be called for the particular event.
Here is a great post for more about the Observer Pattern
Refactoring Guru - Observer
How does it help you?
your code will look something like the following...
interface AlbumHeaderProps {
// ...
onPlayerWidgetPress(): Promise<void> // analogous to .subscribe(Observer)
}
const AlbumHeader = ({
onPlayerWidgetPress,
}: AlbumHeaderProps) => {
return (
<View style={styles.container}>
// ...
<PlayerWidget
onPress={onPlayerWidgetPress}
/>
// ...
</View>
)
}
Then in the code that calls this component...
<AlbumHeader
onPlayerWidgetPress={async () => { // analogous to .update() method on observer
//... your function
}}
/>
NOTE: I don't know how or if PlayerWidget honors/awaits promises so I can't speak to concurrency control here.
Your final solution may vary, but hopefully this helps to get you part of the way there.
with react-native, I want to use componentWillMount without using a class
await Font.loadAsync({
gotham_medium: require("../../assets/GothamMedium_1.ttf")
});
}
const Button = (props: TouchableOpacityProps & ButtonProps) => (
<TouchableOpacity {...props} style={styles.button}>
<Text style={styles.title}>{props.title}</Text>
</TouchableOpacity>
);
export default Button;
But I have a problem on the device :
error on the device
It says the problem is on this line (and it is):
async componentWillMount = () => {
When you use an async function, the async keyword goes right before () => (a vanilla js syntax error). Like this:
componentWillMount = async () => {
But, that's not the main problem. When not using a class, you need the useEffect hook.
So, try something like this (the whole component, and deleting componentWillMount):
const Button = (props: TouchableOpacityProps & ButtonProps) => {
useEffect(async () => {
await Font.loadAsync({
gotham_medium: require("../../assets/GothamMedium_1.ttf")
});
}, []);
return (
<TouchableOpacity {...props} style={styles.button}>
<Text style={styles.title}>{props.title}</Text>
</TouchableOpacity>
);
};
And at the top of the file:
import { useEffect } from 'react';
You can use Hooks for this,
from the docs,
If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
And
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
useEffect(async () => {
await Font.loadAsync({
gotham_medium: require("../../assets/GothamMedium_1.ttf")
});
},[]);
I've imported a custom component into my screen and rendered it in the render() function. Then, created a ref to that custom component. Now, the render() function simply looks like this.
render() {
return (
<View>
<MyComponent ref={component => this.myComponent = component} />
</View>
)
}
Then, I've created another function to access the state of my custom component. I wrote it like this.
myFunction = (ref) => {
ref.setState({ myState: myValue })
}
Then, I called that function like this.
this.myFunction(this.myComponent)
But, it does not work. It gives me the following error.
null is not an object (evaluating 'ref.setState')
Actually what I need this myFunction to do is,
this.myComponent.setState({ myState: myValue })
Can you please help me to solve this problem?
ref is not your this object. it's dom for your componnet. For setState you need this of your component.
you can pass this as argument.
myFunction(this)
Now you will be able to do ref.setState in myFunction.
function myFunction(ref) {
ref.setState({ myState: myValue })
}
To use setState, just use your component's context (this keyword). The context also have your ref in it, so you don't need to pass it as an argument if you are inside one component(not forwarding down to children)
myFunction = (event) => {
this.myComponent // -> points to your ref, DOM element
this.setState() // use your setState like that
}
Don't forget to bind your context in parent component if you want to pass the handler to the child components. Refer to this useful topic
EDIT: Based on your comment, I guess you want to update the parent state by calling a handler in some other component. To do that, you need to create a handler in your parent component, bind the context and pass it as a property to the child component. Next up, you need to assign this handler in your child component. You cannot pass a context with setState method via argument or ref, this is just not how it works in javascript and in react.
Example:
// ParentComponent.js
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 1,
};
this.onChangeHandler = this.onChangeHandler.bind(this);
}
onChangeHandler(event) {
this.setState({
value: someNewValue // will update state on parent component
})
}
render() {
return (
<View>
<SomeComponent>{this.state.value}</SomeComponent>
<ChildrenComponent onChangeHandler={this.onChangeHandler} />
</View>
);
}
}
// ChildrenComponent.js
const ChildrenComponent = (props) => (
<View>
<Button
onPress={props.onChangeHandler}
title="click me to change parent state"
/>
</View>
);
Hopefully, this is what you need :)
I have declared an array of objects in the state that represent TextInput fields. Now I want to add value onChangeText of my inputs but as soon as I try to write something on the input I get the error: 'this.state.sede_array is not a function'. I did console.log of the array and I see that only the first letter of my input is added to the value property and then the error fires up. Please help to figure out what is happening.
export default class Form extends Component {
constructor(props) {
super(props);
this.state = {
sede_array:[
{id:1, value:null},
{id:2, value:null},
{id:3, value:null},
]
};
}
render() {
console.log(this.state.sede_array)
let sede_list= this.state.sede_array.map((val, key) => {
return(
<View key={key}>
<Text style={styles.titolo}>Indirizzo Sede
{this.state.sede_array[key].id}</Text>
<TextInput style={styles.input}
onChangeText={value => {
sede_array[key].value = value;
this.setState({sede_array})
}}
/>
</View>
)});
I have a feeling you are clobbering your array. Inside the onChangeText event handler, you are setting state; but do you know what the value is? You use sede_array without (this.state) as a prefix. I'm assuming sede_array variable doesn't exist globally, thus it would assign 'undefined' to this.state.sede_array when you call setState(). Since setState will trigger another render, on that render cycle it would crash because this.state.sede_array would be undefined. You can fix this by adding this.state. infront of sede_array inside onChangeText.
Problem
I'm trying to use parameters I pass with react navigation in react native inside of functions. I define the parameters like this:
render() {
const { params } = this.props.navigation.state;
return (
And whenever I try to use a parameter in a function, for example params.userID I get an error saying, "can't find variable params". When I try to use
this.setState({passUserID: this.props.navigation.state.userID})
that just returns null. I would pass the parameters into the function, but I call functions from within other functions. The impractical solution I have been using is making a user press a button that defines a bunch of states with all of the parameters in them:
<Button
onPress={() =>{
this.setState({passKey: params.postKey})
this.setState({passUserID: params.userID})
this.setState({passContent: params.postContent})
this.setState({firebaseItems:''});
this.setState({fontLoaded:true});
}}
title="Press to load"
color="#ff0037"
/>
But this is impractical for the user, because each time they want to load this screen they have to press a button.
So how can I use params inside of functions, or what other solutions can I try?
Code
class Feed extends React.Component {
//...
//Where I send the parameters and navigate to the other screen
<TouchableWithoutFeedback onPress={() => {
this.props.navigation.navigate(
'CommentScreen',{postKey: item.key, postContent: item.content, passNameID: item.nameID, userID: item.userID}
)
}}>
//...
</TouchableWithoutFeedback>
//...
And then later:
class Comments extends React.Component {
//...
render() {
//where I receive the parameters
const { params } = this.props.navigation.state;
return (
//...
//How I use parameters
<Text>
{params && params.postContent}
</Text>
params is declared in render(). If you want to use the values in another function, you can use this.props.navigation.state.
Note: when you want to set several state values, you can do it all in one call to this.setState():
this.setState({
passKey: params.postKey,
passUserID: params.userID,
passContent: params.postContent,
firebaseItems:'',
fontLoaded:true
});