Controlling view position of initialScrollIndex of FlatList - react-native

I want to start the offset of my list but move the initial viewPosition. The FlatList is currently showing the current image + next image on the list but rather would want to show the current image + previous one.
I'm already using initialScrollIndex and I can scrollToIndex of the image but I'm thinking that it might show some jank on first mount
Here is the image with initialScrollIndex exactly on the item I want.
The behavior I want is using initialScrollIndex and showing the previous item instead of the next.
export default class App extends Component {
flatList = null;
componentDidMount() {
setTimeout(() => {
this.flatList.scrollToIndex({
index: 3,
animated: false,
viewPosition: 1
})
}, 10)
}
setFlatList = (ref) => {
this.flatList = ref;
}
getItemLayout = (_, index) => ({
index,
length: 600,
offset: 600 * index
});
renderItem = ({ index }) => {
const backgroundColor =
index === 0 ? "blue" : index === 1 ? "red" : index === 2 ? "green" : index === 3 ? "yellow" : "white";
return (
<View style={{ height: 600, backgroundColor, width: 450, justifyContent: "center", alignItems: "center" }}>
<Text>{`item${index}`}</Text>
</View>
);
}
render() {
return (
<View style={styles.container}>
<View style={{ ...StyleSheet.absoluteFillObject }}>
<FlatList keyExtractor={(_, index) => `${index}`} getItemLayout={this.getItemLayout} ref={this.setFlatList} data={["yo", "1", "2", "3", "4"]} renderItem={this.renderItem} />
</View>
</View>
);
}

Related

How to get the active index of an item of a flatlist?

I'm trying to highlight an indicator based on the active item of a flatlist , How i'm trying to do it is like this :
const [activeIndex, setActiveIndex] = useState(0);
const windowWidth = useWindowDimensions().width;
const onFlatlistUpdate = useCallback(({ viewableItems }: any) => {
if (viewableItems.length > 0) {
setActiveIndex(viewableItems[0].index);
}
console.log(viewableItems);
}, []);
const renderItem: ListRenderItem<string> = ({ item }) => {
return (
<Image
style={[styles.image, { width: windowWidth - 40 }]}
source={{ uri: item }}
/>
);
};
return (
<View style={styles.root}>
<FlatList
keyExtractor={(key) => key}
decelerationRate="fast"
snapToInterval={windowWidth - 20}
snapToAlignment="center"
data={images}
renderItem={renderItem}
horizontal
showsHorizontalScrollIndicator={false}
viewabilityConfig={{
viewAreaCoveragePercentThreshold: 50,
}}
onViewableItemsChanged={onFlatlistUpdate}
/>
I'm using a callback so don't get the changing onviewableitems on the fly isn't supported but it seems like the
<View style={styles.dots}>
{images.map((image, index) => (
<View
style={[
styles.dot,
{
backgroundColor: index === activeIndex ? "#c9c9c9" : " #ededed",
},
]}
/>
))}
</View>
when i change the item on the flatlist the chosen item becomes highlighted and all previous highlighted items are also highlighted if that makes sense , so if i'm on the first image of the flatlist the first dots is highlighted
Render item provides an index prop to your component
const renderItem: ListRenderItem<string> = ({ item, index }) => {

Understanding UI State : Dynamic TextInput loses focus after each key press

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>
);
}

create Carousel in React Native using FlatList

I'm creating a carousel component in React Native using a FlatList and I use useState hook to control the index of image, images load properly and the problem is I cant use my buttons to control the carousel. for example when I tap on right arrow first time doesn't work but when I tap again it goes to next image.
here's my code:
const { width: windowWidth, height: windowHeight } = Dimensions.get("window");
const slideList = Array.from({ length: 5 }).map((_, i) => {
return {
id: i.toString(),
image: `https://picsum.photos/1440/2842?random=${i}`,
};
});
const Carousel = () => {
const [current, setCurrent] = useState(0);
const length = slideList.length;
const flatListRef = useRef();
const renderItem = ({ item }) => {
const arr = Object.values( item );
return (
<View style={styles.imagesContainer}>
<Image style={styles.image} source={{ uri: item.image }} />
</View>
);
}
const goNextSlide = () => {
setCurrent(current < length -1 ? current + 1 : 0);
flatListRef.current.scrollToIndex({ index: current, animated: true });
};
const goPrevSlide = () => {
setCurrent(current <= length - 1 && current >= 0 ? current -1 : 0);
flatListRef.current.scrollToIndex({ index: current, animated: true });
};
console.log(current)
return (
<View style={styles.screen}>
<View style={styles.controls}>
<TouchableOpacity style={styles.controlleft} onPress={goPrevSlide}>
<CarouselLeftArrow style={styles.leftArrow} size={28} fill='black' />
</TouchableOpacity>
<TouchableOpacity style={styles.controlRight} onPress={goNextSlide}>
<CarouselRightArrow style={styles.rightArrow} size={28} fill='black' />
</TouchableOpacity>
</View>
<FlatList
data={slideList}
keyExtractor={item => item.id}
renderItem={renderItem}
horizontal={true}
showsHorizontalScrollIndicator={false}
pagingEnabled={true}
ref={flatListRef}
/>
</View>
)
}
const styles = StyleSheet.create({
imagesContainer: {
width: windowWidth,
height: 250
},
image: {
width: '100%',
height: '100%'
},
controls: {
backgroundColor: 'yellow',
flexDirection: 'row',
justifyContent: 'space-between',
position: 'absolute',
zIndex: 2,
width: '100%',
top: 100
},
controlLeft: {
},
controlRight: {
}
})
export default Carousel;
any help would be appreciated.
goPrevSlide
setCurrent(current <= length - 1 && current >= 0 ? current -1 : 0);
When current >= 0 is not correct because if current equals zero then you set -1 to current in this case. Replace statement like setCurrent(current ? current - 1 : length - 1);
Since updating state is an async action, you can not handle updated variable immediately, you need to use effect hook in order to catch it.
useEffect(() => {
// fires every time when "current" is updated
flatListRef.current.scrollToIndex({ index: current, animated: true });
}, [current]);
Remove setCurrent function from both handler
try to give width and height to the images, you need that if source is uri.
see you code working at snack (without buttons)

Animate Single item on flatlist

How i can animate a single item from flatlist ?
I tryed adding a value to isolate the selected record but didnt' work,maybe i wrote something wrong. Atm the animation is on ALL records
Flatlist:
<View style={style.container}>
<FlatList
data={this.state.ReturnedArray}
width='100%'
ItemSeparatorComponent={this.FlatListItemSeparator}
renderItem={this.renderizza}>
Render Flalist :
renderizza = (item) => {
var str = JSON.parse(JSON.stringify(item.item.itemType)).name + ' ' + JSON.parse(JSON.stringify(item.item.itemType)).cognome
let acronym = str.split(/\s/).reduce((response, word) => response += word.slice(0, 1), '')
return (
<View style={style.containerFlat}>
<View style={style.containerFlat1}>
<Text style={style.txt}>{JSON.parse(JSON.stringify(item.item.itemType)).name} {JSON.parse(JSON.stringify(item.item.itemType)).cognome}</Text>
</View>
<TouchableOpacity style={style.containerFlat2} onPress={() => this.badge(JSON.parse(JSON.stringify(item.item.itemType)).expoToken)}>
<Animated.View style={[style.animatedView, { opacity: this.state.fadeValue}]}><Text>ADDED</Text></Animated.View>
<Text style={style.AvatarTxt} >{acronym}</Text>
</TouchableOpacity>
</View>
)
}
And here the animation :
badge(chiavi) {
console.log(chiavi)
if ((this.state.SelectedUser).includes(chiavi)) {
this.state.SelectedUser.splice(this.state.SelectedUser.indexOf(chiavi), 1)
Animated.timing(this.state.fadeValue, {
toValue: 0,
duration: 0
}).start();
} else {
this.state.SelectedUser.push(chiavi)
Animated.timing(this.state.fadeValue, {
toValue: 1,
duration: 0
}).start()
}
}
Hot to fix ?
Thank you
One way is to maintain an active item in the state and use extraData prop for this variable in Flatlist.Then conditionally render between the Animated and normal Item.
animate = index => {
this.setState({
activeItem: index,
});
//Rest of animation code.
};
<TouchableOpacity onPress={e => this.animate(index)}>
{this.state.activeItem === index && (
<Animated.View
style={[
styles.button,
{ transform: [{ scale: this.animationMap }] },
]}
>
<Text>{item.title}</Text>
</Animated.View>
)
}
{this.state.activeItem !== index && (
<View style={styles.button}>
<Text>{item.title}</Text>
</View>
)
}
</TouchableOpacity>
Here is an sample Demo

React Native:How to change dynamic view borderColor colour?

I have created 5 Text programatically in react native .
I want to change the borderColor colour of view By click item.
I tried using below code .But its changing all 5 views borderColor colour .
I want change borderColor colour of only one view.
for (var i = 0; i < 4; i++) {
pills.push (this.renderPill (i));
}
renderPill (index) {
return (
<TouchableOpacity key={index} style={ this.state.status ? boxStyle : boxStyleSelected } onPress={this.itemClick.bind(this)}>
<View >
<Text>
{index}
</Text>
</View>
</TouchableOpacity>
);
}
}
multiItemClick (index) {
this.setState({ status: true });
}
boxStyle: {
borderColor: '#ffffff',
},
boxStyleSelected: {
borderColor: '#000000',
}
You should use FlatList to render your item or component list.
Here are an Example:
render() {
return (
<View style={styles.container}>
<FlatList
data={ this.state.listItem }
ItemSeparatorComponent = {this._itemSeparator}
keyExtractor={(item, index) => index}
renderItem={this._renderItem}
selected={this.state.selected}
/>
</View>
);}
Separate the item list
_itemSeparator = () => (<View style={{ height: 1, backgroundColor: "#607D8B" }}/>);
Render the Item list
_renderItem = (item) => {
return (
<TouchableOpacity
key={item.index}
style={this.state.selected == item.index ? styles.boxSelected : styles.boxStyle}
onPress={()=>this._itemClick(item)}
>
<View style={{alignItems:'center'}}>
<Text>
{item.item.key}
</Text>
</View>
</TouchableOpacity>
);}
Event on Click item to Change the Style
_itemClick(item) { this.setState({selected:item.index}) }
Data State
constructor(props){
super(props);
this.state = {
selected: null,
listItem: [
{key: 'One'},
{key: 'Two'},
{key: 'Three'},
{key: 'Four'},
{key: 'Five'},
{key: 'Six'},
{key: 'Seven'},
{key: 'Eight'},
{key: 'Nine'},
{key: 'Ten'},
{key: 'Eleven'},
{key: 'Twelve'}
]
};}
Style Sheet your item
const styles = StyleSheet.create({
container :{
justifyContent: 'center',
flex:1,
margin: 10,
paddingTop:50
},
boxStyle: {
backgroundColor: '#778788',
padding: 10,
margin:3
},
boxSelected: {
backgroundColor: 'red',
padding: 10,
margin:3
}
});
enter image description here
This is what i got for you, Hope it will save your time.
The problem is, that you are using the state for all views. Try to modify the code to something, that depends on the index you clicked.
for (var i = 0; i < 4; i++) {
pills.push (this.renderPill (i));
}
renderPill (index) {
return (
<TouchableOpacity key={index} style={ this.state.status[index] ? boxStyle : boxStyleSelected } onPress={(index) => this.itemClick(index)}>
<View >
<Text>
{index}
</Text>
</View>
</TouchableOpacity>
);
itemClick(index) {
let status = this.state;
status[index] = true;
this.setState({
status: status
})
I have not tested this, so I am not 100% sure about the syntax, but i think the idea is clear. You have to save wich item is clicked in an array, so you know what item has to be rendered with the different border.
But I also would really advice to use a FlatList here https://facebook.github.io/react-native/docs/flatlist.html Because what you are trying to do is render a clickable List. That will give you way more control over the items, their direct styling and the list container. Also you would not need to have two separate lists, but could use an array of objects like this
{
value: 1
clicked: true
}
which makes it more readable and maintainable.
edit: there is actually a code sample for a multiselect on the link provided, i adapted it to your pills. Just hand the array to the component from somewhere else. Id and title is needed. View looks like this:
Code goes like this:
export default class MyView extends React.Component {
constructor(props) {
super(props);
this.state = {
pills: [{id: 1, title: 'Pill 1'},
{id: 2, title: 'Pill 2'},
{id: 3, title: 'Pill 3'}],
};
}
render() {
const {pills} = this.state;
return (
<MultiSelectList data={pills}/>
);
}
}
class MultiSelectList extends React.PureComponent {
state = {selected: (new Map())};
_keyExtractor = (item, index) => item.id;
_onPressItem = (id) => {
// updater functions are preferred for transactional updates
this.setState((state) => {
// copy the map rather than modifying state.
const selected = new Map(state.selected);
selected.set(id, !selected.get(id)); // toggle
return {selected};
});
};
_renderItem = ({item}) => (
<MyListItem
id={item.id}
onPressItem={this._onPressItem}
selected={!!this.state.selected.get(item.id)}
title={item.title}
/>);
render() {
return (
<FlatList
data={this.props.data}
extraData={this.state}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
/>
);
}
}
class MyListItem extends React.PureComponent {
_onPress = () => {
this.props.onPressItem(this.props.id);
};
render() {
const color = this.props.selected ? "red" : "black";
return (
<TouchableOpacity onPress={this._onPress}>
<View>
<Text style={{borderStyle: 'solid', borderWidth: 2, borderColor: color }}>
{this.props.title}
</Text>
</View>
</TouchableOpacity>
);
}
}