I want to be able to use data collected from an API and stored in JSON to be used as component names for my Tab Navigator.
Example:
const InstructionsTemplate = ({ navigation, route }) => {
...
}
const PrinterTemplate = ({ navigation, route }) => {
...
}
const ScannerTemplate = ({ navigation, route }) => {
...
}
export const JobScreen = ({ navigation, route }) => {
const json = {
"steps": [
{
"step_slug": 1,
"step_type": "InstructionsTemplate"
},
{
"step_slug": 2,
"step_type": "PrinterTemplate"
},
{
"step_slug": 3,
"step_type": "InstructionsTemplate"
},
{
"step_slug": 4,
"step_type": "ScannerTemplate"
}
]
};
const [data, setData] = useState(json.steps);
return (
<JobTab.Navigator screenOptions={({ route }) => ({ headerShown: true })}>
{data.map((step) => {
return (
<JobTab.Screen
key={step.step_slug}
name={step.step_slug}
options={{
title: "",
headerRight: () => (
<Pressable onPress={() => navigation.navigate('Jobs')}>
<Ionicons name='close-outline' size={32} color={isDarkMode ? IndigoColors.white : IndigoColors.black}/>
</Pressable>
)
}}
component={step.step_type}
initialParams={{ stepSlug: step.step_slug, stepType: step.step_type, ... }}
/>
)
})}
</JobTab.Navigator>
)
};
Line 52 particularly is causing me problems.
Tried passing the variable through in double curly brackets {{step.step_type}} and without curly brackets step.step_type.
Passing through the component name as text works: component={InstructionsTemplate}
Related
I'm making an app that have notes, and when develop the delete function, i faced this error, the useState do not update when use alongside with redux dispatch function ( even the redux function run, the useState do not run ) , i tried to create the same issue on codesandbox, but weird is it WORKING TOTALLY FINE ON WEB?!
Here is the code:
NoteList.tsx
function NoteList(props: noteListI) {
const { title, note, id, date, selectStatus } = props; //they are props
const nav = useNavigation(); //for navigation
const [isDeleteChecked, setDeleteChecked] = useState(false);
const dispatch = useDispatch();
const data = useSelector((state: RootState) => state.persistedReducer.note); // note item from redux
const toggleSelectedButton = useSelector(
(state: RootState) => state.toggle.enableSelectedButton
); // to show selected button icon
const onNavDetail = () => {
nav.navigate(RouteName.EDIT_NOTE, {
date: date,
note: note,
header: title,
id: id,
});
};
const toggleSelectButton = () => {
dispatch(switchToggle());
}; // toggle delete button function
const setDeleteItem = () => {
setDeleteChecked(!isDeleteChecked);
dispatch(toggleSelect({ id: id }));
}; ////==>>> the issue here the 'setDeleteChecked' not even work
return (
<TouchableOpacity
onLongPress={() => {
toggleSelectButton();
}}
style={CONTAINER}
onPress={() => (!toggleSelectedButton ? onNavDetail() : setDeleteItem())}
>
<View style={NOTE_ITEM_CONTAINER}>
<Text>{isDeleteChecked?.toString()}</Text> ==>always false, why????!
<View>
<View row centerV style={HEADER_CONTAINER}>
<View>
<AppText bold style={HEADER_TEXT}>
{title}
</AppText>
</View>
{toggleSelectedButton && (
<View>
{selectStatus ? ( ===> this is from redux and work but slow
<CheckIcon name="checkcircle" size={size.iconSize} />
) : (
<CheckIcon name="checkcircleo" size={size.iconSize} />
)}
</View>
)}
</View>
<View style={NOTE_CONTAINER}>
<AppText numberOfLines={7}>{note}</AppText>
</View>
</View>
<View
style={{
alignSelf: "flex-end",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
>
<AppText>{moment(date).format("h:mmA MMM Do YY")}</AppText>
</View>
</View>
</TouchableOpacity>
);
}
export default memo(NoteList);
I use these from flatlist, here is the main flatlist code:
export default function NoteListScreen() {
const [user, setUser] = useState<any>();
const nav = useNavigation();
// useEffect(() => {
// dispatch(loadDefault());
// }, []);
const dispatch: AppDispatch = useDispatch();
const data = useSelector((state: RootState) => state.persistedReducer.note);
const userInfo: user = useSelector(
(state: RootState) => state.persistedReducer.firebase.userInfomation
);
useEffect(() => {
dispatch(fetchNote(userInfo.email)); //fetch note from firebase
}, []);
return (
<SafeAreaView style={CONTAINER}>
{data.length === 0 ? (
<>
<ScrollView>
<HeaderNote />
<AppText style={EMPTY_NOTE}>
Hmm, so don't have any secret yet
</AppText>
</ScrollView>
<FooterNote />
</>
) : (
<View style={CONTAINER}>
<FlatList
removeClippedSubviews
data={data}
style={{
marginBottom:
Platform.OS === "ios"
? onePercentHeight * 15
: onePercentHeight * 12,
}}
keyExtractor={() => {
return (
new Date().getTime().toString() +
Math.floor(
Math.random() * Math.floor(new Date().getTime())
).toString()
);
}}
ListHeaderComponent={() => <HeaderNote />}
renderItem={({ item, index }) => {
return (
<NoteList ==> here , the note list that faced error
note={item.note}
title={item.header}
date={item.date}
id={item.id}
selectStatus={item.selectStatus}
/>
);
}}
/>
<FooterNote />
</View>
)}
</SafeAreaView>
);
}
Here is the reducer code:
const noteReducer = createSlice({
name: "note",
initialState: NoteList,
reducers: {
addNote: (state, action: PayloadAction<NoteI>) => {
const newNote: NoteI = {
id:
new Date().getTime().toString() +
Math.floor(
Math.random() * Math.floor(new Date().getTime())
).toString(),
header: action.payload.header,
note: action.payload.note,
date: new Date(),
selectStatus: false,
};
state.push(newNote);
},
toggleSelect: (state, action: PayloadAction<NoteI>) => {
return state.map((item) => {
if (item.id === action.payload.id) {
return { ...item, selectStatus: !item.selectStatus };
}
return item;
});
}, ///========>This is the reducer using in the note function
loadDefault: (state) => {
return state.map((item) => {
return { ...item, selectStatus: false };
});
},
resetNote: (state) => {
return (state = []);
},
editNote: (state, action: PayloadAction<NoteI>) => {
return state.map((item) => {
if (item.id === action.payload.id) {
return {
...item,
note: action.payload.note,
header: action.payload.header,
date: action.payload.date,
};
}
return item;
});
},
},
extraReducers: (builder) => {
builder.addCase(fetchNote.fulfilled, (state, action) => {
state = [];
return state.concat(action.payload);
});
},
});
Here is the image of what i'm talking about, the code in image from noteList.tsx, the first piece of code i post here
Here is the quick gif:
In above gif, the false must return true then false everytime i click ( as above code ) but i don't why it never change value, the black dot also change color because it use value using in the same function using with this value, but when setDeleteItem fire, it NOT fire the setDeleteChecked(!isDeleteChecked)
Here is the demo that i made, but it WORK TOTALLY FINE, but in my app, it make weird error https://codesandbox.io/s/nostalgic-neumann-0497v?file=/redux/some-redux.tsx
Please help, i'm trying to provide must as i can, i stuck for days for this, thank you so much, if you need any detail, just tell me
Here is the full code:
import * as React from 'react';
import { View, ScrollView, StyleSheet } from 'react-native';
import {
Appbar,
Searchbar,
List,
BottomNavigation,
Text,
Button,
} from 'react-native-paper';
const AccordionCollection = ({ data }) => {
var bookLists = data.map(function (item) {
var items = [];
for (let i = 0; i < item.total; i++) {
items.push(
<Button mode="contained" style={{ margin: 10 }}>
{i}
</Button>
);
}
return (
<List.Accordion
title={item.title}
left={(props) => <List.Icon {...props} icon="alpha-g-circle" />}>
<View
style={{
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'flex-start',
backgroundColor: 'white',
}}>
{items}
</View>
</List.Accordion>
);
});
return bookLists;
};
const MusicRoute = () => {
const DATA = [
{
key: 1,
title: 'Zain dishes',
total: 21,
},
{
key: 2,
title: 'Sides',
total: 32,
},
{
key: 3,
title: 'Drinks',
total: 53,
},
{
key: 4,
title: 'Aesserts',
total: 14,
},
];
const [data, setData] = React.useState(DATA);
const [searchQuery, setSearchQuery] = React.useState('');
const [sortAZ, setSortAZ] = React.useState(false);
const onChangeSearch = (query) => {
setSearchQuery(query);
const newData = DATA.filter((item) => {
return item.title.toLowerCase().includes(query.toLowerCase());
});
setData(newData);
};
const goSortAZ = () => {
setSortAZ(true);
setData(
data.sort((a, b) => (a.title > b.title ? 1 : b.title > a.title ? -1 : 0))
);
};
const goUnSort = () => {
setSortAZ(false);
setData(DATA);
};
return (
<View>
<Appbar.Header style={styles.appBar}>
<Appbar.BackAction onPress={() => null} />
<Searchbar
placeholder="Search"
onChangeText={onChangeSearch}
value={searchQuery}
style={styles.searchBar}
/>
<Appbar.Action
icon="sort-alphabetical-ascending"
onPress={() => goSortAZ()}
/>
<Appbar.Action icon="library-shelves" onPress={() => goUnSort()} />
</Appbar.Header>
<ScrollView>
<List.Section title="Accordions">
<AccordionCollection data={data} />
</List.Section>
</ScrollView>
</View>
);
};
const AlbumsRoute = () => <Text>Albums</Text>;
const MyComponent = () => {
const [index, setIndex] = React.useState(0);
const [routes] = React.useState([
{ key: 'music', title: 'Music', icon: 'queue-music' },
{ key: 'albums', title: 'Albums', icon: 'album' },
]);
const renderScene = BottomNavigation.SceneMap({
music: MusicRoute,
albums: AlbumsRoute,
});
return (
<BottomNavigation
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderScene={renderScene}
/>
);
};
const styles = StyleSheet.create({
appBar: {
justifyContent: 'space-between',
},
searchBar: {
width: '60%',
shadowOpacity: 0,
borderRadius: 10,
backgroundColor: '#e4e3e3',
},
});
export default MyComponent;
Expo Snack Link
There are two weird mechanisms.
First
If I remove sortAZ(true) in goSortAZ() and sortAZ(false) in goUnSort(), the state data stops updating after you press on (1) sort and (2) unsort buttons more than three times.
Second
If I remove DATA array outside the component, sort and unsort buttons does not work/update.
If I do not remove these two, I can sort and unsort the list.
I feel that the code is messy although it achieves the function.
My questions is:
Why adding extra state (sortAZ) helps to update other state (data)?
Just totally remove sortAZ variable (no need to use it unless you somehow want to have a loading status, but since you are not making http requests, that's not necessary) and replace goSortAZ with the following:
Remember to clone the original array in order to create a new copy and then sort that copy.
This is working fine.
const goSortAZ = () => {
setData(
[...data].sort((a, b) => (a.title > b.title ? 1 : b.title > a.title ? -1 : 0))
);
};
i would suggest using the same technique for the unSort method too.
Am making an authentification flow using react native i have 3 screens Welcome Screen with two buttons to either sign in or sign up , i used react navigation version 5
every time i press either the sign in or sign up button i get the following error " The action Navigate with payload : SignIn was not handled by any navigator " and am not exactly sure why ?
my question is how do i resolve this issue and navigate correctly to the other screen
The code used for the app.js
export default function App() {
const [state, dispatch] = React.useReducer(authReducer, {
token: null,
errorMessage: "",
});
React.useEffect(() => {
const bootstrapAsync = async () => {
const userToken = await AsyncStorage.getItem("userToken");
dispatch({ type: "RESTORE_TOKEN", token: userToken });
};
bootstrapAsync();
}, []);
const authContext = React.useMemo(
() => ({
// CLEARING ERROR MESSAGES WHEN SWITCHING SIGNIN-SIGNUP
clearErrorMessage: async () => {
dispatch({ type: "clear_error_message" });
},
// AUTOMATIC SIGNIN ONLY USING TOKENS ON USER DEVICE
tryLocalSignin: async () => {
const navigation = useNavigation();
const token = await AsyncStorage.getItem("token");
if (token) {
// if token exists
dispatch({ type: "SIGN_IN", payload: token });
navigation.navigate("MainTabScreen");
} else {
// if token doesnt exist
navigation.navigate("Welcome");
}
},
signIn: async ({ email, password }) => {
const navigation = useNavigation();
try {
const response = await userAPI.post("/signin", { email, password });
await AsyncStorage.setItem("token", response.data.token);
// using signin since the logic is the same
dispatch({ type: "SIGN_IN", token: response.data.token });
// making use of the navigate component to access navigation
// and redirect the user
navigation.navigate("MainTabScreen");
} catch (err) {
console.log(err);
dispatch({
type: "add_error",
payload: "Something went wrong with sign in",
});
}
},
signOut: async () => {
const navigation = useNavigation();
await AsyncStorage.removeItem("token");
dispatch({ type: "SIGN_OUT" });
navigation.navigate("Welcome");
},
signUp: async ({ email, password }) => {
const navigation = useNavigation();
try {
const response = await userAPI.post("/signup", { email, password });
await AsyncStorage.setItem("token", response.data.token);
dispatch({ type: "SIGN_IN", payload: response.data.token });
// making use of the navigate component to access navigation
// and redirect the user
navigation.navigate("MainTabScreen");
} catch (err) {
dispatch({
type: "add_error",
payload: "Something went wrong with sign up",
});
}
},
}),
[]
);
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer>
{state.token === null ? <AuthNavigator /> : <AppNavigator />}
</NavigationContainer>
</AuthContext.Provider>
);
}
The code used for signinScreen.js
const SigninScreen = ({ navigation }) => {
const { signIn } = React.useContext(AuthContext);
return (
<ImageBackground
source={require("../../assets/background.png")}
style={styles.image}
>
{/* <NavigationEvents onWillBlur={clearErrorMessage} /> */}
<AuthForm
headerText="Welcome back!"
subText="Log in with your email and discover the universe."
//errorMessage={state.errorMessage}
AppOnSubmit={signIn}
submitButtonText="Log in"
/>
</ImageBackground>
);
};
SigninScreen.navigationOptions = () => {
return {
headerShown: false,
};
};
const styles = StyleSheet.create({
container: {
//flex: 1,
//justifyContent: "center",
//marginBottom: 150,
},
image: {
position: "absolute",
left: 0,
top: 0,
width: Dimensions.get("window").width,
height: Dimensions.get("window").height,
},
});
export default SigninScreen;
AppNavigator.js :
const AppNavigator = () => (
<AppTabs.Navigator
initialRouteName="MainTabScreen"
activeColor={colors.shade1}
style={{ backgroundColor: "tomato" }}
>
<AppTabs.Screen
name="Home"
component={MainTabScreen}
options={{
tabBarColor: "#292B34",
tabBarIcon: ({ color }) => (
<SimpleLineIcons name="home" size={24} color={colors.shade2} />
),
}}
/>
<AppTabs.Screen
name="SearchScreen"
component={SearchScreen}
options={{
tabBarLabel: "Search",
tabBarColor: "#292B34",
tabBarIcon: ({ color }) => (
<Feather name="search" size={24} color={colors.shade2} />
),
}}
/>
<AppTabs.Screen
name="SaveScreen"
component={SaveScreen}
options={{
tabBarLabel: "Save",
tabBarColor: "#292B34",
tabBarIcon: ({ color }) => (
<Feather name="bookmark" size={24} color={colors.shade2} />
),
}}
/>
<AppTabs.Screen
name="AccountScreen"
component={AccountScreen}
options={{
tabBarLabel: "Account",
tabBarColor: "#292B34",
tabBarIcon: ({ color }) => (
<MaterialCommunityIcons
name="account-circle-outline"
size={24}
color={colors.shade2}
/>
),
}}
/>
</AppTabs.Navigator>
);
export default AppNavigator;
AuthNavigator.js
const Stack = createStackNavigator();
const AuthNavigator = () => (
<Stack.Navigator
screenOptions={{
headerStyle: { backgroundColor: "#221e4f" },
headerTintColor: "white",
title: "",
}}
>
<Stack.Screen
name="Welcome"
component={WelcomeScreen}
options={{ headerShown: false }}
/>
<Stack.Screen name="signIn" component={SigninScreen} />
<Stack.Screen name="signup" component={SignupScreen} />
</Stack.Navigator>
);
This approach that you are doing is error prone. I believe, you shouldn't use navigation.navigate('SomeScreen') on your context functions.
For example; SIGN_IN action is dispatched and it will change the state of token which you are using as conditional to decide which navigator should it use. Dispatch function is not an async so you can't rely on React to change state and mount AppNavigator before navigation.navigate("MainTabScreen") is called. Instead use initialRouteName="MainTabScreen" prop.
I don't know anything about your AppNavigator or AuthNavigator but; misusing of this approach can cause the root of the problem. If you share more detail about your navigators we can help more.
Please check shared video form understanding my issue
https://drive.google.com/file/d/1GKU07Mv7IjiLnrfps5gWpfMPsMphvRDv/view
I need to Flatlist screen every time empty because this below code every time Flatlist API called and then after change Flatlist data.
App Flow
first screen: shows category
second screen: shows selected category quotes
import { StyleSheet,Button,View, Text,FlatList,Dimensions,TouchableHighlight,Clipboard,ToastAndroid,Share,YellowBox } from 'react-native';
import { createAppContainer,NavigationEvents } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
import { createDrawerNavigator } from 'react-navigation-drawer';
import { createSwitchNavigator } from 'react-navigation-switch-transitioner'
import Icon from 'react-native-vector-icons/Ionicons';
import Style from '../constants/Style';
import Spinner from 'react-native-loading-spinner-overlay';
export default class QuoteActivity extends React.Component {
static navigationOptions = ({ navigation }) => {
return {
title: navigation.getParam('Title', 'Default Title'),
headerStyle: {
backgroundColor: navigation.getParam('BackgroundColor', '#5F4B8BFF'),
},
headerTintColor: navigation.getParam('HeaderTintColor', '#fff'),
headerLeft: (
<Icon
style={{ paddingLeft: 15,paddingTop: 5,color:'#FFFFFF' }}
onPress={() => navigation.goBack(null)}
name="ios-arrow-back"
size={40}
/>
)
};
};
constructor(props) {
super(props);
YellowBox.ignoreWarnings([
'Warning: componentWillMount is deprecated',
'Warning: componentWillReceiveProps is deprecated',
]);
}
state = {
spinner: false,isLoading: false,
dataSource:[]
}
// ON FOCUS CALL API
componentDidMount() {
this._doApiCall();
}
// API CALL
_doApiCall = () => {
this.setState({
spinner: true,isLoading:true
});
const { navigation } = this.props;
let CategoryId = navigation.getParam('CategoryId');
fetch('API_URL', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
}
}).then((response) => response.json())
.then((responseJson) => {
this.setState({
// SAMPLE JSON
// {
// "data": [
// {
// "Id": "462",
// "CategoryId": "5",
// "Title": "Being 'Single' is My Attitude!"
// },
// {
// "Id": "464",
// "CategoryId": "5",
// "Title": "I never dreamed about success. I worked for it."
// }
// ],
// "success": "1",
// "message": "2 Quotes found.",
// "service_time": "0.030284881591797 seconds"
// }
// SAMPLE JSON END
spinner:false,isLoading:false,
dataSource:responseJson.data
})
})
.catch((error) => {
console.error(error);
});
};
// Copy to clipboard
writeToClipboard = async (text) => {
await Clipboard.setString(text);
ToastAndroid.show('Quote copied!', ToastAndroid.SHORT);
};
// Share quotes
shareQuotes = async (text) => {
const result = await Share.share({
message:text.toString()+'\n\n Download app from play store',
});
};
_keyExtractor = (item) => item.id;
// RENDER DATA ITEMS
_renderItem = ({item}) => {
return (
<View style={Style.CategoryTitleList}>
<Text style={Style.CategoryTitle}>{item.Title}</Text>
<View style={[{ flexDirection:'row',justifyContent: 'center',alignItems:'center',textAlign:'center',alignSelf:"center"}]}>
<Icon name="ios-copy" onPress={() => this.writeToClipboard(item.Title)} title="Copy" size={25} color="#5F4B8BFF" style={[{margin:5,alignItems:'flex-end',paddingTop:3,paddingRight:20,paddingLeft:20 }]} />
<Icon name="ios-share" onPress={() => this.shareQuotes(item.Title)} size={25} color="#5F4B8BFF" style={[{margin:5,alignItems:'flex-end',paddingTop:3,paddingRight:20,paddingLeft:20 }]} />
</View>
</View>
)
}
render() {
// RENDER DATA ITEMS INSIDE RENDER FNS
renderItem = ({ item, index }) => {
return (
<View style={Style.CategoryTitleList}>
<Text style={Style.CategoryTitle}>{item.Title}</Text>
<View style={[{ flexDirection:'row',justifyContent: 'center',alignItems:'center',textAlign:'center',alignSelf:"center"}]}>
<Icon name="ios-copy" onPress={() => this.writeToClipboard(item.Title)} title="Copy" size={25} color="#5F4B8BFF" style={[{margin:5,alignItems:'flex-end',paddingTop:3,paddingRight:20,paddingLeft:20 }]} />
<Icon name="ios-share" onPress={() => this.shareQuotes(item.Title)} size={25} color="#5F4B8BFF" style={[{margin:5,alignItems:'flex-end',paddingTop:3,paddingRight:20,paddingLeft:20 }]} />
</View>
</View>
);
};
return (
<View style={Style.QuotesListView}>
<NavigationEvents
onDidFocus={this._doApiCall}
onWillFocus={() => this.setState({spinner: true})}
/>
<FlatList
data={this.state.dataSource}
renderItem={renderItem} // USED INSIDE RENDER FNS
refreshing={this.state.isLoading}
onRefresh={this._doApiCall}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={1000}
initialNumToRender={1}
removeClippedSubviews={false}
keyExtractor={this._keyExtractor}
/>
</View>
);
}
}```
Before doing the API Call you can clean the DataSource array that you have.
// API CALL
_doApiCall = () => {
this.setState({
spinner: true,isLoading:true,
dataSource: []
});
...
You can handle that in 2 different ways.
whenever you call _doApiCall function, change initial value of dataSource as an empty array.
_doApiCall = () => {
this.setState({
spinner: true,
isLoading: true,
dataSource: []
});
}
Using ternary operator, diaplay spinner when data loading & if not diaplay faltlist
{
this.state.isLoading ?
<Show your Spinner /> :
<FlatList
data={this.state.dataSource}
renderItem={renderItem}
refreshing={this.state.isLoading}
onRefresh={this._doApiCall}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={1000}
initialNumToRender={1}
removeClippedSubviews={false}
keyExtractor={this._keyExtractor}
/>
}
Feel free for doubt. hope this helps you.
What is the difference between functional component and class component to define function's property type inside?
For example,
In class component, there is no problems.
class ListView extends React.Component {
...
renderItem: SectionListRenderItem<Circle> = ({ item }) => { // no lint warning
const { circleStore } = this.props
return (
<CircleList
hideButton
title={item.title}
desc={item.desc}
iconSource={{ uri: item.logoImageUrl }}
onPress={() => circleStore && circleStore.setCurrentCircle(item)}
></CircleList>
)
}
render() {
return (
<SectionListView ... />
)
}
}
...
In functional component, it makes 'item' is missing in props validation - eslint(react/prop-types) ploblem as below.
...
const ListView = (props: Props) => {
const renderItem: SectionListRenderItem<Circle> = ({ item }) => ( // lint warning
<CircleList
hideButton
title={item.title}
desc={item.desc}
iconSource={{ uri: item.logoImageUrl }}
onPress={() => {}}
></CircleList>
)
...
return (
<SectionListView ... />
)
}
...
It can be fixed as below code
const renderItem: SectionListRenderItem<Circle> = ({ item }) => (
change to
const renderItem: SectionListRenderItem<Circle> = ({ item }: { item: Circle }) => (
Circle is the interface I declared.
I wonder why this happens and what difference there is.
Only renderItem in stateless component makes a lint error in comparison to a class component.
Full source
interface Props {
popularCircles: Circle[]
recentCircles: Circle[]
onPressSection?: (section: Circle) => void
onPressCircle?: (circle: Circle) => void
}
export const Main = ({
popularCircles,
recentCircles,
onPressCircle,
}: Props) => {
const renderSectionHeader = ({ section: { title } }: SectionHeader) => {
return (
<SectionHeaderCell
title={title}
buttonTitle={''}
disabled
></SectionHeaderCell>
)
}
const renderItem: SectionListRenderItem<Circle> = ({
item,
}) => ( // This line makes a eslint warning only in functional component.
<CircleList
hideButton
title={item.title}
desc={item.desc}
iconSource={{ uri: item.logoImageUrl }}
onPress={() => onPressCircle && onPressCircle(item)}
></CircleList>
)
return (
<Container>
<SectionList
sections={[
{
title: 'Title1',
data: popularCircles,
},
{
title: 'Title2',
data: recentCircles,
},
]}
renderSectionHeader={renderSectionHeader}
renderItem={renderItem}
keyExtractor={(item, index) => item + index}
contentContainerStyle={{ flex: 1 }}
/>
</Container>
)
}
export default Main
Circle.d.ts
export interface Circle {
circleId: number
title: string
desc: string
logoImageUrl?: string
}