I'm making a chat bot app in react native and I am facing some difficulty in showing the messages that are saved in my firebase. The message box shows up empty. Moreover, three messages show up whereas there is only one message in my firebase. I am using React-Redux for state management and react-native-router-flux for navigation
Here is the code of my Login.js:
import React, {Component} from 'react'
import {View, Text, TouchableOpacity, StatusBar, ImageBackground} from 'react-native'
import {connect} from 'react-redux'
import {login, loginUser} from '../actions'
import {Input, Card, Button, Spinner} from './common'
class Login extends Component {
loginUser() {
const {email, password} = this.props
this.props.loginUser({email, password})
}
renderButton() {
if(this.props.loading) {
return <Spinner size='large' />
}
return (
<Button style={{backgroundColor: '#2D99FC', color: 'white'}}
text='Sign in'
onPress={this.loginUser.bind(this)}
/>
)
}
render() {
return(
<ImageBackground
style={{width: '100%', height: '100%'}}
source={require('../assets/images/bg.jpg')}
>
<StatusBar hidden />
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
<Text style={{fontSize: 25, fontWeight: 'bold', margin: 10}}>Login</Text>
<Text style={{color: 'grey', margin: 10}}>Stay Connected</Text>
<Card>
<Input
placeholder='name#example.com'
iconName='user'
value={this.props.email}
onChangeText={value => this.props.login({prop: 'email', value})}
/>
<Input
placeholder="******"
iconName='lock'
secureTextEntry
value={this.props.password}
onChangeText={value => this.props.login({prop: 'password', value})}
/>
<Text style={{fontSize: 20, color: 'red'}}>
{this.props.error}
</Text>
{this.renderButton()}
<TouchableOpacity>
<Text style={{color:'#2D99FC', fontWeight: 'bold', margin: 10, marginBottom: 30}}>Forgot Password?</Text>
</TouchableOpacity>
<View style={{flexDirection: 'row', margin: 10}}>
<Text>Don't have an account? </Text>
<TouchableOpacity>
<Text style={{color:'#2D99FC', fontWeight: 'bold'}}>Sign Up</Text>
</TouchableOpacity>
</View>
</Card>
</View>
</ImageBackground>
);
}
}
const mapStateToProps = state => {
const {email, password, loading, error} = state.auth
return {email, password, loading, error}
}
export default connect(mapStateToProps, {login, loginUser})(Login)
Here is my code of AuthAction.js:
import {AsyncStorage} from 'react-native'
import firebase from 'firebase'
import {Actions} from 'react-native-router-flux'
import User from '../User'
import {REGISTER, SIGN_UP_USER, SIGN_UP_USER_SUCCESS, SIGN_UP_USER_FAIL, LOGIN, LOGIN_USER, LOGIN_USER_SUCCESS, LOGIN_USER_FAIL, UPDATE_USERS} from './types'
export const register = ({prop, value}) => {
return {
type: REGISTER,
payload: {prop, value}
}
}
export const createUser = ({email, password, conPass, name}) => {
return(dispatch) => {
dispatch({type: SIGN_UP_USER})
if(password !== conPass) {
signupUserFail(dispatch, 'Passwords do not match')
}
else {
firebase.auth().createUserWithEmailAndPassword(email, password)
.then(user => {
signupUserSuccess(dispatch, user)
addData(name, email)
})
.catch(() => signupUserFail(dispatch, 'Authentication Failed'))
}
}
}
const addData = async (name, email) => {
await AsyncStorage.setItem('userName', name)
await AsyncStorage.setItem('userEmail', email)
User.name = name
User.email = email
firebase.database().ref('users/' + User.name).set({name, email})
}
const signupUserSuccess = (dispatch, user) => {
dispatch({
type: SIGN_UP_USER_SUCCESS,
payload: user
})
}
const signupUserFail = (dispatch, error) => {
dispatch({
type: SIGN_UP_USER_FAIL,
payload: error
})
}
export const login = ({prop, value}) => {
return {
type: LOGIN,
payload: {prop, value}
}
}
export const loginUser = ({email, password}) => {
return(dispatch) => {
dispatch({type: LOGIN_USER})
firebase.auth().signInWithEmailAndPassword(email, password)
.then(user => loginUserSuccess(dispatch, user))
.catch(() => loginUserFail(dispatch, 'Authentication Failed'))
}
}
const loginUserSuccess = (dispatch, user) => {
dispatch({
type: LOGIN_USER_SUCCESS,
payload: user
})
firebase.database().ref('users')
.on('child_added', (val) => {
let person = val.val()
person.name = val.key
dispatch({type: UPDATE_USERS, payload: person})
})
Actions.chat()
}
const loginUserFail = (dispatch, error) => {
dispatch({
type: LOGIN_USER_FAIL,
payload: error
})
}
Here is my code to AuthReducer.js:
import {REGISTER, SIGN_UP_USER, SIGN_UP_USER_SUCCESS, SIGN_UP_USER_FAIL, LOGIN, LOGIN_USER, LOGIN_USER_SUCCESS, LOGIN_USER_FAIL, UPDATE_USERS} from '../actions/types'
import User from '../User'
const INITIAL_STATE = {email: '', password: '', conPass: '', loading: false, error: '', user: null, name: '', user: []}
export default (state = INITIAL_STATE, action) => {
console.log(action)
switch(action.type) {
//Registration
case REGISTER:
return {...state, [action.payload.prop]: action.payload.value}
case SIGN_UP_USER:
return {...state, loading: true, error: ''}
case SIGN_UP_USER_SUCCESS:
return {...state, ...INITIAL_STATE, user: action.payload}
case SIGN_UP_USER_FAIL:
return {...state, error: action.payload, password: '', conPass: '', loading: false}
//Login
case LOGIN:
return {...state, [action.payload.prop]: action.payload.value}
case LOGIN_USER:
return {...state, loading: true, error: ''}
case LOGIN_USER_SUCCESS:
return {...state, ...INITIAL_STATE, user: action.payload}
case LOGIN_USER_FAIL:
return {...state, error: action.payload, password: '', loading: false}
case UPDATE_USERS:
User.name = action.payload.name
User.email = action.payload.email
//default
default:
return state
}
}
Here is my code to Chat.js:
import React, {Component} from 'react'
import {View, SafeAreaView, Text, TextInput, TouchableOpacity, StatusBar, Dimensions, FlatList} from 'react-native'
import {connect} from 'react-redux'
import {messageChanged, sendMessage, messagesListFetch} from '../actions'
import { Header } from './common';
import User from '../User';
class Chat extends Component {
onMessageChange(text) {
this.props.messageChanged(text)
}
sendMessage() {
const {message} = this.props
this.props.sendMessage(message)
}
componentDidMount() {
this.props.messagesListFetch()
}
render() {
return(
<View>
<View>
<StatusBar hidden />
<Header text='Chat with Me' />
</View>
<SafeAreaView>
<FlatList
scrollEnabled
style={{padding: 10, height: Dimensions.get('window').height * 0.8}}
data={this.props.messageList}
renderItem={(item) => {
return(
<View style={{
flexDirection: 'row',
width: '60%',
alignSelf: item[0] === User.name ? 'flex-end' : 'flex-start',
backgroundColor: item[0] === User.name ? '#00897B' : '#7CB342',
borderRadius: 5,
marginBottom: 10
}}>
<Text style={{color: '#FFF', padding: 7, fontSize: 16}}>{item[1]}</Text>
<Text style={{color: '#EEE', padding: 3, fontSize: 12}}>{item[2]}</Text>
</View>
)
}}
keyExtractor={(item, index) => item[0]}
/>
<View style={{flexDirection: 'row', alignItems: 'center', marginHorizontal: 5}}>
<TextInput
style={styles.input}
placeholder='Type a message...'
value={this.props.message}
onChangeText={this.onMessageChange.bind(this)}
/>
<TouchableOpacity onPress={this.sendMessage.bind(this)} style={{paddingBottom: 10, marginLeft: 5}}>
<Text>Send</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
</View>
);
}
}
const styles = {
input: {
padding: 10,
borderWidth: 1,
borderColor: '#CCC',
width: '80%',
marginBottom: 10,
borderRadius: 5,
},
}
const mapStateToProps = state => {
let messages = state.chat.messageList
let messageList = []
Object.keys(messages).map((key, index) => {
messageList.push(messages[key])
})
let message = state.chat.message
return {message, messageList}
}
export default connect(mapStateToProps, {messageChanged, sendMessage, messagesListFetch})(Chat)
Here is my code to ChatActions.js:
import firebase from 'firebase'
import {MESSAGE_CHANGED, MESSAGE_SENT, GET_MESSAGE_LIST} from './types'
import User from '../User'
export const messageChanged = (text) => {
return {
type: MESSAGE_CHANGED,
payload: text
}
}
export const sendMessage = (textMessage) => {
return(dispatch) => {
if(textMessage.length > 0) {
console.log(User.name)
let msgId = firebase.database().ref('messages').child(User.name).child('Bot').push().key
let updates = {}
let message = {
message: textMessage,
time: firebase.database.ServerValue.TIMESTAMP,
from: User.name
}
updates['messages/' + User.name + '/' + 'Bot' + '/' + msgId] = message
updates['messages/' + 'Bot' + '/' + User.name + '/' + msgId] = message
firebase.database().ref().update(updates)
dispatch({type: MESSAGE_SENT})
}
}
}
export const messagesListFetch = () => {
return(dispatch) => {
setTimeout(() => {
firebase.database().ref('messages').child(User.name).child('Bot')
.on('child_added', (value) => {
dispatch({type: GET_MESSAGE_LIST, payload: value.val()})
// console.log(value.val())
})
}, 1000)
}
}
Here is my code to ChatReducer.js:
import {MESSAGE_CHANGED, MESSAGE_SENT, GET_MESSAGE_LIST} from '../actions/types'
const INITIAl_STATE = {message: '', messageList: []}
export default (state = INITIAl_STATE, action) => {
switch(action.type) {
case MESSAGE_CHANGED:
return {...state, message: action.payload}
case MESSAGE_SENT:
return {...state, message: ''}
case GET_MESSAGE_LIST:
return {...state, messageList: action.payload}
default:
return state
}
}
Here is my User.js:
User = {
name: '',
email: ''
}
export default User
I want the message to show, and to show only once, not thrice. How can I do that? Can anyone help me here?
Related
I am having issues when a user signs up using firebase auth, I am unable to capture the username so that it can be used in other parts of the app.
The error that i see in the console is:
"Function DocumentReference.set() called with invalid data. Unsupported field value: undefined (found in field displayName in document"...)
Would really appreciate if someone can point me to where the mistake is. Here are my files:
AuthProvider.js
import React, { createContext, useState } from "react";
import { auth, db } from "../firebase";
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
return (
<AuthContext.Provider
value={{
user,
setUser,
login: async (email, password) => {
try {
await auth.signInWithEmailAndPassword(email, password);
} catch (e) {
console.log(e);
}
},
register: async (email, password, display_name) => {
try {
await auth
.createUserWithEmailAndPassword(email, password)
.then(async (res) => {
const userInfo = {
displayName: display_name,
};
await db
.collection("users")
.doc(res.user.uid)
.set(userInfo)
.then(() => {
console.log("user added")
console.log(res);
});
});
} catch (e) {
console.log(e);
}
},
logout: async () => {
try {
await auth.signOut();
} catch (e) {
console.log(e);
}
},
}}
>
{children}
</AuthContext.Provider>
);
};
SignUpScreen.js
import React, { useContext, useState } from "react";
import {
View,
Text,
TouchableOpacity,
Image,
Platform,
StyleSheet,
ScrollView,
Alert,
} from "react-native";
import FormInput from "../components/FormInput";
import FormButton from "../components/FormButton";
import SocialButton from "../components/SocialButton";
import { AuthContext } from "../navigation/AuthProvider";
const SignupScreen = ({ navigation }) => {
const [display_name, setDisplay_Name] = useState();
const [email, setEmail] = useState();
const [password, setPassword] = useState();
const [confirmPassword, setConfirmPassword] = useState();
const { register } = useContext(AuthContext);
const registerCheck = () => {
if (
display_name != "" &&
email != "" &&
password != "" &&
confirmPassword != ""
) {
if (password == confirmPassword) {
register(email, password);
} else {
Alert.alert("Oops! Passwords did not match");
}
} else {
Alert.alert("Oops! Some information is missing");
}
console.log(display_name, email, password, confirmPassword);
};
return (
<ScrollView contentContainerStyle={styles.container}>
<Text style={styles.text}>Create an Account</Text>
<FormInput
labelValue={display_name}
onChangeText={(userDisplay_Name) => setDisplay_Name(userDisplay_Name)}
placeholderText="Name"
iconType="user"
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
/>
<FormInput
labelValue={email}
onChangeText={(userEmail) => setEmail(userEmail)}
placeholderText="Email"
iconType="user"
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
/>
<FormInput
labelValue={password}
onChangeText={(userPassword) => setPassword(userPassword)}
placeholderText="Password"
iconType="lock"
secureTextEntry={true}
/>
<FormInput
labelValue={confirmPassword}
onChangeText={(userConfirmPassword) =>
setConfirmPassword(userConfirmPassword)
}
placeholderText="Confirm Password"
iconType="lock"
secureTextEntry={true}
/>
<FormButton
buttonTitle="Sign Up"
onPress={() => registerCheck(email, password)}
/>
<View style={styles.textPrivate}>
<Text style={styles.color_textPrivate}>
By registering, you confirm that you accept our{" "}
</Text>
<TouchableOpacity onPress={() => alert("Terms Clicked!")}>
<Text style={[styles.color_textPrivate, { color: "#e88832" }]}>
Terms of service
</Text>
</TouchableOpacity>
<Text style={styles.color_textPrivate}> and </Text>
<Text style={[styles.color_textPrivate, { color: "#e88832" }]}>
Privacy Policy
</Text>
</View>
<SocialButton
buttonTitle="Sign Up with Facebook"
btnType="facebook"
color="#4867aa"
backgroundColor="#e6eaf4"
onPress={() => {}}
/>
<SocialButton
buttonTitle="Sign Up with Google"
btnType="google"
color="#de4d41"
backgroundColor="#f5e7ea"
onPress={() => {}}
/>
<TouchableOpacity
style={styles.navButton}
onPress={() => navigation.navigate("Login")}
>
<Text style={styles.navButtonText}>Have an account? Sign In</Text>
</TouchableOpacity>
</ScrollView>
);
};
export default SignupScreen;
const styles = StyleSheet.create({
container: {
backgroundColor: "#f9fafd",
flex: 1,
justifyContent: "center",
alignItems: "center",
padding: 20,
},
text: {
fontFamily: "Helvetica",
fontSize: 28,
marginBottom: 10,
color: "#051d5f",
},
navButton: {
marginTop: 15,
},
navButtonText: {
fontSize: 18,
fontWeight: "500",
color: "#2e64e5",
fontFamily: "Helvetica",
},
textPrivate: {
flexDirection: "row",
flexWrap: "wrap",
marginVertical: 35,
justifyContent: "center",
},
color_textPrivate: {
fontSize: 13,
fontWeight: "400",
fontFamily: "Helvetica",
color: "grey",
},
});
I am adding a unique key to the FlatList and in theory only the button i press is supposed to be deleted but instead all elements are deleted
import { render } from "react-dom";
import {
View,
Text,
StyleSheet,
FlatList,
Button,
TouchableOpacity,
} from "react-native";
import { Context } from "../context/BlogContext";
import { Entypo } from "#expo/vector-icons";
const IndexScreen = function () {
const { state, addBlogPost, deleteBlogPost } = useContext(Context);
return (
<View>
<Button title="Add Post" onPress={() => addBlogPost()} />
<FlatList
data={state}
keyExtractor={(blogPosts) => blogPosts.title}
renderItem={({ item }) => {
return (
<View style={styles.row}>
<Text style={styles.title}>
{item.title} - {item.id}
</Text>
<TouchableOpacity
onPress={() => {
deleteBlogPost(item.id);
}}
>
<Entypo style={styles.icon} name="trash" />
</TouchableOpacity>
</View>
);
}}
/>
</View>
);
};
const styles = StyleSheet.create({
row: {
flexDirection: "row",
justifyContent: "space-between",
paddingHorizontal: 10,
paddingVertical: 20,
borderTopWidth: 1,
borderColor: "gray",
},
title: {
fontSize: 18,
},
icon: {
fontSize: 24,
},
});
export default IndexScreen;
Context Screen:
import createDataContext from "./createDataContext";
const blogReducer = function (state, action) {
switch (action.type) {
case "delete_blogpost":
return state.filter((blogPosts) => {
blogPosts.id !== action.payload;
});
case "add_blogpost":
return [
...state,
{
id: Math.floor(Math.random() * 99999),
title: `Blog Post #${state.length + 1}`,
},
];
default:
return state;
}
};
const addBlogPost = function (dispatch) {
return () => {
dispatch({ type: "add_blogpost" });
};
};
const deleteBlogPost = (dispatch) => {
return (id) => {
dispatch({ type: "delete_blogpost", payload: id });
};
};
export const { Context, Provider } = createDataContext(
blogReducer,
{ addBlogPost, deleteBlogPost },
[]
);
and
export default function (reducer, actions, initialState) {
const Context = React.createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
// actions === { addBlogPost : (dispatch) => {return () => {}}}
const boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
}
i dont know how to solve this. all titles are unique as they have different numbers but still all are deleted!!!!!!!!!!!!
here is the pic of the app
This is probably a dumb question, but I've been following some react native / expo tutorials on how to make a login screen with Firebase. The tutorial I followed also made a loading screen (using a loading state) that will display while Firebase is connecting. Unfortunately, the loading state never ends for me, and I don't know how to fix it.
Here is my Navigator.js file:
import React, { useReducer } from "react";
import { View, ActivityIndicator } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from "#react-navigation/stack";
import { createDrawerNavigator } from "#react-navigation/drawer";
import { AuthContext } from '../contexts/UserContext';
import { firebase } from "../utils/FirebaseConfig";
import Login from "../screens/Login";
import Signup from "../screens/Signup";
import Home from "../screens/Home";
const Drawer = createDrawerNavigator();
const RootStack = createStackNavigator();
export default AppNavigation = (props) => {
const initialLoginState = {
isLoading: true,
user: null,
};
const loginReducer = (prevState, action) => {
switch (action.type) {
case 'LOGIN':
return {
...prevState,
user: action.user,
isLoading: false,
};
case 'LOGOUT':
return {
...prevState,
user: null,
isLoading: false,
};
case 'REGISTER':
return {
...prevState,
user: action.user,
isLoading: false,
};
}
};
const [loginState, dispatch] = useReducer(loginReducer, initialLoginState);
const authContext = React.useMemo(() => ({
signIn: async (email, password) => {
return firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(async (userCred) => {
dispatch({ type: 'LOGIN', user: userCred.user });
return true;
}).catch((error) => {
if (error.code === "auth/user-disabled") {
alert("User disabled!");
}
if (error.code === "auth/invalid-email") {
alert("That email address is invalid!");
}
if (error.code === "auth/user-not-found") {
alert("User not found, please sign up!");
}
if (error.code === "auth/wrong-password") {
alert("Wrong password!");
}
return false;
})
},
signOut: async () => {
return firebase.auth().signOut().then(async () => {
dispatch({ type: 'LOGOUT' });
});
},
signUp: (email, password) => {
return firebase.auth().createUserWithEmailAndPassword(email, password).then((userCred) => {
dispatch({ type: 'REGISTER', user: userCred.user });
}).catch((error) => {
if (error.code === "auth/email-already-in-use") {
alert("That email address is already in use!");
}
if (error.code === "auth/invalid-email") {
alert("That email address is invalid!");
}
if (error.code === "auth/operation-not-allowed") {
alert("Operation is not allowed!");
}
if (error.code === "auth/weak-password") {
alert("The password is too weak!");
}
return false;
})
}
}), []);
if (loginState.isLoading) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" color="grey" />
</View>
);
}
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer>
{loginState.user !== null ? (
<Drawer.Navigator >
<Drawer.Screen name="Home" user={loginState.user} component={Home} />
</Drawer.Navigator>
) :
(
<RootStack.Navigator headerMode='none'>
<RootStack.Screen name="Login" component={Login} />
<RootStack.Screen name="Signup" component={Signup} />
</RootStack.Navigator>
)
}
</NavigationContainer>
</AuthContext.Provider>
)
}
Here is my Home.js File:
import React from 'react';
import { useContext } from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { AuthContext } from "../contexts/UserContext";
function Home(props) {
const { signOut } = useContext(AuthContext);
return (
<View style={{ flex: 1 }}>
<Text>Welcome</Text>
<Text>Email: {props.user?.email}</Text>
<Text>uid: {props.user?.uid}</Text>
<Text>displayName: {props.user?.displayName}</Text>
<Text>photo: {props.user?.photoURL}</Text>
<TouchableOpacity onPress={() => signOut()} style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", width: "100%" }} >
<Text style={{ fontWeight: "bold", fontSize: 18 }}>Logout</Text>
</TouchableOpacity>
</View>
);
}
export default Home;
Here is the Login.js file:
import React, { useContext, useState } from 'react';
import { KeyboardAvoidingView, View, Text, TextInput } from 'react-native';
import { AuthContext } from "../contexts/UserContext";
function Login(props) {
const [data, setData] = useState({
email: "",
password: ""
})
const { signIn } = useContext(AuthContext);
const emailInputChange = (val) => {
setData({
...data,
email: val
})
}
const passwordChange = (val) => {
setData({
...data,
password: val
});
}
const LoginHandle = () => {
signIn(data.email, data.password)
}
return (
<KeyboardAvoidingView>
<View style={{ flex: 1 }}>
<Text>Email: </Text>
<View >
<TextInput
placeholder="Email"
autoCapitalize="none"
onChangeText={(val) => emailInputChange(val)}
/>
</View>
<Text>Password: </Text>
<View>
<TextInput
placeholder="Password"
autoCapitalize="none"
secureTextEntry={true}
onChangeText={(val) => passwordChange(val)}
/>
</View>
<TouchableOpacity onPress={() => LoginHandle()} style={{ width: "100%", height: 50, justifyContent: "center", alignItems: "center", borderRadius: 10 }}>
<Text style={{ color: "#FFF", fontSize: 18, fontWeight: "bold" }}>Login</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
);
}
export default Login;
And finally, the Signup.js file:
import React, { useContext, useState } from 'react';
import { KeyboardAvoidingView, View, Text, TextInput } from 'react-native';
import { AuthContext } from "../contexts/UserContext";
function Signup(props) {
const [data, setData] = useState({
email: "",
password: ""
})
const { signUp } = useContext(AuthContext);
const emailInputChange = (val) => {
setData({
...data,
email: val
})
}
const passwordChange = (val) => {
setData({
...data,
password: val
});
}
const SignupHandle = () => {
signUp(data.email, data.password)
}
return (
<KeyboardAvoidingView>
<View style={{ flex: 1 }}>
<Text>Email: </Text>
<View >
<TextInput
placeholder="Email"
autoCapitalize="none"
onChangeText={(val) => emailInputChange(val)}
/>
</View>
<Text>Password: </Text>
<View>
<TextInput
placeholder="Password"
autoCapitalize="none"
secureTextEntry={true}
onChangeText={(val) => passwordChange(val)}
/>
</View>
<TouchableOpacity onPress={() => SignupHandle()} style={{ width: "100%", height: 50, justifyContent: "center", alignItems: "center", borderRadius: 10 }}>
<Text style={{ color: "#FFF", fontSize: 18, fontWeight: "bold" }}>Signup</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
);
}
export default Signup;
I followed a tutorial on youtube.
I wrote the same he did, but I'm facing this issue
ERROR: Actions must be plain objects. Use custom middleware for async
functions
Store/index.js
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from '../reducers'
export default function configureStore() {
let store = createStore(
rootReducer,
applyMiddleware(thunk)
);
return store
}
actions/index.js
import { ADD_TODO, TOGGLE_TODO } from './ActionTypes'
let nextId = 0
export const addTodo = (text) => ({
type: ADD_TODO,
id: nextId++,
text
})
export const toggleTodo = (di) => ({
type: TOGGLE_TODO_,
id
})
reducers/index.js
import { combineReducers } from "redux";
import todosReducer from './todosReducer'
import visibiltyReducer from './visibiltyReducer'
const rootReducer = combineReducers({
todosReducer,
visibiltyReducer
})
export default rootReducer
reducers/todoreducer.js
import { ADD_TODO, TOGGLE_TODO } from '../actions/ActionTypes'
const todosReducer = (state = [], action) => {
console.log("TODO Reducer: " + action.type)
switch (action.type) {
case ADD_TODO:
return [
...state, {
id: action.id,
text: action.text,
completed: false
},
]
case TOGGLE_TODO:
return state.map(todo => (todo.id == action.id) ? { ...todo, completed: !todo.completed } : todo)
default:
return state
}
}
export default todosReducer
App.js
render() {
return (
<View style={styles.container}>
<Provider store={store}>
<TodoApp />
</Provider>
</View>
);
}
As using dispatch showing the issue at dispatch line. Please help where I went wrong.
import React, { Component } from "react";
import {
View,
TextInput,
StyleSheet,
TouchableOpacity
} from "react-native";
import { Icon } from "native-base";
import { connect } from 'react-redux'
import addTodo from '../redux/actions'
let nextId = 0
class AddTodo extends Component {
constructor(props) {
super(props)
this.state = {
text: ''
}
}
addingTodo = (text) => {
// alert("TODO " + text)
if (text.length > 0) {
this.props.dispatch({ type: "ADD_TODO", id: nextId++, text: text })
// this.props.dispatch(addTodo(text))
alert("TODO Added")
this.setState({ text: '' })
} else alert("Can't add empty")
}
render() {
return (
<View style={{ flexDirection: 'row', width: '100%' }}>
<View style={styles.container}>
<TextInput
value={this.state.text}
placeholder="Add ToDo"
onChangeText={(text) => this.setState({ text })}
style={{ height: 50, flex: 1, backgroundColor: '#dddddd' }}
/>
<TouchableOpacity
onPress={() => this.addingTodo(this.state.text)}>
<Icon
name='md-add-circle'
size={30} style={{ color: '#3456ff', backgroundColor: 'white', borderRadius: 20 }} />
</TouchableOpacity>
</View>
</View >
);
}
}
export default connect()(AddTodo);
const styles = StyleSheet.create({
container: {
marginStart: 10,
marginEnd: 10,
width: '100%',
height: 60,
alignItems: 'center',
justifyContent: 'center',
padding: 15,
flexDirection: 'row',
backgroundColor: '#dddddd',
}
});
I'm using https://facebook.github.io/react-native/docs/navigation.html by the way.
I'm trying to use the StackNavigator to go from Login.js to AboutDendro.js. What's wrong in my <Button/> component that's throwing that error in my iOS simulator?
Here's Login.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { ScrollView, Text, TextInput, View, Button, StyleSheet } from 'react-native';
import { login } from '../redux/actions/auth';
import {AuthenticationDetails, CognitoUser, CognitoUserAttribute, CognitoUserPool} from '../lib/aws-cognito-identity';
import StackNavigator from 'react-navigation';
import AboutDendro from './AboutDendro';
const awsCognitoSettings = {
UserPoolId: 'something',
ClientId: 'something'
};
class Login extends Component {
constructor (props) {
super(props);
this.state = {
page: 'Login',
username: '',
password: ''
};
}
get alt () { return (this.state.page === 'Login') ? 'SignUp' : 'Login'; }
handleClick (e) {
e.preventDefault();
const userPool = new CognitoUserPool(awsCognitoSettings);
// Sign up
if (this.state.page === 'SignUp') {
const attributeList = [
new CognitoUserAttribute({ Name: 'email', Value: this.state.username })
];
userPool.signUp(
this.state.username,
this.state.password,
attributeList,
null,
(err, result) => {
if (err) {
alert(err);
this.setState({ username: '', password: '' });
return;
}
console.log(`result = ${JSON.stringify(result)}`);
this.props.onLogin(this.state.username, this.state.password);
}
);
} else {
const authDetails = new AuthenticationDetails({
Username: this.state.username,
Password: this.state.password
});
const cognitoUser = new CognitoUser({
Username: this.state.username,
Pool: userPool
});
cognitoUser.authenticateUser(authDetails, {
onSuccess: (result) => {
console.log(`access token = ${result.getAccessToken().getJwtToken()}`);
this.props.onLogin(this.state.username, this.state.password);
},
onFailure: (err) => {
alert(err);
this.setState({ username: '', password: '' });
return;
}
});
}
}
togglePage (e) {
this.setState({ page: this.alt });
e.preventDefault();
}
static navigationOptions = {
title: 'AboutDendro',
};
render() {
const { navigate } = this.props.navigation;
const App = StackNavigator({
Home: { screen: Login },
Profile: { screen: AboutDendro },
});
return (
<ScrollView style={{padding: 20}}>
<Button
title="Go to Jane's profile"
onPress={() =>
navigate('AboutDendro', { name: 'AboutDendro' })
}
/>
<Text style={{fontSize: 27}}>{this.state.page}</Text>
<TextInput
placeholder='Email Address'
autoCapitalize='none'
autoCorrect={false}
autoFocus={true}
keyboardType='email-address'
value={this.state.username}
onChangeText={(text) => this.setState({ username: text })} />
<TextInput
placeholder='Password'
autoCapitalize='none'
autoCorrect={false}
secureTextEntry={true}
value={this.state.password}
onChangeText={(text) => this.setState({ password: text })} />
<View style={{margin: 7}}/>
<Button onPress={(e) => this.handleClick(e)} title={this.state.page}/>
<View style={styles.firstView}>
<Text onPress={(e) => this.togglePage(e)} style={styles.buttons}>
{this.alt}
</Text>
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
buttons: {
fontSize: 12,
color: 'blue',
flex: 1
},
firstView: {
margin: 7,
flexDirection: 'row',
justifyContent: 'center'
}
});
const mapStateToProps = (state, ownProps) => {
return {
isLoggedIn: state.auth.isLoggedIn
};
}
const mapDispatchToProps = (dispatch) => {
return {
onLogin: (username, password) => { dispatch(login(username, password)); }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
It is because navigation is not in your props. It is a part of your App component you created. But you do nothing with this component.
You should have an App.js file, with your stackNavigator, set your Login component as your default component in your stackNavigator's parameters.
Take a look at this documentation
I try to refactor your code.
in component render maybe you can just write :
render() {
const { navigate } = this.props.navigation;
return (
<ScrollView style={{padding: 20}}>
<Button
title="Go to Jane's profile"
onPress={() =>
navigate('Profile', { name: 'AboutDendro' })
}
/>
<Text style={{fontSize: 27}}>{this.state.page}</Text>
<TextInput
placeholder='Email Address'
autoCapitalize='none'
autoCorrect={false}
autoFocus={true}
keyboardType='email-address'
value={this.state.username}
onChangeText={(text) => this.setState({ username: text })} />
<TextInput
placeholder='Password'
autoCapitalize='none'
autoCorrect={false}
secureTextEntry={true}
value={this.state.password}
onChangeText={(text) => this.setState({ password: text })} />
<View style={{margin: 7}}/>
<Button onPress={(e) => this.handleClick(e)} title={this.state.page}/>
<View style={styles.firstView}>
<Text onPress={(e) => this.togglePage(e)} style={styles.buttons}>
{this.alt}
</Text>
</View>
</ScrollView>
);
}
}
And you separate component StackNavigation below component render() like:
const App = StackNavigator({
Home: { screen: Login },
Profile: { screen: AboutDendro },
});
And then import component App in your index.ios.js like:
import './Login';
just that. Maybe my answer can help you.