Build a custom seconds and minutes input in React Native? - 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):

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!

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

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.

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

need clarification about LayoutAnimation

I've been trying to understand how to use LayoutAnimation and the docs haven't been very helpful.
is there a better source? anyways here is this code which demonstrates the 3 different types of animations with layoutAnimation. It is an app with 3 buttons and 3 boxes which move across the screen differently. I am failing to understand what causes the boxes to animate. I don't see a function call making it animate. I only see conditional statements in the style attribute. the attribute seems to know nothing about the layoutAnimation. Yet it does animate.
here is the code
import React, { useState } from "react";
import {
View,
Platform,
UIManager,
LayoutAnimation,
StyleSheet,
Button
} from "react-native";
if (
Platform.OS === "android" &&
UIManager.setLayoutAnimationEnabledExperimental
) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
export default function App() {
const [firstBoxPosition, setFirstBoxPosition] = useState("right");
const [secondBoxPosition, setSecondBoxPosition] = useState("left");
const [thirdBoxPosition, setThirdBoxPosition] = useState("left");
const toggleFirstBox = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setFirstBoxPosition(firstBoxPosition === "left" ? "right" : "left");
};
const toggleSecondBox = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.linear);
setSecondBoxPosition(secondBoxPosition === "left" ? "right" : "left");
};
const toggleThirdBox = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
setThirdBoxPosition(thirdBoxPosition === "left" ? "right" : "left");
};
return (
<View style={styles.container}>
{/* button demonstrating easing animation*/}
<View style={styles.buttonContainer}>
<Button title="EaseInEaseOut" onPress={toggleFirstBox} />
</View>
{/* button demonstrating linear animation*/}
<View style={styles.buttonContainer}>
<Button title="Linear" onPress={toggleSecondBox} />
</View>
{/* button demonstrating spring animation*/}
<View style={styles.buttonContainer}>
<Button title="Spring" onPress={toggleThirdBox} />
</View>
{/*The three boxes demonstrating animation types*/}
<View
style={[
styles.box,
firstBoxPosition === "left" ? null : styles.moveRight
]}
/>
<View
style={[
styles.box,
secondBoxPosition === "left" ? null : styles.moveRight
]}
/>
<View
style={[
styles.box,
thirdBoxPosition === "left" ? null : styles.moveRight
]}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "flex-start",
justifyContent: "center"
},
box: {
height: 100,
width: 100,
borderRadius: 5,
margin: 8,
backgroundColor: "blue"
},
moveRight: {
alignSelf: "flex-end"
},
buttonContainer: {
alignSelf: "center"
}
});
The idea (and the major gotcha) with LayoutAnimation is that it sets an animation for ALL subsequent layout changes until it's removed. It just works automatically with no additional setup.

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