how to show Loader till API get data using redux? - react-native

I am new to react and redux. I have implemented API fetching using redux but not sure where should i put code for loader till API gives data and error message if no network or API fails.
everything is working fine I am getting data too..only thing i stick is how to show activity indicator and error message. Is there any way to do that? Thanks in advance :)
reducer.js
export const GET_REPOS = 'my-awesome-app/repos/LOAD';
export const GET_REPOS_SUCCESS = 'my-awesome-app/repos/LOAD_SUCCESS';
export const GET_REPOS_FAIL = 'my-awesome-app/repos/LOAD_FAIL';
const initialState = {
repos: [],
loading: false,
error: null
};
export default function reducer(state = initialState , action) {
switch (action.type) {
case GET_REPOS:
return { ...state, loading: true };
case GET_REPOS_SUCCESS:
return { ...state, loading: false, repos: action.payload.data };
case GET_REPOS_FAIL:
return {
...state,
loading: false,
error: 'Error while fetching repositories',
};
default:
return state;
}
}
export function listRepos(photos) {
return {
type: GET_REPOS,
payload: {
request: {
url: `photos/`
}
}
};
}
export function listThumb(albumId) {
return {
type: GET_REPOS,
payload: {
request: {
url: `photos?albumId=${albumId}`
}
}
};
}
home.js
import React, { Component } from 'react';
import { ActivityIndicator } from 'react-native-paper';
import { View, Text, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
import styles from '../HomeComponent/style';
import { Ionicons } from '#expo/vector-icons';
import { listRepos } from '../../../reducer';
class Home extends Component {
componentDidMount() {
this.props.dispatch(fetchProducts());
this.props.listRepos('');
}
FlatListItemSeparator = () => (
<View style={styles.flatListItemSeparator} />
)
renderItem = ({ item }) => (
<View style={styles.listRowContainer}>
<TouchableOpacity onPress={() => this.props.navigation.navigate('ThumbnailViewScreen', {
albumID: item.id,
})} style={styles.listRow}>
<View style={styles.listTextNavVIew}>
<Text style={styles.albumTitle}> {item.title} </Text>
<Ionicons name='md-arrow-dropright' style={styles.detailArrow} />
</View>
</TouchableOpacity>
</View>
);
render() {
const { error, loading, products } = this.props;
if (error) {
return <Text>adadf </Text>;
}
if (loading) {
return (
<View style={{ flex: 1, paddingTop: 30 }}>
<ActivityIndicator animating={true} size='large' />
</View>
);
}
const { repos } = this.props;
return (
<View style={styles.MainContainer} >
<FlatList
styles={styles.container}
data={repos}
renderItem={this.renderItem}
ItemSeparatorComponent={this.FlatListItemSeparator}
/>
</View>
);
}
}
const mapStateToProps = state => {
let storedRepositories = state.repos.map(repo => ({ key: repo.id, ...repo }));
return {
repos: storedRepositories
};
};
const mapDispatchToProps = {
listRepos
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);

const mapStateToProps = state => {
let storedRepositories = state.repos.map(repo => ({ key: repo.id, ...repo }));
return {
repos: storedRepositories
};
};
loading may not have been passed as a prop to Home. To fix that,
add it to mapStateToProps:
return {
repos: storedRepositories,
loading: state.loading
};

Related

Cannot update a component (`ForwardRef(BaseNavigationContainer)`) while rendering a different component (`Signinscreen`)

I am getting the following warning:
Warning: Cannot update a component (ForwardRef(BaseNavigationContainer)) while rendering a different component (Signinscreen). To locate the bad setState() call inside Signinscreen, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
Signinscreen.js
import React, { Component, useEffect } from 'react'
import { View, Text, TextInput, Image } from 'react-native'
// STYLE
import styles from "./style"
// FORMIK
import { Formik } from 'formik'
// COMPONENT
import Navigationcomponent from "../../assets/components/navigationcomponent"
// STORAGE
import AsyncStorage from '#react-native-async-storage/async-storage'
// REDUX
import { connect } from 'react-redux'
// REDUX ACTION
import { initialaccount,user_signin } from '../../actions/index'
// YUP
import * as yup from 'yup'
// IMAGE
const mainimage = require("../../assets/images/mainlogo.png")
// SCREEN
import Homescreen from "../homescreen";
const SigninSchema = yup.object().shape({
username: yup
.string()
.min(6)
.max(12)
.required('Please, provide your username!')
.nullable(),
password: yup
.string()
.min(6)
.required('Please, provide your password!')
.nullable(),
})
const Signinscreen = ({navigation, initialaccount_d, user_signin_d, user_s}) => {
const storeData = async (values) => {
try {
// await AsyncStorage.setItem('me', JSON.stringify(values))
// await initialaccount_d(true)
// console.log(values)
await user_signin_d(values)
//
// navigation.navigate("Initialhomescreen")
} catch (e) {
// saving error
}
}
const validatecheck = () => {
AsyncStorage.setItem('me', JSON.stringify(user_s.usersignin_success))
navigation.navigate("Initialhomescreen")
// console.log(user_s.usersignin_success)
// if (user_s.usersignin_success != null) {
// // console.log(user_s.usersignin_success)
// // navigation.navigate("Initialhomescreen")
// if (user_s.usersignin_success.status == 200) {
// // console.log("user_s.usersignin_success")
// await AsyncStorage.setItem('me', JSON.stringify(user_s.usersignin_success))
// navigation.navigate("Initialhomescreen")
// }
// }
}
// ONBACK PRESS
useEffect(() => {
// validatecheck()
navigation.addListener('beforeRemove', e => {
// REMOVE INPUT VALUE BEFORE LEAVE SCREEN
user_signin_d(null)
});
},[])
if (user_s.usersignin_success != null && user_s.usersignin_success.status == 200) {
// validatecheck()
return(
<View>
{validatecheck()}
<Text>Load...</Text>
</View>
)
} else {
return(
<Formik
// validationSchema={SigninSchema}
initialValues={{ username: null, password: null}}
onSubmit={values => storeData(values)}>
{({ handleChange, handleSubmit, values, errors, touched }) =>
(
<View style={styles.main}>
<View style={styles.mainconf}>
<Image style={styles.imageconf} source={mainimage}></Image>
<View style={styles.inputconfmain}>
<Text style={styles.titleconf}>
Create now, for ever...
</Text>
<TextInput
style={styles.inputconf}
placeholder="username"
placeholderTextColor="gray"
onChangeText={handleChange('username')}
value={values.username}>
</TextInput>
{touched.username && errors.username &&
<Text style={{ fontSize: 12, color: 'red', alignSelf: "center" }}>{errors.username}</Text>
}
<TextInput
style={styles.inputconf}
placeholder="password"
placeholderTextColor="gray"
secureTextEntry={true}
onChangeText={handleChange('password')}
value={values.password}>
</TextInput>
{touched.password && errors.password &&
<Text style={{ fontSize: 12, color: 'red', alignSelf: "center" }}>{errors.password}</Text>
}
<Text onPress={handleSubmit} style={styles.btnsignupconf}>
Sign in
</Text>
{user_s.usersignin_success != null ? (user_s.usersignin_success.status == 400 ? <Text style={styles.warningmsg}>{user_s.usersignin_success.message}</Text> : (null)) : null}
</View>
</View>
</View>
)}
</Formik>
)
}
}
const mapStateToProps = (state) => ({
user_s: state.user
})
const mapDispatchToProps = (dispatch) => ({
initialaccount_d: (value) => dispatch(initialaccount(value)),
user_signin_d: (value) => dispatch(user_signin(value))
})
export default connect(mapStateToProps, mapDispatchToProps)(Signinscreen)
Homescreen.js
import React, { Component, useEffect, useState } from 'react'
import { View, Text, Image, SafeAreaView, BackHandler } from 'react-native'
// COMPONENT
import Navigationcomponent from "../../assets/components/navigationcomponent";
// STORAGE
import AsyncStorage from '#react-native-async-storage/async-storage';
// STYLE
import styles from "./style"
// REDUX
import { connect } from 'react-redux'
// REDUX ACTION
import { initialaccount } from '../../actions/index'
const Homescreen = ({navigation, initialaccount_s, initialaccount_d, user_s}) => {
// useEffect(() => {
// myaccount()
// }, []);
// myaccount = async () => {
// try {
// const value = await AsyncStorage.getItem('me')
// if(value !== null) {
// // value previously stored
// console.log('Yay!! you have account.')
// // await navigation.navigate("Homescreen")
// await initialaccount_d(true)
// } else {
// console.log('Upss you have no account.')
// // await navigation.navigate("Welcomescreen")
// await initialaccount_d(false)
// }
// } catch(e) {
// // error reading value
// }
// }
// Call back function when back button is pressed
const backActionHandler = () => {
BackHandler.exitApp()
return true;
}
useEffect(() => {
console.log(user_s.usersignin_success)
// Add event listener for hardware back button press on Android
BackHandler.addEventListener("hardwareBackPress", backActionHandler);
return () =>
// clear/remove event listener
BackHandler.removeEventListener("hardwareBackPress", backActionHandler);
},[])
// const validatecheck = () => {
// console.log(user_s.usersignin_success)
// // if (user_s.usersignin_success == null || user_s.usersignin_success.status == 400) {
// // navigation.navigate("Signinscreen")
// // }
// }
clearAll = async () => {
try {
await AsyncStorage.clear()
await initialaccount_d(false)
navigation.navigate("Initialscreen")
console.log('Done. cleared')
} catch(e) {
console.log('Upss you have no account.')
}
}
const getData = async () => {
try {
const value = await AsyncStorage.getItem('me')
if(value !== null) {
// value previously stored
console.log(value)
} else {
console.log('Upss you have no account.')
}
} catch(e) {
// error reading value
}
}
return(
<View style={styles.main}>
<View style={styles.logoutconf}>
<Text onPress={() => clearAll()}>LOGOUT</Text>
<Text onPress={() => getData()}>cek data</Text>
</View>
<Navigationcomponent style={styles.mainconf} />
</View>
)
}
const mapStateToProps = (state) => ({
initialaccount_s: state.user,
user_s: state.user
})
const mapDispatchToProps = (dispatch) => ({
initialaccount_d: (value) => dispatch(initialaccount(value))
})
export default connect(mapStateToProps, mapDispatchToProps)(Homescreen)
I encountered the same problem and I think that passing navigation.navigate("<screen_name>");} to another screen's onPress() prop (like
onPress={() => {navigation.navigate("<screen_name>");}
or
...
const navFunction = () => {
navigation.navigate("<screen_name>");
};
return(
<TouchableOpactiy onPress={() => {navFunction();} />
...
</TouchableOpacity>
);
...
).
I solved this by not passing the navigation from the onPress prop in the homeScreen and instead using the navigation prop inherent to the otherScreen.
// The homescreen.
const App = props => {
return(
<TouchableOpactiy
onPress={() => {
props.navigation.navigate('OtherScreen');
}}
>...</TouchableOpacity>
);
};
// The other screen.
const App = ({navigation}) => {
return(
<TouchableOpacity
onPress={() => {
navigation.navigate('HomeScreen');
}}
>...</TouchableOpacity>
);
};

How do I store and get the theme state using AsyncStorage

I'm tying to store the theme state in the app, so that the chosen theme will be persistent after the app is closed. I have two files, one is the App.js file which has the navigation, and the other is the DrawerContent.js file that has the content of the drawer as well as the switch for the theme. toggleTheme is passed to the DrawerContent through useContext.
These are the snippets of the App file.
import {
NavigationContainer,
DefaultTheme as NavigationDefaultTheme,
DarkTheme as NavigationDarkTheme
} from '#react-navigation/native';
import {
Provider as PaperProvider,
DefaultTheme as PaperDefaultTheme,
DarkTheme as PaperDarkTheme
} from 'react-native-paper';
import AsyncStorage from '#react-native-community/async-storage';
const THEME_KEY = 'theme_color';
export default function App() {
const [isDarkTheme, setIsDarkTheme] = useState(false);
{/* Themes */ }
const CustomDefaultTheme = {
...NavigationDefaultTheme,
...PaperDefaultTheme,
colors: {
...NavigationDefaultTheme.colors,
...PaperDefaultTheme.colors,
background: '#ffffff',
text: '#333333',
tab: '#0789DC'
}
}
const CustomDarkTheme = {
...NavigationDarkTheme,
...PaperDarkTheme,
colors: {
...NavigationDarkTheme.colors,
...PaperDarkTheme.colors,
background: '#333333',
text: '#ffffff',
tab: '#2c3e50'
}
}
const theme = isDarkTheme ? CustomDarkTheme : CustomDefaultTheme;
// AsyncSotrage
const storeTheme = async (key, isDarkTheme) => {
try {
await AsyncStorage.setItem(THEME_KEY, JSON.stringify(isDarkTheme))
setIsDarkTheme(isDarkTheme);
console.log(isDarkTheme)
} catch (error) {
alert(error);
}
}
const getTheme = async () => {
try {
const savedTheme = await AsyncStorage.getItem(THEME_KEY)
if (savedTheme !== null) {
setIsDarkTheme(JSON.parse(savedTheme));
}
} catch (error) {
alert(error);
}
}
const authContext = useMemo(() => ({
toggleTheme: () => {
setIsDarkTheme(isDarkTheme => !isDarkTheme);
// storeTheme(isDarkTheme => !isDarkTheme);
console.log(isDarkTheme);
}
}),
[]
);
useEffect(() => {
getTheme();
// Fetch the token from storage then navigate to our appropriate place
const bootstrapAsync = async () => {
let userToken;
try {
userToken = await AsyncStorage.getItem(USER_TOKEN)
if (userToken !== null) {
setUserToken(JSON.parse(userToken))
}
} catch (e) {
alert(e)
}
// After restoring token, we may need to validate it in production apps
// This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away.
dispatch({ type: 'RESTORE_TOKEN', token: userToken });
};
bootstrapAsync();
}, []);
return (
<PaperProvider theme={theme}>
<AuthContext.Provider value={authContext}>
<NavigationContainer theme={theme}>
...
</NavigationContainer>
</AuthContext.Provider>
</PaperProvider>
)
}
This is the DrawerContent code.
import {
useTheme,
Avatar,
Title,
Caption,
Paragraph,
Drawer,
Text,
TouchableRipple,
Switch
} from 'react-native-paper';
export function DrawerContent(props) {
const paperTheme = useTheme();
const { signOut, toggleTheme } = React.useContext(AuthContext);
return (
<View style={{ flex: 1 }}>
<DrawerContentScrollView>
<View style={styles.drawerContent}>
...
<Drawer.Section title="Preferences">
<TouchableRipple onPress={() => { toggleTheme() }}>
<View style={styles.preference}>
<Text>Dark Theme</Text>
<View pointerEvents="none">
<Switch
value={paperTheme.dark}
/>
</View>
</View>
</TouchableRipple>
</Drawer.Section>
</View>
</DrawerContentScrollView>
</View>
)
}
Perhaps the effective synchronous access of AsyncStorage can solve your problem. It is recommended that you use the react-native-easy-app open source library react-native-easy-app, through which you can access any data in AsyncStorage like memory objects.
For specific usage, maybe you can refer to the StorageController file in Project Sample_Hook

React Redux is not updating the state

I am searching for a couple of days and I can't find out why redux is not updating the state I don't see any problem in my code please help me to find the problem.
it is a simple login project. I can see that data changes inside the reducer when debugging but it's not being mapped to props and state is not changing.
this is my code:
actions.js
import {
LOGIN_SUCCESS,
LOGIN_FAILURE,
} from './types'
export const loginSuccess = (user) => ({
type: LOGIN_SUCCESS,
payload: user
})
export const loginFailure = (error) => ({
type: LOGIN_FAILURE,
payload: error
})
loginReducer.js
import {
LOGIN_SUCCESS,
LOGIN_FAILURE,
} from './types'
const initialState = {
user: null,
errorMessage: null
}
export const loginReducer = (state = initialState, action) => {
switch (action.type) {
case LOGIN_SUCCESS:
return {...state,user: action.payload, errorMessage: null }
case LOGIN_FAILURE:
return {...state,errorMessage: action.payload }
default:
return state;
}
}
loginScreen.js
import React from 'react'
import {
Text,
View,
ImageBackground,
Button,
StyleSheet
} from 'react-native'
import { TextField } from 'react-native-material-textfield';
import { loginSuccess, loginFailure } from './reduxx/actions'
import { connect } from "react-redux";
class LoginScreen extends React.Component {
constructor(props) {
super(props)
this.state = {
email: "",
password: "",
}
}
_handlePress() {
this.props.login(this.state.email, this.state.password)
// user in propa is undifined
this.props.user
}
render() {
let { email, password } = this.state
return (
<ImageBackground source={require('./images/loginBackground.jpg')} style={styles.imageBackground}>
<View style={styles.mainContainer}>
<View style={styles.box}>
<TextField label="Email" value={email} onChangeText={email => this.setState({ email })} />
<TextField label="Password" textContentType="password" value={password} onChangeText={password => this.setState({ password })} />
<Button onPress={() => {
this._handlePress()
}} color="green" title="Sign in" style={styles.button} />
</View>
<View style={styles.bottomTextContainer}>
<Text style={{ color: "white" }}>Don't have an account?</Text>
<Text style={{ color: "lightgreen" }} onPress={() => this.props.navigation.navigate("Register")}> Sign up</Text>
</View>
</View>
</ImageBackground>
)
}
}
function mapStateToProps(state) {
return {
user: state.user,
errorMessage: state.errorMessage
}
}
function mapDispatchToProps(dispatch) {
return {
login: (email, password) => {
try {
let user = //get user from database
dispatch(loginSuccess(user))
} catch (error) {
dispatch(loginFailure(error))
}
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
const styles = StyleSheet.create({
imageBackground: {
width: '100%',
height: '100%'
},
mainContainer: {
flex: 1,
justifyContent: "center"
},
box: {
backgroundColor: 'white',
padding: 8,
margin: 8
},
button: {
margin: 10
},
bottomTextContainer: {
position: "absolute",
bottom: 8,
alignSelf: "center",
flexDirection: "row"
}
});
app.js
import React from 'react';
import { Provider } from "react-redux"
import store from "./reduxx/store";
import AppContainer from './Navigator'
export default class App extends React.Component {
render() {
return (
<Provider store={store} >
<AppContainer />
</Provider>
);
}
}
store.js
import {createStore} from "redux";
import { combineReducers } from 'redux';
import {loginReducer} from './loginReducer'
const rootReducer = combineReducers({
loginReducer,
});
export default store = createStore(rootReducer)
The state which comes into mapStateToProps contains all the combined reducers, so we need to access the reducer name first from the state (like state.loginReducer.user), before trying to access the data of that reducer.
PFB code which should work:
function mapStateToProps(state) {
return {
user: state.loginReducer.user,
errorMessage: state.loginReducer.errorMessage
}
}
Change here :
function mapStateToProps(state) {
return {
user: state.user,
errorMessage: state.errorMessage
}
}
TO
function mapStateToProps(state) {
return {
login: state.loginReducer
}
}
And then this.props.user to this.props.login.user and this.props.errorMessage to this.props.login.errorMessage in all the occurrence.

this.props.navigation.navigate() is not an object

Why is this.props.navigation.navigate() not available in my component? In fact, this.props is completely empty? My setup is that App.js adds StartScreen.js and ListScreen.js to a stackNavigator. ListScreen.js is then used as a component in Startscreen.js and will serve as a stacknavigation...
ListScreen.js
import React, { Component } from "react";
import { ScrollView, View, Text, FlatList, ActivityIndicator } from "react-native";
import { List, ListItem } from 'react-native-elements';
import Styles from 'app/styles/Styles';
import Vars from 'app/vars/Vars';
class ListScreen extends Component {
/*
static navigationOptions = {
title: 'Välkommen',
headerStyle: Styles.header,
headerTintColor: Vars.colors.primary,
headerTitleStyle: Styles.headerTitle,
};
*/
constructor(props) {
super(props);
console.log(props);
this.state = {
loading: true,
data: [],
page: 1,
seed: 1,
error: null,
refreshing: false
};
}
componentDidMount() {
this.fetchItems();
}
fetchItems = () => {
const { page, seed } = this.state;
const url = 'http://appadmin.test/api/menu/'+this.props.id;
this.setState({ loading: true });
fetch(url)
.then(res => res.json())
.then(res => {
this.setState({
data: page === 1 ? res : [...this.state.data, ...res],
error: res.error || null,
loading: false,
refreshing: false
});
})
.catch(error => {
this.setState({ error, loading: false });
});
}
handleRefresh = () => {
console.log('several handleRefresh?');
this.setState(
{
page: 1,
seed: this.state.seed + 1,
refreshing: true
},
() => {
this.fetchItems();
}
);
};
handleLoadMore = () => {
console.log('several handleLoadMore?');
this.setState(
{
page: this.state.page + 1
},
() => {
this.fetchItems();
}
);
};
renderFooter = () => {
if (!this.state.loading) return null;
return (
<View
style={{
paddingVertical: 20,
}}
>
<ActivityIndicator animating size="large" />
<Text style={Styles.textCenter}>
Laddar innehåll...
</Text>
</View>
);
};
renderListItem = ({ item }) => (
<ListItem
title = {item.title}
id = {item.id}
automaticallyAdjustContentInsets={false}
item = {item}
leftIcon={{
name: item.icon,
style: Styles.listitemIcon,
size: 36,
}}
onPress = {this.onItemPress}
style = { Styles.listItem }
titleStyle = { Styles.listitemTitle }
/>
)
onItemPress = (item) => {
this.props.navigation.navigate('List', {
id: item.id
});
}
render() {
return (
<ScrollView contentContainerStyle={Styles.listContainer}>
<FlatList
automaticallyAdjustContentInsets={false}
data = { this.state.data }
style={Styles.list}
keyExtractor={item => "key_"+item.id}
renderItem = { this.renderListItem }
ListFooterComponent={this.renderFooter}
onRefresh={this.handleRefresh}
refreshing={this.state.refreshing}
onEndReachedThreshold={50}
/>
</ScrollView>
);
}
}
export default ListScreen;
StartScreen.js
import React, { Component } from "react";
import {
View,
ScrollView,
Text,
StyleSheet,
Linking,
FlatList
} from "react-native";
import Styles from 'app/styles/Styles';
import Vars from 'app/vars/Vars';
import ListScreen from 'app/screens/ListScreen';
import { List, ListItem } from 'react-native-elements';
import Menu from './../../assets/data/navigation.json';
class StartScreen extends Component {
static navigationOptions = {
title: 'Välkommen',
headerStyle: Styles.header,
headerTintColor: Vars.colors.primary,
headerTitleStyle: Styles.headerTitle,
};
constructor(props) {
super(props);
this.state = {
loading: false,
data: [],
page: 1,
seed: 1,
error: null,
refreshing: false,
};
}
componentDidMount() {
this.fetchList();
}
fetchList = () => {
const { page, seed } = this.state;
const url = 'http://appadmin.test/api/menu/root';
this.setState({ loading: true });
fetch(url)
.then(response => {
return response.json();
})
.then(json => {
this.setState({
data: json,
error: null,
loading: false,
refreshing: false
});
})
.catch(error => {
this.setState({ error, loading: false });
});
}
render() {
return (
<ScrollView style={Styles.scrollContainer}>
<View style={Styles.hero}>
<Text style={[Styles.h1, Styles.whiteText]}>
Region Halland
</Text>
<Text style={[Styles.lead, Styles.whiteText]}>
Välkommen
</Text>
</View>
<ListScreen id=""/>
</ScrollView>
);
}
}
export default StartScreen;
App.js
import React from 'react';
import {
StyleSheet,
Text,
View,
Image,
} from 'react-native';
import initCache from "app/utilities/initCache";
import { AppLoading } from 'expo';
import {
createBottomTabNavigator,
createStackNavigator
} from 'react-navigation'
import StartScreen from 'app/screens/StartScreen';
import ListScreen from 'app/screens/ListScreen';
import AboutScreen from 'app/screens/AboutScreen';
import { icon } from 'app/components/Image.js';
import Ionicons from 'react-native-vector-icons/Ionicons'
const StartStack = createStackNavigator({
Start: {screen: StartScreen, tabBarLabel: 'Hem'},
List: {screen: ListScreen},
}, {
navigationOptions : {
headerTitle: <Image
source={ icon }
style={{ width: 30, height: 30 }} />,
tabBarLabel: 'hem'
}
});
const AboutStack = createStackNavigator({
About: AboutScreen,
});
const Tabs = createBottomTabNavigator({
StartStack: {screen: StartStack, navigationOptions: { title: 'Hem'}},
AboutStack: {screen: AboutStack, navigationOptions: { title: 'Om Region Halland'}}
}, {
navigationOptions: ({ navigation }) => ({
tabBarIcon: ({ focused, tintColor }) => {
const { routeName } = navigation.state;
let iconName;
if (routeName === 'AboutStack') {
iconName = `ios-information-circle${focused ? '' : '-outline'}`;
} else if (routeName === 'StartStack') {
iconName = `ios-home${focused ? '' : '-outline'}`;
}
// You can return any component that you like here! We usually use an
// icon component from react-native-vector-icons
return <Ionicons name={iconName} size={24} color={tintColor} />;
},
}),
tabBarOptions: {
activeTintColor: '#0b457e',
inactiveTintColor: 'gray',
},
})
export default class App extends React.Component {
state = {
appIsReady: false,
};
componentWillMount() {
this._loadAssetsAsync();
}
async _loadAssetsAsync() {
try {
await initCache({
fonts: [
{'scala-sans-regular': require('./assets/fonts/ScalaSans-Regular.ttf')},
{'scala-sans-bold': require('./assets/fonts/ScalaSans-Bold.ttf')},
]
});
} catch (e) {
console.warn(
'There was an error caching assets (see: main.js), perhaps due to a ' +
'network timeout, so we skipped caching. Reload the app to try again.'
);
console.log(e.message);
} finally {
this.setState({ appIsReady: true });
}
}
render() {
if (this.state.appIsReady) {
return (
<Tabs />
);
} else {
return <AppLoading />;
}
}
}
const { navigate } = this.props.navigation;
then use it as : navigate(‘Signup’)}

How to expand and collapse specify section using SecionList?

I call an api https://obscure-reaches-65656.herokuapp.com/api/getCloseTime?city=Miaoli&sTime=21&eTime=24 to my react-redux action and use SectionList to show the data.
With official tutorial, i use SectionList it will just show all of the section, i try to find the way when click title that can expand or collapse the section.
I find my sectionComp and renderSectionItem use the same title so i try use this.state{ title: '', expand: false }
When i click 國興戲院 use this.setState({ title: '國興戲院', expand: true }) and use like if(this.state.expand) {} in renderSectionItem
Obviously its not working because i may have a lot of section.
I have no idea what next step should i try.
Any help would be appreciated. Thanks in advance.
Here is my SectionList data:
Here is my class component:
import React, { Component } from 'react';
import { Text, SectionList, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
import { View } from 'react-native-animatable';
import { fetchSearchTime } from '../actions';
import { Spinner } from './common';
import GetUserTime from '../function/common/GetUserTime';
class MovieCloseTime extends Component {
constructor(props) {
super(props);
const { selectedCity, firstSliderValue, secondSliderValue }
= this.props.navigation.state.params;
this.state = {
selectedCity,
firstSliderValue,
secondSliderValue,
};
}
componentDidMount() {
const { selectedCity, firstSliderValue, secondSliderValue } = this.state;
this.props.fetchSearchTime({
selectedCity, firstSliderValue, secondSliderValue
});
}
sectionComp = (info) => {
const theaterCn = info.section.title;
console.log('Title info is =>');
console.log(info);
return (
<TouchableOpacity
onPress={() => console.log('Hot to expand and collapse specify section when click here?')}
>
<View style={{ flex: 1, backgroundColor: '#DCDCDC' }}>
<Text style={styles.sectionTitle}>{theaterCn}</Text>
</View>
</TouchableOpacity>
);
}
renderSectionItem = (info) => {
const cnName = info.item.cnName;
console.log('Section info is =>');
console.log(info);
return (
<TouchableOpacity
onPress={() => {
this.props.navigation.navigate('MovieDetail', {
enCity: this.state.selectedCity,
cnName
});
}
}
>
<View style={{ flex: 1, flexDirection: 'column' }}>
<Text style={styles.sectionSubTitle}>{cnName}</Text>
<View style={{ flexDirection: 'row', flexWrap: 'wrap', backgroundColor: '#F8F8FF' }}>
{info.item.releasedTime.map((value, index) => {
const theTime = GetUserTime.getAsiaTime(value, 'YYYY/MM/DD HH:mm:ss');
const hour = theTime.getHours();
const minute = (theTime.getMinutes() < 10 ? '0' : '') + theTime.getMinutes();
return (
<Text style={styles.sectionTimeTitle} key={index}>{`${hour}:${minute}`}</Text>
);
})
}
</View>
</View>
</TouchableOpacity>
);
}
render() {
const movieData = this.props.searchTime;
if (this.props.loading) {
return <Spinner text='Loading...' />;
}
console.log('movieData is =>');
console.log(movieData);
return (
<View style={{ flex: 1 }}>
<SectionList
renderSectionHeader={this.sectionComp}
renderItem={this.renderSectionItem}
sections={movieData}
keyExtractor={(item, index) => index}
ItemSeparatorComponent={() => <View style={styles.separator} />}
/>
</View>
);
}
}
const mapStateToProps = (state) => {
const searchTime = state.searchTime.searchList;
const loading = state.searchTime.loading;
return { searchTime, loading };
};
const styles = {
// some styles
};
export default connect(mapStateToProps, { fetchSearchTime })(MovieCloseTime);
Here is my action fetchSearchTime:
export const fetchSearchTime = ({ selectedCity, firstSliderValue, secondSliderValue }) => {
return (dispatch) => {
dispatch({ type: SEARCH_TIME_REQUEST });
console.log(`https://obscure-reaches-65656.herokuapp.com/api/getCloseTime?city=${selectedCity}&sTime=${firstSliderValue}&eTime=${secondSliderValue}`);
fetch(`https://obscure-reaches-65656.herokuapp.com/api/getCloseTime?city=${selectedCity}&sTime=${firstSliderValue}&eTime=${secondSliderValue}`)
.then(response => response.json())
.then(responseData => {
const movieData = responseData.reduce((r, s) => {
r.push({ title: s.theaterCn, id: s._id, expand: true, data: s.movie });
return r;
}, []);
//dispatch({ type: SEARCH_TIME, payload: responseData });
dispatch({ type: SEARCH_TIME, payload: movieData });
})
.catch((error) => console.log(error));
};
};
about type SEARCH_TIME reducer:
// with others type
import {
SEARCH_TIME_REQUEST,
SEARCH_TIME
} from '../actions/types';
const INITIAL_STATE = {
searchList: [],
loading: true,
selectedCity: '',
firstSliderValue: '',
secondSliderValue: ''
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case SEARCH_TIME_REQUEST:
return {
searchList: [],
loading: true,
};
case SEARCH_TIME:
return {
searchList: action.payload,
loading: false
};
default:
return state;
}
};