I created a Boolean state for my shopping app in which when a user clicks the buy button, it should set that state to true and display the cart total in a container, but it's not working.
Below is my code:
reducer:
if (action.type === SHOW_CART) {
return {
...state,
show: action.showCart,
};
const initialstate = {
show: false,
}
That's my reducer with initial state :
export const showCart = () => {
return {
type: SHOW_CART,
showCart: true,
};
};
That's my action:
<View style={styles.buy}>
<Text
onPress={() => {
this.props.addToCart(item.id);
this.props.showCart();
}}
>
Buy Once
</Text>
</View>
const mapStateToProps = (state) => {
return {
items: state.clothes.jeans,
};
};
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (id) => dispatch(addToCart(id)),
showCart: () => dispatch(showCart()),
};
};
In this code I used a flat list, in the flat list I used onPress to show the cart container and to dispatch my action
App
export default function App() {
return (
<>
<Provider store={Store}>
<NavigationContainer>
<AppNavigation />
</NavigationContainer>
<ViewChart />
</Provider>
</>
);
}
ViewCart.js
<View>
{this.props.show ? (
<View style={styles.total}>
<Text style={styles.totaltext}>Total:</Text>
<Text style={styles.priceTotal}>{this.props.total}</Text>
<View style={styles.onPress}>
<Text
style={styles.pressText}
onPress={() => this.props.navigation.navigate("Cart")}
>
View Cart
</Text>
</View>
</View>
) : null}
</View>
const mapStateToProps = (state) => {
return {
total: state.clothes.total,
show: state.clothes.show,
};
};
export default connect(mapStateToProps)(ViewChart);
In App.js I added the conatiner of view cart, which should be displayed when user clicks buy
and in view cart I'm getting error undefined is not an object (evaluating '_this.props.navigation.navigate')
Change
this.props.showCart;
to
this.props.showChart();
Related
I am new to React and React Native and I am trying to build an cooking recipe app. I am trying to update my dish state in DishTabNavigator.js from DishIngredients.js so that I can send the data to firebase from DishTabNavigator.js, however I do not know how I can update the state. I tried lifting the state up but I couldn't do it. Been stuck on this for a day now. Any help would be appreciated
DishTabNavigator.js
const Tab = createMaterialTopTabNavigator();
export default function MyTabs({ navigation }) {
const [dish, setDish] = useState([{ ingredients: [] }]);
React.useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<TouchableOpacity style={styles.iconright}>
<Text>Send dish to Firebase</Text>
<FontAwesome name="save" size={24} color="black" />
</TouchableOpacity>
),
});
});
return (
<Tab.Navigator>
<Tab.Screen
name="Ingredient"
component={DishIngredients}
ingredients={dish}
/>
</Tab.Navigator>
);
}
DishIngredients.js
class DishIngredients extends Component {
constructor(props) {
super(props);
this.state = {
ingredients: [],
textInput: [],
};
//Add TextInput
addTextInput = (index) => {
let textInput = this.state.textInput;
textInput.push(
<TextInput
style={styles.textInput}
onChangeText={(text) => this.addValues(text, index)}
/>
);
this.setState({ textInput });
};
//Function to add values into the states
addValues = (text, index) => {
let dataArray = this.state.ingredients;
let checkBool = false;
if (dataArray.length !== 0) {
dataArray.forEach((element) => {
if (element.index === index) {
element.text = text;
checkBool = true;
}
});
}
if (checkBool) {
this.setState({
ingredients: dataArray,
});
} else {
dataArray.push({ text: text, index: index });
this.setState({
ingredients: dataArray,
});
}
};
//function to console the output
getValues = () => {
console.log("Data", this.state.ingredients);
this.props.ingredients = this.state.ingredients;
console.log(this.props.ingredients);
};
render() {
return (
<View>
<View style={styles.icon}>
<View style={{ margin: 10 }}>
<TouchableOpacity>
<Ionicons
name="add"
size={24}
color="black"
onPress={() => this.addTextInput(this.state.textInput.length)}
/>
</TouchableOpacity>
</View>
<View style={{ margin: 10 }}>
<Button title="Remove" onPress={() => this.removeTextInput()} />
</View>
</View>
{this.state.textInput.map((value) => {
return value;
})}
<Button title="Get Values" onPress={() => this.getValues()} />
</View>
);
}
}
export default DishIngredients;
You could use React Context
const DishContext = React.createContext({
dish: [],
setDish: () => {},
});
export const useDishContext = () => React.useContext(DishContext);
export const AppDishProvider = ({children}) => {
const [dish, setDish] = useState([{ingredients: []}]);
const defaultDish = {
dish,
setDish,
};
return (
<DishContext.Provider value={defaultDish}>{children}</DishContext.Provider>
);
};
Then
/** Wrap your TabNavigator with AppDishProvider */
<AppDishProvider>
<YourTabNavigator />
</AppDishProvider>
Then
/**
* In DishIngredients, or your TabNavigator...
* You get access to dish-state, and setDish using your
* custom-hook `useDishContext`
*/
const { dish, setDish } = useDishContext();
And if you're using class-based component
class DishIngredient extends React.Component {
static contextType = DishContext;
/**
* Current context-value could be accessed by
* `this.context`
*/
render() {
const {dish, setDish} = this.context;
/** .... */
}
}
Assuming you're not having redux wired in your app... ReactContext is your way to go...
Without changing a lot in your current code you can do like this
const addIngredient = (ingredient) => {
// TODO: ingredient
};
return (
<Tab.Navigator>
<Tab.Screen name="Ingredient">
{(props) => (
<DishIngredients
{...props}
ingredients={dish}
addIngredient={addIngredient}
/>
)}
</Tab.Screen>
</Tab.Navigator>
);
As you see there is a function addIngredient (You can rename as you wish) that will be introduced as prop in DishIngredients like this.props.addIngredient(<Your Ingredient text>).
ReactContext also a nice solution above but I think, this will force your app re-render every time your context changes. It might impact on your performance.
I'm working on a music app, and I have a Purchase quota, that allows users to use the app.
So if a user plays 20 track music I will appear to him a modal or something.
So I have a component that's for play music "controller" name as <MusicPlayer/>,
I add it in every screen I have music tracks there when user press to any track card I navigate them to screen that contains
so i want to do some counter when user playing a music increase it +1
so i don't know where can I make this dispatch, in MusicPlayer components?
or in every screen that contained this component and pass it as props?
code
actions/countPlayAction.js
import {SET_COUNT} from './types';
export const setCount = count => {
return {
type: SET_COUNT,
payload: count,
};
};
reducer/countPlayReducer.js
import {SET_COUNT} from '../actions/types';
let initial_state = {
count: 0,
};
const countPlayReducer = (state = initial_state, action) => {
switch (action.type) {
case SET_COUNT:
state = {
...state,
count: state.count + action.payload,
};
break;
}
return state;
};
export default countPlayReducer;
musicPlayer component
class MusicPlayer extends Component {
constructor(props) {
super(props);
this.state = {
tunes: props.tunes,
currentTrackIndex: props.currentTrackIndex,
rate: 1,
duration: 1,
currentTime: 0,
paused: true,
loading: true,
};
}
onLoad = data => {
this.setState({
duration: Math.floor(data.duration),
loading: false,
paused: true,
});
};
render() {
return (
<View>
<View>
<Video
ref={ref => {
this.player = ref;
}}
source={{
uri: this.state.tunes[this.state.currentTrackIndex].url,
}}
paused={this.state.paused}
playInBackground={true}
playWhenInactive={true}
onLoad={this.onLoad}
onProgress={this.onProgress.bind(this)}
onEnd={this.onEnd}
controls={false}
/>
<View style={styles.time}>
<View style={styles.timeChildView}>
<Text style={styles.timeChildViewText}>
{this.minutesAndSeconds(this.state.currentTime)}
</Text>
</View>
<View style={styles.timeChildView}>
<Text style={styles.timeChildViewText}>
{this.minutesAndSeconds(this.state.duration)}
</Text>
</View>
</View>
<View style={styles.slider}>
{/* For circle play */}
<Slider
thumbTintColor="#ff4865"
maximumTrackTintColor="grey"
minimumTrackTintColor="#ff4865"
style={styles.seekBar}
step={1}
minimumValue={0}
maximumValue={this.state.duration}
value={this.state.currentTime}
onValueChange={this.changeValue}
onSlidingComplete={this.onSlidingComplete}
/>
</View>
<View style={styles.controls}>
{this.state.loading ? (
<ActivityIndicator size="large" color="#ff4865" />
) : (
<View style={styles.flexRow}>
<View>
{!this.state.paused ? (
<Button
transparent
style={styles.btnSection}
color="white"
onPress={() =>
this.setState({paused: !this.state.paused})
}>
<Icon name="md-pause" style={styles.iconColor} />
</Button>
) : (
<Button
transparent
style={styles.btnSection}
color="white"
onPress={() =>
this.setState({paused: !this.state.paused})
}>
<Icon name="md-play" style={styles.iconColor} />
</Button>
)}
</View>
</View>
)}
</View>
</View>
</View>
);
}
}
export default MusicPlayer;
What I made, it's increase count +1 in every time i play a track, but i don't know if that's a right way or not
in music player component
onLoad = data => {
this.setState({
duration: Math.floor(data.duration),
loading: false,
paused: true,
});
this.props.setCount(1)
reactotron.log(store.getState());
};
const mapDispatchToProps = dispatch => {
return {
setCount: count => {
dispatch(setCount(count));
},
};
};
// export default MusicPlayer;
export default connect(null, mapDispatchToProps)(MusicPlayer);
It looks logically done, although have a feeling of overhead introduced by redux (that's usual on small scale apps). I would also advice to change setCount to incrementCount, so that you don't need to have an absolute value in the component which increases the value.
You followed the Component-Container principle, what is not bad, but in your case you don't get any data from mapStateToProps and it forced you to have some boilerplate code inside of mapDispatchToProps.
An idea how to simplify it - you can wrap your component with connect and then you'll have dispatch available under props object inside of your component, so that you can immediately dispatch the action from onLoad. It will look like this:
onLoad = data => {
this.props.dispatch(setCount(1));
};
Final couple of words, if you don't have any other use cases for redux have a look at React.Context as you'll have less boilerplate with it :)
I have a parent component that maps through an array of chapters and renders (an exercise) a child component for every item found and passes an array of exercises to it.
class ExercisesScreen extends Component {
showSelectedItemList = (screenName, text) => {
Navigation.push("ExercisesStack", {
component: {
name: screenName,
options: navOptionsCreator(text)
}
});
};
get chapters() {
return this.props.chapters.map(chapter => (
<TouchableOpacity key={chapter.id}>
<ExercisesList
onPress={() =>
this.showSelectedItemList(chapter.screenName, chapter.name)
}
exercises={chapter.exercises}
/>
</TouchableOpacity>
));
}
render() {
return <View>{this.chapters}</View>;
}
}
const mapStateToProps = state => ({
chapters: chaptersSelector(state)
});
When this child component receives the array of exercises, it maps through it and renders a list of exercises.
class ExercisesList extends Component {
render() {
return this.props.exercises.map(exercise => (
<View key={exercise.id}>
<TouchableOpacity
style={styles.button}
onPress={() =>
this.props.showSelectedItemList(exercise.screenName, exercise.name)
}
>
<Image source={exercise.icon}/>
<View>
<Text>{exercise.name}</Text>
</View>
<Image source={arrow} />
</TouchableOpacity>
<View />
</View>
));
}
}
ExercisesList.propTypes = {
onPress: PropTypes.func,
exercises: PropTypes.arrayOf(PropTypes.object)
};
The result I get from both components rendered simultaneously:
The question is, what should I do in order for them to render themselves separately and show the corresponding ExercisesList for every chapter in ExercisesScreen?
Make your child component ExercisesList as functional component that only show the corresponding ExercisesList for every chapter not perform any rendering.
Like below:
const ExercisesList = (props) => {
const { exercises } = props;
return({
exercises.map(exercise, index) => renderExcercise(exercise, index)
})
}
const renderExcercise = (exercise, index) => {
return(
<View key={exercise.id}>
<TouchableOpacity
style={styles.button}
onPress={() =>
this.props.showSelectedItemList(exercise.screenName, exercise.name)
}
>
<Image source={exercise.icon}/>
<View>
<Text>{exercise.name}</Text>
</View>
<Image source={arrow} />
</TouchableOpacity>
<View />
</View>
)
}
export default ExercisesList;
ExercisesList.propTypes = {
onPress: PropTypes.func,
exercises: PropTypes.arrayOf(PropTypes.object)
};
I have 2 components named A and B, In B I have a list of Languages to be selected and updated in component A. I am using Redux for state management when I change the Language from the list I can see that the states are updated(using redux-logger to get logs). But the problem is when I go back to Component A using react-navigation the updated state value is not updated I can see only the old value of state only
ScreenA.js
class ScreenA extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedLanguage: this.props.state.defaultLangName
}
}
render(
return (
<Container>
<Content style={{ backgroundColor: item.backgroundColor }}>
<View style={styles.logoContainer}>
<Text>test page</Text>
</View>
<View style={styles.cardParent}>
<View style={styles.card}>
<Item style={styles.listItem}>
<Button transparent style={styles.contentChecked} onPress={() => this._openLang()}>
<Text style={styles.listbtn}>{this.state.selectedLanguage}</Text>
<Icon name='ios-arrow-forward' style={styles.iconChecked}/>
</Button>
</Item>
</View>
</View>
</Content>
</Container>
);
)
}
export default connect(
state => ({ state : state.introauthenticate }),
dispatch => ({
actions: bindActionCreators(introActions, dispatch)
})
)(ScreenA);
ScreenB.js
class ScreenB extends Component {
constructor(props){
super(props);
this.state = {
langChecked: '0'
};
}
FlatListItemSeparator = () => {
return (
<View
style={{
height: 1,
width: "100%",
backgroundColor: "#999",
}}
/>
);
}
_selectLanguage (val, name){
this.setState({ langChecked: val });
this.props.actions.changeLanguage(val,name, this.props.navigation.navigate);
//this.props.navigation.navigate('Intro');
}
renderItem = (item )=> {
return(
<TouchableHighlight
style={styles.boxSelect}
underlayColor="transparent"
onPress={() => this._selectLanguage(item.Value, item.Name)}
>
<View style={styles.contentChecked}>
<Text style={styles.item} > {item.Name} </Text>
{this.state.langChecked === item.Value && <Icon name="ios-checkmark-circle" style={styles.iconChecked}/>}
</View>
</TouchableHighlight>
)
}
render() {
return (
<Container>
<Header>
<Left>
<Button transparent onPress={() => this.props.navigation.goBack()}>
<Icon name='ios-arrow-back' />
</Button>
</Left>
<Body>
<Title>Languages</Title>
</Body>
<Right />
</Header>
<Content>
<FlatList
data={ langs }
keyExtractor={(item) => item.Value}
ItemSeparatorComponent = {this.FlatListItemSeparator}
renderItem={({item}) => this.renderItem(item)}
/>
</Content>
</Container>
);
}
}
export default connect(
state => ({ state: state.introauthenticate }),
dispatch => ({
actions: bindActionCreators(introActions, dispatch)
})
)(ScreenB);
reducer.js
export const CHANGE_LANGUAGE = "CHANGE_LANGUAGE";
export function changeLanguage(langValue,langName,navigateTo) { // Fake authentication function
return async dispatch => {
try {
if (langValue && langName) { //If the email and password matches
const session = { langValue : langValue,langName:langName } // Create a fake token for authentication
setTimeout(() => { // Add a delay for faking a asynchronous request
dispatch(setLanguage(session)) // Dispatch a successful sign in after 1.5 seconds
navigateTo('Intro') // If successfull login navigate to the authenticated screen
}, 1500)
}
} catch (err) { // When something goes wrong
console.log(err)
}
};
}
function setLanguage(lang){
return {
type: types.CHANGE_LANGUAGE,
data: {
lang: lang
}
};
}
const initialsliderState = {
defaultLang:'en',
defaultLangName:'English',
};
export default function introauthenticate(state = initialsliderState, action = {}) {
switch (action.type) {
case types.CHANGE_LANGUAGE:
return {
...state,
defaultLang: action.data.lang.langValue,
defaultLangName: action.data.lang.langName,
};
default:
return state;
}
}
Logger:
LOG %c prev state "introauthenticate": {"defaultLang": "nl", "defaultLangName": "Deutsch", "isAuthSlider": false, "requestingsliderRestore": false}
LOG %c action {"data": {"lang": {"langName": "English", "langValue": "en"}}, "type": "CHANGE_LANGUAGE"}
LOG %c next state "introauthenticate": {"defaultLang": "en", "defaultLangName": "English", "isAuthSlider": false, "requestingsliderRestore": false}}
You are initializing the state of ScreenA with a value passed as a prop and never update it. As you are using redux to store the current language you do not need any state in ScreenA. When you connect a component you pass it the relevant data from your store as props. It seems like you are trying to "override" the state by passing it in as state but that does not update the state as it will be in this.props.state rather then in this.state. What you need to do is to just pass the language as a prop to ScreenA:
export default connect(
state => ({ selectedLanguage : state.introauthenticate.defaultLang }),
dispatch => ({
actions: bindActionCreators(introActions, dispatch)
})
)(ScreenA);
and then read the selected language from props:
<Text style={styles.listbtn}>{this.props.selectedLanguage}</Text>
When you update your store the component will re-render with the new language. You do not need any additional state in the component itself for data that you have in your redux store.
I'm using the React Native core Navigator component but having trouble figuring out how to pass data between components when pressing buttons in the Navigation Bar. Here is some example code of the setup that I have.
const NavigationBarRouteMapper = {
Title: (route, navigator) => {
let title;
switch (route.component.displayName) {
case 'FirstScreen':
title = 'First Screen';
break;
}
return (
<Text>
{title}
</Text>
)
},
LeftButton: (route, navigator) => {
let onButtonPress, buttonTitle;
switch (route.component.displayName) {
case 'SecondScreen':
buttonTitle = 'Close';
onButtonPress = () => navigator.pop();
break;
}
return (
<TouchableOpacity
onPress={onButtonPress}>
<Text>
{buttonTitle}
</Text>
</TouchableOpacity>
)
},
RightButton: (route, navigator) => {
let onButtonPress, buttonTitle;
switch (route.component.displayName) {
case 'SecondScreen':
buttonTitle = 'Save';
onButtonPress = () => {}; // #TODO Call onButtonPress in SecondScreen component
break;
}
return (
<TouchableOpacity
onPress={onButtonPress}>
<Text>
{buttonTitle}
</Text>
</TouchableOpacity>
);
}
};
const App = React.createClass({
renderScene(route, navigator) {
return <route.component navigator={navigator} {...route.props} />;
},
render() {
return (
<Navigator
style={styles.appContainer}
initialRoute={{component: SplashScreen}}
renderScene={this.renderScene}
navigationBar={
<Navigator.NavigationBar
routeMapper={NavigationBarRouteMapper}
/>
}
/>
);
}
});
const FirstScreen = React.createClass({
onButtonPress() {
this.props.navigator.push({component: SecondScreen});
},
render() {
return (
<View>
<TouchableHighlight onPress={this.onButtonPress}>
<Text>
Click Me
</Text>
</TouchableHighlight>
</View>
);
}
});
const SecondScreen = React.createClass({
getInitialState() {
return {
input: ''
}
},
onButtonPress() {
if (this.state.input.length) {
// Do something with this.state.input such as POST to remote API
this.props.navigator.pop();
}
},
render() {
return (
<View style={styles.container}>
<TextInput
onChangeText={(input) => this.setState({input})}
value={this.state.input}
/>
</View>
);
}
});
You can see from the comments that I have a value stored in the state of SecondScreen that I want to do something with when someone hits the Save button. Any ideas?
In your renderScene function, while returning 'route.component' you can pass props and read them in the component.