React native context api not updated - reactjs

I'm using RN NetInfo to check if user connected to internet using component <NetworkProvider /> and I want to pass this components stats to all screens and components in my app.
The problem is context api works good when I use it inside render function but when I use inside componentDidMount or componentWillMount the state not changed. Return initial value of isConnected state.
Please read comment in code
so this my code
NetworkProvider.js
import React,{PureComponent} from 'react';
import NetInfo from '#react-native-community/netinfo';
export const NetworkContext = React.createContext({ isConnected: true });
export class NetworkProvider extends React.PureComponent {
state = {
isConnected: true // initial value
};
componentDidMount() {
NetInfo.isConnected.addEventListener('connectionChange', this.handleConnectivityChange);
}
componentWillUnmount() {
NetInfo.isConnected.removeEventListener('connectionChange', this.handleConnectivityChange);
}
handleConnectivityChange = isConnected => this.setState({ isConnected });
render() {
return (
<NetworkContext.Provider value={this.state}>
{this.props.children}
</NetworkContext.Provider>
);
}
}
this index.js
...
import { NetworkContext } from '../components/NetworkProvider';
export default class index extends Component {
static navigationOptions = {};
static contextType = NetworkContext;
constructor(props) {
super(props);
this.state = {
...
};
}
componentDidMount() {
// return object state but with inital value {isConnected :true}
console.log(this.context);
//this.fetchData(this.state.page);
}
render() {
// here when I use this.context return object {isConnected:true/false} depends on internet connection status on device
return(
<FlatList
...
/>
)
}
}
...

Related

How to set component state with context.state?

I recently ran into an issue with React Context.
I have some data stored in localstorage which I only intend to use when there is no data available from the context provider.
The data from localstorage is stored in my component's state.
I would like to override this.state if there is available data coming from context.
The struggle is that I don't know how to set the state when the context can only be used in the render method.
It is a very bad practice to call a setState in the render and I have no idea how to get the context.state outside of the render.
If there is no data from
There is some sample code below.
Any ideas are welcome which are taking me closer to the solution.
constructor(props) {
super(props);
this.state = {
data: ''
}
}
componentDidMount() {
this.setState({
data: localstorage.getItem('data')
})
}
render() {
return (
<>
<AppContext.Consumer>
{context => (
<>
{typeof context.state.data !== 'undefined'&&
<div>
{/*Print out data from this.state or from context.state*/}
</div>
}
</>
)}
</AppContext.Consumer>
</>
)
}
You can access to context outside render with following trick:
import { PageTitleContext } from '../lib/pageTitleProvider';
import { Helmet } from 'react-helmet';
import * as PropTypes from 'prop-types';
class PageTitle extends Component {
componentDidMount() {
if (this.props.title)
this.props.context.setTitle(this.props.title);
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.props.title !== prevProps.title)
this.props.context.setTitle(this.props.title);
}
render() {
if (this.props.title) {
return <Helmet>
<title>{this.props.context.title}</title>
</Helmet>;
}
if (!this.props.title) {
return this.props.context.title;
}
}
}
PageTitle.propTypes = {
title: PropTypes.string,
context: PropTypes.object.isRequired,
};
export default (props) => (
<PageTitleContext.Consumer>
{(context) => <PageTitle {...props} context={context}/>}
</PageTitleContext.Consumer>
)

Passing a component into React setState value

I've got a Meteor app using React. I've added Session variables and want to pass the new Session value (which will be another React component) into another react component.
The user will click the p-tag in the SideNav and reset the Session to a React component.
SideNav component:
import React from 'react';
import { Session } from 'meteor/session';
import SonataContent from './sonata-content';
export default () => {
injectSonataText = () => {
const sonataContent = <SonataContent/>;
Session.set('MainContent', sonataContent); /* Set Session value to component */
};
return (
<div className="side-nav">
<h2>Explore</h2>
<p onClick={this.injectSonataText.bind(this)}><i className="material-icons">child_care</i><span> Sonatas</span></p>
</div>
)
}
In the MainWindow, Tracker.autorun re-runs and sets the state to the component and renders the new state value.
Main Window component:
import React from 'react';
import { Session } from 'meteor/session';
import { Tracker } from 'meteor/tracker';
export default class MainWindow extends React.Component {
constructor(props) {
super(props);
this.state = {
text: ""
}
}
componentDidMount() {
this.mainWindowTracker = Tracker.autorun(() => {
const text = Session.get('MainContent');
this.setState({text: text});
});
}
componentWillUnmount() {
this.mainWindowTracker.stop();
}
render() {
return (
<p>{this.state.text}</p>
)
}
}
I'm getting an error "Invariant Violation: Objects are not valid as a React child". Is this caused by the component being used in setState? Is there a way to do this?
Session set function accepts as a value EJSON-able Object which I think may not work with React Object.
However I would try (only a guess though):
injectSonataText = () => {
Session.set('MainContent', SonataContent); /* Set Session value to component */
};
...
export default class MainWindow extends React.Component {
constructor(props) {
super(props);
this.state = {
Component: null,
}
}
componentDidMount() {
this.mainWindowTracker = Tracker.autorun(() => {
const MainContent = Session.get('MainContent');
this.setState({Component: MainContent});
});
}
componentWillUnmount() {
this.mainWindowTracker.stop();
}
render() {
const { Component } = this.state;
return (
<p>
{
Component && <Component />
}
</p>
)
}
}

Remove a React component from the DOM

I have this piece of code (which I've simplified for posting here) that creates a component and renders it
const getComponentToRender = (user, path) => {
switch(path ){
case 'ChangePassword':
return <ChangePassword user={ user } />;
case 'NewPassword':
return <NewPassword user={ user } />;
case 'PasswordExpire':
return <PasswordExpire user={ user } />;
default:
return null;
}
}
class UserAdmin extends React.Component {
static propTypes = {
user: PropTypes.object.isRequired
};
render() {
const component = getComponentToRender(this.props.user, 'ChangePassword' );
return(
<div id='user-admin-wrapper'>
{component}
</div>
)
}
componentWillUnmount(){
}
}
When I navigate away from UserAdmin the componentWillUnmount gets called.
Q: What is the simplest way to actually remove the component ChangePassword or any other component (by its name) from the DOM when componentWillUnmount executes.
OR, removing the component at any point, without waiting for componentWillUnmount
Using react-dom 15.6.1 . btw
Un-mounting a component will un-mount(remove) all the child components it contains. So after componentWillUnmount the component you rendered inside it will be removed.
If you need to control over components that rendered without un-mounting you use conditional render logic.
Example
class SomeComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
shouldIRender: true
};
}
componentDidMount() {
setTimeout(() => {
this.setState({shouldIRender: false});
}, 5000);
}
render() {
return(
<div>
<ComponentThatAlwaysHere />
{ this.state.shouldIRender === true ? <ComponentThatRemovesAfterStateChange /> : null }
{ this.state.shouldIRender === true && <AnotherComponentThatRemovesAfterStateChange /> }
</div>
)
}
}

Unable to read a state from a reactjs component after it is set

I have set a simple example here: https://www.webpackbin.com/bins/-KhBJPs2jLmpQcCTSRBk
This is my class in the question:
import React, { Component, PropTypes } from 'react';
import { Redirect } from 'react-router-dom';
class RedirectMessage extends Component {
componentWillMount () {
const { timeToRedirect } = this.props;
const time = timeToRedirect || 5000;
console.log(`timer is set to ${time}`)
this.timer = setInterval(this.setoffRedirect.bind(this), time)
}
componentWillUnmount () {
clearInterval(this.timer);
}
setoffRedirect () {
console.log('setoffRedirect');
clearInterval(this.timer);
this.setState({startRedirect: true})
}
getDestination() {
const { destination } = this.props;
return destination || '/';
}
render() {
const {
message,
startRedirect
} = this.props;
console.log(`setoffRedirect >>> ${startRedirect}`);
if (startRedirect) {
return (
<Redirect to={this.getDestination()} />
)
}
return (
<div >
{message}
</div>
);
}
}
export default RedirectMessage;
What I want to achieve is to display a message before I redirect to another url
Here are the logic:
set up a timer in componentWillMount
When the timer callback is called, it uses setState to set startRedirect to true
In render if startRedirect is true, a Redirect tag will be rendered can cause url redirect
However the problem is the startRedirect remains undefined even after the timer callback is called. What have I missed?
class SomeClass extends Component {
constructor(props){
super(props);
this.state = {startRedirect: false};
}
}
The state object is not defined when using classes (es6). In cases where it is needed, you need to define it in the constructor to make it accessible.
You are also targeting startRedirect from this.props, instead of this.state within render

forceUpdate is not re-rendering children

I'm using the react, redux react-router stack for my webapp. In the top level component's(the component that renders on the root path) componentDidMount I'm subscribing to the store as shown below
import NotificationsList from './components/notifier';
import React from 'react';
let Spinner = ({
isVisible,
showSpinner,
solidBackdrop
}) => (
<div style={{opacity: solidBackdrop ? 1 : 0.5}} className={"spinner " + (isVisible ? '' : 'hide')}></div>
);
export default class AppPage extends React.Component {
static contextTypes = {
store: React.PropTypes.object,
router: React.PropTypes.object
};
handleDismissNotification(notification) {
this.context.store.dispatch({
type: 'REMOVE_NOTIFICATION',
data: notification
});
}
componentDidMount() {
this.context.store.subscribe(() => this.forceUpdate());
}
render() {
let state = this.context.store.getState();
let props = {
notifications: state.notifications,
handleDismiss: this.handleDismissNotification.bind(this)
};
return (
<div className="'apppage-container">
{this.props.children}
<NotificationsList {...props} />
<Spinner isVisible={state.initialFetchInProgress || state.requestInProgress}
showSpinner={!state.initialFetchInProgress} solidBackdrop={state.initialFetchInProgress}/>
</div>
);
}
}
this.props.children here renders the component shown below
import Header from './components/header';
import React from 'react';
class ContentPage extends React.Component {
static contextTypes = {
store: React.PropTypes.object
};
render() {
let user = this.context.store.getState().user;
return <div className="content-container">
<Header user/>
</div>
}
}
export default ContentPage;
The problem is that when the first time a render happens, everything goes fine. Then when the render happens through forceUpdate, the child component is not getting re-rendered.
I think I got it. Every container component should be subscribed to the store separately. So accordingly, ContentPage should also have
componentDidMount() {
this.context.store.subscribe(() => this.forceUpdate());
}
As you replied to yourself, indeed the container component should subscribe to the store , but in addition to the subscription, it's good practice for the the container to also unsubscribe when unmounted :
componentDidMount() {
this.unsubscribe = this.context.store.subscribe(() => this.forceUpdate());
}
componentWillUnmount() {
this.unsubscribe();
}

Resources