Is it possible to compose more different parts of component to variable?
documentation
const App = () => {
let element;
element = <View>
<Text>text</Text>
</View> // --> OK
element = element + <View>
<Text>text2</Text>
</View> // --> TypeError: Cannot read properties of undefined (reading 'height')
element.push(<View>
<Text>text3</Text>
</View>); // --> TypeError: element.push is not a function
return <>
{element}
</>
}
export default App;
I use reactjs 17.0.2, typescript and "#react-pdf/renderer": "2.3.0".
Update
Based on your question here, this should work:
<Document>
<Page size="A4" orientation="landscape">
{/* -- Table LEFT: -- */}
<View>
{/* -- Table Head: -- */}
<View>
<Text>Index</Text>
<Text>Brand</Text>
<Text>Type</Text>
</View>
{/* -- Table Body: -- */}
{data?.cars?.length &&
data.cars.map(({ id, brand, type }, index) => (
<View key={`${id}-left`}>
<Text>{index + 1}</Text>
<Text>{brand || ''}</Text>
<Text>{type || ''}</Text>
</View>
))}
</View>
</Page>
<Page size="A4" orientation="landscape">
{/* -- Table RIGHT: -- */}
<View>
{/* -- Table Head: -- */}
<View>
<Text>Color</Text>
<Text>Fuel</Text>
</View>
{/* -- Table Body: -- */}
{data?.cars?.length &&
data.cars.map(({ id, color, fuel }) => (
<View key={`${id}-right`}>
<Text>{color || ''}</Text>
<Text>{fuel || ''}</Text>
</View>
))}
</View>
</Page>
</Document>
The issue seems to be with how you're handling arrays, not with rending React elements.
If you want to access the properties of the object in an array element, you can destructure the element, so instead of
data.cars.map((car, index) => (<Text>{car.color}</Text>))
you can do
data.cars.map(({id, brand, type, color, fuel}, index) => (<Text>{color}</Text>));
If you're not performing any operations on the array elements, you can use an implicit return instead of an explicit return:
// explicit return
data.cars.map(({id, brand, type, color, fuel}, index) => {
// do something else here
return (
<Text>{color}</Text>
)
});
// implicit return
data.cars.map(({id, brand, type, color, fuel}, index) => (<Text>{color}</Text>));
Also, when you're rending known text values in React, you don't need to wrap it in curly braces ({}), you can just render the text directly.
Instead of
<Text>{'color'}</Text>
you can just put
<Text>color</Text>
unless it's required by whatever library you're using. I'm not familiar with #react-pdf/renderer.
One more thing to keep in mind is that the key for list items in React should be something stable. Using array indices as keys is discouraged (see React docs).
Original answer
If you want to render an element this way, you could do something like this:
const App = () => {
let element = [];
// Each child in a list needs a unique "key" prop
element.push(<View key={someUniqueKey}>
<Text>text</Text>
</View>)
element.push(<View key={someOtherUniqueKey}>
<Text>text2</Text>
</View>)
element.push(<View key={oneMoreUniqueKey}>
<Text>text3</Text>
</View>);
return <>
{element}
</>
}
export default App;
Personally, I haven't seen anyone render components like this.
The strategy you are looking for is called conditional rendering, and there are different ways to do this depending on the situation.
For example, if you're trying to dynamically render data from an API response, you could do something like this:
const App = () => {
const { data } = fetchDataFromAPI();
return (
<>
<View>
<Text>text</Text>
</View>
{data?.text2 && (
<View>
<Text>{data.text2}</Text>
</View>
)}
{data?.text3 && (
<View>
<Text>{data.text3}</Text>
</View>
)}
</>
);
};
export default App;
You can check out the React docs for conditional rendering and rendering lists.
(Note: The above links are for the beta docs. If you prefer the classic(?) docs: conditional rendering and lists)
Related
Im trying to pass a function handleNewFavourite (which updates my favouriteList state array) from my HomeScreen to my DetailsScreen via navigation params but Im getting the following error: Non-serializable values were found in the navigation state
How should I pass functions that modified the state between different stack screens?
HomeScreen code:
<FlatList
data={checkCategory()}
renderItem={({item}) => (
<TouchableOpacity
onPress={() =>
navigation.navigate('Details', {
item,
handleNewFavourite,
})
}>
<LessonCard lesson={item} />
</TouchableOpacity>
)}
/>
DetailScreen code:
const LessonDetails = ({lesson, handleNewFavourite}: LessonProps) => {
const [favourite, setFavourite] = useState<boolean>(lesson.favourite);
return (
<LessonDetailsContainer>
<LessonDetailsInfoContainer>
<LessonDetailsCategoryHead>
<LessonDetailsCategory>{lesson.category}</LessonDetailsCategory>
<TouchableOpacity
onPress={() => {
setFavourite(!favourite);
handleNewFavourite(lesson);
}}>
<LessonDetailsFavouriteIcon>
{favourite ? '❤️' : '🤍'}
</LessonDetailsFavouriteIcon>
</TouchableOpacity>
</LessonDetailsCategoryHead>
<LessonDetailsTitle>{lesson.title}</LessonDetailsTitle>
<LessonDetailsAuthor>{lesson?.author}</LessonDetailsAuthor>
</LessonDetailsInfoContainer>
<LessonDetailsCardImage
source={{
uri: lesson.image,
}}
/>
<LessonDetailsContentContainer>
<LessonDetailsDescriptionText>
{lesson.content}
</LessonDetailsDescriptionText>
</LessonDetailsContentContainer>
</LessonDetailsContainer>
);
};
export default LessonDetails;
For situation like this, you should learn global state management. ( Context API - Redux etc. )
I think you are disrupting in the wrong way the parameters passed to DetailScreen it should be something like this:
const LessonDetails = ({route}: LessonProps) => {
const {lesson, handleNewFavourite} = route.params;
// The rest of your component here
}
As the documentation here suggested. But as #omerfarukose mentioned is not a bad idea to use state management in this particular scenario
I'm trying to render data from an API but it does not have an id or any unique data to distinguish the item
This is how the data looks like when I do console.log
Array [
Object {
"photo1": "www.picexample.com",
},
Object {
"photo2": "www.picexample.com",
},
]
This is my code:
Home.js
const [portfolio, setPortfolio] = React.useState([]);
const renderItem= ({item}) => {
return (
<View>
<Image source={{uri: ?}} resizeMode="cover" />
</View>
);
}
useEffect (() => {
.then((result) => {
setPortfolio(result.myImage);
console.log(portfolio);
})
});
return (
<ScrollView>
<FlatList
scrollEnabled={false}
data={portfolio}
renderItem={renderItem}
keyExtractor={?}
/>
</ScrollView>
);
UPDATE (Based on Joel Hager)
const keyExtractor = (portfolio, idx) => `${Object.keys(portfolio)}-${idx}`
const renderItem = ({item}) => {
console.log(item);
return (
<View>
<Image source={{uri: item}} resizeMode="cover" />
</View>
);
}
return (
<FlatList
scrollEnabled={false}
data={portfolio}
renderItem={renderItem}
keyExtractor={keyExtractor}
/>
);
Without being able to post working React Native code, this will explain what it's doing conceptually.
It's taking the item instance in the array that's being passed (the 'item')
It's grabbing all keys for it: Object.keys()
It's displaying the first one
There are some caveats: It expects a value. You could always use null coalescence to do something else, but I'd have to know more about the data to cover those cases. If you always get a key, you'll always get a value. You can also add the index value in the extractor to give it some more specificity.
Your react code should look like this:
keyExtractor={(item, idx) => `${Object.keys(item)}-${idx}`}
note: It's always good to extract the function outside of the flatlist so it isn't recreated every render. So it would look something like this:
const keyExtractor = (item, idx) => `${Object.keys(item)}-${idx}`
...
<Flatlist
...
keyExtractor={keyExractor}
...
More info on keyExtractor:
https://reactnative.dev/docs/flatlist#keyextractor
The template literal will put the value of the string of the key and the index with a '-' between them.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
const payload = [
{
"photo1": "www.picexample.com",
},
{
"photo2": "www.picexample.com",
},
]
payload.map((item, idx) => console.log(Object.keys(item)[0] + `-${idx}`));
How I can fix the following error: Warning: Encountered two children with the same key, [object Object]. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
That is my List:
<List style={custom.PartList}>
<FlatList extraData={this.state} data={this.state.data} keyExtractor={this._keyExtractor.bind(this)} renderItem={this._renderItem.bind(this)} />
</List>
That is my List Item:
/* Render Item - Render One Row - Item - (Tool) */
_renderItem({ item }) {
const custom = styles(this.props);
return (
<View style={custom.PartView}>
<ListItem style={custom.PartListItem} onPress={() => this._handleRead(item.tool_id, item.tool_name, item.tool_description, item.tool_count, item.tool_availability)}>
<Image style={custom.PartImage} source={require('#app/assets/images/tools.png')}/>
<Text style={custom.PartName}>{item.tool_name}</Text>
</ListItem>
</View>
);
}
/* /Render Item - Render One Row - Item - (Tool)/ */
And that is my keyExtractor method:
/* Key Extractor Method - For Index Tools */
_keyExtractor(index) {
return index.toString();
}
/* /Key Extractor Method - For Index Tools/ */
For FlatList Code
<List style={custom.PartList}>
<FlatList extraData={this.state} data={this.state.data}
keyExtractor={(item, index) => index.toString()}
renderItem={this._renderItem.bind(this)} />
</List>
You're binding this to keyExtractor function, so it'll provide this object as a first parameter (where you refer it as index). Therefore the return value will always be the string presentation of Object (= [Object object])
The simple solution is just to declare keyExtractor={this.keyExtractor} without any binding.
For me it worked adding exactly like this:
<FlatList
data={mySkills}
keyExtractor={this.keyExtractor}
renderItem={({item, index}) => <SkillCard key={index} skill={item} />}
/>
How do I get rid of this warning? I know I need to get rid of setState functions in the render method, but I need them, so where should I put them?
export default class List<T> extends React.PureComponent<ListProps<T>> {
state = { wrapped: false, iconName: "arrow-down" };
render(): React.Node {
const { rows, renderRow, title, onPress } = this.props;
if (this.state.wrapped === true) {
list = undefined;
this.setState({ iconName: "arrow-up" });
} else {
list = rows.map((row, index) => (
<View key={index} style={index !== rows.length - 1 ? styles.separator : {}}>
{renderRow(row, index)}
</View>
));
this.setState({ iconName: "arrow-down" });
}
return (
<TouchableWithoutFeedback>
<View style={styles.container}>
<View style={[styles.separator, styles.relative]}>
<Text style={styles.title}>{title}</Text>
<IconButton
style={styles.icon}
onPress={() => this.setState({ wrapped: !this.state.wrapped })}
name={this.state.iconName}
color="black"
/>
</View>
{list}
</View>
</TouchableWithoutFeedback>
);
}}
No, you don't need to get rid of setState calls in your render method in general. You just need to put them so that they are not called in each render call (by binding them to user events like clicks for example) and thereby trigger another re-render, that again calls setState and again re-renders and so on.
So in your particular case, you are firing setState right in the beginning in the if() { ... } else { ... } statements. No matter what this.state.wrapped is, you end up at setState.
Here is a possible solution for how you might want to change your code specifically to make it what I assume you want it to make:
export default class List<T> extends React.PureComponent<ListProps<T>> {
state = { wrapped: false };
render(): React.Node {
const { rows, renderRow, title, onPress } = this.props;
const { wrapped } = this.state;
return (
<TouchableWithoutFeedback>
<View style={styles.container}>
<View style={[styles.separator, styles.relative]}>
<Text style={styles.title}>{title}</Text>
<IconButton
style={styles.icon}
onPress={() => this.setState({ wrapped: !wrapped })}
name={wrapped ? "arrow-up" : "arrow-down"}
color="black"
/>
</View>
{!wrapped && (
<View key={index} style={index !== rows.length - 1 ? styles.separator : {}}>
{renderRow(row, index)}
</View>
)}
</View>
</TouchableWithoutFeedback>
);
}}
Because the value of your icon is directly correlated to wrapped, you don't need to specifically set the icon in the state. Rather infer it from wrapped.
I'm creating a list of collapsible using the react native flatlist component.
I'm using ref attribute to get the item clicked.
But when i try to access the ref from the click event, it doesn't take effect on the clicked item but on the last item in the flatlist.
export default class Update extends Component {
renderItems (data, index) {
return (
<TouchableNativeFeedback
onPress={() => this.expandView()}
>
<View style={[styles.itemWrapperExpandable]}>
<View style={styles.itemHeader}>
<View style={styles.itemAvatar}>
<Image source={require('../images/logo.png')} style={styles.avatar}></Image>
</View>
<View style={styles.itemContent}>
<Text style={[styles.itemTitle, styles.black]}>{data.name}</Text>
<Text style={[styles.rating, styles.grey]}>
{data.rating}<Icon name="star"></Icon>
</Text>
<Text style={[styles.content, styles.black]}>{data.description}</Text>
</View>
<View style={styles.itemBtn}>
<Icon name="chevron-down" style={{ color: '#000', fontSize: 22 }}></Icon>
</View>
</View>
<View ref={(e) => this._expandableView = e } style={[styles.itemBody]}>
<Text style={styles.itemBodyText}>
some more information about this update will appear here
some more information about this update will appear here
</Text>
</View>
</View>
</TouchableNativeFeedback>
);
}
expandView () {
LayoutAnimation.easeInEaseOut();
if (this._expandableView !== null) {
if (!this.state.isExpanded) {
// alert(this.state.isExpanded)
this._expandableView.setNativeProps({
style: {height: null, paddingTop: 15, paddingBottom: 15,}
})
}
else {
this._expandableView.setNativeProps({
style: {height: 0, paddingTop: 0, paddingBottom: 0,}
});
}
this._expandableView.setState(prevState => ({
isExpanded: !prevState
}));
}
}
render() {
return (
<FlatList
data={this.state.data}
renderItem={({ item, index }) => this.renderItems(item, index)}
/>
)
}
}
I also tried placing using the index of the items, but couldn't make it work.
Any way around this? I think the ref are overwritten by the next one when the items are rendering.
You are right about your assumption. Ref is overwriting with the next item so ref is the last item's ref. You can use something like below to set each items ref separately.
ref={(ref) => this.refs[data.id] = ref}
Of course this solution assumes you have a unique id or sort in your item data.
To paraphrase the React Native docs, direct manipulation (i.e. refs) should be used sparingly; unless you need it for some other reason I'm unaware of, refs aren't necessary in this case. Typically, the best way to keep track of selected items in a FlatList is by utilizing the keyExtractor and extraData props in conjunction with a Javascript Map object in state.
The way React is able to keep track of items being added/removed/modified is by using a unique key prop for each item (preferably an id, or if necessary indexes work if the list order will not change). In a FlatList, this is handled "automagically" if you will using the keyExtractor prop. To keep track of the selected item, we can add/remove items from our Map object whenever we click on one. Map is a type of object like an array that holds key-value pairs. We'll use this in state to store a key item.id and a boolean value true for each item that is selected.
So, we end up with something like this:
export default class Update extends Component {
state = {
data: [],
selected: (new Map(): Map<string, boolean>)
}
renderItems = ({ item }) => {
// note: the double ! operator is to make sure non boolean values are properly converted to boolean
return (
<ExpandableItem
item={item}
selected={!!this.state.selected.get(item.id)}
expandView={() => this.expandView(item)}
/>
);
}
expandView (item) {
LayoutAnimation.easeInEaseOut();
this.setState((state) => {
const selected = new Map(state.selected);
selected.set(item.id, !selected.get(item.id));
return {selected};
});
// the above allows for multiple expanded items at a time; the following will simultaneously close the last item when expanding a new one
// this.setState((state) => {
// const selected = new Map();
// selected.set(item.id, true);
// return {selected};
// });
}
render() {
return (
<FlatList
data={this.state.data}
keyExtractor={(item, index) => `${item.id}`}
renderItem={this.renderItems}
/>
);
}
}
const ExpandableItem = ({ item, selected, expandView }) => {
return (
<TouchableNativeFeedback onPress={expandView}>
<View style={styles.itemWrapperExpandable}>
{/* ...insert other header code */}
<View style={[styles.itemBody, selected && styles.itemBodySelected]}>
<Text style={styles.itemBodyText}>
some more information about this update will appear here
</Text>
</View>
</View>
</TouchableNativeFeedback>
);
}
You'll have to play around with the styles.itemBodySelected to make it look how you want. Note that the separate functional component <ExpandableItem /> for the renderItem isn't required, just how I prefer to structure my code.
Helpful links:
https://facebook.github.io/react-native/docs/flatlist.html
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
https://reactjs.org/docs/lists-and-keys.html#keys