Access data via redux for SectionList - react-native

I have a initial state as below in reducer
const initialState = {
medicationschedule: [
{
date: '2019-08-27',
medications: [
{
title: '8.00 AM',
data: [
{name:'item1', isTaken: 1,mg: '500 mg',capsules:'capsule'},
{name:'item2', isTaken: 4,mg: '2000 mg',capsules:'teaspoon'}
]},
{
title: '12.03 PM',
data: [
{name:'item3', isTaken: 2,mg: '500 mg',capsules:'capsule'},
{name:'item4', isTaken: 1,mg: '500 mg',capsules:'capsule'}
]},
{
title: '3.30 PM',
data: [
{name:'item1', isTaken: 3,mg: '500 mg',capsules:'capsule'}
]},
]
}
],
medication: [
{
title: '8.00 AM',
data: [
{name:'item1', isTaken: 1,mg: '500 mg',capsules:'capsule'},
{name:'item2', isTaken: 4,mg: '2000 mg',capsules:'teaspoon'}
]},
{
title: '12.03 PM',
data: [
{name:'item3', isTaken: 2,mg: '500 mg',capsules:'capsule'},
{name:'item4', isTaken: 1,mg: '500 mg',capsules:'capsule'}
]},
{
title: '3.30 PM',
data: [
{name:'item1', isTaken: 3,mg: '500 mg',capsules:'capsule'}
]},
]
};
I have my class as below for the React Native SectionList.
class HomeScreen extends Component {
state = {
selectedDate: Date(),
isModalVisible: false,
};
render() {
return (
<SafeAreaView style={styles.containter}>
<SectionList
renderItem={({item, index, section}) => <MedicineRow showTitle={0} key={index} setWidth='80%' title={item.name} mg={item.mg} capsules={item.capsules} onPress={() => this.medicineRowTapped(item.name)} medstatus={item.isTaken}/>}
stickySectionHeadersEnabled={false}
renderSectionHeader={({section: {title}}) => (
<SectionTitle showTitle={true} title={title}/>
)}
sections={
this.props.filteredMedications
}
keyExtractor={(item, index) => item + index}
/>
</SafeAreaView>
)
}
}
function mapStateToProps(state) {
return {
filteredMedications : state.medication
}
}
export default connect(mapStateToProps)(HomeScreen)
The list succesfully loads if I access the medication as given in the mapStateToProps. But if I attempt to filter the data inside medictionschedule based on the date then the sectionlist doesn't load anything in the screen.
Filtering in an external function also not helping here as shown below.
medicineForTheDate = () => {
this.props.filteredMedications.filter((schedule) => {
if (schedule.date === '2019-08-27') {
return schedule.medications
}
})
}
then inside the SectionList I would call this.medicineForTheDate()
<SectionList
renderItem={({item, index, section}) => <MedicineRow showTitle={0} key={index} setWidth='80%' title={item.name} mg={item.mg} capsules={item.capsules} onPress={() => this.medicineRowTapped(item.name)} medstatus={item.isTaken}/>}
stickySectionHeadersEnabled={false}
renderSectionHeader={({section: {title}}) => (
<SectionTitle showTitle={true} title={title}/>
)}
sections={
this.medicineForTheDate()
}
keyExtractor={(item, index) => item + index}
/>
I also tried filtering inside the mapsStateToProps but that also didn't help.
function mapStateToProps(state) {
return {
filteredMedications : state.medicationschedule.filter((schedule)=>{ schedule.date === '2019-08-27' })
}
}
and then...
<SectionList
renderItem={({item, index, section}) => <MedicineRow showTitle={0} key={index} setWidth='80%' title={item.name} mg={item.mg} capsules={item.capsules} onPress={() => this.medicineRowTapped(item.name)} medstatus={item.isTaken}/>}
stickySectionHeadersEnabled={false}
renderSectionHeader={({section: {title}}) => (
<SectionTitle showTitle={true} title={title}/>
)}
sections={
this.props.filteredMedications.medications
}
keyExtractor={(item, index) => item + index}
/>
How do I go about filtering data in this context?

For a start, the filter method expects a function, which takes in a parameter (the item inside the array) and returns a true (if the item should be in the filtered array) or a false (if the item should NOT be in the filtered array)
For example:
const arr = [1, 2, 3, 4, 5];
const evenNumbersOnly = arr.filter((item) => {
if (item % 2 === 0) {
// Even (Want it in the list
return true;
} else {
// Odd (Don't want it in the list
return false;
}
});
// evenNumbersOnly will now be [2, 4]
Try changing your filter method and see if the filtered array is what you are expecting. From there, I believe you will be able to continue debugging if it still does not show expected results.

Try this!
medicineForTheDate() {
let filteredValue = this.props.filteredMedications.filter((schedule) => {
if (schedule.date === '2019-08-27') {
return schedule.medications
}
});
return filteredValue;
}

You are close but you have some minor errors in each of your approaches.
In approach 1 with the external method you are not returning a value from the method and filter is not working like you expect. The filter method on an array will return another array only containing the elements of the original array where the filter clause returns a truthy value. So in your example even though you return schedule.medications you are still ending up with an array with the original data. The most similar code to your intention I believe is this:
medicineForTheDate = () => {
const matchingMedications = this.props.filteredMedications.filter((schedule) => {
return schedule.date === '2019-08-27'; // filter if dates are equal
})
if (matchingMedications.length) { // if we have a result
return matchingMedications[0].medications;
}
// decide about a default value if there is no match
}
That said I think a for loop is more clear:
medicineForTheDate = () => {
const {filteredMedications} = this.props;
for (let i=0; i < filteredMedications.length; i++) {
const schedule = filteredMedications[i];
if (schedule.date === '2019-08-27') {
return schedule.medications
}
}
// decide if you want to return a different value if no match is found
}
For the second case you have similar errors - using filter incorrectly and not actually returning a value from filter. Again something like
function mapStateToProps(state) {
return {
filteredMedications : state.medicationschedule.filter((schedule)=>{ return schedule.date === '2019-08-27' })[0]
}
}
will work as long as you account for filteredMedications being undefined in your component in that case if there is no match for the date.

Related

Why response type is different?

I am building a simple gallery management app with React Native.
In gallery page, I called "FetchGallery" function and in there, I got some response.
This is constructor part.
this.state = {
mainGalleryData: [
{
bg_url: '',
country_id: 0
}
],
};
That is the code for response.
_onFetchGalleryBySite = (e) => {
fetch(config.api.getGalleryInfo + '/' + e, {
method: 'GET',
headers: this.state.myHeaders
})
.then((response) => response.json())
.then((responseJSON) => {
console.log('resJSON=>', responseJSON['gallery_list']); // => log is in the below.
responseJSON['gallery_list'].map(item => {
if (item != "") {
let obj = {}
obj.bg_url = item.bg_url
obj.country_id = item.country_id
this.state.mainGalleryData.push(obj)
}
})
})
.catch(err => console.log('_onFetchGalleryInfoErr=>', err))
}
This is the log of fetched data
resJSON => [{ "bg_url": "staff_upload/bgremoval_20201008030228.png", "country_id": "3" },
{ "bg_url":"Guest/1/image/bgremoval_20201004222851.png", "country_id": "3" }]
And in the render() , I used this data in the flatList
That is the code for FlatList
<FlatList
data={mainGalleryData}
renderItem={this.renderGallery}
keyExtractor={(item, index) => index.toString()}
/>
This is the renderGallery.
renderGallery = (item) => (
<TouchableOpacity onPress={() => console.log('itemClicked=>', item)} style={styles.overlay}>
<Image source={newImg} style={styles.newImg} />
{
this.state.gallery_id == item.location_id ?
<Text style={{ fontWeight: '700' }} numberOfLines={1}>{item.item.title}</Text>
:
<Text style={{ fontSize: 12 }} numberOfLines={1}>{item.item.title}</Text>
}
</TouchableOpacity>
)
When I click one of those data, I want to get this data. But the result is different that I expect.
I want: { "bg_url": "staff_upload/bgremoval_20201008030228.png", "country_id": "3" }
result: { "index": 2, "item": {"Guest/1/image/bgremoval_20201004222851.png", "country_id": "3"}}
So I had to get country_id with "item.item.country_id".
I want to get country_id with "item.country_id".
What is this <"index": 2> ?
Before pushing response in state, please clear "mainGalleryData[]"
because default it has 1 item in state which makes index
inappropriate.
Do this
console.log('resJSON=>', responseJSON['gallery_list']);
const responseData = responseJSON['gallery_list'];
const data = this.state.mainGalleryData.concat(responseData); <-- add this line -->
this.state({ mainGalleryData: data });

Apollo-Client refetch - TypeError: undefined is not an object

I have a flatlist in react-native and I am trying to refetch the data when pulling it down (the native refresh functionality). When I do, I am getting this error:
Typeerror: undefined is not an object
I can't figure out what is going wrong. I am using
Expo SDK 38
"#apollo/client": "^3.1.3",
"graphql": "^15.3.0",
This is my code:
export default function DiscoverFeed({ navigation }) {
const theme = useTheme();
const { data, error, loading, refetch, fetchMore, networkStatus } = useQuery(
GET_RECIPE_FEED,
{
variables: { offset: 0 },
notifyOnNetworkStatusChange: true,
}
);
if (error) return <Text>There was an error, try and reload.</Text>;
if (loading) return <Loader />;
if (networkStatus === NetworkStatus.refetch) return <Loader />;
const renderItem = ({ item }) => {
return (
<View style={styles.cardItems}>
<RecipeCard item={item} navigation={navigation} />
</View>
);
};
return (
<SafeAreaView style={styles.safeContainer} edges={["right", "left"]}>
<FlatList
style={styles.flatContainer}
data={data.recipe}
removeClippedSubviews={true}
renderItem={renderItem}
refreshing={loading}
onRefresh={() => {
refetch();
}}
keyExtractor={(item) => item.id.toString()}
onEndReachedThreshold={0.5}
onEndReached={() => {
// The fetchMore method is used to load new data and add it
// to the original query we used to populate the list
fetchMore({
variables: {
offset: data.recipe.length,
},
});
}}
/>
</SafeAreaView>
);
}
I have a typepolicy like so:
export const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
recipe: {
merge: (existing = [], incoming, { args }) => {
// On initial load or when adding a recipe, offset is 0 and only take the incoming data to avoid duplication
if (args.offset == 0) {
console.log("offset 0 incoming", incoming);
return [...incoming];
}
console.log("existing", existing);
console.log("incoming", incoming);
// This is only for pagination
return [...existing, ...incoming];
},
},
},
},
},
});
And this is the query fetching the data:
export const GET_RECIPE_FEED = gql`
query GetRecipeFeed($offset: Int) {
recipe(order_by: { updated_at: desc }, limit: 5, offset: $offset)
#connection(key: "recipe") {
id
title
description
images_json
updated_at
dishtype
difficulty
duration
recipe_tags {
tag {
tag
}
}
}
}
`;

How to render an array within a JSON object in react-native

I am developing an app with react-native , using RESTfull api with laravel backend , I have an object that has arrays in it . I am using it to create user's profile page . but I don't know how to render those arrays.
here is my api response:
{
meta: {
code: 200,
message: 'Success',
errors: []
},
data: {
users: [
{
personal_information: {
full_name: 'hesam sameni',
avatar: 'https://aaa/file/image/hesamsameni.jpeg',
rate: '0.0',
phone: [],
location: {
name: 'something',
lat: 00,
long: 00,
address: 'something',
distance: 00
},
about: {
quote: 'something',
bio: 'something'
},
topics: {
a: 'a',
b: 'b',
c: 'c',
d: 'd'
},
stats: {
a: 10,
b: 0,
c: 0,
d: 0,
e: 0
},
experiences: [
{
title: 'something',
organization: 'something',
start: 'something',
end: 'something'
}
],
educations: [
{
title: 'something1',
university: 'something1',
major: 'something1',
start: 'something1',
end: 'something1'
},
{
title: 'something2',
university: 'something2',
major: 'something2',
start: 'something2',
end: 'something2'
}
]
}
}
]
}
};
For example how can I show educations in user's profile?
here is the render method ( I already got data from api and stored in state {this.state.dataSource}
render method:
render() {
return (
<View>
<FlatList
data={this.state.dataSource}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => (
<View>
<Text>Name: {item.personal_information.full_name}</Text>
<Text>Rate: {item.personal_information.Rate}</Text>
<Text>Educations:</Text>
// Here I need to display all of user's educations
</View>
)}
/>
</View>
);
}
I am not sure if I had to use flatlist since it is only data for 1 specific user , but I didn't know how to render data in other way than flatlist .
try this it will help you achive what you want.
if not tell me..
<FlatList
data={this.state.itemArray}
keyExtractor={(item, index) => index.toString()}
renderItem={({item}) =>{
console.warn(item.data.users[0].personal_information.educations)
return(<FlatList
data={item.data.users[0].personal_information.educations}
keyExtractor={(item, index) => "D"+index.toString()}
renderItem={({item}) =>{
console.warn(item)
}
}/>)
}
}/>
if u want to get something from personal_information
try this example : console.warn(item.data.users[0].personal_information.full_name)
So .map itself only offers one value you care about... That said, there are a few ways of tackling this:
// instantiation
this.state.dataSource
//Using `.map` directlly
this.state.dataSource.map((obj, index)=> {
console.log(obj);
return(
<View />
)
});
// what's built into Map for you
this.state.dataSource.forEach( (val, key) => console.log(key, val) );
// what Array can do for you
Array.from( this.state.dataSource ).map(([key, value]) => ({ key, value }));
// less awesome iteration
let entries = this.state.dataSource.entries( );
for (let entry of entries) {
console.log(entry);
}
Note, I'm using a lot of new stuff in that second example... ...Array.from takes any iterable (any time you'd use [].slice.call( ), plus Sets and Maps) and turns it into an array... ...Maps, when coerced into an array, turn into an array of arrays, where el[0] === key && el[1] === value; (basically, in the same format that I prefilled my example Map with, above).
I'm using destructuring of the array in the argument position of the lambda, to assign those array spots to values, before returning an object for each el.
If you're using Babel, in production, you're going to need to use Babel's browser polyfill (which includes "core-js" and Facebook's "regenerator").
I'm quite certain it contains Array.from.
render(){
console.log(this.state.data.users[0].personal_information.educations,'cdscdscdcds')
return(
<div>
{this.state.data.users.map((value)=>{
console.log(value,'cd')
})}
</div>

renderRows how to get value of last item

I am rendering data fetched from an api response.
I want to be able to compare the current row being rendered with the last one.
Example: comparing item day with the last item. to be able to insert a separator of the day.
this is just a simple example of what I am trying to do.
//data
const array = [
{day: '3/14', name: 'item1'},
{day: '3/14', name: 'item2'},
{day: '3/14', name: 'item3'},
{day: '3/15', name: 'item4'}
]
if new day, I will insert a separator. the intention is to group data by day.
let lastDay = null;
renderRows(item, index) {
//conditions
let day = item.day;
if(day != lastDay) daySeparator = true;
//store last day
lastDay = day;
return (
<ListItem noIndent style={style.listRow}>
<Row style={[style.tableRow,{alignItems: 'center'}]}>
<Text>{item.name}</Text>
</Row>
</ListItem>
);
}
My problem is that renderRows does not work like for loops, it render each item as separate without being able to keep track of previous rendered items.
is it possible to do it as so, or do I need to manipulate the array before passing it to RenderRows? if so, what's the most optimal way to do it, foreach? for loop?
Now that your question is more clear I can suggest you to use something like <SectionList> (see doc).
In order to use a SectionList you have to pass it a sectioned array. With the data structure you posted I would group items by day:
const array = [
{ day: "3/14", name: "item1" },
{ day: "3/14", name: "item2" },
{ day: "3/14", name: "item3" },
{ day: "3/15", name: "item4" }
];
const grouped = array.reduce(
(result, item) => ({
...result,
[item["day"]]: [...(result[item["day"]] || []), item]
}),
{}
);
const sectionedList = Object.keys(grouped).map(item => ({
title: item,
data: grouped[item]
}));
Which will give you the following structure:
[
{
title: "3/14",
data: [
{ day: "3/14", name: "item1" },
{ day: "3/14", name: "item2" },
{ day: "3/14", name: "item3" }
]
},
{
title: "3/15",
data: [{ day: "3/15", name: "item4" }]
}
];
Now you can use it in a <SectionList>, for example:
<SectionList
renderItem={({item, index, section}) => {...}}
renderSectionHeader={({section: {title}}) => (
<Text style={{fontWeight: 'bold'}}>{title}</Text>
)}
sections={sectionedList}
keyExtractor={(item, index) => item + index}
/>
This way you can properly customize everything.
I really hope this could help you!
One option is to make a new array with map from your initial array. Hope this will get you on the right direction :)
const array = [1, 2, 3, 4]
export default class App extends React.Component {
render() {
const newArr = array.map ((item, idx) => {
const newItem = idx === array.length - 1 ? 'Gotta keep them separated' : item
return <Text>{newItem}</Text>
})
return (
<View>
{newArr}
</View>
);
}
}
Here's a working expo snack

React Native / Using strings as data in FlatList?

I have a JSON with the following shape for ~50 _source entries:
{
"hits": [
{
"_source": {
"name": "henry",
"jobs": ["judge", "lawyer"]
}
},
{
"_source": {
"name": "henry",
"jobs": ["dev", "waitress"]
}
}
// ...
]
}
Thanks to the community's help, I extracted each jobs as below:
const result = hits.reduce((acc, item) => acc = [item._source.jobs[0], ...acc], [])
console.log(result) // this is an array
I extracted each item from result to add a string (for example "welcome judge"):
for(i in result)
{
var message = 'welcome'+ result[i] //this is a string
}
So now, I want to use a flatlist to render my message:
constructor() {
super()
this.state = { dataSource:'' }
}
componentDidMount() {
fetch('uri')
.then(response => response.json())
.then(json => {
const result = hits.reduce((acc, item) => acc = [item._source.jobs[0], ...acc], []) // this is an array
for(i in result)
{
var message = 'welcome'+ result[i] //this is a string
}
this.setState({ dataSource : messsage})
}
renderItem =({item}) => {
return(
<View>
<Text>item</Text>
</View>)
}
render() {
return (
<View>
<FlatList
data= {[this.state.dataSource]}
renderItem= {this.renderItem}
/>
</View>
);
}
I got only one message (and not my list) and the warning 'missing key for item'
You should have keyExtractor={(x, i) => i.toString()} in your flatlist.
<FlatList
data= {[this.state.dataSource]}
keyExtractor={(x, i) => i.toString()}
renderItem= {this.renderItem}
/>
Here is a FlatList keyExtractor definition.
You will only get a single message, because your input data is just a string (converted to an array in render() to fit specifications). You change your single string variable in every iteration and update with the last changed one. You do need to push every string into an array, before you continue to the next item in the Iterable.
constructor() {
super()
this.state = { dataSource: [] }
}
componentDidMount() {
fetch('uri')
.then(response => response.json())
.then(json => {
// Get all jobs in a single array
const results = hits.reduce((acc, item) => acc = [item._source.jobs[0], ...acc], []);
// Iterate over results, concatenate with 'welcome' and push into a new array
let messages = [];
for(i in result)
{
let message = 'welcome'+ result[i];
messages.push(message);
}
// Update state with the new array 'messages'
this.setState({ dataSource : messages })
}
renderItem = ({ item }) => {
return(
<View>
<Text>{item}</Text>
</View>
);
}
render() {
return (
<View>
<FlatList
data={this.state.dataSource}
keyExtractor={(x, i) => i.toString()}
renderItem= {this.renderItem}
/>
</View>
);
}
Because your data source contains single string. Here you are updating the message var on each iteration, so it will have just the last string of the result array with 'hello' prepended.
for(i in result)
{
var message = 'welcome'+ result[i]
}
You should do something like this
componentDidMount() {
fetch('uri')
.then(response => response.json())
.then(json => {
const result = hits.reduce((acc, item) => acc = [item._source.jobs[0], ...acc], [])
let messages=[];
for(i in result)
{
messages.push('welcome '+ result[i]); //push each element in array
}
this.setState({ dataSource : messsages})
}
Use key extractor to remove missing key warning
render() {
return (
<View>
<FlatList
data= {[this.state.dataSource]}
renderItem= {this.renderItem}
keyExtractor={(item, index) => item + index}
/>
</View>
);
}