I am new to React-native can anyone please tell me how I would create a header menu with a progress bar, something like the attached image. Any advice much appreciated...
I have made progress, how can I remove the space inbetween each step?
<View style={styles.stepIndicatorBox}>
{stepsIds.map((step, idx) => {
const words = steps[step].split(' ');
const activeStepStyle = step === activeStep && styles.activeStep;
return (
<View key={`${step}${idx}`}>
<Svg height="25" width="100">
<Line
x1="10"
y1="10"
x2="100"
y2="10"
stroke={theme.colors.blue}
strokeWidth="2"
/>
<Circle
cx="50"
cy="10"
r="3"
stroke={theme.colors.blue}
strokeWidth="2.5"
fill={theme.colors.lightBlue}
/>
</Svg>
{words.map(w =>
<Text style={[styles.stepName, activeStepStyle]}>
{w}
</Text>
)}
</View>
);
})}
</View>
My styles are:
const styles = StyleSheet.create({
sceneContainer: {
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
top: 0,
},
stepIndicatorBox: {
height: theme.utils.em(4),
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: theme.colors.lightBlue,
paddingHorizontal: theme.metrics.mainPadding,
},
activeStep: {
...theme.fontStyles.smallBold,
color: theme.colors.activeStepName,
fontWeight: 'bold',
},
stepName: {
...theme.fontStyles.small,
color: theme.colors.blue,
textAlign: 'center',
},
});
I used the following code and react-native-svg:
_renderStepIndicator = () => {
const { navigation } = this.props; // {dispatch}
const { steps, getStep } = stepsOptions;
const route = navigation.state.routes[navigation.state.routes.length - 1];
const activeStep = getStep(route.routeName);
const stepsIds = Object.keys(steps);
const { height, width } = Dimensions.get('window');
const stepWidth = width / 5;
const RouteComponent = StepIndicatorRouter.getComponentForRouteName(
route.routeName
);
if (RouteComponent.navigatorType === STEP_INDICATOR) {
return null;
}
return (
<View style={styles.stepIndicatorBox}>
{stepsIds.map((step, idx) => {
const words = steps[step].split(' ');
const activeStepStyle = step === activeStep && styles.activeStep;
return (
<View key={`${step}${idx}`} style={styles.stepIndicatorStep}>
<Svg height="25" width={stepWidth}>
<Line
x1="0"
y1="15"
x2={stepWidth}
y2="15"
stroke={theme.colors.blue}
strokeWidth="2"
/>
<Circle
cx="40"
cy="15"
r="3"
stroke={theme.colors.blue}
strokeWidth="2"
fill={
step === activeStep
? theme.colors.blue
: theme.colors.lightBlue
}
/>
</Svg>
{words.map(w =>
<Text style={[styles.stepName, activeStepStyle]}>
{w}
</Text>
)}
</View>
);
})}
</View>
);
};
Related
i got a problem with react native maps callout , each marker update its position every 10 seconds, callout starts hidden but, when you press the marker it open the callout and keep it open each new position update, but sometimes the callout content is places out of bounds of the callout container.
image:
callout content not fit in it container
code:
`
import * as React from "react"
import { Text, View,StyleSheet } from 'react-native';
import { Marker,Callout } from 'react-native-maps';
import Svg, { Path, Circle } from "react-native-svg";
import moment from "moment";
import "moment/min/locales.min.js";
import { FontAwesome, FontAwesome5 } from '#expo/vector-icons';
import useStatusImei from "../../hooks/useStatusImei";
import { fecha,timediff } from "../../utils/App";
import {MapaContext} from "../../context/MapaContextProvider";
moment.locale("es");
const Icon = ({ignicion,velocidad,vel_max,grados,color}) =>{
return ignicion == 1 && velocidad > 1 ?
(
<Svg width={60} height={60} xmlns="http://www.w3.org/2000/svg" style={{ transform: [{ rotate: `${grados}deg` }] }}>
<Path
style={[styles.ign,{ stroke: velocidad >= vel_max ? '#ed6b75' : "#fff", fill: color }]}
d="m38.828 45.578 14.599 7.815-7.085-16.295a17.412 17.412 0 0 0 1.338-6.704c0-7.495-4.729-13.869-11.36-16.348L30.213 0l-6.107 14.046c-6.63 2.479-11.36 8.853-11.36 16.348 0 2.376.479 4.639 1.338 6.704L7 53.393l14.599-7.815a17.37 17.37 0 0 0 8.614 2.283c3.136 0 6.071-.837 8.615-2.283z"
/>
</Svg>
)
:
(
<Svg width={40} height={40} xmlns="http://www.w3.org/2000/svg">
<Circle style={[styles.off,{fill: color}]} cx={20} cy={20} r={18} />
</Svg>
)
}
const InfoWindow = ({numero,marca,info,color,time_color,fecha_hora_gps, ignicion,velocidad,vel_max,fecha_hora_ultimo_encendido,odometro, odometro_ultimo_encendido, fecha_hora_inicio_relenti,fecha_hora_ultimo_apagado,fecha_hora_fin_ultimo_viaje,fecha_hora_inicio_ultimo_viaje,odometro_fin_ultimo_viaje,odometro_inicio_ultimo_viaje}) =>{
let informacion, detalle,color_velocidad;
if( ignicion == 1){
if(Math.floor(velocidad)>1){//en movimiento
if( Math.floor(velocidad) < vel_max && Math.floor(velocidad) >=( vel_max - 5 )){
color_velocidad= '#F1C40F';
}else if(Math.floor(velocidad)>=vel_max){
color_velocidad= '#ed6b75';
detalle = (<Text style={{color:color_velocidad,fontWeight:'500'}}>¡Exceso de Velocidad!</Text>);
}else {
color_velocidad = '#26C281';
detalle = (
<View style={styles.info}>
<FontAwesome5 name="code-branch" size={15} color="#5867dd" />
<Text style={styles.text}> {timediff((moment().unix(fecha_hora_gps) - moment(fecha_hora_ultimo_encendido).unix()),0)} </Text>
<Text style={styles.text}> {( ( odometro - odometro_ultimo_encendido) / 1000 ).toFixed(1)}kms</Text>
</View>
);
}
informacion = (
<View>
<View><Text style={[styles.vel,{color:color_velocidad}]}>{ Math.round(velocidad)} kms/h</Text></View>
<View>
{detalle}
</View>
</View>
);
}else{//en relenti
informacion = (<View><Text style={{color,fontWeight:'500'}}>En relentí {timediff((moment().unix() - moment(fecha_hora_inicio_relenti).unix()),0)}</Text></View>);
}
}else{
informacion = (
<View>
<View><Text style={styles.text}>Apagado desde {moment(fecha_hora_ultimo_apagado).calendar()}</Text></View>
<View style={styles.info}>
<FontAwesome5 name="code-branch" size={15} color="#5867dd" />
<Text style={styles.text}> {timediff((moment(fecha_hora_fin_ultimo_viaje).unix() - moment(fecha_hora_inicio_ultimo_viaje).unix()),0)} </Text>
<Text style={styles.text}> {((odometro_fin_ultimo_viaje - odometro_inicio_ultimo_viaje) / 1000 ).toFixed(1)} kms </Text>
</View>
</View>
);
}
//
return (
<Callout>
<View style={styles.calloutContainer} >
<Text><Text style={styles.bold}>{numero}</Text> <Text style={styles.text}>{marca}</Text></Text>
{informacion}
<Text style={[styles.text,{fontSize:12}]} ><FontAwesome name="cloud-upload" size={14} color={time_color} /> {fecha(fecha_hora_gps,5)}</Text>
</View>
</Callout>);
}
const Unidad = ({ unidad,aforos = false }) => {
const {setUnidadFocus,unidad_focus} = React.useContext(MapaContext);
const item = useStatusImei({...unidad},aforos);
const markerRef = React.useRef(undefined);
const handleCallout = ()=>{
markerRef.current.showCallout();
setUnidadFocus({imei:item.imei,fecha_hora_gps:item.fecha_hora_gps,marker:markerRef});
}
React.useEffect(()=>{
if(unidad_focus && unidad_focus.imei == item.imei && !unidad_focus.marker){
handleCallout()
}
},[markerRef.current]);
let fz =12;
if (item && item.latitud && item.longitud) {
if(item.numero.length > 6)
fz = 9
if(item.numero.length > 5)
fz = 10
if(item.numero.length > 4)
fz = 11
return (
<Marker
ref = {(ref)=>{markerRef.current = ref}}
key={item.imei}
coordinate={{ latitude: item.latitud, longitude: item.longitud }}
anchor={{ x: 0.5, y: 0.5 }}
style={styles.marker}
tracksViewChanges={false}
flat={true}
onPress={()=>{handleCallout()}}
>
<Icon {...item} />
<Text style={[styles.markerLabel,{fontSize:fz}]}>{item.numero}</Text>
<InfoWindow {...item} />
</Marker>
);
}
return null;
};
const styles = StyleSheet.create({
marker:{ justifyContent: "center", alignItems: "center" },
calloutContainer: {
width:250,
height:100,
justifyContent:'center',
alignItems: 'center',
padding:5
},
bold:{
fontWeight:'600',
color:"#5867dd",
fontSize:16
},
text:{
color:'#57585a'
},
markerLabel: { color: '#FFFFFF', position: 'absolute', fontSize: 13, fontWeight: '400' },
info:{flexDirection:"row",alignItems:'center',justifyContent:'space-around'},
off:{ strokeWidth: 4, strokeOpacity: 0.5, stroke: "#fff", fillRule: "nonzero" },
ign:{strokeWidth: 6, strokeOpacity: 0.5, fillRule: "nonzero"},
vel: {fontWeight:'500',textAlign:'center'}
})
export default Unidad
`
I read along this forum, changed the components structure, set fixed width and height callout container and keep it with basic text but in each change the problem keep apearing only sometimes, in expo app and in production app.
Screens and navigating is fine, but getting data from users has been a struggle. Not only are the UI elements different, the means to capture input and store in state effectively across various input types is troubling me. Here is an example of what I think is a simple UX yet I cannot get the text inputs to focus correctly.
In the below example, the desire is to have a list of items within a horizontal scroll view and when i click on the arrow of an item, the screen scrolls and a form to edit the item appears with one or more text boxes. Future enhancements were to have this second panel have more fields based on the type of field from the list, but i cant even get a simple text box to work properly
I've got some code to boot, copy and paste as app.js in an expo init project and it should run
main question: how to retain focus on inputs on the detail panel
import React from "react";
import {
Dimensions,
FlatList,
SafeAreaView,
Text,
TextInput,
TouchableOpacity,
View,
} from "react-native";
const init_items = [
{ name: "start", value: 12500, type: "num" },
{ name: "end", value: 12700, type: "num" },
{ name: "time", value: 123.45, type: "time" },
];
const main_color = "#dddddd";
const _base = 3;
const _width = Dimensions.get("window").width;
export default function App() {
const [index, setIndex] = React.useState(0);
const [items, setItems] = React.useState(init_items);
const [curItem, setCurItem] = React.useState("");
const ref = React.useRef(null);
const textRef = React.useRef(null);
React.useEffect(() => {
console.log("index chsnaged?", index);
//if (!index) return;
ref.current?.scrollToIndex({ index, animated: true });
}, [index]);
React.useEffect(() => {
setIndex(curItem === "" ? 0 : 1);
}, [curItem]);
const useCurItem = () => {
if (curItem == "") return;
return items.find((item) => item.name == curItem);
};
const setCurItemValue = (value) => {
console.log("update " + curItem + " to " + value);
const new_items = items.map((item) => {
if (item.name == curItem) return { ...item, value: value };
return item;
});
console.log("new_items: ", new_items);
setItems(new_items);
};
const Button = ({ type, press }) => {
return (
<TouchableOpacity onPress={() => press(type)}>
<Text style={{ fontSize: 20, fontWeight: "900", margin: _base }}>
{type == "arrow" ? ">" : "X"}
</Text>
</TouchableOpacity>
);
};
const ListPanel = () => {
return (
<View>
{items.map((item) => {
return (
<View
key={item.name}
style={{
margin: _base,
}}
>
<Text style={{ fontWeight: "600", margin: _base }}>
{item.name}
</Text>
<View
style={{
alignItems: "center",
backgroundColor: "white",
borderRadius: _base,
flexDirection: "row",
justifyContent: "space-between",
margin: _base,
padding: _base,
}}
>
<Text>{item.value}</Text>
<Button type="arrow" press={() => setCurItem(item.name)} />
{/* <EmojiButton
name={"fat_arrow"}
onPress={() => setCurItem(item.name)}
size={20}
/> */}
</View>
</View>
);
})}
</View>
);
};
const DetailPanel = () => {
let thisItem = useCurItem();
if (!thisItem) return null;
return (
<View style={{ width: "100%" }}>
{/* <EmojiButton name="arrow_left" onPress={() => setCurItem("")} /> */}
<Button type="cancel" press={() => setCurItem("")} />
<Text>{curItem}</Text>
<Text>{thisItem?.value}</Text>
<Text>{thisItem.type}</Text>
{thisItem.type == "num" && (
<TextInput
ref={textRef}
onChangeText={(text) => setCurItemValue(text)}
// onSubmitEditing={() => textRef.current.focus()}
style={{ backgroundColor: "white", margin: 2 }}
value={thisItem.value.toString()}
/>
)}
</View>
);
};
const screens = [
{ name: "listing", panel: <ListPanel /> },
{ name: "detail", panel: <DetailPanel /> },
];
return (
<View style={{ marginTop: 30 }}>
<Text>Sample sliding inputs</Text>
<FlatList
bounces={false}
horizontal
keyExtractor={(item) => item.name}
ref={ref}
showsHorizontalScrollIndicator={false}
data={screens}
renderItem={({ item, index: fIndex }) => {
console.log("rendering " + item);
return (
<View
style={{
backgroundColor: main_color,
height: 300,
width: _width,
padding: _base,
}}
>
<Text> {item.name}</Text>
{item.panel}
</View>
);
}}
/>
<Text>index: {index}</Text>
<Text>curItem: {curItem}</Text>
<TouchableOpacity onPress={() => setCurItem("")}>
<View>
<Text>reset</Text>
</View>
</TouchableOpacity>
</View>
);
}
Having an issue with my ScrollView. I use it in a couple different places in my application, and most of them are working exactly as expected.
However, in one component it is working very strangely - if I swipe quickly, it will sometimes work, but usually not, and if I swipe gently or only a small amount, it doesn't work at all. I render a couple different things inside the ScrollView, but can't work out why any of them might be causing a problem, and can't spot anything obvious that's different between the one that doesn't work and the others, so I'm really at my wits end!
I am testing it on Android.
Here's what I think are the relevant bits of code for the page, but I've also put the full code below - please let me know if there's any other detail that would be useful:
const wait = (timeout) => {
return new Promise((resolve) => setTimeout(resolve, timeout));
};
export default function PotluckStandalone(props) {
const potlucks = useSelector((state) => state.potlucks);
const potluck = potlucks.find(
({ idCode }) => idCode === props.route.params.idCode
);
const [refreshing, setRefreshing] = React.useState(false);
const onRefresh = React.useCallback(() => {
setRefreshing(true);
wait(2000).then(() => setRefreshing(false));
}, []);
const dispatch = useDispatch();
const Reply = () => {
return (
<View>
<FlatList
keyExtractor={(item, index) => index}
data={potluck.replies}
renderItem={({ item }) => (
<View>
<Card
containerStyle={{
borderRadius: 12,
borderWidth: 1,
elevation: 0,
backgroundColor: "rgba(255,255,255,0.6)",
overflow: "hidden",
}}
style={{ borderColor: "rgba(255,255,255,0.1)" }}
>
<Card.Title>{item.bringer} is bringing...</Card.Title>
<Card.Divider />
{item.bringing.map((bringItem, index) => {
return (
<Text key={index}>
{bringItem}
{index < item.bringing.length - 2 ? ", " : ""}
{index === item.bringing.length - 2 ? " and " : ""}
</Text>
);
})}
</Card>
</View>
)}
/>
</View>
);
};
if (!potluck) {
return <Text>Loading...</Text>;
} else {
return (
<ImageBackground
source={require("../images/background.png")}
style={{ width: "100%", height: "100%", alignItems: "center" }}
>
<ScrollView
style={styles.page}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
>
<Card
containerStyle={{
borderRadius: 12,
borderWidth: 1,
elevation: 0,
backgroundColor: "rgba(255,255,255,0.6)",
overflow: "hidden",
}}
style={{ borderColor: "rgba(255,255,255,0.1)" }}
>
<Card.Title>
<Text>{potluck.potluckTitle}</Text>
</Card.Title>
<Card.Divider />
<Text>Host: {potluck.potluckHost}</Text>
<Text>Theme: {potluck.potluckTheme}</Text>
<Text>
Essentials:
{potluck.essentials.map((essential, index) => {
return (
<Text key={index}>
{" "}
{essential}
{index < potluck.essentials.length - 2 ? ", " : ""}
{index === potluck.essentials.length - 2 ? " and " : ""}
</Text>
);
})}
</Text>
<Card.Divider />
<Reply />
</Card>
<Bringing
potluck={potluck}
setReplySnack={() => setReplySnack(true)}
/>
</ScrollView>
</ImageBackground>
);
}
}
const styles = StyleSheet.create({
page: {
width: "90%",
paddingTop: 50,
paddingBottom: 250,
},
});
Full code here:
import { StatusBar } from "expo-status-bar";
import React, { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import {
ScrollView,
View,
Text,
FlatList,
RefreshControl,
SafeAreaView,
Button,
Share,
ImageBackground,
} from "react-native";
import { useDispatch } from "react-redux";
import { Card } from "react-native-elements";
import Bringing from "./Bringing";
import { updatePotluck } from "../actions/potlucks";
import { render } from "react-dom";
import { StyleSheet } from "react-native";
import Snackbar from "react-native-snackbar-component";
const wait = (timeout) => {
return new Promise((resolve) => setTimeout(resolve, timeout));
};
export default function PotluckStandalone(props) {
const potlucks = useSelector((state) => state.potlucks);
const potluck = potlucks.find(
({ idCode }) => idCode === props.route.params.idCode
);
const [refreshing, setRefreshing] = React.useState(false);
const onRefresh = React.useCallback(() => {
setRefreshing(true);
wait(2000).then(() => setRefreshing(false));
}, []);
const dispatch = useDispatch();
const [potluckSnackIsVisible, setPotluckSnackIsVisible] = useState(false);
const [replySnackVisible, setReplySnackVisible] = useState(false);
React.useEffect(() => {
props.route.params.success
? setPotluckSnackIsVisible(true)
: setPotluckSnackIsVisible(false);
}, []);
const onShare = async () => {
try {
const result = await Share.share({
message: `Join me for a potluck | whatLuck https://whatluck.netlify.app/potlucks/${potluck.idCode}`,
});
if (result.action === Share.sharedAction) {
if (result.activityType) {
// shared with activity type of result.activityType
} else {
// shared
}
} else if (result.action === Share.dismissedAction) {
// dismissed
}
} catch (error) {
alert(error.message);
}
};
const setReplySnack = () => setReplySnackVisible(true);
const Reply = () => {
return (
<View>
<FlatList
keyExtractor={(item, index) => index}
data={potluck.replies}
//style={styles.flatlist}
renderItem={({ item }) => (
<View>
<Card
containerStyle={{
borderRadius: 12,
borderWidth: 1,
elevation: 0,
backgroundColor: "rgba(255,255,255,0.6)",
overflow: "hidden",
}}
style={{ borderColor: "rgba(255,255,255,0.1)" }}
>
<Card.Title>{item.bringer} is bringing...</Card.Title>
<Card.Divider />
{item.bringing.map((bringItem, index) => {
return (
<Text key={index}>
{bringItem}
{index < item.bringing.length - 2 ? ", " : ""}
{index === item.bringing.length - 2 ? " and " : ""}
</Text>
);
})}
</Card>
</View>
)}
/>
</View>
);
};
if (!potluck) {
return <Text>Loading...</Text>;
} else {
return (
<ImageBackground
source={require("../images/background.png")}
style={{ width: "100%", height: "100%", alignItems: "center" }}
>
<ScrollView
style={styles.page}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
>
<Card
containerStyle={{
borderRadius: 12,
borderWidth: 1,
elevation: 0,
backgroundColor: "rgba(255,255,255,0.6)",
overflow: "hidden",
}}
style={{ borderColor: "rgba(255,255,255,0.1)" }}
>
<Card.Title>
<Text>{potluck.potluckTitle}</Text>
</Card.Title>
<Card.Divider />
<Button onPress={onShare} title="Invite your friends" />
<Text>Host: {potluck.potluckHost}</Text>
<Text>Theme: {potluck.potluckTheme}</Text>
<Text>
Essentials:
{potluck.essentials.map((essential, index) => {
return (
<Text key={index}>
{" "}
{essential}
{index < potluck.essentials.length - 2 ? ", " : ""}
{index === potluck.essentials.length - 2 ? " and " : ""}
</Text>
);
})}
</Text>
<Card.Divider />
<Reply />
</Card>
<Bringing
potluck={potluck}
setReplySnack={() => setReplySnack(true)}
/>
<Snackbar
visible={potluckSnackIsVisible}
textMessage="Potluck created successfully!"
autoHidingTime={3000}
/>
<Snackbar
visible={replySnackVisible}
textMessage="Reply posted successfully!"
autoHidingTime={3000}
/>
</ScrollView>
</ImageBackground>
);
}
}
const styles = StyleSheet.create({
page: {
width: "90%",
paddingTop: 50,
paddingBottom: 250,
},
});
I have implemented a list like Instagram explore page using this question React native grid like instagram
but it is not performant at all with the layout I have. is there any other way to implement this?
the other problem is in re-rendering different items for some rows when scrolling, I manage to reduce it by changing the conditions in which item should be rendered but there is still a problem.
I have uploaded a video of my app here: https://youtu.be/gP7kbwKDeNA
I chunck my content to 3 item arrays and then pass it to this component this is my code:
const albumIcon = require("../../assets/icons/stream.png");
const videoIcon = require("../../assets/icons/play.png");
const { width } = Dimensions.get("window");
let isLeft = false;
let url = null;
let icon = null;
function CustomGridList(props){
const onRefresh = (loadMore = false) => {
if (loadMore === true) {
loadMore();
} else {
props.refresh(props.query);
}
};
const loadMore = () => {
props.refresh(props.query, true);
};
const getItemKey = (index,item) => {
return "item".concat(index);
};
const _renderPhoto = (item, isLarge) => {
url = null;
icon = null;
if (item && item.videos.length > 0) {
url = item.videos[0].thumbnail;
icon = videoIcon;
} else if (item && item.images.length > 1) {
url = item.images[0].url;
icon = albumIcon;
} else if(item && item.images.length > 0){
url = item.images[0].url;
}else{
url = 'https://castlebba.co.uk/wp-content/uploads/2017/04/default-image-620x600.jpg'
}
return (
<TouchableOpacity style={{marginRight: 1}}>
<CachedImage
source={{uri: url}}
style={{ flex: 1}}
afterClickFunc={() => console.log('clicked')}
useTouchableOpacity
width={isLarge ? (width*(2/3)+4) : width/3}
height={isLarge ? 200: 100}
/>
<View
style={{
position: 'absolute',
top: 4,
right: 4,
}}
>
<CustomIcon boldColor={'#fff'} icon={icon} />
</View>
</TouchableOpacity>
);
};
const _renderRow = (index, item) => {
console.log(index, item );
console.log('indexable -> ', ++index%3 );
let item1 = item[0] !== null? item[0]: null;
let item2 = item[1] !== null? item[1]: null;
let item3 = item[2] !== null? item[2]: null;
if((index+1) % 3 === 0){
if (isLeft){
isLeft = false;
}else{
isLeft = true;
}
return (
<View style={{flexDirection: isLeft? 'row' : 'row-reverse', height: 200}}>
{_renderPhoto(item1, true)}
<View style={{flexDirection: 'column'}}>
<View style={{flex:1, height: 100}}>
{_renderPhoto(item2,false)}
</View>
<View style={{ flex:1, height: 100}}>
{_renderPhoto(item3,false)}
</View>
</View>
</View>
);
}else{
return (
<View style={{flexDirection: 'row', height: 100}}>
{_renderPhoto(item1, false)}
{_renderPhoto(item2, false)}
{_renderPhoto(item3, false)}
</View>
);
}
}
const { loading, content } = props;
if(loading && !content) {
return (
<View style={{ marginTop: 30, height: 100, alignItems: 'center' }}>
<ActivityIndicator
color={getThemeStyle().color_main}
size={'large'}
/>
</View>
);
}
if (content === null) {
return <View />;
}
return (
<View style={styles.flatListUserListWrapper}>
<View style={styles.albumContainer}>
<CustomFlatList
{...props}
showsVerticalScrollIndicator={false}
style={[
styles.albumContainer,
content.length > 0 ? { margin: 1 } : {},
]}
content={content}
renderItem={({ index,item }) => _renderRow(index,item)}
itemSeparator={() => <View style={{ width: '100%', height: 1 }} />}
keyExtractor={(index,item) => getItemKey(item)}
enableLoadMore={true}
loading={loading}
onRefresh={() => onRefresh()}
loadMore={()=>loadMore()}
pagePostType={"search"}
canSendPost={false}
/>
</View>
</View>
);
}
CustomGridList.propTypes = {
error: PropTypes.string,
loading: PropTypes.bool,
refresh: PropTypes.func,
navigation: PropTypes.object,
content: PropTypes.array,
query: PropTypes.string,
navigateTo: PropTypes.func, //TODO implement this one on click
};
export default CustomGridList = React.memo(CustomGridList);
I separated different rows and put them in different files then changed the way I was selecting rows with big tiles, it got a little bit better but still rerendering more than it's supposed to. I'm not doing anything dynamicly.
const albumIcon = require("../../../assets/icons/stream.png");
const videoIcon = require("../../../assets/icons/play.png");
const { width } = Dimensions.get("window");
let isLeft = false;
let url = null;
let icon = null;
function CustomGridList(props){
const onRefresh = (loadMore = false) => {
if (loadMore === true) {
loadMore();
} else {
props.refresh(props.query);
}
};
const loadMore = () => {
props.refresh(props.query, true);
};
const getItemKey = (index,item) => {
return "item".concat(index);
};
const _renderRow = (index, item) => {
if((index+1)%6 === 0){
return(<LargeTiledRow item={item} width={width} reverse={false} navigateTo={props.navigateTo}/>)
}else if((index+4)%6 === 0){
return(<LargeTiledRow item={item} width={width} reverse={true} navigateTo={props.navigateTo}/>)
}else{
return (<GridSimpleRow item={item} width={width} navigateTo={props.navigateTo}/>);
}
}
const { loading, content } = props;
if(loading && !content) {
return (
<View style={{ marginTop: 30, height: 100, alignItems: 'center' }}>
<ActivityIndicator
color={getThemeStyle().color_main}
size={'large'}
/>
</View>
);
}
if (content === null) {
return <View />;
}
return (
<View style={styles.flatListUserListWrapper}>
<View style={styles.albumContainer}>
<CustomFlatList
{...props}
showsVerticalScrollIndicator={false}
style={[
styles.albumContainer,
content.length > 0 ? { margin: 1 } : {},
]}
content={content}
renderItem={({ index,item }) => _renderRow(index,item)}
itemSeparator={() => <View style={{ width: '100%', height: 1 }} />}
keyExtractor={(index,item) => getItemKey(item)}
enableLoadMore={true}
loading={loading}
onRefresh={() => onRefresh()}
loadMore={()=>loadMore()}
pagePostType={"search"}
canSendPost={false}
/>
</View>
</View>
);
}
function areEqual(prevProps, nextProps) {
prevProps.content === nextProps.content
}
export default CustomGridList = React.memo(CustomGridList,areEqual);
I'm having a hard time figuring out which props I need to change in the initial view below the Navbar. Or is there a prop for the navbar I need to change? Basically, the navbar is hiding the top portion of the underlying view. I'm attaching a screen cap here:
Here is my code.
Navigator code:
var routeMapper = {
LeftButton: function(route, navigator, index, navState) {
if(index > 0) {
return (
<TouchableHighlight
underlayColor="transparent"
onPress={() => { if (index > 0) { navigator.pop() } }}>
<Text style={ styles.leftNavButtonText }>Back</Text>
</TouchableHighlight>)
}
else { return null }
},
RightButton: function(route, navigator, index, navState) {
return null;
},
Title: function(route, navigator, index, navState) {
return <Text style={ styles.navbarTitle }>EST4Life</Text>
}
};
module.exports = React.createClass({
renderScene: function(route, navigator) {
var Component = ROUTES[route.name]; // ROUTES['signin'] => Signin
// return the component with props to the current route and the navigator instance
return <Component route={route} navigator={navigator} />;
},
render: function() {
// return an instance of Navigator
return (
<Navigator
style={styles.container}
initialRoute={{name: 'signin'}}
renderScene={this.renderScene}
configureScene={() => { return Navigator.SceneConfigs.FloatFromRight; }}
navigationBar={<Navigator.NavigationBar
routeMapper={routeMapper}
style={styles.navBarStyle}
/>}
/>
)
}
});
var styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
},
navBarStyle: {
backgroundColor: 'gray',
flex: 1,
borderWidth: 1
},
navbarTitle: {
color: 'white',
},
rightNavButtonText: {
color: 'white',
},
leftNavButtonText: {
color: 'white',
},
})
signin.js
module.exports = React.createClass({
render: function() {
if (this.state.user == '') { // this.state.user = '' when signin is initiated
return <View style={styles.container}><Text>Loading...</Text></View>
} else if (this.state.user == null){ // this.state.user == null when user is not logged in
return (
<View style={styles.container}>
<Text>Sign In</Text>
<Text style={styles.label}>Username:</Text>
<TextInput
style={styles.input}
value={this.state.username}
onChangeText={(text) => this.setState({username: text})}
/>
<Text style={styles.label}>Password:</Text>
<TextInput
secureTextEntry={true}
style={styles.input}
value={this.state.password}
onChangeText={(text) => this.setState({password: text})}
/>
<Text style={styles.label}>{this.state.errorMessage}</Text>
<Button text={'Sign In'} onPress={this.onLoginPress} />
<Button text={'I need an account..'} onPress={this.onSignupPress} />
</View>
); // onPress={this.onPress} - pass in the onPress method below to TouchableHighlight
} else { // clear view when user is logged in
return <View><Text></Text></View>
}
}, // end of render
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
borderWidth: 4,
borderColor: 'green'
},
input: {
padding: 5, // space between text and inside of box
height: 40,
borderColor: 'gray',
borderWidth: 1,
borderRadius: 5,
margin: 5,
width: 200,
alignSelf: 'center',
},
label: {
fontSize: 18
}
});
Thanks.
Update: I figured out how to extract the heights involved with the navbar.
Code for use where the component is created:
var NavBarHeight = navigator.props.navigationBar.props.navigationStyles.General.NavBarHeight
var StatusBarHeight = navigator.props.navigationBar.props.navigationStyles.General.StatusBarHeight
var TotalNavHeight = navigator.props.navigationBar.props.navigationStyles.General.TotalNavHeight
Code to use in any scene thereafter:
var NavBarHeight = this.props.navigator.props.navigationBar.props.navigationStyles.General.NavBarHeight
var StatusBarHeight = this.props.navigator.props.navigationBar.props.navigationStyles.General.StatusBarHeight
var TotalNavHeight = this.props.navigator.props.navigationBar.props.navigationStyles.General.TotalNavHeight
Give your signin.js container some marginTop. That will get it done.
Can you try using sceneStyle for navigator? This applies to every scene being rendered using this navigator. So you can try something like { marginTop: 55 } and adjust from there based on the height of your navigation bar.
https://facebook.github.io/react-native/docs/navigator.html#scenestyle