How can I excatly do a similar Date system like the one in the Whatsapp chat screen?
As you can see the messages are in a group by date, I mean they are separated by date.
Here is a ScreenShot that i found for better explanation:
I do this in a FlatList, while rendering the messages one by one.
Here is what i did
let previousDate = "";
if (index > 0) {
previousDate = moment(this.state.messages[index - 1].created_at).format(
"L"
);
} else {
previousDate = moment(this.state.messages.created_at).format("L");
}
let currentDate = moment(item.created_at).format("L");
So, i created a functional component for renderItem prop of the FlatList, so item and index comes from the actual data from the FlatList.
What i'm trying to do here is, basically grabbing the current rendering item's created_at and compare it with the previous item's created_at, and to do that, i'm using the original data which is stored in the state. But unfortunately when the FlatList rendering the very first item which has index number 0 there is no previous element to compare in the original data in state, that's why i checking if is greater than 0 go and grab date from previous indexed item. And in the Else case, which means when rendering the first item, do not look for previous item and just get the created_at.
And below i check if the currentDate and previousDates are NOT the same, render a custom component else do not render anything.
{previousDate && !moment(currentDate).isSame(previousDate, "day") ? ( // custom component) : null}
It's should work like that, but the major problem is, i used inverted FlatList for to able to messages go from bottom of the screen to the top. But now, becouse of it's a inverted flatlist the items being rendering from bottom to the top and it gives me result like this:
NOTE: At the beginning the messages were coming also reversed but i fixed this with sending them also reversed from the DB.
So, i don't know how do i able to achieve my goal, and do it like on the first picture.
Thank you!
I use a helper function (generateItems) to address the problem that you are describing. Here is the code that I use to group my messages by day and then render either a <Message /> or a <Day /> in the renderItem prop. This is using an inverted FlatList as you described.
import moment from 'moment';
function groupedDays(messages) {
return messages.reduce((acc, el, i) => {
const messageDay = moment(el.created_at).format('YYYY-MM-DD');
if (acc[messageDay]) {
return { ...acc, [messageDay]: acc[messageDay].concat([el]) };
}
return { ...acc, [messageDay]: [el] };
}, {});
}
function generateItems(messages) {
const days = groupedDays(messages);
const sortedDays = Object.keys(days).sort(
(x, y) => moment(y, 'YYYY-MM-DD').unix() - moment(x, 'YYYY-MM-DD').unix()
);
const items = sortedDays.reduce((acc, date) => {
const sortedMessages = days[date].sort(
(x, y) => new Date(y.created_at) - new Date(x.created_at)
);
return acc.concat([...sortedMessages, { type: 'day', date, id: date }]);
}, []);
return items;
}
export default generateItems;
For reference here is my list as well as the renderItem function:
<MessageList
data={generatedItems}
extraData={generatedItems}
inverted
keyExtractor={item => item.id.toString()}
renderItem={renderItem}
/>
function renderItem({ item }) {
if (item.type && item.type === 'day') {
return <Day {...item} />;
}
return <Message {...item} />;
}
This is how i did it in react,
Create a new Set() to store dates uniquely
const dates = new Set();
When looping through chats array, check if date already exists in unique Set before rendering date
chats.map((chat) => {
// For easier uniqueness check,
// Formated date string example '16082021'
const dateNum = format(chat.timestamp, 'ddMMyyyy');
return (
<React.Fragment key={chat.chat_key}>
// Do not render date if it already exists in set
{dates.has(dateNum) ? null : renderDate(chat, dateNum)}
<ChatroomChatBubble chat={chat} />
</React.Fragment>
);
});
Finally, when date has been rendered, add date num into array so it doesn't render again
const renderDate = (chat, dateNum) => {
const timestampDate = format(chat.timestamp, 'EEEE, dd/MM/yyyy');
// Add to Set so it does not render again
dates.add(dateNum);
return <Text>{timestampDate}</Text>;
};
Related
Problem: I have 2 Functional Components component1,component2 and 1 FlatList having its own RenderComponent.
When I merge this 3 Components(Comp1, Comp2 & Flatlist) under one View I get output as seen in image 1. I expect an output as Image 2. How can this be achieved and Is this possible.
Image 1 (Output I am getting)
Image 2 (Expected Output)
What you can do is to add your 1st component explicitly at the first index of your Flatlist, 2nd component at the last index, and traverse the list additionally two more times.
E.g: If the length of data is 10 then you should consider the length 12 so this would work
// consider this is the data to show in flalist
const data = [{...}, {...}, {...}];
// Add single dummy item to the beginning of an array
data.unshift({...});
// Add single dummy item to the end of an array
data.push({...});
<FlatList
columnWrapperStyle={{justifyContent: 'space-between'}}
data={data}
numColumns={2}
renderItem={({ item, index }) => {
// first item
if (index === 0) {
return <ComponentOne />
}
// last item
if (index === data.length - 1) {
return <ComponentTwo />
}
return <Item />
}}
/>
I've tried about everything I could find on the forums etc, re this error, but no success.
Most solutions seem to be
format="DD/MM/YYYY HH:mm"
or
Moment (being locale driven)
I return the selected date into a chip which displays fine, but the 'RangeError.Invalid time value' issue persists into the chip after the correct selected date is rendered in it.
const [effectiveSelectedDate, setEffSelectedDate] = useState();
const handleEffDateChange = (date,name) =>{
setEffSelectedDate(date);
}
export const makeColumns = (columns, language, rawFilters, filters, format_functions) => {
return columns.map((item, i) => {
if (data.className.search("date_time") > -1) {
logic: (.......... ),
display: (filterList, onChange, index, column) => (
<MuiPickersUtilsProvider
utils={DateFnsUtils}
locale={localeMap[i18next.language]} >
<KeyboardDatePicker
fullWidth
variant='inline'
placeholder='yyyy-MM-dd'
format='yyyy-MM-dd'
margin='normal'
id='date-picker-inline'
name='effectiveDate'
value={effectiveSelectedDate}
onChange={handleEffDateChange}
KeyboardButtonProps={{ 'aria-label': 'change date', }}
/>
</MuiPickersUtilsProvider>
)
}
}
);
What am I missing?
Thanks
try this one
const [effectiveSelectedDate, setEffSelectedDate] = useState();
const handleEffDateChange = (date,name) =>{
setEffSelectedDate(date);
}
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<KeyboardDatePicker
fullWidth
variant='inline'
placeholder='yyyy-MM-dd'
format='yyyy-MM-dd'
margin='normal'
id='date-picker-inline'
name='effectiveDate'
value={effectiveSelectedDate}
onChange={handleEffDateChange}
KeyboardButtonProps={{
'aria-label': 'change date',
}}
/>
</MuiPickersUtilsProvider>
I am trying to create a reactive filter for an array in Vue. My starting array comes from an API call which returns this.features (geojson features). I am filtering on a nested array. This works -- but when I enter a search term and then backspace back out to an empty string, and enter another string, I am not filtering the original array but appear to be filtering the already-filtered array. How could I filter again on the original array from the API call?
computed property:
filteredFeatures() {
if (this.searchTerm == '') {
return this.features
}
// filter on nested array
let filtered = this.features.filter(feature => {
feature.properties.site_observations = feature.properties.site_observations.filter(
el => JSON.stringify(el).match(this.searchTerm, 'i')
)
return feature.properties.site_observations.length > 0
})
return filtered
}
I have looked at Vue filtering objects property but I cannot make that code work (it uses Object.assign()). Thanks for any ideas.
Your computed property is mutating feature.properties.site_observations, that's a nono. Computed properties should be read only.
filteredFeatures() {
if (this.searchTerm == '') {
return this.features
}
// filter on nested array
let filtered = this.features.filter(feature => {
const site_observations = feature.properties.site_observations.filter(
el => JSON.stringify(el).match(this.searchTerm, 'i')
)
return site_observations.length > 0
})
return filtered
}
It seems here is your problem:
feature.properties.site_observations = feature.properties.site_observations.filter(
el => JSON.stringify(el).match(this.searchTerm, 'i')
)
Because this code filter feature and alter the proprieties of feature.properties.site_observations. Then, in the next read the value is alter. We say that your function it is not pure, because it alter the state of feature.
So, what you should do is:
let anotherVariable = feature.properties.site_observations.filter(
el => JSON.stringify(el).match(this.searchTerm, 'i')
)
Therefore, on a function, avoid alter state of objects, this lead to bugs.
On further checking, the above answer returns all site_observations, not just the ones that match the search. A much better solution is the following, using map to avoid overwriting the data, and the object spread operator to perform an object assign, and drilling down through the nested objects as follows:
filteredFeatures() {
return this.features
.map(feature => ({
...feature,
properties: {
site_observations: feature.properties.site_observations.filter(
element => {
return JSON.stringify(element).match(new RegExp(this.search, 'i'))
}
)
}
}))
.filter(feature => feature.properties.site_observations.length)
}
I have a custom component that provides the rendered list of data when the data is available.
But when the data is not available in the example case below where data=[], I want to apply a different style to it.
return (
<FollowableArticleListTemplate
style={hasData}
title={ title }
data={ [] }
isFollowing={ isFollowing }
onToggleFollow={ id ? () => toggleSourceFollow(id, isFollowing, title) : null }
setListRef={ this.setListRef }
contentType="source"
/>
);
What I'm looking for is something like this:
style={hasData}
i.e if the data.length > 0
use this style for this component: style={hasData}
otherwise use this style component: style={noData}
This is easily achieved by using the ternary operator. First, you have to conditionally use data using state, not by giving its value directly into the component.
return (
<FollowableArticleListTemplate
style={myData.length > 0 ? hasData : noData}
title={ title }
data={myData}//this is your state
isFollowing={ isFollowing }
onToggleFollow={ id ? () => toggleSourceFollow(id, isFollowing, title) : null }
setListRef={ this.setListRef }
contentType="source"
/>
);
```
So basically I want to be able to collect all the values from multiple inputs and set that array as a state. Here is what I am currently working with:
this.state.basket.map(b => {
return (
<View>
<InputSpinner
style={styles.spinnerQty}
max={50}
min={1}
step={1}
rounded={false}
showBorder
colorMax={"#2a292d"}
colorMin={"#2a292d"}
value={b.qty}
onChange={num => {
this.setState({ popUpQty: num });
}}
/>
<View style={styles.hrLine}></View>
</View>
);
});
So I am iterating my basket and setting a spinner with a value from axios output. So there are now multiple InputSpinner with multiple values.
My question is, how can I collect all the values of the onChange, and push it to an array which will eventually become a state. Something like QuantityState: [] would be the values of all the InputSpinner. Hope that made sense. Any help is appreciated. Thanks!
PS. InputSpinner is an npm package from here.
Through this code you can dynamically add/update onChange number on it's particular array instance. num key will be added when a particular onChange trigger so at the end you will get its values which placed on it's index and if key not found that means onChange never triggered for that index
state = {
spinnerData : {},
basket: []
}
this.state.basket.map((b, index) => {
return (
<View>
<InputSpinner
style={styles.spinnerQty}
max={50}
min={1}
step={1}
rounded={false}
showBorder
colorMax={"#2a292d"}
colorMin={"#2a292d"}
value={b.qty}
onChange={num => {
const newbasket = [...this.state.basket];
newbasket[index]["num"] = num;
this.setState({ basket:newbasket });
}}
/>
<View style={styles.hrLine}></View>
</View>
);
});