Exposing a method on a React Component - javascript

This is my code
import React, { Component } from "react";
import { render } from "react-dom";
class CView extends Component {
someFunc() {
alert(1);
}
render() {
return <div>Hello, there</div>;
}
}
class App extends Component {
getControl() {
this.cv = <CView />;
return this.cv;
}
render() {
return (
<div>
<h2 onClick={() => this.cv.someFunc()}>Click Me</h2>
{this.getControl()}
</div>
);
}
}
render(<App />, document.getElementById("root"));
Also available on https://codesandbox.io/s/k2174z4jno
When I click on the h2 tag, I get an error saying someFunc is not defined. How can I expose a function of a component so that other components can access it?
Thanks

I think that this.cv = <CView />; will not directly return instance of CView component.
onClick={() => {
console.log(this.cv instanceof CView); // false
this.cv.someFunc();
}}
But if you try to use refs you will access it.
class App extends Component {
constructor(props) {
super(props)
this.cv = React.createRef();
}
onClick() {
this.cv.current.someFunc();
}
render() {
return (
<div>
<h2 onClick={() => this.onClick()}>Click Me</h2>
<CView ref={this.cv} />
</div>
);
}
}
It is more "React way" though. https://codesandbox.io/s/vy61q9o8xy

Related

How to change component inside of render with a button click?

I'm new at programming at all especially in ReactJS. Actually I'm running into a problem.
I just wanna change the page. As simple as it sounds for me it is a 2 day Google-Search-Nothing-Found-Adventure without fun.
I made a component called <Frontpage />
It renders directly in the beginning but I want to change it with a click on a button.
This is my code:
import React from 'react';
import './App.css';
import Frontpage from './Frontpage'
import Question from './components/Question'
class App extends React.Component {
constructor() {
super()
this.state = {
showComponent: true,
}
}
handleClick() {
if (this.state.showComponent) {
return <Question />
} else {
console.log("Something went wrong!")
}
// console.log("The button was clicked!")
// document.getElementById('page')
// document.getElementsByClassName('App-Header')
}
render() {
return (
<div className="App">
<header className="App-header">
<div id="page">
<Frontpage />
</div>
<button id="button" onClick={this.handleClick}>Los geht's</button>
</header>
</div>
);
}
}
export default App;
When I click it, it always says: "TypeError: Cannot read property 'state' of undefined"
I tried a lot but nothing worked.
Try something like this. Change you handleClick function to an arrow function and render the component based on the state.
Update: notice that i have used a Fragment to wrap the button and Question elements/components
import React,{Fragment} from 'react';
import './App.css';
import Frontpage from './Frontpage'
import Question from './components/Question'
class App extends React.Component {
constructor() {
super()
this.state = {
showComponent: false,
}
}
handleClick =()=> {
this.setState({showComponent:true})
}
render() {
return (
<div className="App">
<header className="App-header">
<div id="page">
{this.state.showComponent? <Fragment><Question /><button id="button" onClick={this.handleClick}>Los geht's</button></Fragment> : <Frontpage /> }
</div>
</header>
</div>
);
}
}
export default App;
You have to bind the handleClick to your class component or in other case, use an arrow function:
To bind the method at the bottom of your state inside the constructor add this :
this.handleClick = this.handleClick.bind(this);
To use the arrow function change the syntax of your method as:
this.handleClick = () => {
if (this.state.showComponent) {
return <Question />
} else {
console.log("Something went wrong!")
}
}
import React from 'react';
import './App.css';
import Frontpage from './Frontpage'
import Question from './components/Question'
class App extends React.Component {
constructor() {
super()
// Start with a hide component
this.state = {
showComponent: false,
}
// Binding your function
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
const { showComponent } = this.state;
// Use state to trigger a re-render
this.setState({
showComponent: !showComponent;
})
}
render() {
const { showComponent } = this.state;
return (
<div className="App">
<header className="App-header">
<div id="page">
{
// Conditionally render a component
showComponent
? (<Question />)
: (<Frontpage />)
}
</div>
<button id="button" onClick={this.handleClick}>Los get's</button>
</header>
</div>
);
}
}
export default App;
Try this one.
import Question from './components/Question'
class App extends React.Component {
constructor() {
super()
this.state = {
showComponent: true,
}
}
handleClick() {
this.setState({ showComponent: true });
}
render() {
const { showComponent } = this.state;
return (
<div className="App">
<header className="App-header">
<div id="page">
<Frontpage />
</div>
{ showComponent ? <Question /> : null }
<button id="button" onClick={this.handleClick.bind(this)}>Los geht's</button>
</header>
</div>
);
}
}
export default App;

Not rendering component onClick

The component I am trying to render:
import React, { Component } from 'react';
export default class QueryPrint extends Component {
render() {
console.log('working');
return (
<div>Hello</div>
)
}
}
The component that is trying to call it:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
Button,
} from 'reactstrap';
import QueryPrint from './bq_print';
class QueryResults extends Component {
constructor(props) {
super(props);
this.print = this.print.bind(this);
}
print() {
console.log('Clicked');
return (
<QueryPrint />
);
}
render() {
return (
<Button
className='cuts-btn'
color='success'
onClick={this.print}
>
Print
</Button>
)
}
}
function mapStateToProps(state) {
return {
query_data: state.results.query_data
}
}
export default connect (mapStateToProps, null)(QueryResults);
The console.log('clicked') is working, but the component that is supposed to render in that method doesn't--no console.log('working') or <div>.
Returning something from a click callback has no effect. If you want to render something, you do so in the render method. The click callback's job is to call this.setState(), which will then kick off a render.
Perhaps something like this:
class QueryResults extends Component {
constructor(props) {
super(props);
this.print = this.print.bind(this);
this.state = {
queryPrint: false,
}
}
print() {
console.log('Clicked');
this.setState({ queryPrint: true })
}
render() {
const { queryPrint } = this.state;
return (
<React.Fragment>
{queryPrint && <QueryPrint />}
<Button
className='cuts-btn'
color='success'
onClick={this.print}
>
Print
</Button>
</React.Fragment>
)
}
}
React Native works differently. It is more like a web app - you need to navigate to the other component.
Look at this example its very to the point: https://facebook.github.io/react-native/docs/navigation
Alternatively if you want to make only part of the screen change you will need to include it into your own render and control it thru a flag or a state machine.
https://facebook.github.io/react-native/docs/direct-manipulation

Function passed through props shown non-defined

I have got three components Topicscreen, Listview, Listrow. I am passing the function renderrow, and two other properties defined in my
Topicscreen to Listview.
Now when i call func in Listview, the props are passed to Listrow as defined in renderrow function, but the onRowclick function which is being passed to Listrow is undefined when i checked it in Listrow.
How to solve this error and pass onRowclick as a function to Listrow?
Topicscreen.js
class Topicscreen extends Component {
constructor() {
super();
this.onRowClick = this.onRowClick.bind(this);
}
componentDidMount() {
this.props.dispatch(topicaction.Fetchtopics());
}
renderLoading() {
return <p>Loading...</p>;
}
renderrow(rowid, topic) {
//const selected = this.props.checkselection[rowid]
const selected = "";
return (
<Listrow selected={selected} clicking={this.onRowClick} rowid={rowid}>
<h3>{topic.title}</h3>
<p>{topic.description}</p>
</Listrow>
);
}
onRowClick(rowid) {
this.props.dispatch(topicaction.selectedchoice(rowid));
}
render() {
if (!this.props.topicsByurl) return this.renderLoading();
return (
<div className="TopicsScreen">
Hi I am topic screen
<h1>Choose any three of the below topics</h1>
<Listview
rowid={this.props.topicsurlArray}
row={this.props.topicsByurl}
func={this.renderrow}
/>
</div>
);
}
}
Listview.js
import React, { Component } from "react";
import _ from "lodash";
export default class Listview extends Component {
constructor() {
super();
this.show = this.show.bind(this);
}
show(rowid) {
return this.props.func(rowid, _.get(this.props.row, rowid));
}
render() {
console.log("props in listview", this.props);
return (
<div>
<ul>{_.map(this.props.rowid, this.show)}</ul>
</div>
);
}
}
Listrow.js
import React, { Component } from "react";
export default class Listrow extends Component {
clicking() {
this.props.clicking(this.props.rowid);
}
render() {
console.log("list row called");
console.log("listrow props", this.props);
const background = this.props.selected ? "#c0f0ff" : "#fff";
return (
<div style={{ background }} onClick={this.clicking.bind(this)}>
{this.props.children}
</div>
);
}
}
You also need to bind your renderrow method in the Topicscreen constructor, or this.onRowClick inside of renderrow will not be what you expect.
class Topicscreen extends Component {
constructor(props) {
super(props);
this.onRowClick = this.onRowClick.bind(this);
this.renderrow = this.renderrow.bind(this);
}
// ...
}

Cannot assign a ref to a component in a render prop

Suppose you have a simple react application (see my codesandbox):
import React from 'react';
import { render } from 'react-dom';
class RenderPropComponent extends React.Component {
render() {
console.log(this.example.test());
return this.props.render();
}
}
class Example extends React.Component {
test = () => console.log('Test successful!');
render() {
return <h1>I am an example!</h1>;
}
}
const App = () => (
<RenderPropComponent
render={() => {
return (
<Example ref={node => this.example = node} />
)
}}
/>
);
render(<App />, document.getElementById('root'));
This causes the error:
TypeError
Cannot read property 'test' of undefined
How can I assign a ref to a component rendered via render prop?
I know I can accomplish this with this.props.children as follows:
class RenderPropComponent extends React.Component {
const childComponents = React.Children.map(this.props.children, child => {
return React.cloneElement(child, {ref: node => this.example = node})
});
console.log(this.example.test());
render() {
return <div>{childComponents}</div>;
}
}
...
<RenderPropComponent>
<Example />
</RenderPropComponent>
But I would like to be able to use a render prop to do this! Any suggestions?
Not fully sure if it fits your case, but maybe you can pass the setRef function as an argument to the render prop? Like in this forked sandbox.
import React from 'react';
import { render } from 'react-dom';
const styles = {
fontFamily: 'sans-serif',
textAlign: 'center',
};
class Hello extends React.Component {
constructor(props) {
super(props);
this.setRef = this.setRef.bind(this);
}
setRef(node) {
this.example = node;
}
render() {
console.log(this.example && this.example.test());
return this.props.render(this.setRef);
}
}
class Example extends React.Component {
test = () => console.log('Test successful!');
render() {
return <h1>I am an example!</h1>;
}
}
const App = () => (
<div style={styles}>
<Hello
name="CodeSandbox"
render={(setRef) => {
return (
<Example ref={setRef} />
)
}}
/>
<h2>Start editing to see some magic happen {'\u2728'}</h2>
</div>
);
render(<App />, document.getElementById('root'));
The only problem i see here, is that on initial render this.example will not be available (that's why I've added a guard to the console log) and after it will be set, the rerender will not be triggered (since it's set on the instance and not in the state). If a rerender is needed, we can store the ref in the component state or force the rerender.
On the other hand, if you need a ref to be used in some event handler later on, that should do the trick without rerendering.
Look carefully, this keyword is used in the global scope, not in the scope of the example component.
const App = () => (
<RenderPropComponent
render={() => {
return (
<Example ref={node => this.example = node} />
)
}}
/>
);
If you didn’t spot it yet, take a look at that snippet:
class Foo {
constructor(stuffToDo) {
this.bar = ‘bar’;
this.stuffToDo = stuffToDo
}
doStuff() {
this.stuffToDo();
}
}
new Foo(() => console.log(this.bar)).doStuff();
This will log undefined, not bar, because this is derived from the current closure.

React.js - How to implement a function in a child component to unmount another child from the same parent, and mount another component on it's place?

For example, a component like this:
import React, { Component } from 'react';
import BodyContent from './BodyContent';
import BottomOne from './BottomOne';
import BottomTwo from './BottomTwo';
class App extends Component {
render() {
return (
<div className="App">
<BodyContent />
<BottomOne />
</div>
);
}
}
export default App;
I want to implement a function on BodyContent that unmount BottomOne and mounts BottomTwo instead, so when I activate the function, the code is reestructured to this:
import React, { Component } from 'react';
import BodyContent from './BodyContent';
import BottomOne from './BottomOne';
import BottomTwo from './BottomTwo';
class App extends Component {
render() {
return (
<div className="App">
<BodyContent />
<BottomTwo />
</div>
);
}
}
export default App;
I'm very new to React, so if there's a better way to do it, I'm open to suggestions, but I really need that end result, a function on BodyContent that unmounts BottomOne and mounts BottomTwo.
You can maintain a state which tells which component to render. Something roughly like this
import React, { Component } from 'react';
import BodyContent from './BodyContent';
import BottomOne from './BottomOne';
import BottomTwo from './BottomTwo';
class App extends Component {
changeBottomComponent = (comp) => {
this.setState({ showBottom: comp})
}
render() {
return (
<div className="App">
<BodyContent changeBottomComponent={this.changeBottomComponent}/>
{this.state.showBottom === 1 ? <BottomOne /> : <BotttomTwo />}
</div>
);
}
}
export default App;
To achieve that maintain a state variable in parent component (some kind of identifier for component) and use that state variable to render different component.
Along with that you also need to pass a function from parent to child and use that function to update the parent state value.
Like this:
class App extends Component {
constructor(){
super();
this.state={
renderOne: true,
}
this.update = this.update.bind(this);
}
update(){
this.setState({renderOne: false})
}
render() {
return (
<div className="App">
<BodyContent update={this.update}/>
{this.state.renderOne? <BottomOne /> : <BottomTwo/> }
</div>
);
}
}
Now inside BodyContent component call this.props.update() to render another component.
You can use state or props to render different components.
Example:
import React, {
Component
}
from 'react';
import BodyContent from './BodyContent';
import BottomOne from './BottomOne';
import BottomTwo from './BottomTwo';
class App extends Component {
constructor(props) {
super(props);
this.state = {
decider: false
};
}
render() {
const bottomContent = this.state.decider === true ? <BottomOne /> : <BottomTwo />;
return (
<div className="App">
<BodyContent />
{ bottomContent }
</div>
);
}
}
export
default App;
You can also directly use the components in the state and render them. Could be more flexible this way.
const BottomOne = () => <div>BottomOne</div>;
const BottomTwo = () => <div>BottomTwo</div>;
class Example extends React.Component {
constructor() {
super();
this.state = { show: BottomOne };
this.toggleComponent = this.toggleComponent.bind(this);
}
toggleComponent() {
// Use whatever logic here to decide.
let show = BottomOne;
if (this.state.show === BottomOne) {
show = BottomTwo;
}
this.setState({ show });
}
render() {
return (
<div>
<button onClick={this.toggleComponent}>Change</button>
<this.state.show />
</div>
);
}
}
ReactDOM.render(<Example />, document.getElementById('root'));
<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="root"></div>

Categories

Resources