I'm making simple Todo Application using React Native + Redux following Youtube.
Adding Todo works well. so I took next step, trying to deleting todo got problem. The Video is little bit old, so the version and platform(Mine is Android) is different. so the way of it little different... (ES5/ES6 etc.)
Anyway... I want to send action to dispatcher using mapDispatchToProps's function, onDeleteTodo, but it's not working.
First I tried to connect the component to store, so Added line TodoItem = connect(mapStateToProps, mapDispatchToProps)(TodoItem);. but the error still left.
Something wrong... but I can't find, How can I fix it?
Thanks in advance... below is my code.
import React, {Component} from 'react';
import {
StyleSheet,
Text,
View,
TextInput,
ScrollView,
TouchableOpacity
} from 'react-native';
import {connect} from 'react-redux';
import {addTodo, deleteTodo} from '../actions';
class TodoItem extends Component {
render() {
return (
// ***************************************
// Below line (onPress prop) is problem.
// when I trying to save todo,
// Error "undefined is not a function (evaluating 'this.props.onDeleteTodo(this.props.id)')
<TouchableOpacity onPress={this.props.onDeleteTodo(this.props.id)}>
<View style={styles.todoContainer}>
<Text style={styles.todoText}>
{this.props.text}
</Text>
</View>
</TouchableOpacity>
)
}
}
TodoItem = connect(
mapStateToProps,
mapDispatchToProps
)(TodoItem);
class Main extends Component {
constructor(props) {
super(props);
this.state = {
newTodoText: ""
}
}
render() {
var renderTodos = () => {
return this.props.todos.map((todo) => {
return (
<TodoItem text={todo.text} key={todo.id} id={todo.id}/>
)
})
};
return (
<View style={styles.wrapper}>
<View style={styles.topBar}>
<Text style={styles.title}>
To-Do List
</Text>
</View>
<View style={styles.inputWrapper}>
<TextInput
onChange={(event) => {
this.setState({
newTodoText: event.nativeEvent.text
});
}}
value={this.state.newTodoText}
returnKeyType="done"
placeholder="New Todo"
onSubmitEditing={
() => {
if(this.state.newTodoText && this.state.newTodoText != ''){
this.props.onAddTodo(this.state.newTodoText);
this.setState({
newTodoText: ''
});
}
}
}
underlineColorAndroid='transparent'
style={styles.input}/>
</View>
<ScrollView
automaticallyAdjustContentInsets={false}>
{renderTodos()}
</ScrollView>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
todos: state.todos
}
};
const mapDispatchToProps = (dispatch) => {
return {
onAddTodo: (todo) => {
dispatch(addTodo(todo))
},
onDeleteTodo: (id) => {
dispatch(deleteTodo(id))
}
}
};
Main = connect(
mapStateToProps,
mapDispatchToProps
)(Main);
export default Main
If you write yoru code like this onPress={ this.props.onDeleteTodo(this.props.id) } then you are passing to the onPress property anything that is returned by the function this.props.onDeleteTodo. In other words, this.props.onDeleteTodo is executed when the component is rendering.
If you want to pass this function (and not it's returned value) then you need to write onPress={ this.props.onDeleteTodo.bind(this, this.props.id) }. This way you are passing this function with this as a context and this.props.id as it's first argument. More about this method here: Use of the JavaScript 'bind' method
I found the solution... but I don't know why it works.
change prop to callback function
onPress={this.props.onDeleteTodo(this.props.id)}
==>
onPress={ () => { this.props.onDeleteTodo(this.props.id) } }
: Is onPress prop only receive callback function? I don't know.
Move connect statement to below of const mapStateToProps and mapDispatchToProps
: Is const ... variable only can reference below its declaration? I don't know also.
I don't really know If I've understood your code...
By the way, if you are importing any function from somewhere, I think that you don't have to use dispatch method, since deleteTodo is not a property method.
Try again without dispatch(), moreover, try to call directly deleteTodo() method.
EDIT: in onPress event write this -> onPress={() => deleteTodo(this.props.id)}
It should call the method onces the event is triggered
And let me know if it works!
Related
I want my value to change on the screen when button is pressed. It does change the variable value behind the scenes but has no effect for the outdated value shown on the screen.
export default class App extends Component {
render() {
this.state = {
myVariable: 'egs'
}
const changeValue = () => {
this.state.myVariable = "CHANGED??!!"
}
return (
<View style={styles.container}>
<Text>
{this.state.myVariable}
</Text>
<Button onPress={changeValue} title="CHANGE IT"/>
</View>
);
}
}
I expect to update value to the changed one instead of outdated one.
Move state initialization outside of render as well as the changeValue method
You also cannot mutate statue directly, instead use setState()
This should work:
export default class App extends Component {
state = {
myVariable: 'egs'
}
changeValue = () => {
this.setState({myVariable:"CHANGED??!!"})
}
render() {
return (
<View style={styles.container}>
<Text>
{this.state.myVariable}
</Text>
<Button onPress={changeValue} title="CHANGE IT"/>
</View>
);
}
}
this.state.myVariable = "CHANGED??!!"
change to
this.setState({ myVariable: "CHANGED??!!" })
I've heard that passing an arrow function as a prop is not ideal because it creates a new function every time which will lead to performance issues. However, I'm not entirely sure how to completely move away from them, as can be seen by the example below:
class Home extends Component {
onCardPress = (message) =>{
alert(message)
}
render(){
return(
<View>
<Card
onCardPress={this.onCardPress}
message="Hello world!"
/>
</View>
)
}
}
class Card extends Component {
render(){
const { onCardPress , message } = this.props;
return(
<TouchableOpacity
activeOpacity={0.8}
onPress={()=>{onCardPress(message)}}
/>
)
}
}
I have tried changing onPress in Card to be onPress={onCardPress(message)}, but I know this doesn't work because I am invoking the function rather than passing a function object to the onPress of TouchableOpacity. What is the 'proper' way or best practice to remove the arrow function in TouchableOpacity while still being able to pass the message parameter from the parent component Home?
You could do:
class Card extends Component {
pressHandler = () => this.props.onCardPress(this.props.message);
render() {
return (
<TouchableOpacity
activeOpacity={0.8}
onPress={this.pressHandler.bind(this)}
/>
);
} }
If you want to avoid arrow function, you have to use bind(). Arrow functions will automatically bind "this".
class Home extends Component {
constructor(props) {
super(props);
this.onCardPress = this.onCardPress.bind(this);
}
onCardPress (message) {
alert(message)
}
render(){
return(
<View>
<Card
onCardPress={this.onCardPress}
message="Hello world!"
/>
</View>
)
}
}
class Card extends Component {
render(){
const { onCardPress , message } = this.props;
return(
<TouchableOpacity
activeOpacity={0.8}
onPress={onCardPress(message)}
/>
)
}
}
As I understand it, the issue lies with calling bind inside of render, or returning the handler from yet another lambda, as this will create a new function each time. The conventional way to get around this problem is to bind your handler functions elsewhere -- like in the constructor. In your case, that could look like this:
constructor(props) {
....
this.onCardPress = this.onCardPress.bind(this);
}
...
<Card
onCardPress={this.onCardPress}
message="Hello world!"
/>
Given you alternative option as arrow function already answered in above post.
class Card extends Component {
onClick = () => {
const { onCardPress, message } = this.props;
onCardPress(message);
}
render(){
const { onCardPress , message } = this.props;
return(
<TouchableOpacity
activeOpacity={0.8}
onPress={this.onClick}
/>
)
}
}
You don't need to pass the message prop because you can access it anywhere in the component.
Just supply a function in the onPress prop. And in that function, just access the message prop of the component.
class Home extends Component {
onCardPress = (message) => {
alert(message)
}
render() {
return (
<View>
<Card
onCardPress={this.onCardPress}
message="Hello world!"
/>
</View>
)
}
}
class Card extends Component {
onClick = () => {
const { message, onCardPress } = this.props;
onCardPress(message);
};
render() {
return (
<TouchableOpacity
activeOpacity={0.8}
onPress={this.onClick}
/>
)
}
}
I have my component class, let's call it MyComponent. It has a complicated execution logic, but here's how it looks like with unnecessary code snippets omitted:
class MyComponent extends Component {
justWarn(input) {
console.warn(input);
// do something with `this` as well
}
render() {
return (
<View>
{this.props.myText}
</View>
);
}
}
const mapStateToProps = () => {
// Call the getText function here
return {
myText: getText()
};
}
export default connect(mapStateToProps)(MyComponent);
Now the function getText is defined in some other file, and looks like this:
function getText() {
// Let's assume it returns hard-coded value for the sake of brevity
return (
<Text>
<Text>I am the first part</Text>
<Text
onPress={function(){
// I want to call the function that is defined inside MyComponent
// since this Text will eventually be a part of MyComponent
this.justWarn('Warn me in the yellow box')
}}
>I am the clickable part
</Text>
</Text>
);
}
When I try to call the justWarn method defined inside the MyComponent from the Text as defined above, I get an error undefined is not a function, evaluating this.justWarn.
My question is, I am not using the arrow function, and as such this is not bound on when I declare the onPress handler. It is actually called when Text is a part of MyComponent, so shouldn't this correspond to MyComponent and code above should work fine?
What am I doing wrong here? Is there any way to achieve this by keeping the getText and MyComponent split in two files?
Thanks.
The problem is getText does not have this context so you need to bind it in someway that it gets the context.
One way to achieve this is by changing MyComponent like this-
class MyComponent extends Component {
justWarn(input) {
console.warn(input);
alert(input)
// do something with `this` as well
}
render() {
const myText = this.props.myText.bind(this)
return (
<View>
<Text>something should render</Text>
{myText()}
</View>
);
}
}
const mapStateToProps = () => {
// Call the getText function here
return {
myText: getText
};
}
export default connect(mapStateToProps)(MyComponent);
...
and then using arrow function in onPress event handler like this -
function getText() {
// Let's assume it returns hard-coded value for the sake of brevity
return (
<Text>
<Text>I am the first part</Text>
<Text
onPress={() => {this.justWarn('Warn me in the yellow box')}}
>I am the clickable part
</Text>
</Text>
);
}
...
Hope it helps.
I'm pretty new to React and I'm running into an issue with updating my props in a container. I'm updating my state using WebSockets and the props are being updated in my mapStateToProps function, but my componentWillReceiveProps is not being called despite that.
When the sockets emit, updateGameVariables calls an Action sending the emitted data, which then goes to my reducer which is using the Spread Operator to update state. And then mapStateToProps logs the proper data (which is updating).
Here is the main file I am dealing with (everything is being properly imported I just wanted to cut down on code):
class GameList extends Component {
constructor(props){
super(props);
this.ds = new ListView.DataSource({ rowHasChanged: (r1,r2) => r1 !== r2})
const { games } = props;
this.state = {
games: this.ds.cloneWithRows(games)
}
this.socket = SocketIOClient('http://localhost:9615',{ transports: ['websocket'], query: 'r_var=17' });
this.socket.on('appGames', function(results){
props.dispatch(updateGameVariables(results))
});
}
componentWillReceiveProps(nextProps){
this.setState({
games: this.ds.cloneWithRows(nextProps.games)
})
}
render() {
let { games } = this.state;
return (
<View style={styles.list}>
<ListView
dataSource={games}
renderRow={(rowData, sectionID, rowID) =>
<TouchableOpacity>
<GameIntro key={rowID} game={rowData} />
</TouchableOpacity>
}
>
</ListView>
</View>
)
}
}
function mapStateToProps({games}){
return {
games: games.games, // Array
// rand: Math.random()
}
}
function mapDispatchToProps(dispatch) {
let actions = bindActionCreators({ updateGameVariables });
return { ...actions, dispatch };
}
export default connect(mapStateToProps, mapDispatchToProps)(GameList)
And here is the GameIntro component that is being referenced.
export default props => {
let { game } = props;
return (
<View style={styles.itemContainer}>
<View style={styles.game_timerow}>
<Text style={styles.game_time}>{game.period} {game.minutes}:{game.seconds}</Text>
</View>
</View>
)
}
Also as a note, when I have the rand: Math.random() function uncommented everything updates properly. And so I feel like react simply isn't picking up on updates to the games array. Or I am just not understanding a core concept of React. Any help would be greatly appreciated!
It's likely that you have a mutation problem in your reducer code, and because of that React see the games array as the same, then decide to not update the rendering. It explains why
rand: Math.random()
help React to realize that there is update in the props object and trigger re-render.
http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html might help.
I am using mobX for my react native project. Please consider this store class:
class Birds {
#observable listOne = [];
#observable fetchingListOne = false;
#observable fetchErrorOne = '';
#action setListOne = () => {
this.fetchingListOne = true;
api.getList()
.then((data) => {
this.listOne.replace(data);
this.fetchingListOne = false;
})
.catch((error) => {
this.fetchingListOne = false;
this.fetchErrorOne = error;
});
};
}
And this the react component:
#inject('BirdStore') #observer
export default class Flat extends React.Component {
componentDidMount() {
this.props.BirdStore.setListOne();
}
_renderHeader = () => {
return <Text style={styles.listHeaderText}>
Hello {this.props.BirdStore.listOne.length} is {this.props.BirdStore.fetchingListOne.toString()}
</Text>;
};
_renderItem = ({item}) => {
return <Text style={styles.item}>{item.name}</Text>
};
_renderFooter = () => {
if (this.props.BirdStore.fetchingListOne) {
return <ActivityIndicator/>
}
else {
return null
}
};
render() {
const dataSource = this.props.BirdStore.listOne.slice();
return (
<View style={styles.container}>
<Text>Fetching: {this.props.BirdStore.fetchingListOne.toString()}</Text>
<FlatList
style={styles.listContainer}
ListHeaderComponent={this._renderHeader}
data={dataSource}
renderItem={this._renderItem}
keyExtractor={(item, i) => item.id}
ListFooterComponent={this._renderFooter}
/>
</View>
)
}
}
From above it looks to me that:
When the Flat component mounts, it call the method of the store setListOne().
setListOne() sets fetchingListOne to true and makes an api call.
On the component side, when the fetchingListOne is true, the ActivityIndicator displays, and in the ListHeaderComponent it should display true.
On the store side, after successful/unsuccessful response, it sets fetchingListOne to false.
Finally on the component side, because fetchingListOne is set to false, ActivityIndicator should not display and in the ListHeaderComponent it should display false.
However, this is not what's happening. Here when the setListOne() method is called, after it sets the fetchingListOne to true, the component does not react to the changes made after api call. And the ActivityIndicator keeps displaying and in ListHeaderComponent its displaying true.
What am I doing wrong here? Could you please help me. Thank you
Update
I have added a Text component before the FlatList. Adding a Text component or console logging inside the component class's render method does makes the FlatList react to the changes. I don't know why this is happening though.
The problem you are running into here most probably, is that although Flat is an observer component, FlatList is not (it's an built-in component after all). In this setup _renderFooter and the others are part are rendered by render of FlatList, but not of FlatList. Hence they are not part of the lifecycle of Flat, but of FlatList and as such are not tracked by Mobx
There are two ways to fix this, both pretty simple:
1) declare _renderItem as observer component:
_renderItem = observer(({item}) =>
<Text style={styles.item}>{item.name}</Text>
);
2) use an inline anonymous Observer component:
_renderItem = ({item}) =>
<Observer>{
() => <Text style={styles.item}>{item.name}</Text>}
</Observer>