I'm trying to access two parts of my Redux store within a React Native component that I'm working on and for some reason, probably something simple, I can't get things working.
I'm passing the orderID as a property to the component and then I want to retrieve the appropriate order details from the Redux store: orders[orderID] but when trying to assign the local variable:
const order = this.props.orders[orderID];
I'm getting the error: Cannot read property 'orders' of undefined, i.e.: for some reason the orders part of the Redux store appears to not have connected to the component props.
Code is as follows:
import React from 'react';
import { connect } from 'react-redux';
import {
View,
Text
} from 'react-native';
import Status from '../Status';
import Card from './Card';
import CardSection from './CardSection';
const OrderDetail = ({ orderID }) => {
const order = this.props.orders[orderID];
const {
id,
status,
gross_price,
currency_symbol,
bookings
} = order;
return (
<Card>
<CardSection>
<View style={styles.headerContentStyle}>
<View style={styles.bookingIdHeaderContainerStyle}>
<Text style={styles.headerTextStyle}>Booking #{id}</Text>
</View>
<View style={styles.grossPriceHeaderContainerStyle}>
<Text style={styles.headerTextStyle}>{currency_symbol}{gross_price}</Text>
</View>
<View style={styles.statusHeaderContainerStyle}>
<Status status={status} />
</View>
</View>
</CardSection>
<CardSection>
<View style={styles.orderListContentStyle}>
<Text>Booking #1234</Text>
</View>
</CardSection>
</Card>
);
};
const styles = {
headerContentStyle: {
flex: 1,
flexDirection: 'row',
alignItems: 'center'
},
headerTextStyle: {
fontSize: 18
},
bookingIdHeaderContainerStyle: {
flex: 5
},
grossPriceHeaderContainerStyle: {
flex: 2
},
statusHeaderContainerStyle: {
flex: 2
},
orderListContentStyle: {
}
};
const mapStateToProps = state => {
return ({
orders: state.orders,
bookings: state.bookings
});
};
export default connect(mapStateToProps)(OrderDetail);
Any suggestions?
OrderDetail is a stateless functional component, and functional components don't have a this keyword.
May be this is what you wanted:
const OrderDetail = (props) => {
const order = props.orders[props.orderID];
// ...
Related
I have referred official documentation of react native(https://reactnavigation.org/docs/params).In that I observed that they are passing the static data between screens. But I want to pass data taken from user.
If someone knows how to share data then please help. I have taken help of context api also but I failed to pass the data. Any source or material will also be helpful.
Your issue may be related to your input. It seems you are not capturing your inputs into a state variable.
Check this example from ReactNative input:
https://reactnative.dev/docs/0.65/textinput
import React from "react";
import { SafeAreaView, StyleSheet, TextInput } from "react-native";
const Screen1 = () => {
const [text, onChangeText] = React.useState("Hello world");
return (
<SafeAreaView>
<TextInput
style={styles.input}
onChangeText={onChangeText}
value={text}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
},
});
export default Screen1;
Then you could use navigate:
navigation.navigate('Details', {
param1: text,
});
In the other screen you could read the param1 like this:
route.params.param1
Don't forget
Don't forget to pass route as a parameter in fuction( {route) }{ ... }
Checkout this code snippet.
React.useEffect(() => {
if (route.params?.post) {
// Post updated, do something with `route.params.post`
// For example, send the post to the server
}
}, [route.params?.post]);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button
title="Create post"
onPress={() => navigation.navigate('CreatePost')}
/>
<Text style={{ margin: 10 }}>Post: {route.params?.post}</Text>
</View>
);
}
function CreatePostScreen({ navigation, route }) {
const [postText, setPostText] = React.useState('');
return (
<>
<TextInput
multiline
placeholder="What's on your mind?"
style={{ height: 200, padding: 10, backgroundColor: 'white' }}
value={postText}
onChangeText={setPostText}
/>
<Button
title="Done"
onPress={() => {
// Pass and merge params back to home screen
navigation.navigate({
name: 'Home',
params: { post: postText },
merge: true,
});
}}
/>
</>
);
}
to pass a param to a screen
navigation.navigate('screenName', {
paramName: 'valueYouwantToPass',
});
because you mentioned context API, try
Async Storage
import {AsyncStorage} from 'react-native';
or
Device Event Emitter
import { DeviceEventEmitter} from 'react-native';
Explanation: I am creating a fitness app, my fitness app has a component called WorkoutTimer that connects to the workout screen, and that screen is accessed via the HomeScreen. Inside the WorkoutTimer, I have an exerciseCount useState() that counts every time the timer does a complete loop (onto the next exercise). I have a different screen called StatsScreen which is accessed via the HomeScreen tab that I plan to display (and save) the number of exercises completed.
What I've done: I have quite literally spent all day researching around this, but it seems a bit harder with unrelated screens. I saw I might have to use useContext() but it seemed super difficult. I am fairly new to react native so I am trying my best haha! I have attached the code for each screen I think is needed, and attached a screenshot of my homeScreen tab so you can get a feel of how my application works.
WorkoutTimer.js
import React, { useState, useEffect, useRef } from "react";
import {
StyleSheet,
Text,
View,
TouchableOpacity,
Button,
Animated,
Image,
SafeAreaView,
} from "react-native";
import { CountdownCircleTimer } from "react-native-countdown-circle-timer";
import { Colors } from "../colors/Colors";
export default function WorkoutTimer() {
const [count, setCount] = useState(1);
const [exerciseCount, setExerciseCount] = useState(0);
const [workoutCount, setWorkoutCount] = useState(0);
const exercise = new Array(21);
exercise[1] = require("../assets/FR1.png");
exercise[2] = require("../assets/FR2.png");
exercise[3] = require("../assets/FR3.png");
exercise[4] = require("../assets/FR4.png");
exercise[5] = require("../assets/FR5.png");
exercise[6] = require("../assets/FR6.png");
exercise[7] = require("../assets/FR7.png");
exercise[8] = require("../assets/FR8.png");
exercise[9] = require("../assets/S1.png");
exercise[10] = require("../assets/S2.png");
exercise[11] = require("../assets/S3.png");
exercise[12] = require("../assets/S4.png");
exercise[13] = require("../assets/S5.png");
exercise[14] = require("../assets/S6.png");
exercise[15] = require("../assets/S7.png");
exercise[16] = require("../assets/S8.png");
exercise[17] = require("../assets/S9.png");
exercise[18] = require("../assets/S10.png");
exercise[19] = require("../assets/S11.png");
exercise[20] = require("../assets/S12.png");
exercise[21] = require("../assets/S13.png");
return (
<View style={styles.container}>
<View style={styles.timerCont}>
<CountdownCircleTimer
isPlaying
duration={45}
size={240}
colors={"#7B4FFF"}
onComplete={() => {
setCount((prevState) => prevState + 1);
setExerciseCount((prevState) => prevState + 1);
if (count == 21) {
return [false, 0];
}
return [(true, 1000)]; // repeat animation for one second
}}
>
{({ remainingTime, animatedColor }) => (
<View>
<Image
source={exercise[count]}
style={{
width: 150,
height: 150,
}}
/>
<View style={styles.timeOutside}>
<Animated.Text
style={{
color: animatedColor,
fontSize: 18,
position: "absolute",
marginTop: 67,
marginLeft: 35,
}}
>
{remainingTime}
</Animated.Text>
<Text style={styles.value}>seconds</Text>
</View>
</View>
)}
</CountdownCircleTimer>
</View>
</View>
);
}
const styles = StyleSheet.create({})
WorkoutScreen.js
import React, { useState } from "react";
import { StyleSheet, Text, View } from "react-native";
import WorkoutTimer from "../components/WorkoutTimer";
export default function WorkoutScreen() {
return (
<View style={styles.container}>
<WorkoutTimer />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});
HomeScreen.js
import React from "react";
import { StyleSheet, Text, View, SafeAreaView, Button } from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
import { AntDesign } from "#expo/vector-icons";
import { Colors } from "../colors/Colors";
export default function HomeScreen({ navigation }) {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.pageRef}>SUMMARY</Text>
<Text style={styles.heading}>STRETCH & ROLL</Text>
<View style={styles.content}>
<TouchableOpacity
style={styles.timerDefault}
onPress={() => navigation.navigate("WorkoutScreen")}
>
<Button title="START WORKOUT" color={Colors.primary} />
</TouchableOpacity>
<TouchableOpacity
style={styles.statContainer}
onPress={() => navigation.navigate("StatsScreen")}
>
<AntDesign name="barschart" size={18} color={Colors.primary} />
<Text style={{ color: Colors.primary }}>Statistics</Text>
<AntDesign name="book" size={18} color={Colors.primary} />
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({})
StatsScreen.js
import React from "react";
import { StyleSheet, Text, View } from "react-native";
import { exerciseCount, workoutCount } from "../components/WorkoutTimer";
export default function StatsScreen() {
return (
<View style={styles.container}>
<Text display={exerciseCount} style={styles.exerciseText}>
{exerciseCount}
</Text>
<Text display={workoutCount} style={styles.workoutText}>
{workoutCount}
</Text>
</View>
);
}
const styles = StyleSheet.create({});
Home Screen Image
As far as I can tell, you're almost there! You're trying to get your 2 state
variables from the WorkoutTimer like this:
import { exerciseCount, workoutCount } from "../components/WorkoutTimer";
Unfortunatly this won't work :( . These two variables change throughout your
App's life-time and that kinda makes them "special".
In React, these kinds of variables need to be declared in a parent component
and passed along to all children, which are interested in them.
So in your current Setup you have a parent child relationship like:
HomeScreen -> WorkoutScreen -> WorkoutTimer.
If you move the variables to HomeScreen (HomeScreen.js)
export default function HomeScreen({ navigation }) {
const [exerciseCount, setExerciseCount] = useState(0);
const [workoutCount, setWorkoutCount] = useState(0);
you can then pass them along to WorkoutScreen or StatsScreen with something
like:
navigation.navigate("WorkoutScreen", { exerciseCount })
navigation.navigate("StatsScreen", { exerciseCount })
You'll probably have to read up on react-navigation's documentation for .navigate I'm not sure I remember this correctly.
In order to read the variable you can then:
export default function WorkoutScreen({ navigation }) {
const exerciseCount = navigation.getParam(exerciseCount);
return (
<View style={styles.container}>
<WorkoutTimer exerciseCount={exerciseCount} />
</View>
);
}
and finally show it in the WorkoutTimer:
export default function WorkoutTimer({ exerciseCount }) {
Of course that's just part of the solution, since you'll also have to pass
along a way to update your variables (setExerciseCount and setWorkoutCount).
I encourage you to read through the links I posted and try to get this to work.
After you've accumulated a few of these stateful variables, you might also want to look at Redux, but this is a bit much for now.
Your app looks cool, keep at it!
I ended up solving this problem with useContext if anyone is curious, it was hard to solve initially. But once I got my head around it, it wasn't too difficult to understand.
I created another file called exerciseContext with this code:
import React, { useState, createContext } from "react";
const ExerciseContext = createContext([{}, () => {}]);
const ExerciseProvider = (props) => {
const [state, setState] = useState(0);
//{ exerciseCount: 0, workoutCount: 0 }
return (
<ExerciseContext.Provider value={[state, setState]}>
{props.children}
</ExerciseContext.Provider>
);
};
export { ExerciseContext, ExerciseProvider };
and in App.js I used ExerciseProvider which allowed me to pass the data over the screens.
if (fontsLoaded) {
return (
<ExerciseProvider>
<NavigationContainer>
<MyTabs />
</NavigationContainer>
</ExerciseProvider>
);
} else {
return (
<AppLoading startAsync={getFonts} onFinish={() => setFontsLoaded(true)} />
);
}
}
I could call it with:
import { ExerciseContext } from "../components/ExerciseContext";
and
const [exerciseCount, setExerciseCount] = useContext(ExerciseContext);
This meant I could change the state too! Boom, solved! If anyone needs an explanation, let me know!
I think you have to use Mobx or Redux for state management. That will be more productive for you instead built-in state.
I trying to build a weather app for my training and I have a issues.
I got a Type Error whatever I do. what I intended to do is get a json data from weathermap api and then
show some strings but I couldn't.
here is main content from My app
import React, { Component } from 'react';
import { View, StyleSheet, Text } from 'react-native';
class Content extends Component{
constructor(props) {
super(props);
this.state = {
data: this.props.weather.main,
};
}
render() {
return (
<View style={styles.content}>
<Text style={styles.city}>City Name</Text>
<Text style={styles.itemsize}>Weather {this.state.data}</Text>
<Text style={styles.itemsize}>Description</Text>
<Text style={styles.itemsize}>Temperature Celsius</Text>
<Text style={styles.itemsize}>Pressure</Text>
<Text style={styles.itemsize}>Humidity</Text>
</View>
);
}
}
const styles = StyleSheet.create({
content: {
flex: 1,
justifyContent: 'center',
alignItems:'center'
},
city: {
fontSize: 50,
padding: 20
},
itemsize: {
fontSize: 30,
padding: 5
}
})
export default Content;
and this is my upper component which is trying to get data and pass down.
import React, { Component } from 'react';
import Content from './Content';
import GetWeather from './GetWeather';
class Home extends Component {
constructor(props) {
super(props);
this._getData.bind(this);
this._getData();
this.state = {
data: null,
};
}
_getData = () => {
GetWeather.getWeather().then( json => {
console.log(json);
this.setState({data: json});
});
};
render() {
return (
<Content weather={this.state.data}/>
);
}
}
export default Home;
and last one is code that I wrote to get api data from openweathermap
function getLocation(lat, long) {
return `${API_STEM}lat=${lat}&lon=${long}&appid=${APP_ID}`;
}
function getWeather() {
return fetch(getLocation(LATTITUDE,LONGGITUDE))
.then(response => response.json())
.then(responseJson => {
return { main: responseJson.weather[0].main};})
.catch(err =>console.log(err));
}
export default {getWeather: getWeather};
In your parent component, state never gets data and always remains null. When we want to fetch data from an API, we should use a react lifecycle method called componentDidMount(). So in your parent component, you should either call your _getdata function in componentDidMount or fetch your data in the lifecycle method, like below code which is a better way in my opinion. Also, never initially set your state to null. set it to an empty object.
import React, { Component } from 'react';
import Content from './Content';
import GetWeather from './GetWeather';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: {},
};
}
componentDidMount() {
GetWeather.getWeather().then( json => {
console.log(json);
this.setState({data: json});
});
}
render() {
console.log(this.state.data);
return (
<Content weather={this.state.data}/>
);
}
}
export default App
and then in your child component, you should either use one of updating lifecycle methods (that has risks) or you can change your child component to functional component, for you don't need state.
import React, { Component } from 'react';
import { View, StyleSheet, Text } from 'react-native';
function Content(props) {
return (
<View style={styles.content}>
<Text style={styles.city}>City Name</Text>
<Text style={styles.itemsize}>Weather {props.weather.main}</Text>
<Text style={styles.itemsize}>Description</Text>
<Text style={styles.itemsize}>Temperature Celsius</Text>
<Text style={styles.itemsize}>Pressure</Text>
<Text style={styles.itemsize}>Humidity</Text>
</View>
)
}
const styles = StyleSheet.create({
content: {
flex: 1,
justifyContent: 'center',
alignItems:'center'
},
city: {
fontSize: 50,
padding: 20
},
itemsize: {
fontSize: 30,
padding: 5
}
})
export default Content;
The main problem is that this.state.data in the Home component is set after the Content component is created (after its constructor function is called).
This will generate a TypeError because this.props.weather is undefined and you are trying to access a property this.props.weather.main.
The easiest way to solve this will be to use the props object directly instead of adding those props to the state, here is an example:
<Text style={styles.itemsize}>Weather {this.props.weather}</Text>
Before the request finishes you already set this.state.data inside Content to null and it will not get updated when the component re-renders because the constructor only runs once on mount.
Setting state from props is an anti pattern and should be used only in rare situations.
Instead, read the weather data from this.props which will get updated once the parent component updates his state
You would also need to check if this.props.weather is null before you access .main inside this.props.weather
class Content extends Component {
render() {
const { weather } = this.props
return (
<View style={styles.content}>
<Text style={styles.city}>City Name</Text>
<Text style={styles.itemsize}>
Weather {weather ? weather.main : null}
</Text>
<Text style={styles.itemsize}>Description</Text>
<Text style={styles.itemsize}>Temperature Celsius</Text>
<Text style={styles.itemsize}>Pressure</Text>
<Text style={styles.itemsize}>Humidity</Text>
</View>
)
}
}
I have created a Share icon which on click share's pdf or image file on social accounts like facebook, whatsapp, gmail, etc. I want to pass URL link of shareable file inside class component but getting error. If I hardcode the URL then it works fine but how can I pass URL which I receive from react navigation ?
Working code:
import React, { Component } from 'react';
import { Share, View, TouchableOpacity } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
const shareOptions = {
title: 'Download Brochure',
url: 'https://cpbook.propstory.com/upload/project/brochure/5bc58ae6ca1cc858191327.pdf'
}
export default class Screen extends Component {
onSharePress = () => Share.share(shareOptions);
render() {
return (
<View>
<Text style={styles.infoTitle}>Share: </Text>
<TouchableOpacity onPress={this.onSharePress}>
<Icon style={{paddingLeft: 10, paddingTop: 10}}name="md-share" size={30} color="black"/>
</TouchableOpacity>
</View>
}
}
Above code works fine but below code gives error saying shareOptions is undefined. How can I overcome this problem ? I want to pass file URL inside shareOptions. I am getting file url from react navigation props i.e brochure.
Code:
import React, { Component } from 'react';
import { Share, View, TouchableOpacity } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
export default class Screen extends Component {
onSharePress = () => Share.share(shareOptions); <---Getting error here shareOptions undefined
render() {
const project = this.props.navigation.getParam('project', null);
let { brochure } = project;
const shareOptions = {
title: 'Download Brochure',
url: brochure
}
return (
<View>
<Text style={styles.infoTitle}>Share: </Text>
<TouchableOpacity onPress={this.onSharePress}>
<Icon style={{paddingLeft: 10, paddingTop: 10}}name="md-share" size={30} color="black"/>
</TouchableOpacity>
</View>
)
}
How can I overcome above problem and how can I pass props i.e file URL in above case URL is in brochure props.
Just pass shareOptions object to onSharePress function as param and get shareOptions in onSharePress event handler function
import React, { Component } from 'react';
import { Share, View, TouchableOpacity } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
export default class Screen extends Component {
onSharePress = (shareOptions) => {
Share.share(shareOptions);
}
render() {
const { navigation } = this.props;
const project = navigation.getParam('project', null);
const { brochure } = project;
const shareOptions = {
title: 'Download Brochure',
url: brochure
}
return (
<View>
<Text style={styles.infoTitle}>Share: </Text>
<TouchableOpacity onPress={() => this.onSharePress(shareOptions)}>
<Icon style={{paddingLeft: 10, paddingTop: 10}}name="md-share" size={30} color="black"/>
</TouchableOpacity>
</View>
)
}
}
You're not passing shareOptions to your share method, thus it is undefined.
There are many ways you could refactor your code, below you can see a more viable approach.
Since your render function used none of the logic above your return statement, I have simply migrated all of that into the onSharePress function, naturally if these differ, then you would want to pass it in via a parameter.
export default class Screen extends Component {
onSharePress = () => {
const project = this.props.navigation.getParam('project', null);
let { brochure } = project;
const shareOptions = {
title: 'Download Brochure',
url: brochure
}
Share.share(shareOptions);
}
render() {
return (
<View>
<Text style={styles.infoTitle}>Share: </Text>
<TouchableOpacity onPress={this.onSharePress>
<Icon style={{paddingLeft: 10, paddingTop: 10}}name="md-share" size={30} color="black"/>
</TouchableOpacity>
</View>
)
}
}
In my app I have a FlatList component which renders the component below (effectively a row with small snippets of information), I'd like to have it so that I can click on each component within the FlatList which then takes the user to another screen which provides more details.
I've managed to make it so that each component is clickable and I can make it perform an alert() to show that it is clickable, but using React-Navigation is proving a tad tricky for me to add.
FlatList page:
/* #flow*/
import _ from 'lodash'
import {getAllQuestions} from './questionRepository'
import ProfilePicture from './components/ProfilePicture'
import QuestionerAndQuestion from './questionRow/QuestionerAndQuestion'
import Separator from './components/Separator'
import {Button, FlatList} from 'react-native'
import React, {Component} from 'react'
export default class QuestionList extends Component {
static navigationOptions = ({navigation}) => ({
title: 'AIBU',
headerRight: (
<Button
onPress={_.debounce(() => {
navigation.navigate('Add')
}, 500)}
title="Add"
/>
),
headerStyle: {
backgroundColor: 'rgb(245, 245, 245)',
},
headerLeft: <ProfilePicture size={'small'} />,
})
state = {questions: []}
componentDidMount() {
this.getData()
}
async getData() {
const questions = await getAllQuestions()
this.setState({questions})
}
render() {
return (
<FlatList
ItemSeparatorComponent={Separator}
data={this.state.questions}
renderItem={({item}) => <QuestionerAndQuestion item={item} />}
/>
)
}
}
QuestionerAndQuestion component:
/* #flow */
import ProfilePicture from '../components/ProfilePicture'
import React from 'react'
import TextContainer from './TextContainer'
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
const styles = StyleSheet.create({
row: {
backgroundColor: 'rgb(255, 255, 255)',
height: 125,
flex: 1,
flexDirection: 'row',
},
profilePic: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
},
textBody: {
flex: 6,
},
})
const QuestionerAndQuestion = ({item}: {item: Object}) =>
<TouchableWithoutFeedback onPress={() => navigate would go here?}>
<View style={styles.row}>
<View style={styles.profilePic}>
<ProfilePicture />
</View>
<View style={styles.textBody}>
<TextContainer item={item} />
</View>
</View>
</TouchableWithoutFeedback>
export default QuestionerAndQuestion
I assume you navigated to the QuestionList component, therefore, you have the navigation object on there. What you would want to do is pass the navigation down to the QuestionerAndQuestion as a prop and then you can access it from onPress as a prop.
Something like this:
<QuestionerAndQuestion item={item} navigation={this.props.navigation} />
Hope this helps
This solved my issue.
Within QuestionList
render() {
const navigation = this.props.navigation
return (
<FlatList
ItemSeparatorComponent={Separator}
data={this.state.questions}
renderItem={({item}) =>
<QuestionerAndQuestion item={item} onPress={() => navigation.navigate('Question')} />}
/>
)
}
Within QuestionerAndQuestion
const QuestionerAndQuestion = ({item, onPress}: {item: Object, onPress: Object}) =>
<TouchableWithoutFeedback onPress={onPress}>