I tried to make a network availability component for my app.
My lifecycle component in the network.js
import { Component } from 'react';
import { NetInfo } from 'react-native';
export default class Network extends Component {
constructor(props) {
super(props);
this.state = { connected: null }
}
componentWillMount() {
NetInfo.isConnected.addEventListener('connectionChange', this.handleConnectionChange);
NetInfo.isConnected.fetch().done((isConnected) => this.setState({ connected: isConnected }))
}
componentWillUnmount() {
NetInfo.isConnected.removeEventListener('connectionChange', this.handleConnectionChange);
}
handleConnectionChange = (isConnected) => { this.setState({ connected: isConnected }) }
situation() {
if(this.state.connected)
return true
else
return false
}
}
And my main page :
import React, { Component } from 'react';
import { View, I18nManager, StatusBar, StyleSheet, Text } from 'react-native';
import { Spinner } from 'native-base';
import Network from './Network'
export default class Intro extends Component {
constructor() {
super();
I18nManager.allowRTL(true);
I18nManager.forceRTL(true);
}
render() {
var network = new Network;
alert(network.situation())
if (network==true) {
alert('online')
else
alert('offline')
}
}
But after execution, componentWillMount and componentWillUnmount are not working.
There is really no need to make React component for checking Network connection utility. You can just create a simple Network class like this and initialize/deinitialize it from your app component's lifecycles.
import { NetInfo } from 'react-native';
const NET_INFO = {};
let instance;
export default class Network {
static getInstance() {
return instance || new Network();
}
static initialize() {
NetInfo.isConnected.addEventListener('connectionChange', Network.getInstance().handleConnectionChange);
}
static deinitialize() {
NetInfo.isConnected.removeEventListener('connectionChange', Network.getInstance().handleConnectionChange);
}
handleConnectionChange = (isConnected) => {
NET_INFO.isConnected = isConnected;
}
static isInternetConnected() {
return NET_INFO.isConnected;
}
}
App component:
import React, { Component } from 'react';
import Network from './Network'
export default class Intro extends Component {
componentWillMount() {
Network.initialize();
}
componentWillUnmount() {
Network.deinitialize();
}
render() {
const connected = Network.isInternetConnected()
if (connected ==true)
alert('online')
else
alert('offline')
}
}
Because you are not using Network class as component but as a normal class.
If you want to run life-cycle methods then you need use it as Component.
like this in render method,
<Network />
and if you want to execute anything in parent for network change then use prop functions.
like this in render method,
<Network
connectivityChange={()=>{
//do your stuffs here
}}
/>
you need to call this.props.connectivityChange() in Network component when you want do something in parent.
Related
in the first react file i called an api to get some data and save it in this.state.Data
import React, { Component } from "react";
import axios from "axios";
import Layout from "./Layout";
class Db extends Component {
constructor() {
super();
this.state = {
Data: [],
};
}
componentDidMount() {
axios.get(`https://breakingbadapi.com/api/characters`).then((res) => {
const data = res.data;
this.setState({ Data: data });
});
}
render() {
return <h1>DB working</h1>;
}
}
export default Db;
in the another react file i need to get this.props.Data from Db.js file but i dont know how to get it
import React, { Component } from "react";
import Db from "./Db";
class Filler extends Component {
constructor() {
super();
}
render() {
return <div></div>;
}
}
export default Filler;
for small projects you can use React ContextApi to save states in global level and use it inside components you want.
for big projects you can use state management libraries like Redux. it's too much for small projects.
I've created a React Native app with navigation provided by react-navigation, also integrating redux with react-navigation-redux-helpers, and I'm trying to figure out a good way of implementing a globally-available 'SignOutHeaderButton' component that, when pressed, will dispatch a redux action and perform a navigation operation.
At the moment, I'm having to pass a function via screenProps from the application root component, which is the function that dispatches the redux action. This function is then passed to the UpdatesListView container component via screenProps, and is then passed into the SignOutHeaderButton common component as a prop via navigationOptions.
Is there a better way in which I can implement this so that I don't have to pass any props into the SignOutHeaderButton component and without having to instantiate a signOut() function within each container component in the application from which there will be a 'Sign Out' button?
App.js:
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { reduxifyNavigator } from 'react-navigation-redux-helpers';
import { PersistGate } from 'redux-persist/integration/react';
import { Provider, connect } from 'react-redux';
import { appContainer } from './src/navigators';
import { store, persistor } from './src/store/configureStore';
import { signOut } from './src/actions/auth';
const app = reduxifyNavigator(appContainer, 'root');
const mapStateToProps = state => {
return {
state: state.navReducer
}
}
const AppWithNavigationState = connect(mapStateToProps)(app);
export default class App extends React.Component {
signOut() {
store.dispatch(signOut());
}
render() {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<AppWithNavigationState
screenProps={{
signOut: this.signOut
}} />
</PersistGate>
</Provider>
)
}
}
UpdatesListView.js:
import React from 'react';
import { View, Container, Content, Text, Button } from 'native-base';
import { connect } from 'react-redux';
import SignOutHeaderButton from '../components/common/SignOutHeaderButton';
class UpdatesListView extends React.Component {
constructor(props) {
super(props);
}
static navigationOptions = ({ navigation }) => {
return {
headerTitle: 'Updates',
headerRight: <SignOutHeaderButton signOut={navigation.getParam('signOut')} />
}
}
componentDidMount() {
this.props.navigation.setParams({
signOut: this.props.screenProps.signOut
})
}
render() {
return (
<Container>
<Text>UpdatesListView</Text>
</Container>
)
}
}
const mapStatetoProps = state => {
return {
updates: state.updatesReducer,
tags: state.tagsReducer
}
}
export default connect(mapStateToProps)(UpdatesListView);
SignOutHeaderButton.js:
import React from 'react';
import { Button } from 'react-native';
import { withNavigation } from 'react-navigation';
class SignOutHeaderButton extends React.Component {
constructor(props) {
super(props);
}
signOut() {
this.props.signOut();
this.props.navigation.navigate('AuthStack');
}
render() {
return (
<Button
title="Sign Out"
onPress={this.signOut} />
)
}
}
export default withNavigation(SignOutHeaderButton);
Use redux connect, the idea of redux connect is to bind the store dispatch state to your component this, When you use redux connect you can access any redux store dispatch and state from anywhere in your application this works for both react and react-native, for example for SignOutHeaderButton.js:
import React from 'react';
import { Button } from 'react-native';
import { connect } from 'react-redux';
import { withNavigation } from 'react-navigation';
class SignOutHeaderButton extends React.Component {
constructor(props) {
super(props);
}
signOut() {
this.dispatch(signOut());
this.props.navigation.navigate('AuthStack');
}
render() {
return (
<Button
title="Sign Out"
onPress={this.signOut} />
)
}
}
export default connect()(withNavigation(SignOutHeaderButton));
Also, you can pass the redux store state to your component by passing a function to your connect(/*your function*/) to resolve the state data you want.
for better understanding, you can try this tutorial: https://blog.logrocket.com/react-redux-connect-when-and-how-to-use-it-f2a1edab2013
Note using nested smart components is very common, redux connect has a very smart algorithm to compare the store state changes, please check this: https://hackernoon.com/why-using-nested-connect-react-redux-components-is-good-bd17997b53d2
The component I am trying to render:
import React, { Component } from 'react';
export default class QueryPrint extends Component {
render() {
console.log('working');
return (
<div>Hello</div>
)
}
}
The component that is trying to call it:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
Button,
} from 'reactstrap';
import QueryPrint from './bq_print';
class QueryResults extends Component {
constructor(props) {
super(props);
this.print = this.print.bind(this);
}
print() {
console.log('Clicked');
return (
<QueryPrint />
);
}
render() {
return (
<Button
className='cuts-btn'
color='success'
onClick={this.print}
>
Print
</Button>
)
}
}
function mapStateToProps(state) {
return {
query_data: state.results.query_data
}
}
export default connect (mapStateToProps, null)(QueryResults);
The console.log('clicked') is working, but the component that is supposed to render in that method doesn't--no console.log('working') or <div>.
Returning something from a click callback has no effect. If you want to render something, you do so in the render method. The click callback's job is to call this.setState(), which will then kick off a render.
Perhaps something like this:
class QueryResults extends Component {
constructor(props) {
super(props);
this.print = this.print.bind(this);
this.state = {
queryPrint: false,
}
}
print() {
console.log('Clicked');
this.setState({ queryPrint: true })
}
render() {
const { queryPrint } = this.state;
return (
<React.Fragment>
{queryPrint && <QueryPrint />}
<Button
className='cuts-btn'
color='success'
onClick={this.print}
>
Print
</Button>
</React.Fragment>
)
}
}
React Native works differently. It is more like a web app - you need to navigate to the other component.
Look at this example its very to the point: https://facebook.github.io/react-native/docs/navigation
Alternatively if you want to make only part of the screen change you will need to include it into your own render and control it thru a flag or a state machine.
https://facebook.github.io/react-native/docs/direct-manipulation
For example: i have 2 controll-view container user.cv.jsx and sidebar.cv.jsx
Screen consist of User and Sidebar. Sidebar rendering in User screen.
User container:
import React from 'react'
import {Link} from 'react-router-dom';
import { connect } from 'react-redux';
import UserTypeComponents from '../components/user_type.jsx'
import Sidebar from '../../sidebar/containers/sidebar.cv.js'
import * as showList from '../action/list.action.js';
import * as userLimit from '../action/limit.action.js';
import PropTypes from 'prop-types'
function mapStateToProps (state) {
return {...state}
}
class UserType extends React.Component {
constructor (props, context) {
super(props);
this.context = context;
if(!this.props.oauth.isAuthenticating) {
this.context.router.history.push('/login');
return;
}
}
componentDidMount() {
}
render() {
console.log(this.props);
return (<div>
<Sidebar />
<UserTypeComponents {...this.props} />
</div>);
}
}
UserType.contextTypes = {
router: PropTypes.object
}
export default connect(mapStateToProps)(UserType);
And Sidebar Container:
import React from 'react'
import {Link} from 'react-router-dom';
import ShowSidebar from '../components/sidebar.jsx';
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import Preloader from '../../../helpers/preloader.helper.js'
import * as active from '../action/active.action.js'
import * as list from '../action/list.action.js'
import * as show from '../action/show.action.js'
import {DEFAULT_COMPONENTS} from '../constant/sidebar.const.js';
function mapStateToProps (state) {
return state.sidebar
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
...active,
...list,
...show
}, dispatch);
}
class Sidebar extends React.Component {
constructor (props) {
super(props);
}
listOfLinks(){
const makeRequest = async () => {
try {
const data = await (await fetch('http://localhost:3000/sidebar')).json(),
active = this.activeComponent(data);
this.props.list(data);
this.props.active(active);
} catch (err) {
console.log(err)
}
}
makeRequest()
}
activeComponent(data){
for(let key of data){
if(location.pathname.indexOf(key.name.toLowerCase()) != -1){
return key.name.toLowerCase();
}
}
return DEFAULT_COMPONENTS;
}
componentWillMount() {
this.listOfLinks();
}
activeSidebarState(event){
let parent = event.target.parentNode,
target = _$('.site-sidebar__name', parent),
text = target.innerText.toLowerCase();
this.props.active(text);
}
render() {
const loading = this.props.sidebar.links.length;
return (loading ? <ShowSidebar changeActive={::this.activeSidebarState} active={this.props.sidebar.active} links={this.props.sidebar.links} /> : <Preloader />);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Sidebar);
For all this, action and redusers are written. The sidebar sends a request to the server and requests all the modules and forms links to them, too. The user module is accessing the server and requires all users. The problem is that the preloader is being formed in the sidebar, and when the sidebar is loaded the preloader disappears. But the users still could not boot.
The question is: How to control the loading of the sidebar and the user, so that when these two components are updated, the state remove the preloader.
A common practice is to store isFetching flag in the reducer and update it in respond to fetch actions. For example:
function users(state = { users: [], isFetching: false }, action) {
switch (action.type) {
case 'FETCH_USERS_START':
return { ...state, isFetching: true };
case 'FETCH_USER_SUCCESS':
return { ...state, isFetching: false, users: action.payload.users };
default:
return state;
}
}
Then you can access it from both your components via mapStateToProps and show the preloader.
A main thing here is that you need to move the async call to an action, so reducer will be able to react to it. You can use redux-thunk middleware.
I want to test in all components whether the user has connection to the internet.
I could use NetInfo in each component, but since I am using redux, I thought it could be done easier with a middleware(?).
I have used
import { createStore, applyMiddleware } from 'redux';
const netInfo = store => next => action => {
const listener = (isConnected) => {
store.dispatch({
type: types.NET_INFO_CHANGED,
isConnected,
});
};
NetInfo.isConnected.addEventListener('change', listener);
NetInfo.isConnected.fetch().then(listener);
return next(action);
};
const store = createStore(AppReducer, applyMiddleware(netInfo));
where AppReducer is just combineReducers(navReducer, netInfoReducer, ...).
It does seem to work, but I am really worried if this performs well enough. It seems it is only run once, but I am never removing the listener or anything.
Is this how you normally would do if you want to populate all components with an isConnected variable?
I would create a Higher-Order Component for this:
import React, { Component } from 'react';
import { NetInfo } from 'react-native';
function withNetInfo(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = {};
this.handleChange = this.handleChange.bind(this);
NetInfo.isConnected.fetch().then(this.handleChange);
}
componentDidMount() {
NetInfo.isConnected.addEventListener('change', this.handleChange);
}
componentWillUnmount() {
NetInfo.isConnected. removeEventListener('change', this.handleChange);
}
handleChange(isConnected) {
this.setState({ isConnected });
}
render() {
return <WrappedComponent isConnected={this.state.isConnected} {...this.props} />;
}
}
}
export default withNetInfo;
Then you can wrap whatever component you would like to render:
class MyComponent extends Component {
render() {
const { isConnected } = this.props;
return(
<View>
<Text>
{`Am I connected? ${isConnected}`}
</Text>
</View>
);
}
}
export default withNetInfo(MyComponent);
Bonus: if you want to keep the statics methods of your original component (if you have defined some) you should use the package hoist-non-react-statics to copy the non-react specific statics:
import React, { Component } from 'react';
import { NetInfo } from 'react-native';
import hoistStatics from 'hoist-non-react-statics';
function withNetInfo(WrappedComponent) {
class ExtendedComponent extends Component {
constructor(props) {
super(props);
this.state = {};
this.handleChange = this.handleChange.bind(this);
NetInfo.isConnected.fetch().then(this.handleChange)
}
componentDidMount() {
NetInfo.isConnected.addEventListener('change', this.handleChange);
}
componentWillUnmount() {
NetInfo.isConnected. removeEventListener('change', this.handleChange);
}
handleChange(isConnected) {
this.setState({ isConnected });
}
render() {
return <WrappedComponent isConnected={this.state.isConnected} {...this.props} />;
}
}
return hoistStatics(ExtendedComponent, WrappedComponent);
}
export default withNetInfo;
There shouldn't be a performance issue using middleware to keep "isConnected" in your redux store, but you would want to make sure the listener is only added once. I use https://github.com/michaelcontento/redux-middleware-oneshot to achieve that.
I considered middleware, too, but was also afraid how to handle the sub/unsub. I've decided to go with adding and removing the listener in componentDidMount and componentWillUnmount of my AppContainer class, which holds the rest of the app in my MainNavigator. This class' lifecycle should follow that of the app, and thus make sure to sub/unsub correctly. I am, however, also going to use a redux action to set the status and listen to it in the relevant views to show a 'no connection' banner.