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 :)
Related
I am learning react native at the moment, I got my random background image to work but there is a bug now and I have no idea how to fix it easily, it now changes background everytime I do anything in app.
Main idea was to change image if app opens I added video what seems to be problem at the moment. Somehow background is not changeing when I update my task.
https://www.youtube.com/watch?v=JkAfe6BDZYg&feature=youtu.be&ab_channel=TheMrMaarek
const { height, width } = Dimensions.get("window");
const randomImages = [
require('./images/talv.jpg'),
require('./images/kevad.jpg'),
require('./images/suvi.jpg'),
require('./images/sügis.jpg'),
];
export default class App extends React.Component {
state = {
newToDo: "",
loadedToDos: false,
toDos: {}
};
componentDidMount = () => {
this._loadedToDos();
};
render() {
const { newToDo, loadedToDos, toDos } = this.state;
console.log(toDos);
if (!loadedToDos) {
return <AppLoading />;
}
return (
<ImageBackground source={randomImages[Math.floor(Math.random()*randomImages.length)]} style={styles.container}>
<StatusBar barStyle="light-content" />
<Text style={styles.title}>MS ToDo App</Text>
<View style={styles.card}>
<TextInput
style={styles.input}
placeholder={"Add new task here..."}
value={newToDo}
onChangeText={this._controlNewToDo}
placeholderTextColor={"black"}
returnKeyType={"done"}
autoCorrect={false}
onSubmitEditing={this._addToDo}
underlineColorAndroid={"transparent"}
/>
<ScrollView contentContainerStyle={styles.toDos}>
{Object.values(toDos)
.sort((a, b) => {
return a.createdAt - b.createdAt;
})
.map(toDo => (
<ToDo
key={toDo.id}
deleteToDo={this._deleteToDo}
uncompleteToDo={this._uncompleteToDo}
completeToDo={this._completeToDo}
updateToDo={this._updateToDo}
{...toDo}
/>
))}
</ScrollView>
</View>
</ImageBackground>
you want to just randomize the image in componentDidMount,
state = {
newToDo: "",
loadedToDos: false,
toDos: {},
currentImage: null,
};
...
componentDidMount = () => {
this._loadedToDos();
this.currentImage = randomImages[Math.floor(Math.random()*randomImages.length)];
};
...
<ImageBackground source={this.currentImage} style={styles.container}>
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();
I have made a main screen in which I have added three button in the header, on pressing I want to open three different screens respectively but its not working.
Here's what I've tried:
constructor(props) {
super(props);
this.state = {
initialstate: 0, //Setting initial state for screens
};
}
render(){
return(
<View style={styles.container}>
<TouchableOpacity onPress={() => this.setState({ initialstate: 0})}>
<Image source={require('../../assets/add.png')}
resizeMode="contain"/>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.setState({ cardstate: 1})}>
<Image source={require('../../assets/request.png')}
resizeMode="contain"/>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.setState({ cardstate: 2})}>
<Image source={require('../../assets/send.png')}
resizeMode="contain"/>
</TouchableOpacity>
{this.state.initialstate == 0 ? ( <RequestComp/> ) : ( <TopUpComp/> ) } //Over Here when I use the Third Screen like " : <SendComp/> " it gives me JXS error says "EXPECTED }"
</View>
The first problem is that you have an initialState state variable that is only updated by the first buttons and the other two are setting cardState so even if the ternary statement was formatted correctly it wouldn't have worked either way
But aside from this problem I don't recommend using a ternary for what you're trying to do, because the conditions become difficult to read.
There are multiple ways of doing this, but I like the approach of the accepted answer here React render various components based on three logic paths). The idea is to create an object that holds a mapping of strings to components. Then you can conditionally render an item based on the current key value.
Here's an example of how you could refactor your code to use this approach:
const tabComponents = {
request: <RequestComp />,
topUp: <TopUpComp />,
send: <SendComp />,
};
class CustomTabs extends React.Component {
constructor(props) {
super(props);
this.state = {
cardstate: 'request', // Setting initial state for screens
};
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity
onPress={() => this.setState({ cardstate: 'request' })}>
// Button content...
</TouchableOpacity>
<TouchableOpacity onPress={() => this.setState({ cardstate: 'topUp' })}>
// Button content...
</TouchableOpacity>
<TouchableOpacity
onPress={() => this.setState({ cardstate: 'send' })}>
// Button content...
</TouchableOpacity>
{tabComponents[this.state.cardstate]}
</View>
);
}
}
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 am building my first react native app. I am using Firebase, Redux, and React Native to build my app.
I am using a thunk to fetch data from my database and I want to set my component's local state with that data.
When I console.log(this.props.room) in the render function I can see the data from my database but it's not being added to the local state in componentDidMount(), so I know my thunk and the backend is working properly.
I think my component is rendering before my local state is set in the componentDidMount function. Is there a way to prevent it from rendering while this.setState() is running? My code is below. Or is there another reason this.setState isn't working?
import { connect } from 'react-redux';
import {
Image,
Platform,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
TextInput
} from 'react-native';
import { submitIdea, getRoom } from '../redux/reducers/rooms/actions'
class RoomScreen extends React.Component {
static navigationOptions = {
title: 'Undecided!'
};
constructor(props) {
super(props)
this.state = {
currentUser: '',
submittedIdea: false,
currentUserIdea: '',
roomName: '',
ownerName: '',
peopleAndIdeas: [],
prompt: '',
room: {}
}
this.handleIdeaSubmit = this.handleIdeaSubmit.bind(this)
}
handleIdeaSubmit() {
const { user, roomName } = this.props.navigation.state.params
this.setState({ submittedIdea: true })
this.props.submitIdea(user, this.state.userIdea, roomName)
}
async componentDidMount() {
const { user, roomName } = this.props.navigation.state.params
await this.props.getRoom(roomName)
this.setState({
room: this.props.room
})
}
render() {
return (
<View style={styles.container}>
<ScrollView contentContainerStyle={styles.contentContainer}>
<View>
<Text>Room name: {this.state.room.name}</Text>
</View>
<View>
<Text>This room was created by: {this.state.room.owner}</Text>
</View>
<View>
<Text>What are we deciding? {this.state.room.prompt}</Text>
</View>
{/* checking whether or not the user has submitted an idea and altering the view */}
{!this.state.submittedIdea ?
<View style={styles.container}>
<TextInput
style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
onChangeText={(text) => this.setState({ userIdea: text })}
value={this.state.createRoomName}
placeholder="Enter your idea."
/>
<TouchableOpacity
style={styles.button}
onPress={this.handleIdeaSubmit}
>
<Text> Submit Idea. </Text>
</TouchableOpacity>
</View> :
<View>
<Text>Your idea was: {this.props.userIdea}</Text>
</View>
}
<View style={styles.getStartedContainer}>
<Text>IDEAS</Text>
</View>
<View>
<Text style={styles.getStartedText}>USERS</Text>
</View>
</ScrollView>
</View >
);
}
}
const mapStateToProps = state => ({
room: state.room.room
})
const mapDispatchToProps = (dispatch) => ({
getRoom: (roomName) => dispatch(getRoom(roomName)),
submitIdea: (user, idea, roomName) => dispatch(submitIdea(user, idea, roomName))
})
export default connect(mapStateToProps, mapDispatchToProps)(RoomScreen)```
This is because setState actions are asynchronous and are batched for performance gains. This is explained in documentation of setState.
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
So you can do something like this,
this.setState({foo: 'bar'}, () => {
// Do something here.
});
async componentDidMount() {
const { user, roomName } = this.props.navigation.state.params
await this.props.getRoom(roomName)
this.setState({
room: this.props.room
})
}
You don't have to use local state in this case.
render() {
const {room: {name}} = this.props;
return (
<View style={styles.container}>
<ScrollView contentContainerStyle={styles.contentContainer}>
<View>
<Text>Room name: {name}</Text>
</View>
</ScrollView>
</View>
)
}
Here since you are using redux (global state), you don't have to add it in your local state. You can directly access the value of the room from the props.