Using a local img in flatlist (React-Native) - javascript

My problem is probably very basic, but im new to programming.
Basically I want to see a flatlist with for each category the title and the img
This is my category constructor:
class Category{
constructor(id, title, catImg){
this.id = id;
this.title = title;
this.catImg = catImg;
}
}
these are my 3 categories:
export const CATEGORIES = [
new Category('c1', 'Studeren', require('../assets/studeren.png')),
new Category('c2', 'Wonen', require('../assets/wonen.png')),
new Category('c3', 'EersteVoertuig', require('../assets/voertuig.png'))
];
this is where I want to call the elements:
const CategoryGridTile = props => {
return(
<TouchableOpacity style = {styles.gridItem} onPress = {props.onSelect}>
<View >
<Text>{props.title}</Text>
</View>
<Image source ={props.catImg} style={‌{width: 150, height: 150}}/>
</TouchableOpacity>
);
};
It does work for the props.title part, but it doesnt for the props.catImg part
(Meaning I can see the title, but no image. I have tried to directly put the img path instead of props.catImg, and that works, but thats not how I want to do it)
EDIT: I figured this code is also needed to understand my mistake?
const CategoryScreen = props => {
const renderGridItem = (itemData) => {
return <CategotyGridTile
title ={itemData.item.title}
onSelect={() =>{
props.navigation.navigate({
routeName: 'CategorieVragen',
params: {
categoryId: itemData.item.id
}});
}}/>;
};
return(
<FlatList
data={CATEGORIES}
renderItem ={renderGridItem}
numColums = {2}
/>
)
}

you have to send CATEGORIES as props to CategotyGridTile
in CategoryScreen
return <CategotyGridTile
categories={CATEGORIES}
{...props}
/>
in CategoryGridTile
<Image source ={props.categories.catImg} style={‌{width: 150, height: 150}}/>

Related

React Native/API - onPress returns all links in API instead of the link relevant to the item being pressed

Apologies, couldn't figure out a straightforward way to title this, so thanks in advance.
I am tasked with creating a page of links supplied by an API I made that connects to a CMS. The screen gathers the below data to be viewed on the screen:
Title,
Content
These items, along with a URL live within a "Resource". The URL is not visible to the user, but it is grouped with its own Title and Content.
Currently things show correctly on the screen, but when trying to connect the URL to the Resource, I'm unable to have the page navigate correctly. When I console.log(thing-I'm-returning), it sends me back all URLs for all Resources, and if the private browser opens to a web page, it might open to any in the list. This happens when I press any of the Resources.
Code below (first time posting, I'm fully desperate. Let me know if this looks like trash and I'll correct however is ideal).
const { resourceData } = useResourceContent(binding);
const resourceList = resourceData?.Resources?.map((r, i) => ({
id: i.toString(),
title: r.Title,
url: r.Url,
content: r.Content,
}));
const resourceDetails = resourceData?.Resources;
const { openUrl } = useWebBrowser();
const { resourceData } = useResourceContent(binding);
const resourceDetails = resourceData?.Resources;
const urlList = [];
const handleOpenSite = () => {
resourceDetails?.map((r, i) =>
{if (resourceDetails !== undefined && resourceDetails) {
urlList.push(r.Url);
}
console.log(urlList[i]); //let's say there are 2 resources, each with their own website. This will return both websites no matter what resource I select
//the below is required, as a private browser is required
return openUrl(urlList[i]);
});
};
API looks something like:
[{"Content": "Test. ", "Url": "https://instagram.com", "Title": "blah blah blah"},
{{"Content": "Test2.", "Url": "https://google.com.com", "Title": "blah blah blah"},]
Here's the View, though I'm unsure if it's necessary here.
<View>
<ResourceNavigationList
onPress={handleOpenSite}
small
listItems={resourceList}
backgroundColor="transparent"
/>
</View>
And here's the ResourceNavigationList component, which is likely the issue since it's a little bit nonsense.:
const ResourceNavigationList = ({
listItems,
backgroundColor,
small,
reverse,
onPress,
}) => {
const { ct } = useCountryTranslation();
const colorScheme = useColorScheme();
const bgColor = backgroundColor || Colors[colorScheme].altBackground;
const { openUrl } = useWebBrowser();
const renderItem = ({ item, rUrl }) => {
const handleOnPress = () => {
if (item) {
openUrl(rUrl).toString();
console.log("WHY AREN'T YOU OPENING?");
}
};
return (
<ResourceNavigationListItem
key={key}
reverse={reverse}
small={small}
item={item}
// url={rUrl}
onPress={onPress}
/>
);
};
return (
<FlatList
renderItem={renderItem}
data={listItems}
keyExtractor={(item) => item.id}
style={{
paddingVertical: 20,
backgroundColor: bgColor,
}}
/>
);
};
ResourceNavigationList.propTypes = {
listItems: PropTypes.array.isRequired,
backgroundColor: PropTypes.string,
small: PropTypes.bool,
reverse: PropTypes.bool,
onPress: PropTypes.func,
};
export default ResourceNavigationList;
Finally, here's the ResourceNavigationListItem
const ResourceNavigationListItem = ({ item, onPress, style, small }) => {
const styles = StyleSheet.create({
//styling is here, but leaving it off because it isn't relevant and took up a lot of space
});
return (
<TouchableOpacity onPress={onPress} style={[styles.item, style]}>
<View style={styles.title}>
<Icon style={styles.linkArrow} size={16} icon={faExternalLink} />
</View>
<View style={styles.title}>
<Text style={styles.titleText}>
{item.title ? decode(item.title) : item.title}
</Text>
</View>
<View style={styles.title}>
<Text style={styles.titleText}>
{item.content ? decode(item.content) : item.content}
</Text>
</View>
</TouchableOpacity>
);
};
ResourceNavigationListItem.propTypes = {
item: PropTypes.shape({
icon: PropTypes.object,
title: PropTypes.string,
content: PropTypes.string,
}).isRequired,
onPress: PropTypes.func,
style: PropTypes.object,
small: PropTypes.bool,
};
export default ResourceNavigationListItem;
Thanks so very much.
I've tried mapping and for-looping. I've tried applying the mapping directly to the component. These have gleaned me the most success. Most everything else I've tried didn't return anything at all, or returned everything many times.
I've been struggling for a few days and have found lots of solutions similar to my problem within stackoverflow, but nothing fully relevant/recent (I'm fairly newb with regard to backend tingz). If y'all happen upon something I missed, please be kind, and if you'd be down to help me, I'd be so very grateful.
I took a look and I think this has to do with confusion regarding the props hierarchy within your example.
Right now you are passing onPress down from the parent of ResourceNavigationList:
Parent > onPress
ResourceNavigationList
ResourceNavigationListItem
While there is nothing inherently incorrect about the parent owning the onPress function, you have everything you need to perform the desired onPress functionality within the ResourceNavigationList component. It looks like you were almost there with your handleOnPress function.
Based on the data contract you provided, it looks like you should be able to do this:
const ResourceNavigationList = ({
listItems,
backgroundColor,
small,
reverse,
onPress,
}) => {
const {
ct
} = useCountryTranslation();
const colorScheme = useColorScheme();
const bgColor = backgroundColor || Colors[colorScheme].altBackground;
const {
openUrl
} = useWebBrowser();
const renderItem = ({
item,
rUrl
}) => {
const handleOnPress = () => {
if (item && item ? .Url) {
openUrl(item ? .Url);
// If you do need to perform additional logic controlled
// by the parent component, you can add a quick line to
// execute the onPress function if it has been provided.
if (onPress) onPress(item);
}
};
return ( <
ResourceNavigationListItem key = {
key
}
reverse = {
reverse
}
small = {
small
}
item = {
item
}
onPress = {
onPress
}
/>
);
};
return ( <
FlatList renderItem = {
renderItem
}
data = {
listItems
}
keyExtractor = {
(item) => item.id
}
style = {
{
paddingVertical: 20,
backgroundColor: bgColor,
}
}
/>
);
};
ResourceNavigationList.propTypes = {
listItems: PropTypes.array.isRequired,
backgroundColor: PropTypes.string,
small: PropTypes.bool,
reverse: PropTypes.bool,
onPress: PropTypes.func,
};
export default ResourceNavigationList;

React Native Updating an Array of JSON objects onChangeText, index undefined

I've got a useState variable that has an array of JSON objects, and I'm trying to get the text fields to dynamically render and update with onChangeText, but I'm having a bit of an issue with this.
When I add console.log(index) to updateHashtags, it says it is undefined, and I can't understand what I'm doing wrong or can do to make this work. In theory, the index should be a static number for each text field. So the first text field would have hashtags[0] as its value and use '0' as the index for updating the state of hashtags.
When I use:
console.log('index',index);
inside of updateHashtags, I get:
index undefined
Here's the code:
const updateHashtags = (text, index) => {
let ht = hashtags;
ht[index].name = text;
setHashtags(ht);
}
const hashtagElement = (
<>
<Text style={styles.plainText} >Set Your Values:</Text>
<Text style={styles.instructionsText} >This is what other users use to search for you.</Text>
{hashtags.map((e,index) =>
<TextInput
placeholder='value'
key={e.name + index}
value={hashtags[index].name}
onChangeText={(text,index) => updateHashtags(text,index)}
style={styles.textInput}
/>
)}
<TouchableOpacity
onPress={() => {
let ht = hashtags;
let newht = {
name: '',
weight: 0,
};
ht[ht.length] = newht;
setHashtags(ht);
}}
>
<Ionicons
name="add-circle-outline"
size={40}
color={'black'}
/>
</TouchableOpacity>
</>
);
Maybe the onChangeText functions doens't get index param.
Try it like this:
// we are getting index from here
{hashtags.map((e,index) =>
<TextInput
placeholder='value'
key={e.name + index}
value={hashtags[index].name}
// so no need of taking index from param here
onChangeText={(text) => updateHashtags(text,index)}
style={styles.textInput}
/>
)}
Also for your updateHashTags functions consider this insted:
const updateHashtags = (text, index) => {
// doing it this way ensures your are editing updated version of state
setHashtags((state) => {
let ht = state;
ht[index].name = text;
return ht;
});
}

Attempting to create a dynamic react pdf report generator based on params

Code below:
codesandbox.io/s/muddy-monad-zqwt73?file=/src/App.js
Currently, unsure why, the pages are not making it into the document and an empty Document is rendered.
I am unsure why. To me, this reads as I have created page elements, placed them in a list, and I should be able to map through and render all the pages to the Document. I am unsure why it is not working.
EDIT
As per suggestion, I changed the code to look as follows:
const styles = StyleSheet.create({
header: {
flexDirection: 'row',
textAlign: 'left',
fontSize: '30px',
fontFamily: 'SF Pro Text',
marginBottom: '25px',
marginTop: '30px',
marginLeft: '30px',
justifyContent: 'space-evenly'
}
})
class DavidPDF extends Component {
constructor(name, params) {
super(name, params);
this.name = name;
this.params = params;
this.content = [];
}
buildPages() {
console.log("building")
for (let i = 0; i < this.params.length; i += 1) {
console.log(i)
let jsxPage = this.buildPage(this.params[i])
this.content.push(jsxPage)
}
}
buildPage(pageParams) {
console.log("building indv page")
const pageName = pageParams.pageName;
const viewParams = pageParams.views;
const pageViewParams = [];
for (let i = 0; i < viewParams.length; i += 1) {
let view = this.buildView(viewParams[i]);
pageViewParams.push(view);
}
return (
<Page>
<View>{pageName}</View>
</Page>
)
}
buildView(viewParams) {
if (viewParams.viewType == "headerText") {
const text = viewParams.text;
return (
<View>
<Text style={styles.header}>
{text}
</Text>
</View>
)
}
}
render() {
console.log("rendering")
this.buildPages(this.params)
console.log(this.content)
return (
<Document>
{this.content}
</Document>
)
}
}
function Test() {
const pagesInfo = [
{
pageName: "Xtina's Page",
views: [
{
viewType: "headerText",
text: "Xtina's page"
}
]
}
]
let wtf = ["hi2", "hi1", "hi3"]
const wtfItems = wtf.map((item) => <div>{item}</div>)
return (
<div id="first">
{wtf.map((item) => <div>{item}</div>)}
<PDFViewer>
<DavidPDF name="hello" params={pagesInfo} />
</PDFViewer>
</div>
)
}
export default Test;
--- EDIT ----
Hurrah! That error was fixed. Now we have a new one - the pages will not go in.
There's a few issues, but if you can toss this into codesandbox would be happy to help. You're iterating over maps needlessly, not assigning keys to mapped elements, but the error you are seeing is most likely related to:
const pdf = new DavidPDF("hello", pagesInfo)
This is an instantiation of a class, which is an object, so the error makes sense.
Why not add it into render as so:
<DavidPdf name="hello" params={pageInfp} /> or whatever the props are?
Also, you can run buildPages on componentDidMount, and not worry about calling it from the parent.
Instead of approaching it this way, which does not play well, I have switched to generating a page with all my information of interest and adding print css so that a when a user prints (or I print to send to a user), the data is formatted nicely with break points for pages using only css and #media print. I would recommend this strategy to others and am happy to elaborate further if anyone wants!

How to render AdMob banner in React Flatlist between items?

I have a React Native Flatlist that only re-renders when its data has changed.
I give it the following data (as prop):
const posts = [
{
...post1Data
},
{
...post2Data
},
{
...post3Data
},
{
...post4Data
},
{
...post5Data
},
]
And here is my FlatList renderItem:
const renderItem = useCallback(({ item, index }) => {
const { id, userData, images, dimensions, text } = item;
return (
<View
onLayout={(event) => {
itemHeights.current[index] = event.nativeEvent.layout.height;
}}
>
<Card
id={id}
cached={false}
userData={userData}
images={images}
dimensions={dimensions}
text={text}
/>
</View>
);
}, []);
How can I add an AdMob ad between the FlatList data with a probability of 5% without skiping any data in the posts array?
I have tried this:
const renderItem = useCallback(({ item, index }) => {
const { id, userData, images, dimensions, text } = item;
if (Math.random() < 0.05) return <Ad ... />
return (
<View
onLayout={(event) => {
itemHeights.current[index] = event.nativeEvent.layout.height;
}}
>
<Card
id={id}
cached={false}
userData={userData}
images={images}
dimensions={dimensions}
text={text}
/>
</View>
);
}, []);
But this causes 2 problems:
Some items from data are skipped (not returned)
When the flatlist re-renders (because of some of its props changes) the ads might disappear (there is a chance of 95%).
Any ideas? Should I render the ads randomly in the footer of my Card component like this?
const Card = memo ((props) => {
...
return (
<AuthorRow ... />
<Content ... />
<SocialRow ... /> {/* Interaction buttons */}
<AdRow />
)
}, (prevProps, nextProps) => { ... });
const AdRow = memo(() => {
return <Ad ... />
}, () => true);
I am not really sure about this option, it works but it could violate the admob regulations (?) (because I am adapting the ad to the layout of my card component)
I would appreciate any kind of guidance/help. Thank you.
I'm not sure if you ever found a solution to this problem, but I accomplished this by injecting "dummy" items into the data set, then wrapping the renderItem component with a component that switches based on the type of each item.
Assuming your flatlist is declared like this:
<FlatList data={getData()} renderItem={renderItem}/>
And your data set is loaded into a variable called sourceData that is tied to state. Let's assume one entry in your sourceData array looks like this. Note the 'type' field to act as a type discriminator:
{
"id": "d96dce3a-6034-47b8-aa45-52b8d2fdc32f",
"name": "Joe Smith",
"type": "person"
}
Then you could declare a function like this:
const getData = React.useCallback(() => {
let outData = [];
outData.push(...sourceData);
// Inject ads into array
for (let i = 4; i < outData.length; i += 5)
{
outData.splice(i, 0, {type:"ad"});
}
return outData;
}, [sourceData]);
... which will inject ads into the data array between every 4th item, beginning at the 5th item. (Since we're pushing new data into the array, i += 5 means an ad will be placed between every 4th item. And let i = 4 means our first ad will show after the 5th item in our list)
Finally, switch between item types when you render:
const renderItem = ({ item }) => (
item.type === 'ad'
?
<AdComponent ...props/>
:
<DataComponent ...props/>
);

react native: function not returning react component

So I'm building a DnD app for character sheet management, where it displays the skills and whatnot.
In it I have a class(AbiltityClass) that stores a map of other classes(SkillClass) inside of a variable(_aSkills).
export default class AbilityClass {
constructor(name, aVal, aMod) {
this._abilityName = name; // string
this._abilityVal = aVal; // int
this._abilityMod = aMod; // int
this._aSkills = new Map(); // map of SkillClass objects
}
}
export default class SkillClass {
constructor(name, prof, mod, bonus){
this._nameSkill = name; // string
this._profSkill = prof; // bool
this._modBonus = bonus; // int
this._modSkill = this.evalMod(mod); // int
}
evalMod(mod) {
return mod + this._modBonus;
}
}
The _aSkills variable was originally an Array of SkillClasses, but due to ease-of-accessing, I decided that a Map would be better.
I have a process that will basically iterate through each element in _aSkills and create components out of them through the skillFactory and buildSkill functions which should be placed in the brackets where the skillFactory function is called.
const AbilityContainer = (props) => {
const buildSkill = (value, key) => {
return(
<SkillSet
key={Math.random()}
skillName={key}
prof={value._profSkill}
skillVal={value._modSkill}
setProf={()=>{}}
/>
);
}
const skillFactory = () => {
return(props.ability._aSkills.forEach(buildSkill));
}
return(
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.header_text}>
{props.ability._abilityName}
</Text>
</View>
<View style={styles.statBox}>
<View style={styles.ability}>
<StackedHex isAbility={true} lowerVal={props.ability._abilityVal}/>
</View>
<View style={styles.skills}>
{skillFactory()} // =================> The function call in question
</View>
</View>
</View>
);
};
My issue is that either skillFactory or buildSkill is not returning the SkillSet component.
I've used some print logs and I've verified that buildSkill is receiving the correct data, but something isn't working. I don't get any errors or warnings, and the place where the SkillSet components should be is just empty.
As I said, I changed the _aSkills variable from an Array to a Map recently, and it was working as an Array. This is the format of my previous code:
const AbilityContainer = (props) => {
const buildSkill = (skill) => {
return(
<SkillSet
key = {Math.random()}
prof={skill._profSkill}
setProf={skill._profFunc}
skillName={skill._nameSkill}
skillVal={skill._modSkill}
/>
);
}
const skillFactory = () => {
return (props.ability._aSkills.map(x => buildSkill(x)));
}
return(
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.header_text}>
{props.ability._abilityName}
</Text>
</View>
<View style={styles.statBox}>
<View style={styles.ability}>
<StackedHex isAbility={true} lowerVal={props.ability._abilityVal}/>
</View>
<View style={styles.skills}>
<SkillSet
prof={props.ability._saveProf}
setProf={props.ability._saveProfFunc}
skillName={'Save'}
skillVal={props.ability._saveMod}
/>
{skillFactory()}
</View>
</View>
</View>
);
};
I'm using Android Studio for this build and I'm still relatively new to react-native. I triple-checked all my other components and their stylesheets to make sure they weren't interfering, so I know it has something to do with the AbilityContainer.
I've read up on the forEach function for Maps and as far as I can tell, I'm doing it right, but I feel like there is something that forEach is doing that I don't understand in terms of how it returns things.
Does anyone have any idea what the issue could be?
Thanks in advance.
forEach does something for each element, but doesn't return anything (even if you return in the callback, that's just returning from the cb, not the equivalent of returning from a .map) which is why that version isn't working. If you convert it back to an array ([...props.ability._aSkills].map...) this will give you an array of arrays ([key, value]) from your Map. You could also use a for..of loop over _aSkills.entries().
So now knowing that forEach doesn't return anything, I just added a variable and gave my buildSkill function something to return to.
Here's what I did:
const AbilityContainer = (props) => {
let skills = new Array();
const buildSkill = (value, key) => {
skills.push(<SkillSet
key={Math.random()}
skillName={key}
prof={value._profSkill}
skillVal={value._modSkill}
setProf={()=>{}}
/>);
}
const skillFactory = () => {
props.ability._aSkills.forEach(buildSkill);
return(skills);
}
...
};

Categories