Loop inside a react component - javascript

I have the following react component
const Layout = () => {
return (
<ThemeProvider>
<Flex wrap>
<Box width={[1, 1 / 3]}>
<Text bold>Targeting</Text>
<Iconlist text="Something"/>
</Box>
<Box width={[1, 1 / 3]}>
<Text bold>Tactics</Text>
</Box>
<Box width={[1, 1 / 3]}>
<Text bold>Results</Text>
</Box>
</Flex>
</ThemeProvider>
)
};
export default Layout;
I want to implement something like this:
...
<Iconlist text="Something"/>
<Iconlist text="Something else"/>
<Iconlist text="Something else else"/>
<Iconlist text="Something ...."/>
...
How can I write a loop that can do the above i.e. display multiple Iconlist component. I know how to use props to change the "something" value but I am unable to run a loop.
I tried this:
<Box width={[1, 1 / 3]}>
<Text bold>Targeting</Text>
{
for(var i=0;i<=5;i++){
<Iconlist text="Something"/>
}
}
</Box>
But I am guessing that's not the correct way to inject javascript in between. I am a beginner to React and trying to learn how to do this. What's the best practice?

You can use Array.prototype.map()
The map() method creates a new array with the results of calling a
provided function on every element in the calling array.
Example
<Box width={[1, 1 / 3]}>
<Text bold>Targeting</Text>
{ Array(5).map((i) => (<Iconlist key={i} text={`Something ${i}`} />)) }
</Box>

<Box width={[1, 1 / 3]}>
<Text bold>Targeting</Text>
{
['something',
'something else',
'something else else',
'something ...',
].map((text, index) => <Iconlist key={index} text={text}/>)
}
</Box>

You need first to declare the text values in array like this
const texts = ['something', 'something else' ..]
And in the render method add the following
{ texts.map((text) => <Iconlist text={text}/>) }

You can store your items in an array in the state:
...
constructor(props) {
super(props)
this.state = {
items: [
'something',
'something else',
'something else else',
'something ...'
]
}
}
...
...and then map over the array in your render function:
...
{
this.state.items.map(item => <Iconlist text={ item } />)
}
...
This will then re-render when you update the state.
You could also pass in the array as a prop and then render like this:
...
{
this.props.items.map(item => <Iconlist text={ item } />)
}
...

Related

How to render component via FlatList?

Using react native with typescript and redux toolkit
Hi I'm bothering with render a list of messages via FlatList. By ScrollView everything rendering good but I need to implement infiniti scroll. So I'm doing something like this
const MessagesScreen = () => {
const companyId = useAppSelector(getCompanyId);
const userId = useAppSelector(getUserId);
const {
data: messages,
isLoading,
refetch
} = useGetMessagesQuery({ userId, companyId });
useFocusEffect(refetch);
return (
<FlatList
data={messages}
renderItem={() => {
<Messages messages={messages} />;
}}
/>
);
};
In return() I'm trying to render FlatList with component Messages which is down here:
const Messages = ({ messages }: { messages: Message[] }) => {
const navigation =
useNavigation<RootStackScreenProps<'DrawerNavigator'>['navigation']>();
const { colors } = useTheme();
return (
<View style={styles.container}>
{messages.map(message => {
const createdAt = message.created_at;
const isRead = message.read;
const icon = isRead ? 'email-open-outline' : 'email-outline';
const onClick = () => {
navigation.navigate('Message', {
messageId: message.id
});
};
return (
<TouchableOpacity key={message.id} onPress={onClick}>
<View
style={[styles.message, { borderBottomColor: colors.separator }]}
>
<View style={styles.iconPart}>
<Icon
name={icon}
type="material-community"
style={
isRead
? { color: colors.separator }
: { color: colors.inputFocus }
}
size={24}
></Icon>
</View>
<View style={styles.bodyPart}>
<Text
numberOfLines={1}
style={[isRead ? styles.readSubject : styles.unReadSubject]}
>
{message.subject}
</Text>
<Text
numberOfLines={1}
style={[isRead ? styles.readBody : styles.unReadBody]}
>
{message.body}
</Text>
</View>
<View style={styles.datePart}>
<Text style={{ color: colors.shadow }}>
{dayjs(createdAt).fromNow()}
</Text>
</View>
</View>
</TouchableOpacity>
);
})}
</View>
);
};
Actually behaviour is just rendering white screen with error
Possible Unhandled Promise Rejection (id: 17):
Error: Objects are not valid as a React child (found: object with keys {id, msg_type, created_at, subject, body, author, company_id, read}). If you meant to render a collection of children, use an array instead.
there is problem with your call back function:
you are not returning Messages component
1:Remove curly braces
return (
<FlatList
data={messages}
renderItem={() => <Messages messages={messages}/> }
/>
);
2:Add return statement
return (
<FlatList
data={messages}
renderItem={() => {
return <Messages messages={messages} />;
}}
/>
);
Couple things:
You're using the renderItem callback incorrectly:
<FlatList
data={messages}
renderItem={() => {
// ^ ignoring the renderItem props
return <Messages messages={messages} />;
}}
/>
Here, for each item in the messages array, you're rendering a component and passing all the messages into it. So you'll get repeated elements.
The renderItem callback is passed {item, index} where item is the CURRENT item in the array (index is the index into the array)
See docs here:
https://reactnative.dev/docs/flatlist
The usual thing is the renderItem callback renders ONE item at a time, like this:
<FlatList
data={messages}
renderItem={({item}) => {
return <Message message={item} />;
}}
/>
e.g. I'd make a <Message/> component that renders one item only.

JS Component will not display on page?

My component will not display on the page. There are no errors or any other warnings/messages. I console log the value of gameData and all the data is as it should be. Here's the exported function:
export default function AllGameDeals( gameData ){
const dealArr = new Array()
const deals = () => {
gameData.deals.map((deal) => {
const imageSrc = `https://www.cheapshark.com/img/stores/icons/${(parseInt(deal.storeID)-1)}.png`
deal.imageSrc = imageSrc
dealArr.push(deal)
})
return dealArr
}
deals()
return (
<div>
{dealArr.forEach(gameDeal => (
<Box
key={gameDeal.dealID}
display={{ md: "flex" }}
boxShadow="dark-lg"
p={4}
>
<Box flexShrink={0}>
<Image borderRadius="lg"
width={{ md: 40 }}
height={{ md: 20 }}
src={gameData.info.thumb}
alt={gameData.info.title} />
</Box>
<Box>
<Image
src={gameDeal.imageSrc}
alt={gameDeal.storeID} />
</Box>
<Box>
<Text>{gameDeal.price}</Text>
</Box>
</Box>
))}
</div>
)
}
I feel like I am missing something very obvious...
Try changing from dealArr.forEach to dealArr.map.
The reason is that .forEach returns nothing, but .map returns an array.
You should be using .map instead of .forEach because .forEach doesn't actually return a value
Other possible improvements:
Use useEffect to fetch data on mount of the component (instead of fetching everytime)
Use useState to "persist" your state within the component

React Hook useState value being reset to initial value

The state of a value set using React useState hook gets set to the proper value and then reset to null. Critical code below. The click event that sets the startDate to the current date and time is 3 components down from where startDate is initialized. When setStartDate did not work I created an arrow function, updateStartDate. Both had the same problem where the startDate was changed after the click event (witnessed per the console.log in the top component), but was null just before the next click event (per the console.log in the click event). This is not an async problem as I see the change made before subsequent click.
If this is something that just does not work please explain. I could probably fix with useReducer but prefer to keep the useState if there is something I can do to correct this... If not correctable then I would like to at least understand why it does not work so that I can avoid this problem in the future.
export const DisplayTicTacToeContainer = (props) => {
const [startDate, setStartDate]= useState();
const updateStartDate = (newDate) => {
setStartDate(newDate);
}
useEffect (() => {
setStartDate(null);
}, []);
useEffect(() => {
console.log( "displayTicTacToeContainer useEffect for change of startDate = ", startDate)
}, [startDate]);
return (
<DisplayTicTacToeMatch arrayOfMatchingItems ={arrayOfMatchingItems}
startDate={startDate}
setStartDate={setStartDate}
updateStartDate={updateStartDate}
/>);
}
//-----------------------------------------------
export const DisplayTicTacToeMatch = (props) => {
const { startDate,
setStartDate,
updateStartDate,
} = props;
useEffect(() => {
// Performs some prep and working fine.
}, []);
return (
<TicTacToe
startDate={startDate}
setStartDate={setStartDate}
updateStartDate={updateStartDate}
/>
);
}
//-----------------------------------------------
const TicTacToeContainer = (props) => {
const { startDate,
setStartDate,
updateStartDate,
} = props;
const [board, setBoard] = useState(<Board
updateStartDate={updateStartDate}
startDate={startDate}
setStartDate={setStartDate}/>);
return (
<Board/>
)
}
export default TicTacToeContainer;
I renamed the component to BoardComponent and the state variable to boardLayout. I included the full return portion of the BoardComponent below.
As I am still experiencing the problem I would agree with you that, "DisplayTicTacToeContainer is being mounted twice". Any thoughts on how I can avoid this from happening?
Other than this inability to setStartDate, everything is working fine.
//-----------------------------------------------
const Board = (props) => {
const { updateStartDate,
startDate,
setStartDate,
} = props;
return (
<>
<Grid container maxwidth="lg" alignItems="center" spacing={1}>
<Grid item xs={9}>
<Grid container alignItems="center">
<Grid item xs={9}>
<Typography variant = "body1">
First select a square. Once the "Inquiry" word or phrase appears below, find
the correct response in the column on the right and select that buttton. A correct
response will fill the square previously selected with an "O" or "X".
</Typography>
<div style={{ width: '100%' }}>
<Box
display="flex"
flexWrap="wrap"
p={1}
m={1}
bgcolor="background.paper"
css={{ maxWidth: 900 }}
>
<Box p={1} bgcolor="grey.300">
Inquiry : {inquiry}
</Box>
</Box>
<Box
display="flex"
flexWrap="wrap"
p={1}
m={1}
bgcolor="background.paper"
css={{ maxWidth: 900 }}
>
<Box p={1} bgcolor="grey.300">
Next move by : {currentPlayer}
</Box>
<Box p={1} bgcolor="grey.300">
{showStatus}
</Box>
</Box>
</div>
</Grid>
</Grid>
<MyAux>
{boardLayout.map((row, rowId) => {
const columns = row.map((column, columnId) => (
<Grid key={columnId} item>
<ButtonBase >
<Paper
onClick={(e) => {
clickSquareHandler(e);
}}
elevation={4}
data-coord={rowId + ':' + columnId}
id={"Square" + rowId.toString() + columnId.toString()}
className={classes.Paper}>
<Icon
className={classes.Icon}
style={{fontSize: 78}}>
</Icon>
</Paper>
</ButtonBase>
</Grid>
));
return (
<Grid
key={rowId}
className={classes.Grid}
container
spacing={2}>
{columns}
</Grid>)
})}
</MyAux>
</Grid>
<Grid item xs={3} >
<Paper className={classes.paper}>
<Typography variant = "body1">
Response Options
</Typography>
<ButtonGroup
orientation="vertical"
color="secondary"
aria-label="vertical outlined secondary button group"
>
{responseChoices.map((choice) => (
<Controls.Button
key ={choice.value}
text={choice.value}
variant="contained"
color = "secondary"
onClick={() => {
chooseChecker(choice);
}}
className={
response && response.value === choice.value ? "selected" : ""
}
disabled={!!selected[choice.value]}
fullWidth = "true"
size = "small"
/>
))}
</ButtonGroup>
</Paper>
</Grid>
</Grid>
</>
)
}
BoardContainer.propTypes = {
won: PropTypes.func,
size: PropTypes.number
};
export default BoardContainer;
At least, code below doesn't make much sense.
Please don't set state value as a component.
Also, try to name state variable different from components, since it will confuse you at some ppint.
const [board, setBoard] = useState(<Board
updateStartDate={updateStartDate}
startDate={startDate}
setStartDate={setStartDate}/>);
return (
<Board/>
)
Another possibility is that the DisplayTicTacToeContainer is being mounted twice, but I can't confirm it with the code provided.

How to add load more features if my API did not have pagination - react native?

In Home screen, I get data "Songs" from API and save it in the state "Big array".
And I use FlatList to render it, so it rendered fine but I have a too long scroll!
so in data FlatList prop, I slice the array like this
<FlatList
data={songs.slice(0,9)}
...
/>
and it renders 10 songs as expected :D But I want to add Load more when scroll to end
My thought is
add a flag in state like
state={
page: 10
}
<FlatList
data={songs.slice(0,this.state.page)}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={0}
...
/>
handleLoadMore = () => {
this.state.songs.length <= this.state.page ? this.setState({page: this.state.page + 10, loading: true}) : null
};
So have any other thoughts How can I handle this case?
Edit
Full FlatList code snippet
// recent Songs FlatList
_renderItem = ({item, index}) => {
const {recent_songs} = this.state;
return (
<TouchableNativeFeed
key={item.id}
onPress={() => {
this.props.saveSongs(recent_songs, index);
this.props.isPlaying(true);
this.props.isPauseTrigger(!this.props.isPauseTrigger);
}}
background={TouchableNativeFeedback.Ripple('white')}
delayPressIn={0}
useForeground>
<Card style={styles.card} noShadow={true}>
<FastImage
source={{uri: item.img}}
resizeMode={FastImage.resizeMode.cover}
style={styles.cardImg}
/>
<Body style={styles.cardItem}>
<View style={styles.radioCardName}>
<View style={styles.cardViewFlex}>
<Text
lineBreakMode="tail"
ellipsizeMode="tail"
numberOfLines={1}
style={styles.text}>
{item.name}
</Text>
</View>
</View>
</Body>
</Card>
</TouchableNativeFeed>
);
};
{/* Recent Songs Here*/}
<View style={{marginVertical: 10}}>
<FlatList
style={{flex: 1}}
horizontal={true}
showsHorizontalScrollIndicator={false}
data={recent_songs.slice(0, 10)}
contentContainerStyle={{flexGrow: 1}}
ListEmptyComponent={<EmptyList />}
keyExtractor={(track, index) => track.id.toString()}
initialNumToRender={10}
renderItem={this._renderItem}
/>
</View>
something like this with flatlist and local pagination
constructor(props) {
super(props);
this.state = {
...
itemPerPage : 10
currentPage : 1, // we keep a track inside the state on each page we are for pagination
songs:[], // contain the songs used by flatlist to render
allSongs : [ ] // contain all the songs returned by api
}
}
async componentDidMount() {
// get the songs from api
let allSongs = await GetSONGS();
this.setState({ allSongs , songs: allSongs.slice(0,this.state.currentPage*this.state.itemPerPage ) });
}
handleLoadMore = async() => {
this.setState({
songs: [ ...this.state.songs , allSongs.slice(this.state.currentPage*this.state.itemPerPage,(this.state.currentPage+1)*this.state.itemPerPage ) ] , // concat the old and new data together
currentPage : this.state.currentPage +1
})
}
render(){
return(
<FlatList
data={this.state.songs}
keyExtractor={(item, index) => index.toString()}
initialNumToRender={10} // how many item to display first
onEndReachedThreshold={5} // so when you are at 5 pixel from the bottom react run onEndReached function
onEndReached={() => {
this.handleLoadMore();
}}
/>
)
}
anyway i use RecyclerListView in a previous project to render list of 10 000 elements

When rendering through a helper function I get: "Objects are not valid as a React child" error

I am developing a small project in React Native. I have noticed a weird situation whereby when I render a list through a helper function, I get the eponymous error:
Objects are not valid as a React child
Now this error normally means that I am trying to render an object, which is not the case. I will paste two snippets of code. The first one is how I render the data through a helper function, resulting through an error. The second snippet is how I render the data directly in the render() method and working successfully.
Snippet #1: Rendering through helper function renderUsers() -> does not work
renderUsers = async () => {
return this.props.userList.map(
({ instructions, address, createdDate, _id }) => (
<Card
title={`${moment(createdDate).format("YYYY-MM-DD HH:mm")}`}
key={_id}
>
<Text style={{ marginBottom: 10 }}>{instructions}.</Text>
<Button backgroundColor="#03A9F4" title="Ready to Help!" />
</Card>
)
);
};
...
render() {
return this.props.isFetchingUsers ? null : (
<View style={{ flex: 1 }}>
<ScrollView>
{this.renderUsers()}
</ScrollView>
</View>
);
}
Snippet #2: Rendering directly inside render() function -> works OK
render() {
return this.props.isFetchingUsers ? null : (
<View style={{ flex: 1 }}>
<ScrollView>
{this.props.userList.map(
({ instructions, address, createdDate, _id }) => (
<Card
title={`${moment(createdDate).format("YYYY-MM-DD HH:mm")}`}
key={_id}
>
<Text style={{ marginBottom: 10 }}>{instructions}.</Text>
<Button backgroundColor="#03A9F4" title="Ready to Help!" />
</Card>
)
)}
</ScrollView>
</View>
);
}
What could be the reason?
Your snippet 1 should be like this.
renderUsers = () => {
return this.props.userList.map(
({ instructions, address, createdDate, _id }) => (
<Card
title={`${moment(createdDate).format("YYYY-MM-DD HH:mm")}`}
key={_id}
>
<Text style={{ marginBottom: 10 }}>{instructions}.</Text>
<Button backgroundColor="#03A9F4" title="Ready to Help!" />
</Card>
)
);
};
...
render() {
return this.props.isFetchingUsers ? null : (
<View style={{ flex: 1 }}>
<ScrollView>
{this.renderUsers()}
</ScrollView>
</View>
);
}
You need to remove the keyword async
The async function will return Promise Object, which is not supposed to be a React child.
But you no need async function for Array map.
If you want to render something asynchronously try updating state by this.setState and render it accordingly.
You shouldn't return null in render method!
You should render an element like this:
render() {
<View style={{ flex: 1 }}>
<ScrollView>
{
(!this.props.isFetchingUsers && this.props.userList) &&
this.renderUsers()
}
</ScrollView>
</View>
}
then remove the keyword async from renderUsers method.

Categories