reusable component with react-native linear gradient - react-native

I am trying to make a reusable component with linear gradient which can be used to dynamically change each page's theme/color but i keep getting an error cannot read property of children undefined.
import React from 'react';
import LinearGradient from 'react-native-linear-gradient';
export const GradientStyle = ({ theme }) => {
const { primary, primaryGradient2, primaryGradient1 } = theme;
return (
<LinearGradient
style={{ flex: 1 }}
colors={[primary, primaryGradient2, primaryGradient1]}>
{this.props.children}
</LinearGradient>
);
};
usage
import {GradientStyle} from '../../../styles/theme/GradientTheme'
const theme1 ={
primary: '#4c669f',
primaryGradient2: '#3b5998',
primaryGradient1: '#192f6a'
}
render() {
return (
<GradientStyle colors={theme1}>
.......content
</GradientStyle>
);
}

You cannot use this.props in a functional component. You have to do add it to the destructuring parameter, like this:
export const GradientStyle = ({ children, theme }) => {
const { primary, primaryGradient2, primaryGradient1 } = theme;
return (
<LinearGradient
style={{ flex: 1 }}
colors={[primary, primaryGradient2, primaryGradient1]}>
{children}
</LinearGradient>
);
};
You can then create a GradientStyle like this:
import {GradientStyle} from '../../../styles/theme/GradientTheme'
const theme1 ={
primary: '#4c669f',
primaryGradient2: '#3b5998',
primaryGradient1: '#192f6a'
};
render() {
return (
<GradientStyle theme={theme1}></GradientStyle>
);
}

Related

How to use createRef to toggle Accordion list items

I have a FlatList and each item is an accordion, I m using class based react and I want to be able to toggle each accordion individually using createRef but I was unsuccessful
export default class shopingScreen extends React.component{
constractor(props){
super(props);
this.state = {
showAccordion : false
}
this.accordian = React.createRef();
}
handleListItem(item,index){
return (
<TouchableOpacity ref={this.accordian} onPress={()=> {this.setState(prevState =>({!prevState.showAccordion}) )
<Icon name='chevron-up'/>
</TouchableOpacity>
{this.state.showAccordion&&<Text>{item}</Text>
}
renderList(){
return (
<View>
<FlatList
data ={fakeList}
keyExtractor ={(item,index)=> Math.random().toString()}
renderItem={({item,index})=> this.handleListItem(item,index)}
</View>
)
}
}
Every thing gets much easier if you take handleListItem and make it its own component. Each item needs its own accordion, its own boolean state, its own ref, and its own Animation.Value (for the accordion effect). If you tried to manage all that logic in a single component it gets really messy (see AssetExample.js here)
But when separated your list component from the list item component everything is much cleaner link
// List component
import React from 'react';
import { View, FlatList, StyleSheet } from 'react-native';
import { colorGenerator } from '#phantom-factotum/colorutils';
import ListItem from './ListItem';
const fakeList = colorGenerator(5).map((color, i) => ({
color,
title: 'Item ' + (i + 1),
id: 'list-item-' + i,
}));
export default class ShoppingScreen extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<View>
<FlatList
data={fakeList}
keyExtractor={(item, index) => item.id}
renderItem={({ item, index }) => (
<ListItem item={item} index={index} />
)}
/>
</View>
);
}
}
const styles = StyleSheet.create({});
// list item
import React from 'react';
import { MaterialCommunityIcons } from '#expo/vector-icons';
import {
View,
FlatList,
TouchableOpacity,
Text,
Animated,
StyleSheet,
} from 'react-native';
const ITEM_HEIGHT = 50;
export default class ListItem extends React.Component {
constructor(props) {
super(props);
this.state = {
showAccordion: false,
};
this.itemHeight = new Animated.Value(0);
this.itemRef = React.createRef(null);
}
render() {
const showAccordion = this.state.showAccordion;
const animatedStyle = {
height: this.itemHeight.interpolate({
inputRange: [0, 1],
outputRange: [0, ITEM_HEIGHT],
}),
overflow: 'hidden',
};
return (
<TouchableOpacity
style={[
styles.itemContainer,
{ backgroundColor: this.props.item.color },
]}
ref={this.itemRef}
onPress={() => {
const nextVal = !showAccordion;
Animated.timing(this.itemHeight, {
toValue: nextVal ? 1 : 0,
duration: 200,
}).start();
this.setState((prevState) => ({
...prevState,
showAccordion: nextVal,
}));
}}>
<MaterialCommunityIcons
name={showAccordion ? 'chevron-up' : 'chevron-down'}
/>
<Animated.View style={animatedStyle}>
<Text>{this.props.item.title}</Text>
</Animated.View>
</TouchableOpacity>
);
}
}
const styles = StyleSheet.create({
itemContainer: {
padding: 5,
paddingVertical: 10,
marginVertical: 10,
// overflow: 'hidden',
},
});

TouchableOpacity's onPress is not working

Task.js
export const Task = ({ addTask }) => {
const [focusItem, setFocusItem] = useState(null);
return (
<View style={styles.titleContainer}>
<Text style={styles.title}>What would you like to focus on?</Text>
<View style={styles.container}>
<TextInput
style={{ flex: 1 }}
maxLength={50}
value={focusItem}
onSubmitEditing={()=>({ nativeEvent: { text } }) => setFocusItem(text)}
/>
<RoundedButton
style={styles.addSubject}
size={50}
title="+"
onPress={()=>addTask(focusItem)}
/>
</View>
</View>
);
};
App.js
import React, { useState } from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
import {Task} from './src/features/task/Task'
export default function App() {
const [task, setTask] = useState(null);
return (
<View style={styles.container}>
{
task ?
(<Text></Text>):
(<Task addTask = {setTask}/>)
}
<Text>{task}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#425670',
},
});
I have tried to send the data from Task to App component by setting the value from Task. But onPress is not working.
I could see the text has been set successfully while executing the onSubmitEditing, but the onPress is doing nothing. Please help me fix this.
Thanks in advance
You need to change this
onSubmitEditing={()=>({ nativeEvent: { text } }) => setFocusItem(text)}
to
onSubmitEditing={({ nativeEvent: { text } }) => setFocusItem(text)}
You could also refer I want to update the parent according to the change of the child to react native

How to separate axios call from component in react native?

I am new to react native. I have following component in my project for now I have written for fetching API in same component but want to separate it out. I am getting difficulty for how can i access variable which I am using in "getAlbum" method from outside of component.
Is there standard way to separate API call from component?
import React, { Component } from 'react';
import {
FlatList, Text, View, Image, TouchableOpacity,
} from 'react-native';
import { ActivityIndicator, Provider } from 'react-native-paper';
import axios from 'axios';
import styles from '../style/ThumbnailView.component.style';
import ErrorAlert from '../common/ErrorAlert';
import * as myConstant from '../common/Constants';
export default class HomeScreen extends Component {
// For to Navigation header
static navigationOptions = () => ({
headerTitle: 'Album Information',
});
constructor(props) {
super(props);
this.state = {
isLoading: true,
apiLoadingError: false,
};
}
getAlbums() {
const { navigation } = this.props;
const albumId = navigation.getParam('albumID', 'no data');
axios
.get(
myConstant.API + `photos?albumId=${albumId}`, {timeout: myConstant.TIMEOUT}
)
.then((response) => {
this.setState({
isLoading: false,
dataSource: response.data,
});
})
.catch(err => {
this.setState({isLoading: false, apiLoadingError: true})
});
}
componentDidMount() {
this.getAlbums();
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, paddingTop: 30 }}>
<ActivityIndicator animating={true} size='large' />
</View>
);
}
if (this.state.apiLoadingError) {
return (
<ErrorAlert />
);
}
return (
<React.Fragment>
<Provider>
<View style={styles.listContainer} >
<FlatList
testID='flatlist'
data={ this.state.dataSource } numColumns={3}
renderItem={({ item }) => <View style={styles.listRowContainer}>
<TouchableOpacity onPress={() => this.props.navigation.navigate('AlbumDetailsViewScreen', {
albumTitle: item.title, albumImg: item.url
})} style={styles.listRow}>
<View style={styles.listTextNavVIew}>
<Image source = {{ uri: item.thumbnailUrl }} style={styles.imageViewContainer} />
</View>
</TouchableOpacity>
</View>
}
keyExtractor = { (item, index) => index.toString() }
/>
</View>
</Provider>
</React.Fragment>
);
}
}
You can separate your axios call by making another class with function which will receive 'albumID' as an argument - then add it to your axios link. If you want to call this function from another class just make it static and use like in example below. Then you can map your fetchData to parse it into state. Hope it will help you.
export class Api {
static fetchData = (albumId: string) => {
//here your axios call which will return an array
}
}
export default class HomeScreen extends React.Component {
state = {
//.....
}
receivedData = Api.fetchData('albumID');
//you can map array here to get what you want.
}

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 };
};

Invariant Violation: App(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null

This is my first project in React Native with Redux, I have a component(MyApp) which i am importing in index.js. but it is giving above error.
index.js -
import React from 'react';
import { AppRegistry } from 'react-native';
import MyApp from './component/MyApp';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './reducers';
import thunk from 'redux-thunk';
const store = createStore(rootReducer, applyMiddleware(thunk));
const App = () => {
<Provider store={store}>
<MyApp/>
</Provider>
}
AppRegistry.registerComponent('learningRedux', () => App);
MyApp.js
import React, { Component } from 'react';
import {
Text, View, TouchableHighlight
} from 'react-native';
import { connect } from 'react-redux';
import { fetchPeopleFromAPI } from '../actions/index';
export class MyApp extends Component {
render() {
const { people, isFetching } = props.people
return (
<View style={{
margin: 100,
paddingLeft: 20,
paddingRight: 20
}}
>
<Text style={{
fontSize: 30,
textAlign: 'center'
}}> Redux App </Text>
<TouchableHighlight style={{
backgroundColor: 'blue',
height: 60,
justifyContent: 'center',
alignItems: 'center'
}}
onPress={() => {
props.getPeople
}}>
<Text style={{
color: 'white'
}}> Fetch Data </Text>
</TouchableHighlight>
{
isFetching && <Text> Loading... </Text>
}
{
people.length ? (
people.map((person, index) => {
return (
<View key={index}>
<Text> Name : {person.name} </Text>
<Text> Birth Year : {person.birth_year} </Text>
</View>
)
}
)
) : null
}
</View>
);
}
}
mapStateToProps = (state) => {
return {
people: state.peopleReducer
}
}
mapDispatchToProps = (dispatch) => {
return {
getPeople: () => dispatch(fetchPeopleFromAPI())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MyApp)
I tried another method by creating a const MyApp instead of class and used arrow function by passing props as an argument and then only used return(not render) but it is still not working.
Thanks.
const App = () => {
<Provider store={store}>
<MyApp/>
</Provider>
}
Arrow function above return null. If you're using curly brackets, you should add return keyword. Try add a return keyword first.
const App = () => {
return (
<Provider store={store}>
<MyApp/>
</Provider>
)
}
Compare this code below.
const add = (a, b) => a + b;
const anotherAdd = (a, b) => { return a + b; };
That code equal. The difference is that the first one doesn't need return keyword since it is not using curly brackets.