Passing a component into React setState value - reactjs

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>
)
}
}

Related

Access method on React Context API from Children's Method

I am new to React and trying to make context API. I have read some similar question but I can not get a solution.
My context provider file :
import React, { Component } from 'react'
const MyContext = React.createContext();
class ContextProvider extends Component {
constructor(props) {
super(props)
this.state = {
isLogin: false
}
}
handleLogin = () => {
this.setState({
isLogin : true
})
}
render() {
return (
<MyContext.Provider value={{
...this.state,
handleLogin : this.handleLogin
}}>
{this.props.children}
</MyContext.Provider>
);
}
}
const ContextConsumer = MyContext.Consumer;
export {ContextProvider, ContextConsumer};
I need to change the state by accessing handleLogin() in the ContextProvider.js after user successfull login :
import React, { Component } from 'react'
import {ContextConsumer} from "./ContextProvider";
class Login extends Component {
onHandleSubmit = () => {
// on submit login success :
// --- how to call handleLogin() in ContextProvider.js here ? ----
}
render() {
return (
<div> --- not expected here ---- </div>
)
}
}
BTW, sorry for my English.
Assuming your Login component is wrapped by the ContextProvider higher up in the hierarchy, you can access context inside class component by define a static contextType .
For that you need to export context from ContextProvider first like
export {ContextProvider, ContextConsumer, MyContext };
and then use it like
import React, { Component } from 'react'
import {MyContext} from "./ContextProvider";
class Login extends Component {
static contextType = MyContext;
onHandleSubmit = () => {
// on submit login success :
this.context.handleLogin();
}
render() {
return (
<div> {/* render content here */} </div>
)
}
}
However if you are using a version of react between 16.3.0 and 16.6.0, you need to pass on context using render props pattern like
class Login extends Component {
onHandleSubmit = () => {
// on submit login success :
this.props.context.handleLogin();
}
render() {
return (
<div> --- not expected here ---- </div>
)
}
}
export default (props) => (
<ContextConsumer>
{values=> <Login {...props} context={values} />}
</ContextConsumer>
)

React native context api not updated

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
...
/>
)
}
}
...

Unable to call a property of child react component from parent react component

Following is the jsx code of child & parent parent react components. I am trying to pass the data from child react component to parent react component as property (generateReport) but it throws the error as
Uncaught TypeError: this.props.generateReport is not a function
at MetricsReport.generateReport (metrics-report.jsx:40)
child.jsx
import React, { Component } from 'react';
import {
Row,
Col,
Input,
Collapsible,
CollapsibleItem
} from 'react-materialize';
class MetricsReport extends Component {
constructor(props) {
super(props);
this.state = {
metricsParams: { reportType: '' }
};
this.getReportType = this.getReportType.bind(this);
// Add the below line as per the answer but still facing the problem
this.generateReport = this.generateReport.bind(this);
}
getReportType(event) {
console.log(this.state.metricsParams);
let metricsParams = { ...this.state.metricsParams };
metricsParams.reportType = event.target.value;
this.setState({ metricsParams });
}
generateReport() {
this.props.generateReport(this.state.metricsParams);
}
componentDidMount() {}
render() {
return (
<div class="ushubLeftPanel">
<label>{'Report Type'}</label>
<select
id="metricsDropDown"
className="browser-default"
onChange={this.getReportType}
>
<option value="MetricsByContent">Metrics By Content</option>
<option value="MetricsByUser">Metrics By User</option>
</select>
<button onClick={this.generateReport}>Generate Report</button>
</div>
);
}
}
export default MetricsReport;
parent.jsx
import React, { Component } from 'react';
import MetricsReport from '../components/pages/metrics-report';
class MetricsReportContainer extends Component {
constructor(props) {
super(props);
this.generateReport = this.generateReport.bind(this);
}
generateReport(metricsParams) {
console.log(metricsParams);
}
componentDidMount() {}
render() {
return (
<div>
<MetricsReport generateReport={this.generateReport} />
</div>
);
}
}
export default metricsReportContainer;
You forgot to bind the context this inside child component MetricsReport:
// inside the constructor
this.generateReport = this.generateReport.bind(this);
But you may simply use like this:
<button
onClick={this.props.generateReport(this.state.metricsParams)}
>
Generate Report
</button>
You can handle these scenarios by using anonymous functions instead of normal function. Anonymous function handles your this and removes the need to binding.
generateReport(metricsParams) {
console.log(metricsParams);
}
becomes
generateReport = (metricsParams) => {
console.log(metricsParams);
}
Also in child class
generateReport() {
this.props.generateReport(this.state.metricsParams);
}
becomes
generateReport = () => {
var metricsParams = this.state.metricsParams;
this.props.generateReport(metricsParams);
}

React Higher Order Component that detects dom events that takes functional components as arg

I have a scenario where I want to create an HOC that detects mouse events (e.g. mouseenter, mouseleave) when they occur on the HOC's WrappedComponent, then pass the WrappedComponent a special prop (e.g. componentIsHovered). I got this working by using a ref callback to get the wrapped component instance, then adding event listeners to the wrapped instance in my HOC.
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
export default (WrappedComponent) => {
return class DetectHover extends Component {
constructor(props) {
super(props)
this.handleMouseEnter = this.handleMouseEnter.bind(this)
this.handleMouseLeave = this.handleMouseLeave.bind(this)
this.bindListeners = this.bindListeners.bind(this)
this.state = {componentIsHovered: false}
this.wrappedComponent = null
}
componentWillUnmount() {
if (this.wrappedComponent) {
this.wrappedComponent.removeEventListener('mouseenter', this.handleMouseEnter)
this.wrappedComponent.removeEventListener('mouseleave', this.handleMouseLeave)
}
}
handleMouseEnter() {
this.setState({componentIsHovered: true})
}
handleMouseLeave() {
this.setState({componentIsHovered: false})
}
bindListeners(wrappedComponentInstance) {
console.log('wrappedComponentInstance', wrappedComponentInstance)
if (!wrappedComponentInstance) {
return
}
this.wrappedComponent = ReactDOM.findDOMNode(wrappedComponentInstance)
this.wrappedComponent.addEventListener('mouseenter', this.handleMouseEnter)
this.wrappedComponent.addEventListener('mouseleave', this.handleMouseLeave)
}
render() {
const props = Object.assign({}, this.props, {ref: this.bindListeners})
return (
<WrappedComponent
componentIsHovered={this.state.componentIsHovered}
{...props}
/>
)
}
}
}
The problem is that this only seems to work when WrappedComponent is a class component — with functional components the ref is always null. I would just as soon place the WrappedComponent inside <div></div> tags in my HOC and carry out the event detection on that div wrapper, but the problem is that even plain div tags will style the WrappedComponent as a block element, which doesn’t work in my use case where the HOC should work on inline elements, too. Any suggestions are appreciated!
You can pass the css selector and the specific styles you need to the Higher Order Component like this:
import React, {Component} from 'react';
const Hoverable = (WrappedComponent, wrapperClass = '', hoveredStyle=
{}, unhoveredStyle={}) => {
class HoverableComponent extends Component {
constructor(props) {
super(props);
this.state = {
hovered: false,
}
}
onMouseEnter = () => {
this.setState({hovered: true});
};
onMouseLeave = () => {
this.setState({hovered: false});
};
render() {
return(
<div
className={wrapperClass}
onMouseEnter= { this.onMouseEnter }
onMouseLeave= { this.onMouseLeave }
>
<WrappedComponent
{...this.props}
hovered={this.state.hovered}
/>
</div>
);
}
}
return HoverableComponent;
};
export default Hoverable;
And use Fragment instead of div to wrap your component:
class SomeComponent extends React.Component {
render() {
return(
<Fragment>
<h1>My content</h1>
</Fragment>
)
}
And then wrap it like this
const HoverableSomeComponent = Hoverable(SomeComponent, 'css-selector');

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

Resources