I'm making an app in react native and I'm facing a little problem.
I finished the first layout and now I want to change the style all over the app with a second layout
This is what I have in my parent.
As you can see I use AsyncStorage to check when you open again the app the last selected layout. It all working perfectly.
export default class Home extends React.Component
{
constructor(props){
super(props);
this.state = {
view:0
}
}
componentWillMount()
{
this.checkStructureView();
}
checkStructureView = async() =>
{
const StructureView = await
AsyncStorage.getItem('#StructureView');
if(StructureView == 1)
{
this.setState({
view:1
})
}
else
{
this.setState({
view:0
})
}
}
render()
{
if(this.state.view == 1)
{
return(
<ChangeView/>
...
)
}
else
{
return(
<ChangeView/>
...
)
}
}
}
And this is my component ChangeView. It's a little bit messy because I have for each button active/inactive styles. This is also working perfectly, but the problem is that when I click on the button to change the layout will not change it, only after I refresh the app.
First I added this inside the parent and after I updated the state, the layout has changed instantly but I have more pages where I need to add this component, that's why I'm using an component.
So my question is how can I update instantly the parent state so my layout changes every time I click on the component button without reloading the app.
import React, { Component } from 'react'
import {
View,
Text,
Image,
TouchableOpacity,
AsyncStorage
} from 'react-native'
export default class ChangeView extends Component {
constructor(props){
super(props);
this.state = {
position: this.props.position,
view:0,
view1:require(`../assets/icons/view1_inactive.png`),
view2:require(`../assets/icons/view2_active.png`)
}
}
componentDidMount()
{
this.checkViewStructure();
}
checkViewStructure = async()=>
{
const StructureView = await AsyncStorage.getItem('#StructureView');
if(StructureView == '0')
{
this.setState({
view1:require(`../assets/icons/view1_inactive.png`),
view2:require(`../assets/icons/view2_active.png`)
})
}
else
{
this.setState({
view1:require(`../assets/icons/view1_active.png`),
view2:require(`../assets/icons/view2_inactive.png`)
})
}
}
changeToList = async() =>
{
const StructureView = await AsyncStorage.getItem('#StructureView');
if(StructureView == '0')
{
await AsyncStorage
.setItem('#StructureView', '1')
.then( () => {
//
})
.catch( () => {
alert('Something happened! Please try again later.');
});
this.setState({
view1:require(`../assets/icons/view1_active.png`),
view2:require(`../assets/icons/view2_inactive.png`)
})
}
}
changeToPics = async() =>
{
const StructureView = await AsyncStorage.getItem('#StructureView');
if(StructureView == '1')
{
await AsyncStorage
.setItem('#StructureView', '0')
.then( () => {
//
})
.catch( () => {
alert('Something happened! Please try again later.');
});
this.setState({
view1:require(`../assets/icons/view1_inactive.png`),
view2:require(`../assets/icons/view2_active.png`)
})
}
}
render()
{
if(this.state.position === 0)
return(
<View style={{alignItems:'flex-end',marginTop:20,marginBottom:10,justifyContent:'flex-end',flexDirection:'row'}}>
<View>
<TouchableOpacity
onPress= {() => this.changeToList()}
>
<Image
source={this.state.view1}
style={{width:15,height:21,margin:5}}
/>
</TouchableOpacity>
</View>
<View>
<TouchableOpacity
onPress= {() => this.changeToPics()}
>
<Image
source={this.state.view2}
style={{width:15,height:21,margin:5}}
/>
</TouchableOpacity>
</View>
</View>
)
else
return null
}
}
The ChangeView component only changes state in that specific component. There are several ways of propagating change to the parent component. One way is to implement an onChange prop for the ChangeView component. Your Home component render function would then look like something like this:
render() {
if(this.state.view == 1) {
return(
<ChangeView onChange={ (view) => this.setState({ view }) } />
...
)
} else {
return(
<ChangeView onChange={ (view) => this.setState({ view }) } />
...
)
}
}
You can read more about props here: https://reactjs.org/docs/typechecking-with-proptypes.html
There are other ways of doing this if you have state handler for your application such as Redux.
Related
I am new in react native I am trying to render the count of unread notification for that I called my API in HOC it is working fine for initial few seconds but after that, I started to get the below error
func.apply is not a function
below is my code
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Modal, View } from "react-native";
import { themes } from "./constants";
import { AsyncStorage } from "react-native";
export default (OriginalComponent, animationType) =>
class extends Component {
static propTypes = {
handleFail: PropTypes.func,
theme: PropTypes.string,
visible: PropTypes.bool
};
state = {
modalVisible: true
};
static getDerivedStateFromProps({ visible }) {
if (typeof visible === "undefined") {
setInterval(
AsyncStorage.getItem("loginJWT").then(result => {
if (result !== null) {
result = JSON.parse(result);
fetch(serverUrl + "/api/getUnreadNotificationsCount", {
method: "GET",
headers: {
Authorization: "Bearer " + result.data.jwt
}
})
.then(e => e.json())
.then(function(response) {
if (response.status === "1") {
if (response.msg > 0) {
AsyncStorage.setItem(
"unreadNotification",
JSON.stringify(response.msg)
);
} else {
AsyncStorage.setItem("unreadNotification", 0);
}
}
})
.catch(error => {
alert(error);
// console.error(error, "ERRRRRORRR");
});
} else {
AsyncStorage.setItem("unreadNotification", 0);
}
}),
5000
);
return null;
}
return { modalVisible: visible };
}
handleOpenModal = () => {
this.setState({ modalVisible: true });
};
handleCloseModal = () => {
const { handleFail } = this.props;
this.setState({ modalVisible: false }, handleFail);
};
render() {
const { modalVisible } = this.state;
const { theme } = this.props;
return (
<View>
<Modal
animationType={animationType ? animationType : "fade"}
transparent={true}
visible={modalVisible}
onRequestClose={this.handleCloseModal}
>
<View style={themes[theme] ? themes[theme] : themes.transparent}>
<OriginalComponent
handleCloseModal={this.handleCloseModal}
{...this.props}
/>
</View>
</Modal>
</View>
);
}
};
I have not used getDerivedStateFromProps but, according to the docs, it is called on initial component mount and before each render update.
Thus your code is creating a new interval timer on each update without clearing any of the earlier timers, which could be causing a race condition of some sort.
You may want to consider using the simpler alternatives listed in the docs, or at a minimum, insure that you cancel an interval before creating a new one.
I have an App with a navigationbar with 2 Screens.
When i apply a function on Screen/Component 1 , I want to render or trigger a change in the Second Screen.
is there a way to either re-render the screen on Enter or to update the state of the other screen ?
Component one:
export default class HomeScreen extends React.Component {
constructor() {
super();
}
_onPress(){
try {
await AsyncStorage.setItem('value', 'changed Value');
} catch (error) {
console.log(error.message);
}
console.log("saved: " + this.state.userName )
}
render() {
return (
<View style={styles.container}>
<Button title="btn" onPress={() => this._onPress()} >
</Button>
</View>
)
}
component 2:
export default class SecondScreen extends React.Component {
constructor() {
super();
this.state = {some : ''}
}
async getValue () {
let recievedValue = '';
try {
let promise = await AsyncStorage.getItem('value') || 'cheeseCake';
promise.then((value) => recievedValue = value)
} catch (error) {
// Error retrieving data
console.log(error.message);
}
return recievedValue
}
render() {
var value= this.getValue();
return (
<View style={styles.container}>
<Text>
HERE CHANGED VALUE: {value}
</Text>
<Button onPress={()=> this.setState((prev)=> {some:'Thing'})}>
</Button>
</View>
)
}
When i press the Button on screen 1(HomeScreen) the value is saved.
But it only shows in the secont screen when I trigger a statechange via Button Press.
How do I render the screen when I visit the screen via navigation bar ?
Did you try EventEmiter?
Use this custom event listener: https://github.com/meinto/react-native-event-listeners
eg:
import { EventRegister } from 'react-native-event-listeners'
/*
* RECEIVER COMPONENT
*/
class Receiver extends PureComponent {
constructor(props) {
super(props)
this.state = {
data: 'no data',
}
}
componentWillMount() {
this.listener = EventRegister.addEventListener('myCustomEvent', (data) => {
this.setState({
data,
})
})
}
componentWillUnmount() {
EventRegister.removeEventListener(this.listener)
}
render() {
return <Text>{this.state.data}</Text>
}
}
/*
* SENDER COMPONENT
*/
const Sender = (props) => (
<TouchableHighlight
onPress={() => {
EventRegister.emit('myCustomEvent', 'it works!!!')
})
><Text>Send Event</Text></TouchableHighlight>
)
I'm trying to add a filter to my app, but for some reason selectedValue in the <Picker> component doesn't stick with the option I select. I can see the filter text changing from "all" to "lobby" in the top left, however as soon as the player list fully renders, it changes back to "all." and playerListFilterType prop is set to undefined. I stepped through the code in a debugger, and it stays "lobby" until the list re-renders. The action itself works, so the list is showing accurate results.
Here's what my code looks like:
import React from 'react'
import { View, Picker } from 'react-native'
import PlayerList from '../components/PlayerList'
import { fetchPlayerListAsync, filterPlayers } from '../redux/actions/player_actions';
import NavigationHeaderTitle from '../components/NavigationHeaderTitle'
import PlayerStatusFilterPicker from '../components/pickers/PlayerStatusFilterPicker'
import { connect } from 'react-redux'
class PlayerListScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
const playerStatusFilterPicker = (
<PlayerStatusFilterPicker
playerListFilterType={navigation.getParam('playerListFilterType')}
filterPlayers={navigation.getParam('filterPlayers')}
playerList={navigation.getParam('playerList')}
/>
)
return {
headerTitle: navigation.getParam('headerButton'),
headerRight: playerStatusFilterPicker
}
}
async componentDidMount() {
await this.fetchPlayersAsync();
}
setNavigationParams = () => {
this.props.navigation.setParams({
headerButton: this.headerButton,
playerList: this.props.playerList,
playerListFilterType: this.props.playerListFilterType,
filterPlayers: this.props.filterPlayers
})
}
// navigation header element
headerButton = () => (
<NavigationHeaderTitle
handleDataRequest={this.fetchPlayersAsync}
titleMessage={(this.props.fetchingData) ? 'fetching list of players' : `${this.props.playerList.length} online`}
/>
)
fetchPlayersAsync = async () => {
await this.props.fetchPlayerListAsync();
this.setNavigationParams()
}
render() {
return (
<View>
<PlayerList
playerList={this.props.playerList}
fetchingData={this.props.fetchingData}
handleDataRequest={this.fetchPlayersAsync}
/>
</View>
)
}
}
const mapStateToProps = state => {
return {
fetchingData: state.player.fetchingData,
playerList: state.player.playerList,
unfilteredPlayerList: state.player.unfilteredPlayerList,
playerListFilterType: state.player.playerListFilterType
}
};
export default connect(mapStateToProps, { fetchPlayerListAsync, filterPlayers })(PlayerListScreen)
and here's what the filter component looks like, but I don't think the problem lies here:
import React, { Component } from "react";
import {
View,
Picker
} from "react-native";
import * as constants from '../../constants'
class PlayerStatusFilterPicker extends Component {
render() {
return (
<View>
<Picker
selectedValue={this.props.playerListFilterType}
onValueChange={(itemValue) => this.props.filterPlayers(itemValue, this.props.playerList)}
style={{ height: 40, width: 100 }}
>
<Picker.Item label='all' value='all' />
<Picker.Item label="lobby" value={constants.IN_LOBBY} />
<Picker.Item label="in game" value={constants.IN_GAME} />
</Picker>
</View>
);
}
}
export default PlayerStatusFilterPicker;
Here's what the reducer looks like:
// show only the players that are waiting in the main lobby
case actionTypes.SHOW_PLAYERS_IN_LOBBY: {
const filteredList = action.payload.filter(player => player.status === constants.IN_LOBBY)
return { playerList: filteredList, playerListFilterType: constants.IN_LOBBY, fetchingData: false }
}
// show only the players that are currently playing
case actionTypes.SHOW_PLAYERS_IN_GAME: {
const filteredList = action.payload.filter(player => player.status === constants.IN_GAME)
return { playerList: filteredList, playerListFilterType: constants.IN_LOBBY, fetchingData: false }
}
Fixed it by using componentDidUpdate lifecycle method. Like so:
componentDidUpdate(prevProps) {
if (this.props.playerListFilterType != prevProps.playerListFilterType) {
this.props.navigation.setParams({
playerListFilterType: this.props.playerListFilterType
})
}
}
In my application, I created a timer component. This is a smart component because I wanted to handle the counter state inside the component.
this is my code
import React, { Component } from "react";
import {
View,
Text,
StyleSheet
} from "react-native";
import { RoundedButton} from "../../mixing/UI";
class Timer extends Component {
constructor(props) {
super(props);
this.state = {
counter: 30
}
this.interval = null;
}
componentWillUnmount() {
cleanUp();
}
cleanUp = () => {
clearInterval(this.interval);
}
decreaseCounter = () => {
if (this.state.counter === 0) {
return this.cleanUp();
}
this.setState({counter: this.state.counter - 1});
}
startCounter = () => {
this.interval = setInterval(this.decreaseCounter, 1000);
}
render() {
return (
<View>
<RoundedButton text='Log in' onPress={() => this.startCounter()} />
<Text>{this.state.counter}</Text>
<RoundedButton text='Log in' onPress={() => this.cleanUp()} />
</View>
);
}
}
// styles
const styles = StyleSheet.create({
});
export default Timer;
Now I want to call this from my parent screen. If I pass the counter as a prop,
Now the counter state can't be handled from Timer component. How can I handle the state of the child based on the parent prop.
you can use react component lifecycle componentDidMount()
componentDidMount(){
this.setState({counter: this.props.counter});
}
There after you can use this.setState({counter: this.state.counter - 1})
How to show React Native Homepage if is connected?
and if is not connected, Show a full screen photo? (with a Condition by NetInfo)
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
View,
NetInfo
} from 'react-native';
function ConnectionOk() {
return (
<View >
<Text >
Welcome to React Native1!
</Text>
<Text >
To get started, edit App.js
</Text>
</View>
);
}
function ConnectionNotOk() {
return (
<View>
<Text>not Connected ...</Text>
</View>
);
}
type Props = {};
export default class App extends Component<Props> {
constructor(props) {
super(props)
this.state = {
isConnected: false,
isMounted: true
};
}
componentDidMount() {
// my way of checking internet, don't use both methods
// this.checkInternetConnection();
// Its good idea to attach event listener here, or in constructor
NetInfo.isConnected.addEventListener(
'connectionChange',
this.handleFirstConnectivityChange
);
};
componentWillUnmount() {
this.setState({
isMounted: false
});
// Its good idea to remove all event listener here
NetInfo.removeEventListener(
'connectionChange',
this.handleFirstConnectivityChange
);
};
checkInternetConnection() {
fetch("https://httpbin.org/ip")
.then(response => response.json())
.then(responseJson => {
//update the state only when component is mounted, else it will throw warning
if (this.state.isMounted) {
this.setState({
isConnected: true
});
}
}).catch(err => {
// No internet, redirect to some action if required
})
};
handleFirstConnectivityChange(isConnected) {
if (isConnected) {
this.setState({
isConnected: true
});
} else {
//redirect to some route if required
return <ConnectionNotOk />;
}
render() {
return this.state.isConnected ? < ConnectionOk /> : < ConnectionNotOk />
}
};
}
You can use some flag variable in component state.
From my experience I can say that NetInfo doesn't always gives correct info. ex Internet data is on but no internet connection, NetInfo will return true.
I handle this case by fetching some http api (say https://httpbin.org/ip) which is light and gives correct info about internet.
Also its a good idea to define,add/remove listeners in their appropriate places instead of render.
Try following:
type Props = {};
export default class App extends Component < Props > {
constructor(props) {
super(props)
this.state = {
isConnected: false,
isMounted: true
};
this.checkInternetConnection = this.checkInternetConnection.bind(this);
this.handleFirstConnectivityChange = this.handleFirstConnectivityChange.bind(this);
}
componentDidMount() {
// my way of checking internet, don't use both methods
// this.checkInternetConnection();
// Its good idea to attach event listener here, or in constructor
NetInfo.isConnected.addEventListener(
'connectionChange',
this.handleFirstConnectivityChange
);
}
componentWillUnmount() {
this.setState({
isMounted: false
});
// Its good idea to remove all event listener here
NetInfo.removeEventListener(
'connectionChange',
this.handleFirstConnectivityChange
);
}
checkInternetConnection() {
fetch("https://httpbin.org/ip")
.then(response => response.json())
.then(responseJson => {
//update the state only when component is mounted, else it will throw warning
if (this.state.isMounted) {
this.setState({
isConnected: true
});
}
}).catch(err => {
// No internet, redirect to some action if required
})
}
handleFirstConnectivityChange(isConnected) {
if (isConnected) {
this.setState({
isConnected: true
});
} else {
//redirect to some route if required
//return <ConnectedNotOk / > ;
}
}
render() {
return (
this.state.isConnected ? <ConnectionOk /> : <ConnectionNotOk />
);
}
}
My full example code.
NetInfo.isConnected.fetch().then(isConnected => {
console.log('First, is ' + (isConnected ? 'online' : 'offline'));
});
function handleFirstConnectivityChange(isConnected) {
console.log('Then, is ' + (isConnected ? 'online' : 'offline'));
if (isConnected == false) {
// your image
}
else{
Actions.HomePage() //if connected go to homepage
}
NetInfo.isConnected.removeEventListener(
'connectionChange',
handleFirstConnectivityChange
);
}
NetInfo.isConnected.addEventListener(
'connectionChange',
handleFirstConnectivityChange
);
i am using react-native-router-flux for redirection
You can achieve it by having isConnected flag in state and using network code set it to true or false dynamically. Then inside the render function use below code
{ this.state.isConnected &&
// Your UI code here
}
I have made some changes in your code. Hope it will help you. Please find complete code below:
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
View,
NetInfo
} from 'react-native';
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
isConnected: false,
isMounted: true
};
}
componentWillMount() {
NetInfo.addEventListener(
'connectionChange',
this.handleFirstConnectivityChange
);
}
componentWillUnmount() {
this.setState({
isMounted: false
});
// Its good idea to remove all event listener here
NetInfo.removeEventListener(
'connectionChange',
this.handleFirstConnectivityChange
);
}
handleFirstConnectivityChange(connectionInfo) {
if(connectionInfo.type && connectionInfo.type != "none"){
this.setState({
isConnected: true
});
}else {
this.setState({
isConnected: false
});
}
}
render () {
return (
<View>
{this.state.isConnected &&
<View >
<Text >
Welcome to React Native1!
</Text>
<Text >
To get started, edit App.js
</Text>
</View>
}
{!this.state.isConnected &&
<View>
<Text>not Connected ...</Text>
</View>
}
</View>
)
}
}