onPress Handler of a button updates the state but screen goes blank - react-native

I am learning react native and trying to build a todo app. In the code below, I have two useState hooks one for keeping track of what user enters and allTodo one for keeping track of all the todos to be rendered, also I'm using FlatList for rendering todos but when I submit the todo, the screen just goes blank and nothing appears again until I refresh it again. Also I'm testing the app on chrome by selecting a device in it. could that be a problem? Please let me know what the problem is. Code is below:
import React, { useState } from "react";
import {
StyleSheet,
Text,
View,
TextInput,
FlatList,
TouchableOpacity,
Button,
} from "react-native";
export default function App() {
const [allTodos, setAllTodos] = useState(["a", "b"]);
const [todo, setTodo] = useState("");
const handleSubmit = () => {
setAllTodos(allTodos.push(todo));
setTodo("");
};
return (
<View style={styles.container}>
<Text>{todo}</Text>
<Text>Todo: </Text>
<TextInput
style={styles.input}
placeholder="E.g. Buy eggs"
onChangeText={(val) => setTodo(val)}
/>
<Button title="Add Todo" onPress={handleSubmit} />
<FlatList
data={allTodos}
// keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<TouchableOpacity>
<Text style={styles.item}>{item}</Text>
</TouchableOpacity>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#FBF4E9",
alignItems: "center",
// justifyContent: "center",
paddingTop: 20,
},
item: {
marginTop: 24,
padding: 30,
backgroundColor: "pink",
fontSize: 24,
marginHorizontal: 10,
marginTop: 24,
},
input: {
borderWidth: 1,
borderColor: "#777",
padding: 8,
margin: 10,
width: 200,
},
});
This is what it looks before submitting the todo:
Image after I submit it:
Thank you

Don't mutate state. In your handleSubmit function, you are mutating the state allTodos.
simply change your handleSubmit function to,
const handleSubmit = () => {
const currentTodos = [...allTodos];
currentTodos.push(todo);
setAllTodos(currentTodos);
setTodo('');
};
Also you might want to pass the value prop of TextInput as todo.
Check this Live Snack to see it in action.

Related

How can I send different functions from index in order to re use the footer code?

Im new in React, and Im starting with React native.
I'm working in my project, and in order to re-use code, I'm reading about HOC.
My use case is: I have a lot of views with a footer that have some buttons (one or two, it depends. They might have different actions, some of them navigates to another activity, other execute functions or state updates).
Im trying to execute a navigation.navigate from the "main" view, but I got an error: "Cant find variable: navigation".
This is my code:
index.js
import {
Text,
StyleSheet,
View,
TouchableOpacity,
ScrollView
} from 'react-native';
import withFooter from '../../components/withFooter';
const SignUp = ({ navigation }) => {
return (
<View style={styles.container}>
<View style={{ flex: 3 }}>
<Text>Test</Text>
</View>
</View>
)
};
export default withFooter(SignUp, {
buttons: [
{
text: 'Exit',
action: () => console.log('Exit'),
},
{
text: 'Accept',
action: () => navigation.navigate('PersonalDataSignUp'),
}
]
});
withFooter.js
import { Text, View, TouchableOpacity, StyleSheet } from 'react-native';
const withFooter = (WrappedComponent, { buttons }) => {
const WithFooter = props => {
return (
<>
<WrappedComponent {...props} />
<View style={{
flexDirection: 'row',
padding: 20
}}>
{
buttons.map(button => (
<TouchableOpacity style={[styles.button]} onPress={button.action}>
<Text style={{ fontWeight: '900' }}>{button.text}</Text>
</TouchableOpacity>
))
}
</View>
</>
)
};
return WithFooter;
};
const styles = StyleSheet.create({
button: {
flex: 1,
height: 50,
borderRadius: 5,
backgroundColor: '#FCCC00',
justifyContent: 'center',
alignItems: 'center',
borderColor: 'black',
borderWidth: 1
},
})
export default withFooter;
How can I send different functions from index in order to re use the footer code? Is there any other way to do it?. Thanks in advance!

Flatlist rendering empty space

i'm new to react native and trying to use flatlist inside another component that's been rendered by another flatlist.
My concern is in the nested flatlist used like this:
Card component
import React, { useContext } from "react"
import { View, Image, Text, StyleSheet, FlatList } from "react-native";
import { QueryContext } from "../context/QueryContext"
function Card(props) {
const [query, setQuery] = useContext(QueryContext)
const { genres_list } = query
const { posterURLs, originalTitle, genres } = props
const listData = []
genres.forEach(genre => {
listData.push({ key: genres_list[genre] })
})
const renderItem = (item) => {
return (
<View>
<Text style={styles.item}>
{item.index}
{item.key}
</Text>
</View>
)
}
return (
<View style={styles.container}>
<Image
style={styles.images}
source={{ uri: posterURLs["original"] }} />
<Text style={styles.title}>{originalTitle}</Text>
<FlatList
data={listData}
renderItem={renderItem}
keyExtractor={(item)=> item.key}
/>
</View>
);
}
const styles = StyleSheet.create({
images: {
width: 400,
height: 500
},
container: {
flex: 1,
backgroundColor: 'lightgrey',
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 30
},
item: {
padding: 10,
fontSize: 15,
color: "red",
},
})
I have to prepare the data for the flatlist since is inside an object that comes from the context. Weirdly enough i can see the index from item.index but not the item.key which makes me think is some styling issue but as much change i make i'm unable to see it
The card component is rendered by another flatlist and i don't know if that matters or not.
Thanks in advance
Nothing was showing because the data item.key values were actually stored in item.item.key

react-native-multiselect-view - render containers as "pressed"

react-native-multiselect-view is a seemingly simple library.....however I cant find a way to render a container as "pressed" (i.e. active) without physically pressing it. I want user to make choices and store them in Firebase.....this way when they re-open the app I will fetch choices from Firebase and update UI for their previous selections. Im guessing I need to change the data array data={['item1', 'item2']} to reflect whether item is active or not.....but cant figure out exactly how
import MultiSelectView from 'react-native-multiselect-view';
<MultiSelectView
data={['item1', 'item2']}
inactiveContainerStyle={{padding:2, borderColor: colorConsts.c3, opacity:0.25, borderWidth: 2, borderRadius:sizeConsts.widgetBorderRadius, backgroundColor:'white'}}
activeContainerStyle={{padding:2, borderColor: colorConsts.c2, borderWidth: 2, borderRadius:sizeConsts.widgetBorderRadius, backgroundColor:'white'}}
activeTextStyle={{color:colorConsts.c2}}
inactiveTextStyle={{color:colorConsts.c3}}
inactiveIcon={<IconFA name="plus-circle" size={20} style={{color:colorConsts.c3}}/>}
activeIcon={<IconFA name="minus-circle" size={20} style={{color:colorConsts.c2}}/>}
onSelectionStatusChange={this._onSelectionStatusChange}
/>
I emailed author who kindly came right back to me with: "It's not implemented at the moment. But here the code screenshot where it can be handled. U can raise a PR or fork repo and do it. If u need me to do it for u let me know.."
I decided instead to write my own and use much of the authors code (below)....I pull user preferences from Firebase....into Redux....into local state
/* eslint-disable react-native/no-inline-styles */
import React, {Component} from 'react';
import {View, TouchableOpacity, Text, StyleSheet} from 'react-native';
import {sizeConsts, colorConsts} from '../globalConst';
import IconFA from 'react-native-vector-icons/FontAwesome';
import database from '#react-native-firebase/database';
import {connect} from 'react-redux';
//*********************************************************
//REDUX
//*********************************************************
const passStateToProps = state => ({
arrObjInterests: state.profile.mySelf.arrInterests,
userId: state.auth.userId,
});
class MyMultiSelectView extends Component {
//To update the status of any arrInterests object, run a map on the array and check for some unique value of each object,
//in case of condition=true, return the new object with updated value, else same object.
_onPress = (label, oldValue) => {
//FIRST UPDATE STATE FOR QUICK UI RESPONSE
this.setState(
prevState => ({
//map through prevState interests array and return each object same as you saw it
//.....unless its a match in which case you update the value
arrInterests: prevState.arrInterests.map(obj =>
obj.label === label ? {...obj, value: !oldValue} : obj,
),
}),
() => {
//NEXT UPDATE FBS WITH UPDATED STATE
//prettier-ignore
database().ref('/profiles/users/' + this.props.userId + '/arrInterests').set(this.state.arrInterests);
},
);
};
constructor(props) {
super(props);
//populate state with interests array arrInterests from Redux arrObjInterests
this.state = {
arrInterests: this.props.arrObjInterests,
};
}
render() {
return (
//prettier-ignore
<View
style={{flexDirection: 'row', flexWrap: 'wrap', flex: 1, padding: 15}}>
{/* map through interests array and create TouchableOpacity for each */}
{this.state.arrInterests.map((item, index) => (
<TouchableOpacity key={index} onPress={() => {
this._onPress(item.label, item.value);
}}>
<View style={!item.value ? [styles.container, {borderColor: colorConsts.c3, opacity:0.25}] : [styles.container, {borderColor: colorConsts.c2}]}>
<Text style={!item.value ? [styles.text, {color:colorConsts.c3}] : [styles.text, {color:colorConsts.c2}]}>{item.label}</Text>
{!item.value ? <IconFA name="plus-circle" size={20} style={[styles.icon, {color:colorConsts.c3}]}/> : <IconFA name="minus-circle" size={20} style={[styles.icon, {color:colorConsts.c2}]}/>}
</View>
</TouchableOpacity>
))}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
paddingVertical: 5,
marginTop: 10,
marginRight: 10,
paddingLeft: 10,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
borderWidth: 2,
borderRadius: sizeConsts.widgetBorderRadius,
backgroundColor: '#f8f8f8',
},
text: {
fontSize: 16,
color: 'black',
marginRight: 6,
},
icon: {
width: 20,
marginRight: 2,
},
});
export default connect(
passStateToProps,
null,
)(MyMultiSelectView);

Build a custom seconds and minutes input in React Native?

Im making an exercise timer and I need an input for seconds and minutes.
The ideal UX would 2 select lists side by side. Tapping on the input would open both lists, so different from the web where you can only focus on one at a time.
Ive made a demo here, although it would need to be in a modal or similar:
https://snack.expo.io/#jamesweblondon/intelligent-apple
import * as React from 'react';
import { Text, View, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
const minutes = new Array(10).fill('').map((item, index)=>{
return index;
})
const seconds = new Array(60).fill('').map((item, index)=>{
return index;
})
const Item = ({text, isSelected, onPress}) => {
return(
<TouchableOpacity
onPress={onPress}
style={[styles.item, isSelected && styles.itemIsSelected]}>
{text}
</TouchableOpacity>
)
}
export default function App() {
const [selectedMinute, setSelectedMinute] = React.useState(1);
const [selectedSeconds, setSelectedSeconds] = React.useState(10);
return (
<View style={styles.container}>
<View style={styles.row}>
<Text style={styles.heading}>Minutes</Text>
<ScrollView style={styles.scroll}>
{
minutes.map((item, index)=>{
return <Item
onPress={()=>setSelectedMinute(item)}
text={item}
key={item}
isSelected={selectedMinute === index}
/>
})
}
</ScrollView>
</View>
<View style={styles.row}>
<Text style={styles.heading}>Seconds</Text>
<ScrollView style={styles.scroll}>
{
seconds.map((item, index)=>{
return <Item
onPress={()=>setSelectedSeconds(item)}
text={item}
key={item}
isSelected={selectedSeconds === index}
/>
})
}
</ScrollView>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
height: 300
},
row: {
flex: 1,
},
scroll: {
height: 300
},
heading: {
fontSize: 20,
fontWeight: 'bold',
textAlign: 'center'
},
item: {
padding: 30,
backgroundColor: 'grey',
borderColor: 'white',
borderWidth: 1,
justifyContent: 'center',
alignItems: 'center'
},
itemIsSelected: {
backgroundColor: 'gold'
}
});
Is it OK to build your own inputs in React Native? This would be a a bad idea on the web as it would probably result in worse UX (especially for keyboard navigation) and a component that screen readers wouldn't know how to use.
I couldn't find a library that does quite what I need. This is close but you can only have one list open at a time. It also had a major bug with Redux making it unusable for me:
https://github.com/lawnstarter/react-native-picker-select#readme
Ive been trying to use the Picker component as I imagine it's more semantic? It works how I need on iOS but not Android (see screenshots):

react native spinner doesn't display on bottom

So I got this piece of code, and I want to display the Spinner on the bottom of the screen, just right after the FlatList, but when the function displaySpinner is called nothing displays after the flatlist. I've tried many things like putting trying to display the Spinner on the top of the view and then give it a Top but it's not what I'm looking for.
By the way I'm new in the programming world and more on React Native so I hope everything makes sense to understand my problem
import React, { Component } from 'react';
import { FlatList, StyleSheet, View, Text, Image } from 'react-native';
import axios from 'axios';
import moment from 'moment';
import Card from './Card';
import CardSection from './CardSection';
import Spinner from './Spinner';
class ArticleList extends Component {
state = { articles: [],
refreshing: false,
isLoading: false,
};
componentWillMount() {
this.loadArticles();
}
currentOffset = 0;
reloadContent() {
this.setState({
isLoading: true
});
this.currentOffset += 20;
console.log(this.currentOffset);
this.loadArticles();
}
loadArticles = () => {
const { articles } = this.state;
console.log(this.currentOffset);
axios.get(`https://sportsoftheday.com/wp-json/wp/v2/posts?per_page=20&offset=${this.currentOffset}`)
.then(res =>
this.setState({
articles: this.currentOffset === 0 ? res.data : [...articles, ...res.data],
isLoading: false,
}))
.catch(err => {
console.error(err);
});
};
displaySpinner() {
if (this.state.isLoading === true) {
return <Spinner size='large' />;
}
}
//Apply removeClippedSubviews for eliminating useless data on the screen
render() {
const { articles } = this.state;
this.date = this.date;
this.fimg_url = this.fimg_url;
return (
<View>
<FlatList
data={articles}
renderItem={({ item }) => (
<Card>
<CardSection>
<View style={styles.thumbnailContainerStyle}>
<Image
style={styles.thumbnailStyle}
source={{
uri: item.fimg_url,
cache: 'only-if-cached'
}}
/>
</View>
<View style={styles.headerContentStyle}>
<Text style={{ color: '#B2B2B2' }}>
{moment(item.date).format('dddd, Do MMMM YYYY')}</Text>
<Text
numberOfLines={3}
style={styles.headerTextStyle}
>
{item.title.rendered}
</Text>
</View>
</CardSection>
</Card>
)}
keyExtractor={i => i.id}
onEndReached={this.reloadContent.bind(this)}
onEndReachedThreshold={0.1}
/>
{this.displaySpinner()}
</View>
);
}
}
const styles = StyleSheet.create({
headerContentStyle: {
flexDirection: 'column',
justifyContent: 'space-around',
flex: 1
},
headerTextStyle: {
textAlign: 'justify',
fontSize: 18,
color: 'black',
marginRight: 15
},
thumbnailStyle: {
height: 70,
width: 70
},
thumbnailContainerStyle: {
justifyContent: 'center',
alignItems: 'center',
marginLeft: 10,
marginRight: 10
},
imageStyle: {
height: 300,
flex: 1,
width: null
},
});
export default ArticleList;
First things first, you should always avoid rendering a view/component directly in the renderItem = { } prop of your FlatList. Always send a function that is bounded to your current context that returns a component renderItem = {this._renderItem.bind(this)} or renderItem = {() => renderItem()}. This is not an issue but a usual practice that keeps the code clean and professional. Just a suggestion since you mentioned you're new to RN.
Coming to your question, the spinner shall show up once you wrap your Spinner inside a View component. You can do this either by wrapping your function call <View> {this.displaySpinner()} </View> or return a component that is already wrapped in a View <View> <Spinner/> </View>.
To make this even more effective, wrap everything including your flatlist but excluding your Header if you have one (Obviously) inside a View and give it a style of flex flex: 1 with a direction of column 'flexDirection: 'column'. Now you can justify your content with justifyContent: 'space-around' or 'space-between' whichever works for you.
Final point I'd like to make is again a suggestion. I've been working on RN for a while now but I still find designing the UI one of the most tedious tasks. Hot Reloading helps but not much. To track your UI changes on the screen, you can give the style borderColor: 'red', borderWidth: 1 to your views. This will help you a lot. It sure helps me.
I hope this helps.
Best of luck
Wrap that spinner in a view like View style = {{ position: "absolute", bottom: 0, width: '100%'}}
{this.displaySpinner()}
Close View