MobX componentWillReact not firing - reactjs

I read in the docs that mobx react provides a new lifecycle called componentWillReact. However, it seems that my class only reacts to mobx changes in the render function. componentWillReact is never triggered when my store changes.
I am sending "next" down as a prop. This app does not make use of mobx inject.
import { observer } from 'mobx-react';
#observer class QuickShopNew extends Component {
componentWillReact() {
console.log(this.props.store.next);
}
render(){
//console.log(this.props.store.next);
return(
<div>
</div>
)
}
}

As I can see your component doesn't dereference observed property in the render method. That's why mobx doesn't know that component should be rerendered and componentWillReact should be called on value change.
You can read how observer component work here
And here is simple working example on codepen
const { Component, PropTypes } = React;
const { observable } = mobx;
const { observer } = mobxReact;
// Store for state
class Store {
#observable next = 0;
increaseNext = () => this.next +=1;
}
let store = new Store();
#observer
class MyComponent extends Component {
componentWillReact() {
console.log(this.props.store.next);
}
render() {
return (
<div>
<h1>{this.props.store.next}</h1>
</div>
);
}
}
class App extends Component {
render() {
return (
<div>
<MyComponent
store={store}
/>
<button onClick={store.increaseNext}>
Increase
</button>
</div>
);
}
}
// Insert into container
ReactDOM.render(<App />, document.getElementById('app'));

I think that you should avoid of using the "componentWillReact" and use just standart Mobx services like this example is showing :
If you intended to update the observable variable with action then use computed method to send updated value into UI.
import React from 'react';
import { observable, action, computed } from 'mobx';
import { observer } from 'mobx-react';
class AppStore {
#observable next = 0;
#action updateNext = () => this.next = this.next + 1;
#computed get UI_renderValueNext() {
return this.next ? this.next : 0;
}
}
const appStore = new AppStore();
#observer
class AppComponent extends React.Component {
render(){
return (
<div>
<div>
{this.props.UI_rennderNext}
</div>
<button onClick={this.props.updateNext}>Click ME</button>
</div>
)
}
}
ReactDOM.render(
<AppComponent />, document.getElementById('root')
)

Related

React JS - Pass Provider components methods to this.children

In React can methods be passed to {this.children} in a container consumer model. What I mean to ask is I have a provider component and I need to pass or refer the provider components methods in the child component.
export default class ContainerCompo extends React.Component {
constructor(props) {
super(props);
this.myHocComponent = null;
}
methodOne() {
//some code
}
methodTwo() {
//some code
}
render() {
return (
{this.props.children}
}
}
export default class InputComponent extends React.Component {
constructor(props) {
super(props);
this.myHocComponent = null;
}
validate() {
ContainerCompo.methodOne(param)
}
render() {
return <InputComponent />
}
// Rendering the components
<ContainerCompo>
<InputComponent containerMethods={methods of ContainerCompo}/>
</ContainerCompo>
I hope my question is clear here, please suggest
First create a react context.
import React, { Component, createContext } from 'react';
// Create's authentication context to be use anywhere in the app
const ContainerContext = createContext();
export default ContainerContext;
Then create a provider for it.
export default class ContainerProvider extends Component {
constructor(props) {
super(props);
this.myHocComponent = null;
}
methodOne() {
//some code
}
methodTwo() {
//some code
}
render() {
const { children } = this.props;
return (
<ContainerContext.Provider
value={{
container: {
methodOne: (...params) => this.methodOne(...params),
methodTwo: (...params) => this.methodTwo(...params)
}
}}
>
{children}
</ContainerContext.Provider>
)}}
Wrap your App with the provider.
import ContainerProvider from './ContainerProvider'
<ContainerProvider>
<App />
</ContainerProvider>
Then create a consumer for the context
export default function withContainer(InComponent) {
return function ContainerComponent(props) {
return (
<ContainerContext.Consumer>
{({ container }) => <InComponent {...props} container={container} />}
</ContainerContext.Consumer>
);
};
}
Then import the consumer and user in your components and you will get the methods as props
import withContainer from './ContainerConsumer'
render() {
const { container } = this.props;
return(<div />)
}
export default withContainer(YourComponent);

Accessing variable from imported class from another React script

I'm importing a class from another script in my main React App, and would like to access a variable within that class from the main App. Basically the user types something into a textbox, then clicks a button to add that value to a variable. In the main App I import that class, then have another button to print those values (selectedvalues). I'm not entirely sure how to do it, but this is my code so far:
Class I am importing:
import React, { Component } from 'react';
class MyModule extends Component {
constructor() {
super();
this.state = {
selectedValues: '',
}
}
addValue() {
this.selectedValues += document.getElementById('textBox1').value + ', '
return this.selectedValues
}
render() {
return(
<div>
<input type='text' id='textBox1' />
<button onClick={() => this.addValue()}>Add Value</button>
</div>
)
}
}
export default MyModule
And where I would like to actually access that value
import React, { Component } from 'react';
import MyModule from './myModule.js'
class App extends Component {
constructor() {
super();
this.state = {
}
}
printValues() {
console.log(document.getElementById('themodule').selectedvalues)
}
render() {
return(
<MyModule id='themodule' />
<button onClick={() => printValues()}>Print values</button>
)
}
}
export default App
Is there a way I can do this?
Thanks!
Edit JS-fiddle here https://jsfiddle.net/xzehg1by/9/
You can create Refs and access state and methods from it. Something like this.
constructor() {
this.myRef = React.createRef();
}
render() { ... <MyModule id='themodule' ref={this.myRef} /> }
printValues() {
console.log(this.myRef)
}
more info here https://reactjs.org/docs/refs-and-the-dom.html
Basically, your state (selectedValues) has to go one level up in the React tree. You have to declare it as App's state, and then pass it down to MyModule via props.
Btw in addValue(), you're not changing any state. And this.selectedValues will be undefined. It's this.state.selectedValues, and this.props.selectedValues once you correct your code.
I think you should first read all react concepts and then start working on it. Anyhow i am modifying your code in one way to get your desired functionality but remember this is not best practice you have to use Redux for this kind of features
import React, { Component } from 'react';
class MyModule extends Component {
constructor() {
super(props);
this.state = {
inputValue : ''
};
this.handleInput = this.handleInput.bind(this);
this.addValue = this.addValue.bind(this)
}
handleInput(e){
this.setState({
inputValue : e.target.value
})
}
addValue() {
this.props.addValue(this.state.inputValue);
}
render() {
return(
<div>
<input type='text' id='textBox1' onChange={handleInput} />
<button onClick={this.addValue}>Add Value</button>
</div>
)
}
}
export default MyModule
and your main component should be
import React, { Component } from 'react';
import MyModule from './myModule.js'
class App extends Component {
constructor() {
super(props);
this.state = {
selectedValues : ''
};
this.printValues = this.printValues.bind(this);
this.addValues = this.addValues.bind(this);
}
printValues() {
console.log(this.state.selectedValues);
}
addValues(val){
this.setState({
selectedValues : this.state.selectedValues + " , "+val
})
}
render() {
return(
<React.Fragment>
<MyModule addValue={this.addValues}/>
<button onClick={this.printValues} >Print values</button>
</React.Fragment>
)
}
}
export default App
This should do your work

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

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

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