Access custom component methods through ref doesnt work as expected - javascript

Can someone explain to me why in SomeClass constructor getLoggerClass method returns undefined,but inside onClick() method it returns logger class?
class App extends React.Component {
constructor(props){
super(props)
this.getLoggerClass = this.getLoggerClass.bind(this)
}
render(){
return(
<div>
<LoggerClass ref={(c)=>{this.loggerClass = c}}/>
<SomeClass app={this} />
</div>
)
}
getLoggerClass(){
return this.loggerClass
}
}
class SomeClass extends React.Component {
constructor(props){
super(props)
this.loggerClass = this.props.app.getLoggerClass()
console.log(this.loggerClass)
this.onClick = this.onClick.bind(this)
}
render(){
return <button onClick={this.onClick}>click</button>
}
onClick(){
console.log(this.props.app.getLoggerClass().console)
}
}
class LoggerClass extends React.Component {
render(){
return <div></div>
}
console(v){
console.log(v)
}
test(){}
}
ReactDOM.render(<App />,document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

It's because the ref function on the LoggerClass node hasn't been executed by the time the constructor of SomeClass gets run. The constructor gets executed when creating the virtual DOM, whereas the ref gets executed when the component actually gets mounted to the real DOM (more info about mounting here). Here's what I think you're looking for, with the relevant code in SomeClass's componentDidMount:
class App extends React.Component {
constructor(props){
super(props)
this.getLoggerClass = this.getLoggerClass.bind(this)
}
render(){
return(
<div>
<LoggerClass ref={(c)=>{this.loggerClass = c}}/>
<SomeClass app={this} />
</div>
)
}
getLoggerClass(){
return this.loggerClass
}
}
class SomeClass extends React.Component {
constructor(props){
super(props)
this.onClick = this.onClick.bind(this)
}
componentDidMount() {
this.loggerClass = this.props.app.getLoggerClass()
console.log('in mounted', this.loggerClass.console)
}
render(){
return <button onClick={this.onClick}>click</button>
}
onClick(){
console.log(this.props.app.getLoggerClass().console)
}
}
class LoggerClass extends React.Component {
render(){
return <div></div>
}
console(v){
console.log(v)
}
test(){}
}
ReactDOM.render(<App />,document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Here is the culprit :
constructor(props){
super(props)
this.loggerClass = this.props.app.getLoggerClass()
console.log(this.loggerClass)
this.onClick = this.onClick.bind(this)
}
To understand the problem you must understand the, quote mdn, The constructor method is a special method for creating and initializing an object created within a class.
So basically, this constructor method is used to initialize, and so it is called only one time during the class life. So when your SomeClass first renders, the constructormethod is called and in it you define : this.loggerClass as this.props.app.getLoggerClass() (notice here that you called the function immediately at init time) so what is happening is that :
first constructor() is called
in it you called directly the getLoggerClass() method, this method returns the dom node but at init time it is undefined
Try to not call the getLoggerClass method directly but call it later, you do not need to bind it though, just declare in your SomeClass component a method that just calls the getLoggerClass method like so :
callGetLoggerClassFromProps() {
return this.props.app.getLoggerCLass()
}
this way you give the time to the ref to be assigned.

Related

How to use composition in ReactJs

Well here I want to use one method to another component, And for that I found a way through composition.
And this is what I did for that
file1.js
import ProductList from '../../views/Products/ProductList';
class CloseableTab extends Component {
constructor() {
super();
this.tpItem = () => {
console.log("hello, item clicked");
};
}
render() {
return (
<div>
<ProductList
itemChange={this.tpItem} />
</div>
);
}
}
export default CloseableTab;
Then in productList I want to call the "tpItem" method by calling itemChange in prop.
Though before that I tried to console the 'prop' of product list. So, it shows me null object in the console. And for that I used the code below:
ProductList.js
export default class ProductList extends Component {
constructor() {
super();
};
render() {
console.log(this.props);
return { }
}
}
So, this gives me null object in the console.
I'll appreciate your help, thanks.
Did you make constructor props enabled ?
Just pass props parameter in constructor
constructor(props) {
super(props)
}
The constructor for a React component is called before it is mounted.
When implementing the constructor for a React.Component subclass, you
should call super(props) before any other statement. Otherwise,
this.props will be undefined in the constructor, which can lead to
bugs.
Its not ideal to define functions in the constructor of the component, you can declare them outside of constructor and pass them down, also, in ProductList you are trying to render an object which isn't supported. if you don't want to return anything use return null.
Below code works as expected.
class CloseableTab extends Component {
constructor() {
super();
this.tpItem = () => {
console.log("hello, item clicked");
};
}
render() {
console.log(this.tpItem);
return (
<div>
<ProductList
itemChange={this.tpItem} />
</div>
);
}
}
class ProductList extends Component {
render() {
console.log(this.props);
return null
}
}
However you must write it like
class CloseableTab extends Component {
tpItem = () => {
console.log("hello, item clicked");
};
render() {
console.log(this.tpItem);
return (
<div>
<ProductList
itemChange={this.tpItem} />
</div>
);
}
}
Working sandbox

I am getting undefined while trying to pass array to a class

I am new to ReactJS and i am having issues with passing an array from one class to another class
Here is my Syntax:
class Application extends React.Component{
constructor(){
super()
this.state = {
data: []
}
}
method_one(){
var array = ["one","two"]
this.state =array
}
render() {
return(
<div>{this.method_one()}
{console.log(this.state.data)}
<AppTwo content={this.state.data}/>
</div>
)
}
}
class AppTwo extends React.Component{
render(){
return<div> {console.log(this.props.content)}</div>
}
}
ReactDOM.render(<Application />,document.getElementById("main"))
The console.log() returns undefined.
Can you help me figure out what I am missing?
Your mistake is that yo try to mutate state of component and the loop happens because you call method constantly.
Try this code:
class Application extends React.Component{
constructor(){
super()
this.state = {
data: []
}
this.method_one = this.method_one.bind(this); //get class context to this method
}
method_one(){
var array = ["one","two"]
this.setState({ data: array });
}
render() {
return(
<div onClick={this.method_one}> //do not call this metod constantly you just need to indicate it
{console.log(this.state.data)}
<AppTwo content={this.state.data}/>
</div>
)
}
}
class AppTwo extends React.Component{
render(){
return<div> {console.log(this.props.content)}</div>
}
}
ReactDOM.render(<Application />,document.getElementById("main"))
Sorry for my English:) Hope its help you.

componentDidMount() of parent is not called

I have BaseComponent, from which all another components inherit. But if child component has componentDidMount(), parent's componentDidMount() is not called. Is there any way to call componentDidMount() of parent's component always after componentDidMount() of child component? Here is example.
You can use the "super()" function to call the parents implementation.
componentDidMount() {
console.log('Child mounted.');
super();
}
But this is regarded as an anti-pattern. The suggested approach would be composition (details here). Unfortunately, without know what you are trying to accomplish through inheritance, we can't tell you an alternative through composition. In using your example, it can be done something like this
class Animal extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log('Parent mounted.'); // Not working.
}
render() {
return (<div>{this.props.animalType}</div>);
}
}
class Dog extends React.Component {
componentDidMount() {
console.log('Child mounted.');
}
render() {
return (<Animal animalType="Dog" />);
}
}
React.render(<Dog />, document.body);
In your example, your parent component Animal is not actually the parent but is an independent component since anyways you are rendering the Dog component.
This is the Reason that the componentDidMount of Animal Component is not getting called, in fact the Animal component itself is not being rendered but just defined.
In order for Dog to be a child of Animal component, render it from the Parent component(Animal) and change the code like
class Animal extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log('Parent mounted.'); // Not working.
}
render() {
return (
<Dog/>
)
}
}
class Dog extends Animal {
componentDidMount() {
console.log('Child mounted.');
}
render() {
return <div>Dog.</div>;
}
}
React.render(<Animal />, document.body);
JSFIDDLE

How do you test an abstract class in Enzyme and/or pass implicit variables?

I have a base class that's being extended by several components, and inside that base class, there's an implicit variable that is being passed in from the classes that are extending it. For instance, I have the following as the base class:
export default class BaseCard extends React.Component {
constructor(props, context) {
super(props, context);
}
render() {
return (
<div>
{this.hasData && <span> Has Content </span>}
</div>);
}
}
And the component that extends the BaseCard:
export default class MyCard extends BaseCard {
constructor(props) {
super(props);
this.hasData = true;
}
render() {
return (
<div>
MyCard content
</div>);
}
}
this.hasData is defined inside the MyCard component, but since I'm testing BaseCard, it's not defined inside the class, and therefore, I can't test parts of the DOM that depend on that variable being there. How can I pass it in when testing with Enzyme?
You can set the variable on the instance like this:
const wrapper = mount(<MyCard />);
wrapper.instance().hasData = true;

React - props is empty when calling a callback function from child

I have a button on my main component, when its clicked its open an "Approval pannel", And when the OK is clicked I am calling a callback function to the main component and doing some logic.
I want to pass the callback function(My reasons), The problem is that when the callback function is called, the props and state are undefined.
Why is that happening? Please tell me if any info is missing.
I have added a partial code here:
class MainComponent extends React.Component {
constructor(props){
currentActionConfig = {onOkClick: this.onGenericApprovalOkClicked, ...};
}
onCommandApprovalOkClicked(commandText){
console.log(this.props); <- 'undefined'
}
render(){
return <ActionsApprovalPanel currentActionConfig={this.currentActionConfig}/>
}
}
export default class ActionsApprovalPanel extends React.Component {
render()
{
...
return <ChangeIpApproval onOkClick={this.props.currentActionConfig.onOkClick}/>;
...
}
}
Try these changes
class MainComponent extends React.Component {
constructor(props){
super(props); //1. Call super
this.currentActionConfig = {onOkClick: this.onGenericApprovalOkClicked.bind(this), ...}; // 2.bind this
}
onCommandApprovalOkClicked(commandText){
console.log(this.props); <- 'undefined'
}
render(){
return <ActionsApprovalPanel currentActionConfig={this.currentActionConfig}/>
}
}
export default class ActionsApprovalPanel extends React.Component {
render()
{
...
return <ChangeIpApproval onOkClick={this.props.currentActionConfig.onOkClick}/>;
...
}
}
I think you need to make few changes to your React component.
First: In the constructor call super().
Second:: Define currentActionConfig as a state and try using it as this.state.currentActionConfig
Third: Specify the binding on onCommandApprovalOkClicked(). as
onCommandApprovalOkClicked = (commandText) => {} and similary for other functions.
class MainComponent extends React.Component {
constructor(props){
super(props);
this.state = {
currentActionConfig = {onOkClick: this.onGenericApprovalOkClicked, ...}
};
}
onCommandApprovalOkClicked(commandText){
console.log(this.props); <- 'undefined'
}
render(){
return <ActionsApprovalPanel currentActionConfig={this.state.currentActionConfig}/>
}
}
export default class ActionsApprovalPanel extends React.Component {
render()
{
...
return <ChangeIpApproval onOkClick={this.props.currentActionConfig.onOkClick}/>;
...
}
}
Make these changes and see if they work.

Categories

Resources