Related
am getting an error while running my react native app in expo , my error is Too many renders react limits the number of renders to prevent infinite loop, don't know where am wrong , please try to fix my error. if you have any question please free feel to ask ant time.
Home.js
This is the home.js file where in wrote my all code including css.
import React, { useEffect, useState } from 'react'
import { Text, View, FlatList, StyleSheet, ScrollView, Image } from 'react-native';
import { Avatar } from 'react-native-elements';
import { Searchbar, shadow, Modal, Provider, Portal } from 'react-native-paper';
import { AntDesign } from '#expo/vector-icons';
export default function Home() {
const [searchquery, setSearchquery] = React.useState();
const [visible, setVisible] = React.useState(false);
const showModal = setVisible(true);
const hideModal = setVisible(false);
const containerStyle = { backgroundColor: 'white', padding: 20 };
const [users, setUser] = useState([
{
id: 1,
name: "Ashish Nirvikar"
},
{
id: 2,
name: "Drew Macntyre"
},
{
id: 3,
name: "Jonh Cena"
},
{
id: 4,
name: "Rock Samoa"
},
{
id: 5,
name: "Boby Lashely"
},
])
return (
<View >
<Searchbar
placeholder="Search Contacts"
onChangeText={(query) => setSearchquery(query)}
value={searchquery}
style={{ marginTop: 30, marginHorizontal: 10 }}
/>
<ScrollView>
{
users.map((item, index) => {
return (
<View key={index}>
<Text style={styles.names}>{item.name}</Text>
</View>
)
})
}
</ScrollView>
<Provider>
<Portal>
<Modal visible={visible} onDismiss={hideModal} contentContainerStyle={containerStyle}>
<Text>Example Modal. Click outside this area to dismiss.</Text>
</Modal>
</Portal>
<AntDesign name="plus" size={34} color="black" style={styles.plus} onPress={showModal} />
</Provider>
</View>
);
}
const styles = StyleSheet.create({
customText: {
padding: 10,
marginTop: 20,
textAlign: 'center',
backgroundColor: 'lightgray',
fontWeight: 'bold',
fontSize: 20
},
plus: {
fontSize: 50,
position: 'absolute',
top: 680,
right: 40,
backgroundColor: 'pink',
borderRadius: 15,
borderWidth: 0.5,
padding: 5,
},
names: {
padding: 15,
fontSize: 25,
fontWeight: 'bold',
backgroundColor: 'lightgray',
marginTop: 10,
borderRadius: 20,
color: 'black'
}
});
The showModal and hideModal are functions, define it as a function.
const showModal = () => setVisible(true);
const hideModal = () => setVisible(false);
I'm new to React Native and after following some tutorials I hacked this together but now I want to load some gifs right when the app starts - not after the button click.
Did some research and it looks like it's not possible with functional components and I need to switch to class components to use lifecycle functions like:
componentWillMount(){
this.setState({data : inputObject});
}
All the examples I've read so far don't have functions in their components and I can't figure out what to do with them. So if it is possible to call a function when the app starts using this as is please let me know how, if not, how do I convert this code to class component style? Thanks!
import React, {useState} from 'react';
import {
Dimensions,
StyleSheet,
SafeAreaView,
View,
Image,
FlatList,
} from 'react-native';
import SearchInput from './SearchInput';
export default function App() {
const [allGifResults, setAllGifResults] = useState([]);
function addSearchResultsHandler(searchTerm) {
console.log(searchTerm);
setAllGifResults([]);
fetchResults(searchTerm);
}
function allGifResultsHandler(url) {
setAllGifResults(currentGifs => [...currentGifs, {id: url, value: url}]);
}
function fetchResults(searchTerm) {
fetch(
'http://api.giphy.com/v1/gifs/search?q=' +
searchTerm +
'&api_key=MKSpDwx7kTCbRp23VtVsP4d0EvfwIgSg&limit=50',
)
.then(response => response.json())
.then(responseJson => {
for (let item of responseJson.data) {
allGifResultsHandler(item.images.fixed_height.url);
console.log(item.images.fixed_height.url);
}
})
.catch(error => {
console.error(error);
});
}
return (
<SafeAreaView style={styles.container}>
<View style={styles.screen}>
<SearchInput onSearchButtonPressed={addSearchResultsHandler} />
</View>
<FlatList
keyExtractor={(item, index) => item.id}
data={allGifResults}
numColumns={2}
renderItem={itemData => (
<Image
source={itemData.item.value ? {uri: itemData.item.value} : null}
style={styles.images}
/>
)}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
screen: {
margin: 10,
},
images: {
width: Dimensions.get('window').width / 2 - 20,
height: Dimensions.get('window').width / 2 - 20,
margin: 10,
},
});
import React, {useState} from 'react';
import {
View,
TextInput,
TouchableOpacity,
Text,
StyleSheet,
} from 'react-native';
function SearchInput(props) {
const [searchTerm, setSearchTerm] = useState('');
function inputHandler(enteredText) {
setSearchTerm(enteredText);
}
return (
<View style={styles.inputContainer}>
<TextInput
placeholder="Search Term"
style={styles.input}
onChangeText={inputHandler}
value={searchTerm}
/>
<TouchableOpacity
style={styles.searchButton}
onPress={props.onSearchButtonPressed.bind(this, searchTerm)}>
<Text style={styles.searchButtonText}>Search</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
inputContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: 20,
},
input: {
width: '70%',
borderColor: 'black',
borderWidth: 1,
fontSize: 16,
},
searchButton: {
height: 50,
width: 100,
backgroundColor: 'lightblue',
marginLeft: 10,
},
searchButtonText: {
height: 50,
fontSize: 18,
textAlign: 'center',
textAlignVertical: 'center',
},
});
export default SearchInput;
import React, {useState} from 'react';
import {
Dimensions,
StyleSheet,
SafeAreaView,
View,
Image,
FlatList,
} from 'react-native';
import SearchInput from './SearchInput';
const [allGifResults, setAllGifResults] = useState([]);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
};
this.addSearchResultsHandler = this.addSearchResultsHandler.bind(this);
this.allGifResultsHandler = this.allGifResultsHandler.bind(this);
this.fetchResults = this.fetchResults.bind(this);
}
addSearchResultsHandler(searchTerm) {
console.log(searchTerm);
setAllGifResults([]);
fetchResults(searchTerm);
}
allGifResultsHandler(url) {
setAllGifResults(currentGifs => [...currentGifs, {id: url, value: url}]);
}
fetchResults(searchTerm) {
fetch(
'http://api.giphy.com/v1/gifs/search?q=' +
searchTerm +
'&api_key=MKSpDwx7kTCbRp23VtVsP4d0EvfwIgSg&limit=50',
)
.then(response => response.json())
.then(responseJson => {
for (let item of responseJson.data) {
allGifResultsHandler(item.images.fixed_height.url);
console.log(item.images.fixed_height.url);
}
})
.catch(error => {
console.error(error);
});
}
render(){
return (
<SafeAreaView style={styles.container}>
<View style={styles.screen}>
<SearchInput onSearchButtonPressed={(data)=> this.addSearchResultsHandler(data)} />
</View>
<FlatList
keyExtractor={(item, index) => item.id}
data={allGifResults}
numColumns={2}
renderItem={itemData => (
<Image
source={itemData.item.value ? {uri: itemData.item.value} : null}
style={styles.images}
/>
)}
/>
</SafeAreaView>
);
}
}
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
screen: {
margin: 10,
},
images: {
width: Dimensions.get('window').width / 2 - 20,
height: Dimensions.get('window').width / 2 - 20,
margin: 10,
},
});
import React, {useState} from 'react';
import {
View,
TextInput,
TouchableOpacity,
Text,
StyleSheet,
} from 'react-native';
const [searchTerm, setSearchTerm] = useState('');
class SearchInput extends React.Component {
constructor(props) {
super(props);
this.state = {
};
this.inputHandler = this.inputHandler.bind(this);
}
inputHandler(enteredText) {
setSearchTerm(enteredText);
}
render(){
return (
<View style={styles.inputContainer}>
<TextInput
placeholder="Search Term"
style={styles.input}
onChangeText={inputHandler}
value={searchTerm}
/>
<TouchableOpacity
style={styles.searchButton}
onPress={props.onSearchButtonPressed.bind(this, searchTerm)}>
<Text style={styles.searchButtonText}>Search</Text>
</TouchableOpacity>
</View>
);
}
}
export default SearchInput;
const styles = StyleSheet.create({
inputContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: 20,
},
input: {
width: '70%',
borderColor: 'black',
borderWidth: 1,
fontSize: 16,
},
searchButton: {
height: 50,
width: 100,
backgroundColor: 'lightblue',
marginLeft: 10,
},
searchButtonText: {
height: 50,
fontSize: 18,
textAlign: 'center',
textAlignVertical: 'center',
},
});
export default SearchInput;
I want to add redux implementation to my simple Login application with some react navigations.
This is my App.js file where I'm importing my AppDrawerNavigator
import React, {Component} from 'react';
import {createAppContainer, createStackNavigator} from 'react-navigation';
import HomeScreen from './screens/HomeScreen.js';
/** Importing navigator */
import AppDrawerNavigator from './drawerNavigator';
class App extends React.Component {
render() {
return <AppContainer />;
}
}
export default App;
const AppStackNavigator = createStackNavigator(
{
Home: {screen: HomeScreen},
Welcome: AppDrawerNavigator
},
{
initialRouteName: 'Home',
headerMode: "none",
}
);
const AppContainer = createAppContainer(AppStackNavigator);
This is my index.js file pointing to my main App.js file
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);
Below shows my different screen files.
HomeScreen.js
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
View,
TouchableOpacity,
Alert,
Keyboard,
TextInput,
} from 'react-native';
//HomeScreen
export default class HomeScreen extends React.Component {
constructor(props) {
super(props);
this.state = {username: null, password: null, isPasswordHidden: true, toggleText: 'Show'};
}
handleToggle = () => {
const { isPasswordHidden } = this.state;
if (isPasswordHidden) {
this.setState({isPasswordHidden: false});
this.setState({toggleText: 'Hide'});
} else {
this.setState({isPasswordHidden: true});
this.setState({toggleText: 'Show'});
}
}
//Validate() to check whether the input username is in Mail format
validate = (inputValue) => {
let reg = /^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/ ; // Regex for Emails
// let reg = /^(\+\d{1,3}[- ]?)?\d{10}$/; // Regex for phone numbers
return reg.test(inputValue);
}
clearText(fieldName) {
this.refs[fieldName].clear(0);
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}></Text>
<TextInput
ref={'input1'}
style={styles.input}
placeholder="Username"
onChangeText={value => this.setState({username: value})}
// placeholderTextColor="Grey"
// maxLength={13} // For Indian phone numbers
// onChangeText={(text) => this.validate(text)}
// value={this.state.username}
/>
<TextInput
ref={'input2'}
style={styles.input}
placeholder="Password"
maxLength={10}
secureTextEntry={this.state.isPasswordHidden}
onChangeText={value => this.setState({password: value})}
// placeholderTextColor="rgb(225,225,225)"
/>
<TouchableOpacity
onPress={this.handleToggle}
>
<Text>{this.state.toggleText}</Text>
</TouchableOpacity>
<View style={{padding: 20}}>
<TouchableOpacity onPress={() => {
if (!this.validate(this.state.username)) {
Alert.alert("Invalid");
Keyboard.dismiss();
} else if (this.state.username === 'vinay#gmail.com' && this.state.password === 'password') {
//Alert.alert("Login Successful");
if(this.state.username && this.state.password){
this.props.navigation.navigate('Welcome', {
username: this.state.username,
password: this.state.password,
});
this.setState({username: ""});
this.setState({password: ""});
}else{
alert("Invalid");
}
Keyboard.dismiss();
this.clearText('input1');
this.clearText('input2');
} else if (this.state.username === null && this.state.password === null) {
Alert.alert("Invalid");
} else {
Alert.alert("Login Failed");
this.clearText('input1');
this.clearText('input2');
Keyboard.dismiss();
}
}}>
<View style={styles.button}>
<Text style={styles.buttonText}>LOGIN</Text>
</View>
</TouchableOpacity>
</View>
</View>
);
}
}
/** Stylesheets Defined **/
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
// backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 40,
margin: 10,
padding: 20
// textAlign: 'center',
},
input:{
// height: 40,
// margin: 10,
width: 260,
backgroundColor: 'lightgrey',
marginBottom: 10,
padding: 10,
color: 'black'
},
button: {
marginBottom: 30,
width: 260,
alignItems: 'center',
backgroundColor: '#2196F3',
fontWeight: 'bold'
},
buttonText: {
padding: 20,
color: 'white'
}
});
This is the screen ProfileScreen.js
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
Image,
View,
} from 'react-native';
export default class Profile extends Component {
render() {
return(
<View>
<Image
style={styles.image}
source={{uri: 'https://facebook.github.io/react/logo-og.png'}}
/>
</View>
);
}
}
/** Stylesheets Defined **/
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
// backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 40,
margin: 10,
padding: 20
// textAlign: 'center',
},
input:{
// height: 40,
// margin: 10,
width: 260,
backgroundColor: 'lightgrey',
marginBottom: 10,
padding: 10,
color: 'black'
},
button: {
marginBottom: 30,
width: 260,
alignItems: 'center',
backgroundColor: '#2196F3',
fontWeight: 'bold'
},
buttonText: {
padding: 20,
color: 'white'
},
image: {
width: 200,
height: 200,
margin: 10
}
});
This is the screen SettingsScreen.js
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
View,
} from 'react-native';
export default class Settings extends Component {
render() {
return(
<View style={styles.container}>
<Text>Settings</Text>
</View>
);
}
}
/** Stylesheets Defined **/
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
// backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 40,
margin: 10,
padding: 20
// textAlign: 'center',
},
input:{
// height: 40,
// margin: 10,
width: 260,
backgroundColor: 'lightgrey',
marginBottom: 10,
padding: 10,
color: 'black'
},
button: {
marginBottom: 30,
width: 260,
alignItems: 'center',
backgroundColor: '#2196F3',
fontWeight: 'bold'
},
buttonText: {
padding: 20,
color: 'white'
}
});
This is the screen TabA.js
import React, { Component } from 'react'
import {
View,
Text,
StyleSheet,
} from 'react-native'
export default class TabA extends React.Component {
// static navigationOptions = ({ navigation }) => ({
// title: 'Tab A',
// })
render () {
return (
<View style={styles.container}>
<Text style={styles.text}>I'm Tab A</Text>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#c0392b',
padding: 20,
},
text: {
color: 'white',
fontSize: 40,
fontWeight: 'bold',
}
})
This is the screen TabB.js
import React, { Component } from 'react'
import {
View,
Text,
StyleSheet,
} from 'react-native'
export default class TabB extends React.Component {
// static navigationOptions = ({ navigation }) => ({
// title: 'Tab B',
// })
render () {
return (
<View style={styles.container}>
<Text style={styles.text}>I'm Tab B</Text>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#8e44ad',
padding: 20,
},
text: {
color: 'white',
fontSize: 40,
fontWeight: 'bold',
}
})
This is the screen WelcomeScreen.js
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
View,
} from 'react-native';
export default class WelcomeScreen extends Component {
render() {
const { navigation } = this.props;
const u_name = navigation.getParam('username', 'name');
const p_word = navigation.getParam('password', 'word');
return (
<View style={styles.container}>
<Text style={styles.welcome}>WELCOME</Text>
<Text>USERNAME: {JSON.stringify(u_name)}</Text>
<Text>PASSWORD: {JSON.stringify(p_word)}</Text>
{/* <View style={{padding: 20}}>
<Button style={{margin: 20}}
title="LOGOUT"
onPress={() => this.props.navigation.navigate('Home')}
/>
</View> */}
</View>
);
}
}
/** Stylesheets Defined **/
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
// backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 40,
margin: 10,
padding: 20
// textAlign: 'center',
},
input:{
// height: 40,
// margin: 10,
width: 260,
backgroundColor: 'lightgrey',
marginBottom: 10,
padding: 10,
color: 'black'
},
button: {
marginBottom: 30,
width: 260,
alignItems: 'center',
backgroundColor: '#2196F3',
fontWeight: 'bold'
},
buttonText: {
padding: 20,
color: 'white'
}
});
Below shows my different navigator files
This is the drawer navigator file drawerNavigator.js
import React, {Component} from 'react';
import {
View,
Button,
SafeAreaView,
} from 'react-native';
import {
createDrawerNavigator,
DrawerItems,
} from 'react-navigation';
import TabA from './screens/TabA.js';
import TabB from './screens/TabB.js';
import WelcomeStackNavigator from './stackNavigator';
class Hidden extends React.Component {
render() {
return null;
}
}
const AppDrawerNavigator = createDrawerNavigator({
Welcome: {
screen: WelcomeStackNavigator,
navigationOptions: {
drawerLabel: <Hidden />
}
},
TabA: { screen: TabA },
TabB: { screen: TabB },
// TabC: { screen: TabC },
},{
contentComponent:(props) => (
<View style={{flex:1}}>
<SafeAreaView forceInset={{ top: 'always', horizontal: 'never' }}>
<DrawerItems {...props} />
<Button
title="Logout"
onPress={() => {
props.navigation.navigate('Home')
}}
/>
</SafeAreaView>
</View>
),
drawerOpenRoute: 'DrawerOpen',
drawerCloseRoute: 'DrawerClose',
drawerToggleRoute: 'DrawerToggle'
})
export default AppDrawerNavigator;
This is the stack navigator file stackNavigator.js
import React, {Component} from 'react';
import Icon from 'react-native-vector-icons/Ionicons';
import {
createStackNavigator,
} from 'react-navigation';
import WelcomeTabNavigator from './tabNavigator';
const WelcomeStackNavigator = createStackNavigator({
WelcomeTabNavigator: WelcomeTabNavigator
},
{
defaultNavigationOptions:({navigation}) => {
return {
headerLeft: (
<Icon
style={{paddingLeft: 20}}
onPress={() => navigation.openDrawer()}
name="md-menu"
size={30}
/>
)
};
}
}
);
export default WelcomeStackNavigator;
This is the tab navigator file tabNavigator.js
import React, {Component} from 'react';
import WelcomeScreen from './screens/WelcomeScreen.js';
import Profile from './screens/ProfileScreen.js';
import Settings from './screens/SettingsScreen.js';
import Icon from 'react-native-vector-icons/Ionicons';
import {
createBottomTabNavigator,
} from 'react-navigation';
const WelcomeTabNavigator = createBottomTabNavigator(
{
Welcome: {
screen: WelcomeScreen,
navigationOptions: {
tabBarIcon: ({tintColor}) => (
<Icon
name="md-home"
size={20}
/>
)
}
},
Profile: {
screen: Profile,
navigationOptions: {
tabBarIcon: ({tintColor}) => (
<Icon
name="md-book"
size={20}
/>
)
}
},
Settings: {
screen: Settings,
navigationOptions: {
tabBarIcon: ({tintColor}) => (
<Icon
name="md-settings"
size={20}
/>
)
}
},
},
{
tabBarOptions: {
activeTintColor: '#fb9800',
inactiveTintColor: '#7e7b7b',
style: { height: 40,backgroundColor: '#fff',borderTopWidth:0.5,borderTopColor: '#7e7b7b' },
labelStyle: {fontSize: 15}
},
// navigationOptions:({navigation}) => {
// const {routeName} = navigation.state.routes[navigation.state.index];
// return {
// headerTitle: routeName
// };
// },
navigationOptions:({navigation}) => {
const {routeName} = navigation.state.routes[navigation.state.index];
return {
headerTitle: routeName,
// headerLeft: (
// <Icon
// style={{paddingLeft: 20}}
// onPress={() => navigation.openDrawer()}
// name="md-menu"
// size={30}
// />
// )
};
}
}
)
export default WelcomeTabNavigator;
How do I structure my project and add redux implementation on this login application?
Have you checked their doc? https://redux.js.org/
also, you can see ignite boilerplate implementation for redux in react native apps https://github.com/infinitered/ignite
It seems like a good practice to have your screen code(jsx) separated from your container code (where you'll have your mapDispatchToProps and your mapStateToProps).
So a good 'flow of information' using redux would look something like this:
Screen + container -> (dispatch) -> actions -> (dispatch) -> reducers -> and then stored in the store.
So to give you an idea of how to implement this, I'll show an example on how to use it for your purpose.
LoginScreen.js
export default class LoginScreen extends Component {
constructor(props) {
super(props);
}
login() {
//Here i'm assuming that you have clicked some kind of button
//to submit the login information that you filled in your text input (username and password)
const { username, password } = this.props;
const loginData = {
username,
password
}
//here you'll be passing it to the dispatchToProps in your container
this.props.login(loginData)
}
}
LoginScreenContainer.js
const mapStateToProps = state => {
...state.userReducer,
}
const mapDispatchToProps = (dispatch) => {
//The function that you called on your screen,
//and then we'll be dispatching the loginData to the user_actions
login: (loginData) => {
dispatch(loginUser(loginData))
},
}
//Dont forget to connect both mapStateToProps and mapDispatchToProps to your screen
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
User_Actions.js
export function loginUser(loginData) {
//The code to login would be here, if you are using firebase,
//back4app or any other provider, you would implement the login required here
//Now assuming that the user successfully logged on you would dispatch a success and then store that user in the store
//(just as an example, you can store the username and any other information that you'd like):
if (user) {
dispatch({ type: 'USER_LOGIN', payload: { user } })
} else {
dispatch({ type: 'USER_LOGIN_REJECTED' });
}
}
User_Reducer.js this is your reducer, you can have reducers also to handle the navigation in your app (not recommended though). It's basically a giant switch case that you'll get the dispatch actions.
export default function reducer(state = {
user: null,
}, action) {
const { type, payload } = action
switch (type) {
case 'USER_LOGIN': {
return { ...state, user: payload.user }
}
case 'USER_LOGIN_REJECTED': {
return { ...state, user: null }
}
default: {
return state
}
}
}
Store.js
const rootReducer = combineReducers({
user_reducer,
})
let middleware = [thunk]
if (process.env.NODE_ENV !== 'production') {
middleware = [...middleware, logger] //Logger is an useful library to log your state changes
}
export default createStore(
rootReducer,
undefined,
applyMiddleware(...middleware)
)
App.js and then here you'll wrap your main stackNavigator in the provider tag
import { Provider } from 'react-redux';
import store from './src/redux/store';
render() {
return (
<Provider store={store}>
<RootNavigator />
</Provider>
)
}
Hopefully this helps you understand a basic flow (I'm sure there are other ways to do it) and how to implement redux for your needs.
I have a little problem with an app that I am working on. I have to make something like a note app. There for I have a button that navigates me to another screen where I can write the note and I have a save button that should send me back to the previews screen and the scrollview shold be updated
Here is what I've tried so far:
App.js:
import React, { Component } from 'react';
import Main from './app/components/Main.js';
import NoteBody from './app/components/NoteBody.js';
import {StackNavigator} from 'react-navigation'
const App = StackNavigator({Home: {screen: Main}, WriteNote: {screen: NoteBody}});
export default App;
Main.js:
import React, { Component } from 'react';
import {
View,
Text,
StyleSheet,
TextInput,
ScrollView,
TouchableOpacity,
AsyncStorage,
} from 'react-native';
import Note from './Note';
import NoteBody from './NoteBody.js';
export default class Main extends Component {
static navigationOptions = {
title: 'Notes',
};
constructor(props){
super(props);
this.state = {
noteArray: [],
noteText: '',
};
}
componentDidMount(){
this.getSavedNotes(this.state.noteArray);
}
render() {
let notes = this.state.noteArray.map((val, key)=>{
return <Note key={key} keyval={key} val={val}
deleteMethod={()=>this.deleteNote(key)}/>
});
const { navigate } = this.props.navigation;
return (
<View style={styles.container}>
<ScrollView style={styles.scrollViewContainer}>
<ScrollView style={styles.scrollContainer}>
{notes}
</ScrollView>
<TouchableOpacity onPress={() =>
navigate('WriteNote' ,{onNavigateBack:
this.handleOnNavigateBack.bind(this)})}
style={styles.addButton}>
<Text style={styles.addButtonText}>+</Text>
<Text style={styles.addButtonAditionalText}>Add note</Text>
</TouchableOpacity>
</ScrollView>
</View>
);
}
deleteNote(key){
this.state.noteArray.splice(key, 1);
this.setState({noteArray: this.state.noteArray});
AsyncStorage.setItem('arr', JSON.stringify(this.state.noteArray));
// alert(this.state.noteArray);
}
getSavedNotes = async (noteArray) =>{
try{
let data = await AsyncStorage.getItem('arr');
if(JSON.parse(data))
{
this.setState({noteArray: JSON.parse(data)});
}
}catch(error){
alert(error);
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
scrollContainer: {
flex: 1,
},
addButton: {
position: 'relative',
zIndex: 11,
left: 0,
top: 0,
alignItems: 'flex-start',
justifyContent: 'flex-end',
width: 100,
height: 60,
elevation: 8
},
addButtonText: {
color: '#000',
fontSize: 60,
},
addButtonAditionalText: {
color: '#000',
fontSize: 12,
marginLeft: 40,
position: 'absolute',
bottom: 20,
},
scrollViewContainer: {
flex: 1,
marginBottom: 70,
}
});
Note.js: Here we have the scrollview and the button that navigates you to the NoteBody screen.
import React, { Component } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
} from 'react-native';
export default class Note extends Component {
render() {
return (
<View key={this.props.keyval} style={styles.note}>
<Text style={styles.noteText}>{this.props.val.date}</Text>
<Text style={styles.noteText}>{this.props.val.note}</Text>
<TouchableOpacity onPress={this.props.deleteMethod} style={styles.noteDelete}>
<Text style={styles.noteDeleteText}>Del</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
note: {
position: 'relative',
padding: 20,
paddingRight: 100,
borderBottomWidth:2,
borderBottomColor: '#ededed'
},
noteText: {
paddingLeft: 20,
borderLeftWidth: 10,
borderLeftColor: '#0000FF'
},
noteDelete: {
position: 'absolute',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#2980b9',
padding: 10,
top: 10,
bottom: 10,
right: 10
},
noteDeleteText: {
color: 'white'
}
});
and finally NoteBody: Here is where you can write the body of the note and you have that save button that should also save the data in an AsyncStorage so I can display it even after the app is closed.
import React, { Component } from 'react';
import {
View,
Text,
StyleSheet,
TextInput,
TouchableOpacity,
AsyncStorage,
} from 'react-native';
import Note from './Note.js';
export default class NoteBody extends Component {
static navigationOptions = {
title: 'Note',
};
constructor(props){
super(props);
this.state = {
noteArray: [],
noteText: '',
};
}
componentDidMount(){
this.getSavedNotes(this.state.noteArray);
}
render() {
let notes = this.state.noteArray.map((val, key)=>{
return <Note key={key} keyval={key} val={val}
deleteMethod={()=>this.deleteNote(key)}/>
});
return (
<View style={styles.container}>
<View style={styles.noteBody}>
<TextInput
multiline = {true}
numberOfLines = {1000000}
style={styles.textInput}
placeholder='Write your note here'
onChangeText={(noteText)=> this.setState({noteText})}
value={this.state.noteText}
placeholderTextColor='grey'
underlineColorAndroid='transparent'>
</TextInput>
</View>
<TouchableOpacity onPress={ this.addNote.bind(this) } style={styles.addButton}>
<Text style={styles.addButtonText}>SAVE</Text>
</TouchableOpacity>
</View>
);
}
addNote(){
const { navigate } = this.props.navigation;
if(this.state.noteText){
var d = new Date();
this.state.noteArray.push({
'date':d.getFullYear()+
"/"+(d.getMonth()+1) +
"/"+ d.getDate(),
'note': this.state.noteText
});
this.setState({ noteArray: this.state.noteArray });
this.setState({noteText:''});
AsyncStorage.setItem('arr', JSON.stringify(this.state.noteArray));
this.props.navigation.state.params.onNavigateBack();
navigate('Home');
// alert(this.state.noteArray);
}
}
getSavedNotes = async (noteArray) =>{
try{
let data = await AsyncStorage.getItem('arr');
if(JSON.parse(data))
{
this.setState({noteArray: JSON.parse(data)});
}
}catch(error){
alert(error);
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
noteBody:{
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
zIndex: 10,
alignItems: 'center',
borderBottomWidth:1,
borderTopColor: '#000',
marginBottom: 100,
},
textInput: {
alignSelf: 'stretch',
textAlignVertical: 'top',
backgroundColor: '#fff',
color: '#000',
padding: 20,
borderTopWidth:2,
borderTopColor: '#ededed',
},
addButton: {
position: 'absolute',
zIndex: 11,
left: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center',
width: 300,
backgroundColor: '#00FF00',
height: 60,
elevation: 8
},
addButtonText: {
color: '#fff',
fontSize: 24,
},
});
The save button only saves the last note I wrote and it doesn't even show it on the scrollview immediately. I have to reopen the app to display it.
There are a couple of things which are wrong here:
First you only fetch the saved notes in the constructor of your Main component, which only gets called on instantiation. The recommended place to put this code is in componentDidMount(). Also I don't understand why you are are passing the noteArray state to your getSavedNotes() method.
Next, and this is an important one: your methods to add and delete notes from the array are mutating (splice and push). Since React uses shallow comparison to determine when to re-render, you need to create a new object when using setState(). So for additions you could use concat() and for deletions the omit() function of Lodash is very useful. Alternatively you could use the spread operator.
Disclaimer: I haven't yet read all of your code in detail, so there might still be some additional problems.
Edit: with FlatList
// render() method of Main.js
render() {
const { navigate } = this.props.navigation;
return (
<View style={styles.container}>
<ScrollView style={styles.scrollViewContainer}>
<ScrollView style={styles.scrollContainer}>
<FlatList
data={this.state.noteArray}
renderItem={({item, index}) => this.renderItem(item, index)}
keyExtractor={(item, index) => `${index}`}
/>
</ScrollView>
<TouchableOpacity onPress={() =>
navigate('WriteNote')}
style={styles.addButton}>
<Text style={styles.addButtonText}>+</Text>
<Text style={styles.addButtonAditionalText}>Add note</Text>
</TouchableOpacity>
</ScrollView>
</View>
);
}
renderItem = (item, index) => {
return (
<Note
keyval={index}
val={item}
deleteMethod={()=>this.deleteNote(index)}
/>
);
}
It looks like you could add a listener in Main.js for 'onFocus' events. I would use the callback to query AsyncStorage and set state with the result.
Screen A
Method to do the thing in Screen A that you want to happen when navigating back from Screen B:
handleOnNavigateBack = (foo) => {
this.setState({
foo
})
}
This is the React Navigation method in Screen A to navigate to Screen B:
this.props.navigation.navigate('ScreenB', {
onNavigateBack: this.handleOnNavigateBack})
…or if your handleOnNavigateBack function is not a arrow function (which binds this) you'll need to bind this: (ht #stanica)
this.props.navigation.navigate('ScreenB', {
onNavigateBack: this.handleOnNavigateBack.bind(this)
})
Screen B
After navigating to Screen B and ready to Navigate back to Screen A…
Call the function (passed to Screen B from Screen A) to do the thing in Screen A:
this.props.navigation.state.params.onNavigateBack(this.state.foo)
Then call the React Navigation method to go Back (to Screen A):
this.props.navigation.goBack()
I also updated my question's code so it is the version that dose what is suppose to do.
I want to display image in my react native program which is taken from nasa api. I created a react native project:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
StatusBar,
Image
} from 'react-native';
import api from './utilities/api';
export default class test extends Component {
constructor(props){
super(props);
this.state ={
apod: [],
apodName: ''
}
}
componentWillMount(){
api.getApod().then((res)=> {
this.setState({
apod: res.apod,
apodName: res.url
})
});
}
render() {
console.log("Apod:", this.state.hdurl);
return (
<View>
<StatusBar
backgroundColor="#00FA9A"
barStyle="dark-content"
/>
<Text style={styles.welcome}>
Apod: {this.state.apodName}
</Text>
<Image source={{uri: api.hdurl}}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
thumbnail: {
width: 53,
height: 81,
}
});
I also have api.js file :
var api ={
getApod(){
var url = 'https://api.nasa.gov/planetary/apod?api_key=MY KEY';
return fetch(url).then((res) => res.json());
}
};
module.exports = api;
Result I have on screen, its just writes an image link. But how to display actual image