React Native Datatable doesn't show after moving the fragment into Component - react-native

I've created the following component. React Native Paper Datatable rows aren't showing after moving it into component and linking it to json loop.
If we comment " and uncommented the commented block below, you will see the Datatable is showing. What am I doing wrong with my two components? I've done all console.log. All data are showing correctly but JSX elements aren't rendering inside Datatable.
I've created the following code on Snack: https://snack.expo.dev/#everestster/datatable-component
import React, {useEffect} from 'react';
import type {Node} from 'react';
import {View, ScrollView, Text, StyleSheet, Dimensions} from 'react-native';
import {DataTable as PaperDataTable} from 'react-native-paper';
const DataTable = props => {
const optionsPerPage = [2, 3, 4];
const [page, setPage] = React.useState(0);
const [itemsPerPage, setItemsPerPage] = React.useState(optionsPerPage[0]);
useEffect(() => {
setPage(0);
}, [itemsPerPage]);
const HeaderSection = (): Node => {
console.log(props.items);
if (props.items.length === 0) {
return;
}
return (
<PaperDataTable.Header>
{Object.keys(props.items[0]).forEach(function (key) {
if (key !== 'Id') {
<PaperDataTable.Title style={[styles.allCell]}>
{key}
</PaperDataTable.Title>;
}
})}
</PaperDataTable.Header>
);
};
const BodySection = (): Node => {
return (
<PaperDataTable.Row>
{Object.keys(props.items[0]).forEach(function (key) {
if (key !== 'Id') {
<PaperDataTable.Cell style={[styles.allCell]}>
{key}
</PaperDataTable.Cell>;
}
})}
</PaperDataTable.Row>
);
};
return (
<ScrollView style={styles.tableHolder}>
<ScrollView horizontal={true}>
<View style={{alignItems: 'center'}}>
<PaperDataTable style={styles.table}>
<HeaderSection />
<BodySection />
{/*<PaperDataTable.Header>
<PaperDataTable.Title>Name</PaperDataTable.Title>
<PaperDataTable.Title>Email</PaperDataTable.Title>
</PaperDataTable.Header>
<PaperDataTable.Row>
<PaperDataTable.Cell>John</PaperDataTable.Cell>
<PaperDataTable.Cell>john#gmail.com</PaperDataTable.Cell>
</PaperDataTable.Row>
<PaperDataTable.Row>
<PaperDataTable.Cell>Harry</PaperDataTable.Cell>
<PaperDataTable.Cell>harr#gmail.com</PaperDataTable.Cell>
</PaperDataTable.Row>
<PaperDataTable.Row>
<PaperDataTable.Cell>Jessica</PaperDataTable.Cell>
<PaperDataTable.Cell>jessica#gmail.com</PaperDataTable.Cell>
</PaperDataTable.Row>*/}
<PaperDataTable.Pagination
page={page}
numberOfPages={1}
onPageChange={p => setPage(p)}
optionsPerPage={optionsPerPage}
itemsPerPage={itemsPerPage}
setItemsPerPage={setItemsPerPage}
showFastPagination
optionsLabel={'Rows per page'}
/>
</PaperDataTable>
</View>
</ScrollView>
</ScrollView>
);
};
const styles = StyleSheet.create({
tableHolder: {},
table: {
paddingLeft: 50,
paddingRight: 50,
flex: 1,
},
allCell: {
marginRight: 20,
},
});
export {DataTable};
Any help will be appreciated.

The problem is in your structure. Your current BodySection is not returning the correct structure react-native-paper wants. I rewrote the BodySection function. Here is the snack: https://snack.expo.dev/#truetiem/datatable-component
const BodySection = (): Node => {
return props.items.map(function (item) {
return (
<PaperDataTable.Row>
{Object.keys(item).map((key) => key === 'Id' ? null : (
<PaperDataTable.Cell>
{item[key]}
</PaperDataTable.Cell>
))}
</PaperDataTable.Row>
);
});
};

Related

Customize Action Sheet Message (Expo)

I'm using #expo/react-native-action-sheet, and i want to show a button in the props message.
e.g
import { useActionSheet } from "#expo/react-native-action-sheet"
const { showActionSheetWithOptions } = useActionSheet()
const onPress = () => {
// Here
**const message = '<TouchableOpacity><Text>fsdf</Text></TouchableOpacity>'**
showActionSheetWithOptions(
{
message
},
(buttonIndex) => {
}
)
}
But it is not showing the button as i want
My purpose is to add a date picker in the action sheet.
Expecting answer:
In this case, you can use another library https://gorhom.github.io/react-native-bottom-sheet/ because Action Sheet is about the list of actions.
You can place any content you need for react-native-bottom-sheet and it also supports Expo
import React, { useCallback, useMemo, useRef } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import BottomSheet from '#gorhom/bottom-sheet';
const App = () => {
// ref
const bottomSheetRef = useRef<BottomSheet>(null);
// variables
const snapPoints = useMemo(() => ['25%', '50%'], []);
// callbacks
const handleSheetChanges = useCallback((index: number) => {
console.log('handleSheetChanges', index);
}, []);
// renders
return (
<View style={styles.container}>
<BottomSheet
ref={bottomSheetRef}
index={1}
snapPoints={snapPoints}
onChange={handleSheetChanges}
>
<View style={styles.contentContainer}>
<Text>Awesome 🎉</Text>
</View>
</BottomSheet>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
backgroundColor: 'grey',
},
contentContainer: {
flex: 1,
alignItems: 'center',
},
});
export default App;

How can I use useMemo React Hook in this example

What am I doing wrong here? I want to utilise useMemo so that my RenderItems component doesn't keep flickering when the state (Data2) changes. The Data2 array is in place of an item in my apps state. In practice, Data2 is data fetched from an api, and thus is subject to change and update.
I'm not looking for an alternative in this case, I'd just like to know how to use useMemo in this example - thanks!
import React, { useMemo } from 'react';
import {
View,
Text
} from 'react-native';
const CoursesWidget = (props) => {
const Data2 = [{ id: '11' }, { id: '22' }, { id: '33' }];
const coursesArray = Data2;
const RenderItems = useMemo(() => {
return (
coursesArray
.map((course) => {
return (
<View key={course.id}>
<Text>{course.id}</Text>
</View>
);
}),
[coursesArray]
);
});
//const Finito = useMemo(() => RenderItems(), [])
return (
<View>
<RenderItems />
</View>
);
};
export default CoursesWidget;
Snack: https://snack.expo.dev/rr8toaABT
I would suggest that you use a state and a FlatList instead of creating the elements using map. There is no need to use useMemo at all in this scenario and it will not fix your issue.
import React, { useState } from 'react';
import {
View,
Text,
FlatList,
SafeAreaView
} from 'react-native';
const CoursesWidget = (props) => {
const [data, setData] = useState([{ id: '11' }, { id: '22' }, { id: '33' }])
return (
<SafeAreaView style={{margin: 20}}>
<FlatList
data={data}
renderItem={({ item }) => {
return <View>
<Text>{item.id}</Text>
</View>
}}
keyExtractor={item => item.id}
/>
</SafeAreaView>
);
};
export default CoursesWidget;
Here is an updated version of your snack.
All that needs to be changed is moving the dependency array that you pass to useMemo to be the last parameter, and instead of calling it in the return like a jsx component, you put the value in brackets since it's not really a function anymore:
import React, { useMemo } from 'react';
import {
View,
Text
} from 'react-native';
const CoursesWidget = (props) => {
const Data2 = [{ id: '11' }, { id: '22' }, { id: '33' }];
const coursesArray = Data2;
const RenderItems = useMemo(() => {
return (
coursesArray
.map((course) => {
return (
<View key={course.id}>
<Text>{course.id}</Text>
</View>
);
})
);
}, [coursesArray]);
//const Finito = useMemo(() => RenderItems(), [])
return (
<View>
{ RenderItems }
</View>
);
};
export default CoursesWidget;
Here's the snack: https://snack.expo.dev/5GbI-k8Pb

React Native - searchApi is not a function

I am new in React Native. I try to create a simple searching food restaurant with Yelp. Unfortunately, I get an error:
"searchApi is not a function. (in 'searchApi(term)', 'searchApi' is
"")
Below my code.
useResults.js
import React, { useEffect, useState } from 'react';
import yelp from '../api/yelp';
export default () => {
const [result, setResult] = useState([]);
const [errorMessage, setErrorMessage] = useState('');
const searchApi = async (searchTerm) => {
console.log("hi there");
try {
const response = await yelp.get('/search', {
params: {
limit: 50,
term: searchTerm,
location: 'san jose'
}
});
setErrorMessage(null);
setResult(response.data.businesses);
} catch (err) {
setErrorMessage('Something Went Wrong');
}
};
/*
useEffect(() => {}); //Run the arrow function everytime the component is rendered
useEffect(() => {}, []); // Run the arrow function only when the component is first rendered
useEffect(() => {}, [value]); // Run the arrow function only when the component is first rendered, and when the value is changes
*/
useEffect(() => {
searchApi('pasta');
}, []);
return [searchApi, result, errorMessage];
};
SearchScreen.js
import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import ResultList from '../components/ResultList';
import SearchBar from '../components/SearchBar';
import useResults from '../hooks/useResults';
const SearchScreen = () => {
const [term, setTerm] = useState('');
const [searchApi, result, errorMessage] = useResults();
console.log(result);
return (
<View>
<SearchBar
term={term}
onTermChange={setTerm}
onTermSubmit={() => searchApi(term)}
/>
<View>{errorMessage ? <Text>{errorMessage}</Text> : null}</View>
<Text>We have found {result.length} results</Text>
<ResultList title="Cost Effective" />
<ResultList title="Bit Pricier" />
<ResultList title="Big Spender"/>
</View>
);
};
const styles = StyleSheet.create({
});
export default SearchScreen;
edit :
SearchBar.js
import React from 'react';
import { View, Text, StyleSheet, TextInput } from 'react-native';
import { Feather } from '#expo/vector-icons';
const SearchBar = ({ term, onTermChange, onTermSubmit }) => {
return (
<View style={styles.backgroundStyle}>
<Feather style={styles.iconStyle} name="search" size={30} color="black" />
<TextInput style={styles.inputStyle}
autoCapitalize="none"
autoCorrect={false}
placeholder="Search"
value={term}
onChangeText={onTermChange}
onEndEditing={onTermSubmit}
/>
</View>
)
};
const styles = StyleSheet.create({
backgroundStyle: {
marginTop: 10,
backgroundColor: '#F0EEEE',
height: 50,
borderRadius: 5,
marginHorizontal: 15,
flexDirection: 'row'
},
inputStyle: {
flex: 1,
fontSize: 18,
marginHorizontal: 10
},
iconStyle: {
fontSize: 35,
alignSelf: 'center'
}
});
export default SearchBar;
When I type in search bar and hit done button, I got the error above.
Seems in useResults.js file this: return [searchApi, result, errorMessage]; does not properly return the function. But the result and errorMessage return successfully.
And in this file: SearchScreen.js the error line is shown in here: onTermSubmit={() => searchApi(term)}.
How to fix this?
Try adding a callback to onChangeText.
<TextInput style={styles.inputStyle}
autoCapitalize="none"
autoCorrect={false}
placeholder="Search"
value={term}
onChangeText={() => onTermChange()} // Add fat arrow function here
onEndEditing={onTermSubmit}
/>

react native usememo renderitem not working why?

I want to prevent unneccessary rerender, so I use useMemo.
But I got this error message:
TypeError: renderItem is not a function. (In 'renderItem(props)', 'renderItem' is an instance of Object)
Code:
import * as React from 'react';
import { StyleSheet, Text, View, TouchableOpacity, Image, Dimensions, FlatList } from 'react-native';
import faker from 'faker';
const { width, height } = Dimensions.get('window');
const Advertising = () => {
const data = [
{ id: '1', name: 'Jens', image: faker.image.avatar() },
{ id: '2', name: 'Günther', image: faker.image.avatar() }
];
const renderItem = React.useMemo(() => {
return (
<View>
<Text>Hello</Text>
</View>
)
}, [data]);
return (
<FlatList
data={data}
keyExtractor={item => Math.random(100).toString()}
renderItem={renderItem}
/>
)
};
const styles = StyleSheet.create({
container: {
flex: 1,
}
});
export default React.memo(Advertising);
......................................................................................................................................................................................................
useMemo is a react hook and react hooks can't be used in that way.
I would advice you create a separate component for the this.
const MyComponent = React.memo(({item})=>{
return (<View></View>);
});
and then import like so
const renderItem = ({item}) => {
return <MyComponent />
}
...
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(_item, i)=>i.toString()}
/>
Also consider useCallBack
You have to return your renderItem function as a callback inside useMemo.
const renderItem = React.useMemo(() => () => (
<View>
<Text>Hello</Text>
</View>
), [data])
same as
const renderItem = () => (
<View>
<Text>Hello</Text>
</View>
)
const memoizedRenderItem = React.useMemo(renderItem, [data])

How to avoid data duplicate when navigate screen?

My situation is i have two FlatList component (A and B).
A is my first screen. I use react-navigation to navigate A to B , B will show back arrow on headerLeft. When i click the arrow it will back to A . But the FlatList data is still show B even it is really in A...
My data is from fetch API by react-redux, i think the problem is come from react-redux. Because i test a simple test without react-redux. The problem is gone.
I want to use react-redux create my project. I try to use shouldComponentUpdate like
shouldComponentUpdate = (nextProps, nextState) => {
if (nextProps.movieList === this.props.movieList) {
return false;
}
return true;
};
It is still can't fix my problem when goBack() to another component
I console.log it try to find what is going on with my props data.
When i navigate to B from A. My console.log will show like this, i find A component will be rendered...
Then i click the back arrow on headerLeft to A. The screen is A but the data is still B add my console.log is empty at the same time.
I can't figure it out. Any help would be appreciated. Thanks in advance.
Here is my A component file (B is similar with A):
import React, { Component } from 'react';
import {
View, FlatList, Dimensions,
TouchableOpacity, Image,
ActivityIndicator, Alert, Platform
} from 'react-native';
import { Icon } from 'react-native-elements';
import { connect } from 'react-redux';
import { fetchMainMovieList } from '../actions';
const { width, height } = Dimensions.get('window');
const equalWidth = (width / 2);
class MainActivity extends Component {
static navigationOptions = ({ navigation }) => ({
title: 'MainActivity',
headerLeft:
<TouchableOpacity style={{ marginLeft: 10 }} onPress={() => navigation.navigate('DrawerOpen')} >
<Icon name='menu' />
</TouchableOpacity>
});
componentWillMount() {
this.props.fetchMainMovieList();
}
renderItem({ item }) {
return (
<View>
<Image
source={{ uri: item.photoHref }}
style={{ height: 220, width: equalWidth }}
resizeMode="cover"
/>
</View>
);
}
render() {
const movieData = this.props.movieList.movie;
console.log('A component this.props=>');
console.log(this.props);
if (movieData === []) {
return (
<View style={styles.loadingStyle}>
<ActivityIndicator />
</View>
);
}
return (
<View style={{ flex: 1 }}>
<FlatList
data={movieData}
renderItem={this.renderItem}
numColumns={2}
horizontal={false}
keyExtractor={(item, index) => index}
/>
</View>
);
}
}
const styles = {
loadingStyle: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
}
};
const mapStateToProps = (state) => {
const movieList = state.movieList;
return { movieList };
};
export default connect(mapStateToProps, { fetchMainMovieList })(MainActivity);
Here is my B component file:
import React, { Component } from 'react';
import {
View, FlatList, Dimensions,
Image, ActivityIndicator, Text
} from 'react-native';
import { connect } from 'react-redux';
import { fetchThisWeek } from '../actions';
const { width, height } = Dimensions.get('window');
const equalWidth = (width / 2);
class ThisWeek extends Component {
static navigationOptions = ({ navigation }) => ({
title: 'ThisWeek',
});
componentWillMount() {
this.props.fetchThisWeek();
}
renderItem({ item }) {
return (
<View>
<Image
source={{ uri: item.photoHref }}
style={{ height: 500, width: '100%' }}
resizeMode="cover"
/>
</View>
);
}
render() {
const movieData = this.props.movieList.movie;
console.log('B component this.props=>');
console.log(this.props);
if (movieData === []) {
return (
<View style={styles.loadingStyle}>
<ActivityIndicator />
</View>
);
}
return (
<View style={{ flex: 1 }}>
<FlatList
data={movieData}
renderItem={this.renderItem}
numColumns={1}
horizontal={false}
keyExtractor={(item, index) => index}
/>
</View>
);
}
}
const styles = {
loadingStyle: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
}
};
const mapStateToProps = (state) => {
const movieList = state.movieList;
return { movieList };
};
export default connect(mapStateToProps, { fetchThisWeek })(ThisWeek);
Here is my MyListReducer.js:
import {
MOVIELIST_MAINACTIVITY,
MOVIELIST_THISWEEK,
MOVIELIST_THEATER
} from '../actions/types';
const INITIAL_STATE = {};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case MOVIELIST_MAINACTIVITY:
return action.payload;
case MOVIELIST_THISWEEK:
return action.payload;
case MOVIELIST_THEATER:
console.log(action.payload);
return action.payload;
default:
return state;
}
};
In your reducer you have added the fetched data into the main object in store, instead, you should have to maintain two different variables to save data of those different components separately. Try by changing the reducer as,
import {
MOVIELIST_MAINACTIVITY,
MOVIELIST_THISWEEK,
MOVIELIST_THEATER
} from '../actions/types';
const INITIAL_STATE = {
weeklyMovies:[],
allMovies:[]
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case MOVIELIST_MAINACTIVITY:
return {
...state,
allMovies:action.payload
};
case MOVIELIST_THISWEEK:
return {
...state,
weeklyMovies:action.payload
};
case MOVIELIST_THEATER:
console.log(action.payload);
return action.payload;
default:
return {...state};
}
};
And in your component A and B you should change your mapStateToProps to read data from corresponding objects in store.
For MainActivity component
const mapStateToProps = (state) => {
const movieList = state.allMovies;
return { movieList };
};
and for ThisWeek component
const mapStateToProps = (state) => {
const movieList = state.weeklyMovies;
return { movieList };
};