Accessing variable from imported class from another React script - reactjs

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

Related

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

Property for react component is not defined

After running this code - I got the exception that "title" is not defined. I checked that api returns correct data. And on the debug mode I noticed that render() from Idea component is running earlier than getting the data from API. Can you explain why is it working in this way? And what options I have for resolving this issue?
Thanks
'use strict';
const React = require('react');
const ReactDOM = require('react-dom');
const client = require('./client');
class App extends React.Component {
constructor(props) {
super(props);
this.state = {map: {}};
}
componentDidMount() {
client({method: 'GET', path: '/api/maps/1'}).done(response => {
this.setState({map: response.entity._embedded.map});
});
}
render() {
return (
<Map map={this.state.map}/>
)
}
}
class Map extends React.Component {
render() {
return (
<div id="map_header">
<AddIdeaButton></AddIdeaButton>
<Idea idea={this.props.map.root}></Idea>
</div>
);
}
}
class AddIdeaButton extends React.Component {
render() {
return (
<a id="btn_add">
</a>
);
}
}
class Idea extends React.Component {
render() {
<div id="root">{this.props.idea.title}</div>
}
}
ReactDOM.render(
<App />,
document.getElementById('react')
);
Asynchronous request for data takes some time during which React still renders Map and Idea components. You can simply render Idea conditionally when data is available:
<div id="map_header">
<AddIdeaButton></AddIdeaButton>
{this.props.map.root && (
<Idea idea={this.props.map.root}></Idea>
)}
</div>

Multiple instances of a component shouldn't share state

Say I had a simple inputfieldcomponent like so:
import React, {PropTypes, PureComponent} from 'react';
import {render} from 'react-dom';
import {connect} from 'react-redux';
import updateInput from '../../actions/inputActions';
require('./SimpleInput.sass');
export class SimpleInput extends PureComponent {
constructor(props, context) {
super(props, context);
}
handleChange (event) {
this.props.updateField(event.target.value);
}
renderField () {
return (
<input type="text" value={this.props.value || ''} onChange={this::this.handleChange} placeholder={this.props.initial_value}/>
)
}
render () {
return(
<span>
{this.renderField()}
</span>
)
}
}
const mapStateToProps = (state) => {
return {value: state.value.value}
}
const mapDispatchToProps = (dispatch) => {
return {
updateInput: (value) => dispatch(updateInput(value))
};
};
AddressInput.propTypes = {
initial_value: PropTypes.string
};
AddressInput.defaultProps = {
initial_value: "What's the value?"
};
export default connect(mapStateToProps, mapDispatchToProps)(SimpleInput);
I then render two instances:
</SimpleInput initial_value='blah'/>
</SimpleInput>
However, when this is rendered, any update to one of the two fields updates both of them (due to redux only allowing for a single state).
What is the canonical way to approach this problem?
Here's one approach Abraham and giving an example to what Dan commented on. Check out this jsBin demonstrating the concept of a component (here it's SimpleInput) keeping its own internal state (such as a default placeholder) while still interacting with a Container (listening to onChange). While the example doesn't use Redux (for simplicity of creating the demo) you could easily substitute onChange handles with action dispatchers.
class SimpleInput extends React.Component {
constructor(props) {
super(props);
this.state = {
defaultPlaceholder: 'Default Placeholder'
}
}
render() {
return (
<input
value={this.props.value}
placeholder={this.props.placeholder || this.state.defaultPlaceholder}
onChange={this.props.onChange || ()=>{} }
/>
)
}
}
class SimpleInputContainer extends React.Component {
constructor(props) {
super(props);
this.changeInput1 = this.changeInput1.bind(this);
this.changeInput2 = this.changeInput2.bind(this);
this.state = {
input1: 'foo',
input2: 'bar',
}
}
changeInput1(e) {
this.setState({
input1: e.target.value
})
console.log(this.state);
}
changeInput2(e) {
this.setState({
input2: e.target.value
})
}
render() {
return (
<div>
<SimpleInput value={this.state.input1} onChange={this.changeInput1} />
<br />
<SimpleInput value={this.state.input2} onChange={this.changeInput2} />
<br />
<SimpleInput />
<br />
<SimpleInput placeholder={'Explicit Placeholder'} />
</div>
);
}
}
ReactDOM.render(<SimpleInputContainer />, document.getElementById('app'));
One of my use cases was an address lookup with an input managing it's value internally (no redux) and a lookup button emitting the value of the input via onClick={this.props.onSave} and in the Container handling all side effects/redux actions etc.
Lastly in your demo code, you're rerendering SimpleInput unnecessarily, which looks like a code smell. Hope that helps.

React propTypes errors not showing

I'm having problems with React propTyoes. I'v created a component that require 2 props to work as you guys can see in the code below.
When I use the component in the App file, passing just 1 prop, without the "stateSidebarVisible" it doesn't throw me any error/warning from react...
(I read a lot of things about the NODE_ENV production/development, I searched in my node for process.env and didnt found the NODE_ENV variable by the way).
Any clue?
FFMainHeader
export default class FFMainHeader extends React.Component {
render() {...}
}
FFMainHeader.propTypes = {
stateSidebarVisible: React.PropTypes.bool.isRequired,
handleSidebarChange: React.PropTypes.func.isRequired
};
App
This is where i call the FFMainHeader component.
export default class FFMainApp extends React.Component {
.......
render() {
return (
<div id="FFMainApp">
<FFMainHeader
handleSidebarChange={this.onSidebarChange} />
<FFMainSidebar />
</div>
);
}
}
EDIT
export default class FFMainHeader extends React.Component {
constructor(props) {
super(props);
this.clickSidebarChange = this.clickSidebarChange.bind(this);
}
clickSidebarChange(e) {
e.preventDefault();
(this.props.stateSidebarVisible) ?
this.props.stateSidebarVisible = false :
this.props.stateSidebarVisible = true;
this.props.handleSidebarChange(this.props.stateSidebarVisible);
}
render() {
return (
<header id="FFMainHeader">
<a href="#" onClick={this.clickSidebarChange}>
Abre/Fecha
</a>
</header>
);
}
}
FFMainHeader.propTypes = {
stateSidebarVisible: React.PropTypes.bool.isRequired,
handleSidebarChange: React.PropTypes.func.isRequired
};

Resources