My React app has several similar custom buttons that perform different tasks. The UI is similar among all the buttons, what changes is the action each one must trigger and the title.
The working code for the parent Component containing the buttons is (similar) to the following:
class Page extends Component {
constructor(props) {
super(props);
}
action1(){
//... do stuff...
}
action2(){
//... do stuff...
}
render(){
return(
<div className="table-row">
<div className="table-cell">
<div className="button"
onClick={this.action1.bind(this)}>{"button1"}
</div>
</div>
<div className="table-cell">
<div className="button"
onClick={this.action2.bind(this)}>{"button2"}
</div>
</div>
</div>
);
}
}
Is it possible to pass a method to the child component the same way it is done for a variable value? I want to turn it into something like this:
class Page extends Component {
constructor(props) {
super(props);
}
action1(){
//... do stuff...
}
action2(){
//... do stuff...
}
render(){
return(
<div className="table-row">
<div className="table-cell">
<CustomButton action={this.action1.bind(this)} title={"button1"}/>
</div>
<div className="table-cell">
<CustomButton action={this.action2.bind(this)} title={"button2"}/>
</div>
</div>
);
}
}
class CustomButton extends Component{
constructor(props){
super(props);
}
render() {
return (
<div className="table-cell"><div className="button"
onClick= {this.props.action}>{this.props.title}
</div>
);
}
}
What would be the correct way to handle this situation and the theory behind it?
I'm using React with Meteor, in case it makes a difference.
You can pass props to components. Passed props can have any data type of javascript.
In your case, you want to pass an action props which has a function as the value. Then you access action props in your component and use it.
In short, there is no theory behind it. What you are doing is correct. This is how react handles passing data to other components. Note that this is not the only way to pass data to child components.
Related
I want to be able to abstract the way React component logic works separate the view logic from the handlers
class SuccessLabelWithIcon extends Label{
constructor(props){
super(props);
this.className = this.className + ' success-label';
}
render(){
return <div>
<div onClick={super.onClick}>▲</div> // this works but fires initially too in StackBlitz but not in SO not sure why, but changing to clickHandler dosent work.
</div>
}
}
class Label extends React.Component{
constructor(props){
super(props);
this.className='plain-label';
}
clickHandler = () => {
console.log('Inherited');
}
onClick() {
console.log('Inherited');
}
render(){
return <span className={this.className}>
{this.props.children}
</span>
}
}
// Render it
ReactDOM.render(
<SuccessLabelWithIcon/>,
document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Working StackBlitzLink
Am I wrong with this approach? If No why is this not working for clickHandler, what I am looking at is extending component to have logic and the render functions as separate all the Button clicks and methods will be executed in the parent so that we can have separation of logic and then maybe share code across React and RN, but that is secondary?
My best bet is the clickHandler is attached to some prototypical property of the parent class as it a bound method and the click works as it a normal method not bound to the component but can we bypass this and can we mitigate this problem? And fire method only on click
Your approach is one which is not recommend and maybe even an anti-pattern to react's compositional model. Refer to Composition vs Inheritance section in the docs to see why composition is better over inheritance in react and how to handle some of the problem with composition where developers reach for inheritance. In your case, you should look at Specialization
You should almost always extend React components from React.Component.
class SuccessLabelWithIcon extends Label{
constructor(props){
super(props);
}
render() {
return (
// Specialization
<Label>Success</Label>
)
}
}
class Label extends React.Component{
constructor(props){
super(props);
}
clickHandler = () => {
console.log('Inherited');
}
onClick() {
console.log('Inherited');
}
render(){
return <span className="plain-label" onClick={this.onClick}>
{this.props.children}
</span>
}
}
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Here is a forked working version of your stackblitz.
Working StackBlitzLink
Yes as u have stated your approach is not correct, in React you want to share code usage by components composition instead of inheritance.
I'm gonna use something similar to your example to help you understand the concept, lets say you want to create a base component called IconButton which will be a button with text alongside an icon, and you want to reuse that component to create two more components DangerIconButton (this is a button with an icon that is meant to represent a dangerous situation like a delete operation), and you want to create a SuccessfulIconButton (a button where you want to represent a desired action)
now based on our requirements we see that the functionality across all the three components is the same, basically a click handler but what is different is the visual representation (the icon and the styles applied to the button).
lets create our base component
class IconButton extends React.Component{
render(){
return <button onClick={props.onClick} style={props.style}>
<img src={props.icon} />
<span>button text</span>
</button>
}
}
now our two other components will be much simpler, and will use that base component
class SuccessfulIconButton extends React.Component{
render(){
// notice how i'm creating this component by composition of other components
// happyIcon and green are what makes this a successful butoon
return <IconButton onClick={props.onClick} style={{background: 'green'}} icon={happyIcon} />
}
}
class DangerIconButton extends React.Component{
render(){
// notice that i'm getting the onClick handler here through props, because u want the parent components of this component to have their specific handlers
return <IconButton onClick={props.onClick} style={{background: 'red'}} icon={dangerIcon} />
}
}
of course here u want better naming, and u still want to do some common styling for all your IconButtons like padding, and other stuff. but hopefully the concept came through
As mentioned by #hassaan-tauqir, its usually preferred to use composition rather than inheritance. But if you really want to do it, you can use the below code
class Label extends React.Component{
constructor(props){
super(props);
this.className='plain-label';
}
clickHandler = () => {
console.log('Inherited');
}
onClick() {
console.log('Inherited');
}
render(){
return <span className={this.className}>
{this.props.children}
</span>
}
}
class SuccessLabelWithIcon extends Label{
constructor(props){
super(props);
this.className = this.className + ' success-label';
}
render(){
return (<Label><div>
<div onClick={this.clickHandler}>▲</div> // this works but fires initially too in StackBlitz but not in SO not sure why, but changing to clickHandler dosent work.
</div></Label>);
}
}
// Render it
ReactDOM.render(
<SuccessLabelWithIcon/>,
document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The reason clickHandler doesn't work is because you are using arrow functions to define it (basically it's doesn't have your SuccessLabelWithIcon context, and has instead the context of Label).
You can make it a simple method or you can simply use it with this instead of super (since you are extending the component).
class Label extends React.Component{
constructor(props){
super(props);
this.className='plain-label';
}
clickHandler = () =>{
console.log('Inherited');
}
onClick() {
console.log('Inherited');
}
render(){
return <span className={this.className}>
{this.props.children}
</span>
}
}
class SuccessLabelWithIcon extends Label{
constructor(props){
super(props);
this.className = this.className + ' success-label';
}
render(){
return (<Label><div>
<div onClick={this.clickHandler}>▲</div> // this works but fires initially too in StackBlitz but not in SO not sure why, but changing to clickHandler dosent work.
</div></Label>);
}
}
// Render it
ReactDOM.render(
<SuccessLabelWithIcon/>,
document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Hope this helps you
I've a component which passes javascript objects as props to another component for rendering. After rendering the records as divs, I want to get the clicked object record back for displaying it in a third component.
Each object contains multiple name-value pairs, but I'm only using limited fields for displaying initially. I'm stuck with getting back the entire object with all values. I'm new to React so any help is appreciated. Below is the code:
export class ViewList extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
if (this.props.configs) {
let summaryDisplay = this.props.configs.map((config) =>
<ConfigSummary
key={config.id}
config={config}
onViewConfigClick={this.props.onViewConfigClick}
/>);
} else
return (null);
}
}
class ConfigSummary extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div className="row">
<div className={cSummarySm}> {this.props.config.direction} </div>
<div className={cSummaryMd}> {this.props.config.Object} </div>
<div className={cSummarySm}> {this.props.config.version} </div >
<div className={cSummaryLg}> {this.props.config.jobShortDescription} </div >
<div className={cSummarySm}> <button className="btn btn-link" onClick={this.props.onViewConfigClick}>View</button></div >
</div>);
}
}
On clicking the view button, I'm calling the grandparent component which then calls the parent component to display the selected record in expanded view.
You have to pass some parameters in the onClick function.
You could do it like this:
onClick={()=>this.props.onViewConfigClick(this.props)}
This will pass all params into the grandparent function.
You cald also pass the id:
onClick={()=>this.props.onViewConfigClick(this.props.id)}
My tip: Pass the id as an object so you can later extend the function params easier if needed:
onClick={()=>this.props.onViewConfigClick({
id: this.props.id
})}
I am attempting to pass the changePage class method into a child component called SideBar. When the changePage method is then triggered by an onClick event in the child component I receive the following error:
Uncaught TypeError: this.SetState is not a function
From what I could find in other similar posts I need to bind the changePage method to this. I have done that but I still can't manage to get is to work.
I also saw many suggestions to use ES6 arrow functions for my methods but I get the exact same error message if I do.
I'm still quite new at web development and any help would be appreciated.
Parent Component called Main:
import React from 'react';
import Content from './Content';
import Sidebar from './Sidebar';
export default class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedPage: 'home',
pages: ['home','about','skills','contact'],
};
this.changePage = this.changePage.bind(this);
}
changePage(page) {
console.log(page);
this.SetState({
selectedPage: page,
pages: ['home','about','skills','contact']
});
};
render() {
return (
<div>
<div id="sidebar" className="side-bar">
<Sidebar
changePage={this.changePage}
selectedPage={this.state.selectedPage}
pages={this.state.pages}
/>
</div>
<div id="main" className="main-content">
<Content
selectedPage={this.state.selectedPage}
/>
</div>
</div>
)
}
}
Child Component:
import React from 'react';
export default class Sidebar extends React.Component {
render() {
console.log("content props",this.props);
const buttons = this.props.pages.map(button =>
<span
className='nav-button'
id={button}
key={button}
onClick={() => this.props.changePage(button)}
>
<img src={`./app/images/${button}.svg`} />
</span>
);
return (
<div>
<span>
<img className='headshot' src='./app/images/headshot.jpg' />
</span>
<div className='nav-container'>
{buttons}
</div>
</div>
)
}
}
You have syntax error: this.SetState. Change it to this.setState.
The typo in setState was causing the error, but you may want consider a cleaner way to bind the parent this on the changePage prop. Your way works, but if you changed sidebar Component inclusion to:
<Sidebar
changePage={ (page) => this.changePage(page)}
selectedPage={this.state.selectedPage}
pages={this.state.pages}
/>
That may make it clearer what is going on (changePage prop is a function which takes one parameter and passes that parameter to instance method this.changePage), and removes the binding gymnastics in the constructor.
I'm developing a more complex example of passing props from a component to another. In this case, it's the content of an array(when I click on that content) to a <div>.
You can find the code here: https://codesandbox.io/s/509j5npkwx
(Please check the code in the link above)
TextBox <div> component:
export class TextBox extends React.Component {
constructor(props) {
super(props);
this.state = {
content: "Select A Node To See Its Data Structure Here..."
};
this.changeContent = this.changeContent.bind(this);
}
changeContent(newContent) {
this.setState({
content: newContent
});
}
render() {
return (
<div className="padd_top">
<div className="content_box">{this.state.content}</div>
</div>
);
}
}
export default TextBox;
FileTree component:
export class FileTree extends React.Component {
constructor(props) {
super(props)
this.state = {
activeNode: null
}
this.setActiveNode = this.setActiveNode.bind(this)
}
setActiveNode(name) {
this.setState({ activeNode: name })
}
render() {
return (
<div className="padd_top">{
renderTree(
this.props.root || root,
this.setActiveNode,
this.state.activeNode
)
}
<TextBox />
</div>
)
}
}
I'm trying to do something like this, for further understanding: http://alexcurtis.github.io/react-treebeard/
I understood how to prepare the <div> to receive new information, by substituting the "Select A Node To See Its Data Structure Here..." when I click one of the elements belonging to the file tree.
I also understood how to prepare the file tree to pass content <div>, but in this case, I'm confused about where and how should I apply to the right component.
I'm new to React JS. If you have any tips for me about this issue, I'm very grateful.
Thank you.
I changed a bit the structure of my project, and now I'm looking forward to put <TextBox> and <FileTree> side by side.
More specifically, like this:
export class App extends React.Component {
render() {
return (
<div>
<div className="col-md-12">
<SearchEngine />
</div>
<div className="col-md-6">
<FileTree />
</div>
<div className="col-md-6">
<TextBox content={this.props.activeNode} />
</div>
</div>
);
}
}
I tought it wouldn't be different to pass props to <App>, but again I might be missing something, because it's not passing properly. What is missing here?
Thanks again.
I'm not sure if I understood your question.
Here is my fork: https://codesandbox.io/s/50pv75q8ll
Basically, you pass the activeNode to < TextBox />. Look at line 126 of index.js.
And then, in text_box.js use componentWillReceiveProps() to set the TextBox state with the content prop. Line 18 of text_box.js.
I'm new to React and thus the question.
I have a parent Component - HomePage with a child Component - SideBar.
My child component sidebar needs to pass a data back to the parent on a submit button click which the parent needs to post on an api.
This my parent component,
class HomePage extends React.Component{
constructor(props) {
.......
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(){
//Logic to get the data from the child and post to localhost:8080/
}
render(){
return(
<div className="col-md-2 left-pane">
<Sidebar handleSubmitButton={this.state.handleSubmit}/>
</div>
);
}
}
This is my child component,
class Sidebar extends React.Component {
handleSubmitButton(event) {
event.preventDefault();
}
render() {
return (
<div>
<input type="text"/>
<button type="button" className="btn btn-info btn-icons" onClick={this.props.handleSubmitButton} >
<span className="glyphicon glyphicon-send" aria-hidden="true"/>
</button>
</div>
);
}
}
Sidebar.propTypes = {
handleSubmitButton: React.PropTypes.func
};
My question is how do I grab the input text with the onclick method on the sidebar button click and pass it up to the parent to post it on an api. Any help appreciated.
Your parent Component should not access the input directly, but rather rely on the child component to pass any values to the parent when required.
class HomePage extends React.Component {
constructor(props) {
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(textInputValue){
// The callback passed to the child component will
// submit the data back to it's parent.
// Logic to post to localhost:8080/
}
render(){
return(
<div className="col-md-2 left-pane">
<Sidebar handleSubmitButton={ this.handleSubmit }/>
</div>
);
}
}
class Sidebar extends React.Component {
constructor(props) {
this.handleSubmitButton = this.handleSubmitButton.bind(this);
}
handleSubmitButton(event) {
event.preventDefault();
// By giving the input the `ref` attribute, we can access it anywhere
const textInputValue = this.refs.input.value;
// Submit the value to the parent component
this.props.handleSubmitButton(textInputValue);
}
render() {
return (
<div>
<input type="text" ref="input" />
<button type="button" className="..." onClick={this.handleSubmitButton} >
<span className="glyphicon glyphicon-send" aria-hidden="true"/>
</button>
</div>
);
}
}
This prevents coupling your components together and will ease testing later.
In your child component you can create a property for the ref attribute. Accessing DOM elements directly is usually done by setting refs.
In your parent component you can use an arrow function with a callback which allows you to access the <input> DOM element anywhere within your parent component, by simply typing this.textInput
More information about refs can be found in the official React documentation: https://facebook.github.io/react/docs/refs-and-the-dom.html
Parent component:
handleSubmit() {
console.log(this.textInput.value);
}
<Sidebar
inputRef={(input) => this.textInput = input}
handleSubmitButton={this.state.handleSubmit}
/>
Child component:
<input ref={this.props.inputRef} type="text"/>
There's a couple ways you can go about it. One is you can add an onChange event to the input to update state of the Sidebar component. Then when the submit handler is clicked, pass the value from state, and have the HomePage handleSubmit() accept the value.
The other is to also pass an onChange prop from the HomePage component to the Sidebar. The input would then set the onChange to that prop.