I render several sections and one of the sections is a ScrollView. This ScrollView is a horizontal days calendar and each item is arenderRow() that and has View Text and Button wrapped together within one View:
render()
{
//..
let day = this.renderDaysView();
return(
<View style={{ backgroundColor: 'white' }}>
{day}
</View>
)
}
renderDaysView()
{
let renderRow = (rowData, rowID) => {
return (
<View key={rowID}>
<TouchableOpacity ... >
<View ... >
<Text ... >{currentWeekDay}</Text>
</View>
</TouchableOpacity>
</View>
);
}
return (
<ScrollView ref={(scrollview) => this._daysScrollView = scrollview} horizontal= 'true'>
{
this.state.Days.map((item, index) => renderRow(item, index) )
}
</ScrollView>
}
}
Now, when the screen gets displayed, I would like todays day to be right in the middle of the screen.
Following the Docs, scrollTo() can take an object:
scrollTo( y? , object , x?, animated? )
Currently I am using this hack (which is missing one part) where I multiply the current day of month with a specific value:
componentWillUpdate(newProps: Object, newState: Object) {
this._daysScrollView.scrollTo(
{
x: // if first week, do nothin
newProps.currentDate.getDate() < 6 ?
0
: // elseif if last week, scroll to scrollView width (missing)
newProps.currentDate.getDate() > 27 ?
(newProps.currentDate.getDate() * 55 ) - ( 7 * 52) // TODO: should be scrollview width
: // else someday later, put in in the middle
(newProps.currentDate.getDate() - 4) * 55,
animated: true
}
)
}
So I either need to know the width of _daysScrollView, or jump to that object - 4 (to keep it in middle)
If my object is a component, how can I jump to a specific object? (For ex. if today is the 19th, go to index 19)
Or for my hack,
How can I get the width of my _daysScrollView?(_daysScrellView.width) did not work
Thanks
To use the scrollTo method you would have to get the x/y position of the element you wanted to scroll to. An easier solution might be something like this:
componentDidUpdate() {
let location = window.location;
let dayOfMonth = new Date.getDate();
location.hash = `#${dayOfMonth}`;
}
The new Date.getDate(); will return a number 1 - 31 depending on the day in the month. So if you set the id of each day in your calendar to a number corresponding to its day in the month. The above will scroll to it.
location.hash = `#${dayOfMonth}`;
will then scroll the document to the element with that id attribute.
Set the id of each day to the it's numerical position in the month and window.location.hash to route to the anchor of the day within the page.
EDIT: Try replacing your componentWillUpdate() method with this componentDidUpdate.
Related
My app allows the user to get pictures based on a date range. 1 picture per day (ex: start date 2021-01-01, end date 2021-01-04 = 4 pictures).
So, I would like to display the number of pictures shown based on the chosen date range.
The issue is that if I use data.lenght I will get the correct amount of pictures once the user chooses a date range, however, before the user chooses anything data.lenght = 1 and not 0 zero!
I also tried using item.lenght which returns a undefined value and calculating '(end date) - (start date) = result' but I get a Nan value.
The goal is to display Results = (0) before the user searches a data range and display Results = (number of pics on a given date range) once the user makes a search.
Here is the code:
const searchDateRange = async () => {
const api = await fetch(
`my api`
);
const data = await api.json();
setData(data);
};
then I display this:
<Text style={styles.resultsText}>Results ({data.length})</Text>
</View>
<View style={styles.imageGrid}>
<FlatList
data={data}
keyExtractor={(item, index)=>{return item.date}}
numColumns={numberOfCols}
renderItem={({item, index})=>(
<View style={styles.viewpic}>
<TouchableOpacity onPress={() => navigation.navigate('ImageDetails', item)}>
<Image
style={{
height: 115,
width: square - 12,
margin:6,
}}
source={{uri:item.url}}/>
</TouchableOpacity>
</View>
thanks everyone for your help!
Cheers!
I'm mapping over an array of objects. I only want to display the items that can fit in a single line. The other ones have to be pushed in a new array, that will be displayed elsewhere (a "see more button" + a popover).
In order to do it, I have a ref on the div that encompasses the mapped items. Once its height changes, I know I'm on another line, and that it's time to stop displaying items.
I wrote:
const Component = ({myArray}) => {
let ref = React.useRef();
const { height, width } = useSize(ref);
const availableSpace = 24;
const otherArray = [];
return (
<div ref={ref} style={{display: "flex" flexWrap:"flew-wrap"}}>
{myArray.map({label} => {
height > 24 && otherArray.push({label});
return (
//stuff
)
})}
{otherArray && otherArray.length > 0 && (
<button onClick={()=> showRemainingItemsInAPopover}>
+{otherArray.length}
</button>
)}
)
}
Sadly, React renders all the items, and then pushes all of them again in otherArray.
How to properly separate my array in two in real time?
Thanks!
Maybe you can use filter function :
<div ref={ref}>
{myArray.map({label} => {
height > 24 && otherArray.push({label});
return (
//stuff
)
}).filter(() => height <= 24)}
but i'd like to see more code to you help more.
Not sure how many items are in your array and if you are trying to do this for performance reasons...
Could you not use css to set a max-height: 24px; overflow: hidden; and then use React to toggle the max-height property?
Ie. https://codesandbox.io/s/red-wind-9c94z?fontsize=14&hidenavigation=1&theme=dark
I am creating a calendar component that contains 2 Views with FlatList. Each View is associated with a different data source. Specifically, View 1 will display all days in a month where View 2 will display all days in a week.
Link to video demo, button of year is for view switching
For simplicity, each month/week view is represented by a date as an array item in a bigger array.
For example, each item in the month array is a unique data in a month. An array of size 12 will have 12 unique dates of each month. Each Date() object will then be passed down to my children components for a proper rendering. The result of this particular month array is a list of complete calendar of 12 months.
Note: To improve performance, my child component will only render the current month -1, current month, and current month +1, all other months will be rendered conditionally as soon as user scrolls away.
[
...
Wed May 30 2018 21:51:47 GMT+0800 (+08),
Sat Jun 30 2018 21:51:47 GMT+0800 (+08),
Mon Jul 30 2018 21:51:47 GMT+0800 (+08),
...
]
The same applies to View 2, which is the week view. Except that my data is now contained unique days in a week for all weeks in a month.
Before I implement this approach, I tried to hack something out from my child component. For instance, when users click a button to switch view, it would pass down a prop, with the current Date() object to notify the children as to conditionally render a different view. This is basically switching between a View and a FlatList. Nested FlatList was a bad idea as both lists are scrolling towards the same direction.
Not to mention that my calendar component involve jumping to selected month. Another solution I had implemented is to use only month array for both views by hacking out the indexes from keyExtractor. For example, I would reindex the list as soon as user jumps from one month to another but soon I realized this is an anti-pattern an only causes more problem.
Note: I have implemented shouldComponentUpdate in all my children
components. So scrolling in just 1 View should not be a problem, size
of the data source will not affect at all because only the changes
happen to the -1, 0, and +1 month will be reflected. The
bottleneck of my component is only happening when switching views.
Lost story short, now I have resorted to the current solution where I have 2 different data source (array) for 2 different Views in a FlatList. I have a button for the user to switch between modes. The problem is it takes some time for switching mode (setting states) as every time it re-instantiates the view by calling this prop onViewableItemsChanged from FlatList, which involve some complex calculation. Is there a better approach?
Code (Parent)
renderCalendarList = () => {
return (
<FlatList
pageSize={1}
horizontal={true}
pagingEnabled={true}
scrollEnabled={true}
keyExtractor={this.keyExtractor}
ref={(c) => this.calendarList = c}
getItemLayout={this.getItemLayout}
renderItem={this.renderCalendarComponent}
onViewableItemsChanged={this.onViewableItemsChanged}
data={(this.state.weekMode ? this.state.weekRows : this.state.rows)} />
)
}
switchView = () => {
this.setState({ weekMode: !this.state.weekMode });
// next week is going to be month view
if (this.state.weekMode) {
const mYClone = this.state.monthYears.slice(0);
const selectedDay = DateUtils.formatDateInMY(this.props.selectedDay);
for (let i = 0; i < mYClone.length; i++) {
if (selectedDay === mYClone[i]) {
this.setState({ currentMonth: this.props.selectedDay })
this.calendarList.scrollToIndex({ animated: false, index: i });
}
}
} else { // next week is going to be week view
const rowClone = this.state.weekRows.slice(0);
for (let i = 0; i < rowClone.length; i++) {
if (isSameWeek(rowClone[i], this.props.selectedDay)) {
this.setState({ currentMonth: rowClone[i] });
this.calendarList.scrollToIndex({ animated: false, index: i });
}
}
}
}
Code (Children)
render() {
const { container, monthContainer, weekContainer } = styles;
const { currentMonth, firstDay, style, weekMode } = this.props;
if (!weekMode) {
const days = DateUtils.populateMonth(currentMonth, firstDay);
const weeks = [];
while (days.length) {
weeks.push(this.renderWeek(days.splice(0, 7), weeks.length));
}
return (
<View style={[container, style]}>
<View style={[monthContainer]}>{weeks}</View>
</View>
)
} else {
const startDay = subDays(currentMonth, 3); // focus on middle
const endDay = addDays(startDay, 6)
const days = eachDay(startDay, endDay, 1);
const daysToRender = [];
days.forEach((day, dayID) => {
daysToRender.push(this.renderDay(day, dayID))
});
return (
<View style={style}>
<View style={[weekContainer]}>
{daysToRender}
</View>
</View>
)
}
}
Link to video demo, button of year is for view switching
This is inside a react-native project:
I have a progress bar that has 10 positions, and each position as a possibility of three states - incomplete, active, complete. There is an image that corresponds to each of those states. I was thinking I could use a for loop that would look for the index number of the page, which I'll store in an object, and depending on the relationship between the position in the loop and and the index number, that would determine which image gets displayed. Something like this:
if the position in the loop > index number, display the incomplete image
if the position in the loop = index number, display the active image
if the position in the loop < index number, display the complete image
Beyond this, I'm not even sure on where to start.
In your class/component:
state = {
indexNum: 4, // arbitrary value
}
displayStatus(item) {
if(item.id > this.state.indexNum){ // Incomplete
return <View style={styles.progressPoint}><Text>I</Text></View>;
}
else if(item.id == this.state.indexNum){ // Active
return <View style={styles.progressPoint}><Text>A</Text></View>;
}
else if(item.id < this.state.indexNum){ // Complete : you can use only 'else' here
return <View style={styles.progressPoint}><Text>C</Text></View>;
}
}
In render() of your class/component:
// Positions/Pages - these will serve as basis for .map - you can add more than 'id'
const positions = [{"id": 1},{"id": 2},{"id": 3},{"id": 4},{"id": 5},
{"id": 6},{"id": 7},{"id": 8},{"id": 9},{"id": 10}];
return (
<View style={styles.container}>
{positions.map((item) => (
this.displayStatus(item)
))}
</View>
);
Here's an Expo Snack of the above (based on the scope of your question) to get you started.
You can store the index number of the page in state and update this state at completion of each progress position. Note: if you are not using redux, you may need to pass/handle state (index number) on each page individually (depending on your navigation or components structure).
Hi (Sorry for my english)
I have a problem, I consume a service that bring me a buttons array for a dynamic menu. This array I want to put in the screen, like button have a id for a action in specific.
Code
var sizes = []
for (var i = 0; i < this.state.tallas.length; i++) {
sizes.push(
<TouchableOpacity style={[styles.clothingSizeItem, this.state.idSize === this.state.tallas[i].id ? styles.activeClothingSizeItem:null]}
onPress={() => this.fetchSizeData(this.state.tallas[i].id)}>
<Text style={this.state.idSize === this.state.tallas[i].id ? styles.activeSizeText:null}>
{this.state.tallas[i].name}
</Text>
</TouchableOpacity>
);
}
View
render() {
return(
{sizes}
)
}
The problem is when I press a button from my dinamic menu, the button call a function that receive a parameter, but this parameter comes from service array. So I use "i" param from for, but this variable ("i") can't found after that the screen was rendered.
Screen Shot
error screen
Gracias!
You can use Array#map instead of a for loop:
const sizes = this.state.tallas.map(item => (
<TouchableOpacity style={[styles.clothingSizeItem, this.state.idSize === item.id ? styles.activeClothingSizeItem:null]}
onPress={() => this.fetchSizeData(item.id)}>
<Text style={this.state.idSize === item.id ? styles.activeSizeText:null}>
{item.name}
</Text>
</TouchableOpacity>
);
The reason the for loop doesn't work here is that the index variable i is incremented on every iteration of the loop, so by the time the callback ends up being called (long after the loop has completed), the value i is always it's final value (same as tallas.length).