Using props and other attributes with react component definition - reactjs

I have created a react component and I'm reusing it in other components.
<SC_Button label = "English" btnStyle = "sc-btn-default--sinhala mb-2" onClick={this.handleClick}/>
But function defined at onClick does not execute because it's with props passed to the component. I guess react reads onClick as a prop as well. I'm quite new to react.
Below way works. But I don't want to wrap my react component with an extra div due to a styling issue.
<div onClick={this.handleClick} >
<SC_Button label = "English" btnStyle = "sc-btn-default--sinhala mb-2"/>
</div>
Is there any way to use props along with other attributes in react component definitions ?
Edit :
Parent Component
import React from 'react';
import { withRouter } from "react-router-dom";
import SC_Button from '../components/button';
class Home extends React.Component {
handleClick = () => {
this.props.history.push('/page2');
}
render() {
return (
<div className="container-fluid">
<div className="row sc-overlay sc-overlay-main">
<div className="col-md-12 col-xl-5 offset-xl-1">
<span className = "sc-overlay-main__left">
<span className = "sc-main-image">
<img src={require('../assets/dialog_logo.png')} />
</span>
<h1 className="mt-4">Welcome to MyDialog</h1>
</span>
</div>
<div className="col-md-12 col-xl-5">
<div className="row sc-overlay-main__right">
<label>Choose your preferred language</label>
<SC_Button label = "සිංහල" btnStyle = "sc-btn-default--sinhala mb-2" onClick={this.handleClick}/>
<SC_Button label = "தமிழ்" btnStyle = "sc-btn-default--tamil mb-2" />
<SC_Button label = "English" btnStyle = "sc-btn-default--english mb-2" />
</div>
</div>
</div>
</div>
);
}
}
export default withRouter(Home);
SC_Button Component
import React from 'react';
import { withRouter } from "react-router-dom";
class SC_Button extends React.Component{
constructor(props) {
super(props);
}
render() {
return (
<button type="button" className={`sc-btn-default ${ this.props.btnStyle }`}>{this.props.label}</button>
);
}
}
export default withRouter(SC_Button);

Your <SC_Button /> component, or any custom component you make, doesn't automatically implement an event handler. You're essentially just giving it yet another prop, called onClick, that it just throws away. You have to use the callback you're passing it in the DOM elements it returns:
SC_Button.js
import React from 'react';
import { withRouter } from "react-router-dom";
class SC_Button extends React.Component{
constructor(props) {
super(props);
}
render() {
return (
<button
type="button"
className={`sc-btn-default ${ this.props.btnStyle }`}
onClick={this.props.handleClick}
>
{this.props.label}
</button>
);
}
}
export default withRouter(SC_Button);
There is no need to define a handleClick function in the component, since you will be passing it as a prop every time you instantiate one. This allows different instances to have different behaviors.

Related

ReactJS onclick of a button display mongo collection in table format

Hi I'm new to React and building few things in React and this may seem a very generic question.
I want to show a table on click of button. Below is my code.
import React from 'react';
import { Link }
import Button from 'react-bootstrap/lib/Button';
import Panel from 'react-bootstrap/lib/Panel';
import Grid from 'react-bootstrap/lib/Grid';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import ButtonGroup from 'react-bootstrap/lib/ButtonGroup';
import FormGroup from 'react-bootstrap/lib/FormGroup';
this.state = {
showSubmit: false,
};
submitForm = () => {
window.alert('test');
}
toggleSubmitForm = () => {
this.setState({
showSubmit: !this.state.showSubmit
});
window.alert('test2');
}
export default (props) => {
return (
<AppLayout title="Table Con" activeModules={props.activeModules}>
<Protected>
<div className="container-fluid">
<h4>
Welcome to the page
!
</h4>
</div>
<Button
className="btn btn-secondary"
bsSize="small"
onClick={this.toggleSubmitForm}
>
Show Table
</Button>
{this.state.showSubmit && (
<div className="container-fluid well" id="submitT">
<form onSubmit={this.submitForm}>
<Grid>
<Row>
<Col xs={12}>
<div>
<h3>HERE</h3>
</div>s
<br />
<br />
</Col>
</Row>
</Grid>
<Button type="submit" bsStyle="success" bsSize="large">
Submit
</Button>
</form>
</div>
)}
</Protected>
</AppLayout>
);
};
But when onClick is called, nothing is happening.
I'm not sure where I'm failing.
Also, if i want to call a mongo collection and render the table after I click on Show Table button. What are the changes to be made ?
As #noitse pointed out, you are mixing statefull and stateless component features.
However, React added a new alternative if you want to keep your component as a function, Hooks. Here's what you code will look like as a hook :
import { useState } from 'react'
export default props => {
const[showSubmit, setShowSubmit] = useState(false)
return (
<AppLayout title="Table Con" activeModules={props.activeModules}>
<Protected>
<div className="container-fluid">
<h4>Welcome to the page !</h4>
</div>
<Button className="btn btn-secondary" bsSize="small" onClick={setShowSubmit(true)}>
Show Table
</Button>
{showSubmit && /* Your table*/}
</Protected>
</AppLayout>
);
};
You are combining functional and class component features.
Functional components do not have access to the state unless you are using useState feature (16.3 update). Any "this." is basically undefined in your code.
Rewrite your component like this:
import React, {Component} from 'react' // or PureComponent
// ...other imports
class YourComponent extends Component {
state = {
showSubmit: false
}
submitForm = () => { /* what ever */}
toggleSubmitForm = () => {
this.setState({showSubmit: !this.state.showSubmit})
}
render(){
return(
... your render code
)
}
}
export default YourComponent

Passing button click from child to parent in React

While trying to toggle between two child components, I need to have the trigger button in the child component, and pass the click function through the child component to the in order to toggle the other child component. I'm not sure how to push the props from the child to the parent in order to trigger the toggle.
Parent component
import React from 'react'
import CancelOffer from '../CancelPages/CancelOffer'
import CancelWarning from '../CancelPages/CancelWarning'
class Cancel extends React.Component {
constructor() {
super()
this.state = {
isHidden: true
}
}
toggleOffer() {
this.setState({
isHidden: !this.state.isHidden
})
}
render() {
return (
<div className = 'cancel'
style = {{backgroundImage: `url(${this.props.backgroundImage})`}} >
<div className = 'container' >
{!this.state.isHidden &&
<CancelOffer { ...this.props}/>
}
{this.state.isHidden &&
<CancelWarning { ...this.props}/>
}
{this.state.isHidden &&
<button onClick = {this.toggleOffer.bind(this)} > Click < /button>
}
</div>
</div>
)
}
}
export default Cancel
Child component
import React from 'react'
import SvgIcon from '../SvgIcon/SvgIcon'
import './CancelWarning.scss'
function CancelOffer (props) {
const content = props.config.contentStrings
return (
<div className='cancel-warning'>
<h2 className='heading md'>heading</h2>
<p className='subpara'>subheading</p>
<div className='losses'>
<ul>
<li>text</li>
<li>text</li>
<li>text</li>
</ul>
</div>
<div className='footer-links'>
<a href='/member' className='btn btn--primary btn--lg'>continue</a>
<a href='/cancel' className='cancel-link'>Cancel</a>
//NEED TO HAVE BUTTON HERE AND PASS PROPS TO PARENT TO TOGGLE VIEW
{this.state.isHidden &&
<button onClick = {this.toggleOffer.bind(this)}>Click</button>
}
</div>
</div>
)
}
export default CancelOffer
You can just pass it like regular param. Also, you can use arrow functions instead of binding.
Parent component
import React from 'react'
import CancelOffer from '../CancelPages/CancelOffer'
import CancelWarning from '../CancelPages/CancelWarning'
class Cancel extends React.Component {
constructor() {
super()
this.state = {
isHidden: true
}
this.toggleOffer = this.toggleOffer.bind(this);
}
toggleOffer() {
this.setState({
isHidden: !this.state.isHidden
})
}
render() {
const { isHidden } = this.state
return (
<div className = 'cancel'
style = {{backgroundImage: `url(${this.props.backgroundImage})`}} >
<div className = 'container' >
{!isHidden &&
<CancelOffer toggleOffer={this.toggleOffer} isHidden={isHidden}/>
}
{isHidden &&
<CancelWarning toggleOffer={this.toggleOffer} isHidden={isHidden}/>
}
{isHidden &&
<button onClick = {this.toggleOffer}> Click </button>
}
</div>
</div>
)
}
}
export default Cancel
Child component
import React from 'react'
import SvgIcon from '../SvgIcon/SvgIcon'
import './CancelWarning.scss'
function CancelOffer (props) {
const content = props.config.contentStrings
return (
<div className='cancel-warning'>
<h2 className='heading md'>heading</h2>
<p className='subpara'>subheading</p>
<div className='losses'>
<ul>
<li>text</li>
<li>text</li>
<li>text</li>
</ul>
</div>
<div className='footer-links'>
<a href='/member' className='btn btn--primary btn--lg'>continue</a>
<a href='/cancel' className='cancel-link'>Cancel</a>
{props.isHidden &&
<button onClick = {props.toggleOffer}>Click</button>
}
</div>
</div>
)
}
export default CancelOffer

Setting background image as prop in react

I have a react component that needs to take in a background image from the parent Play component, not set in the stylesheet. I'm trying to pass it as a prop to the component, but not sure quite sure how to get this working:
Main component
import React from 'react'
import ReactDOM from 'react-dom'
import DeviceBlock from '/DeviceBlock/DeviceBlock'
const Play = {
init() {
const container = document.getElementById('blocker-container');
if(container) {
ReactDOM.render(
<DeviceBlock
backgroundImage={'background-image-url.png'}
logo={'url-to-logo.png'} />,
container
)
}
}
}
document.addEventListener('DOMContentLoaded', () => {
Play.init()
})
Blocking component imported into above Play component
import React from 'react'
import './DeviceBlock.scss'
function DeviceBlock (props) {
constructor(props) {
super(props)
this.state = {
backgroundImage: backgroundImage,
logo: logo;
}
}
return (
<div className='device-blocking' style={this.state.backgroundImage}>
<div className='container'>
<div className='logo'>
<img src='{{this.state.logo}}' />
</div>
<div className='message'>
<h1>Content</h1>
<a href='/' className='btn btn--primary btn--lg'>Link</a>
</div>
</div>
</div>
)
}
export default DeviceBlock
You should import your background img
import BackgroundImage from '...png'; // Relative Path to BackgroundImage
import Logo from '...png'; // Relative Path to Logo
and in your state
this.state = {
backgroundImage: `url(${BackgroundImage})`,
logo: Logo
};
Also I noticed that your <img> is wrong
<img src='{{this.state.logo}}' />
Should be
<img src={this.state.logo} />
Also note I saw that you added a constructor inside a function.
If you want to create a react component with state you need to make the component a class.
class DeviceBlock extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
...
}
}
EDIT: This is the updated code for your edit based on passing through props.
import React from 'react'
import ReactDOM from 'react-dom'
import DeviceBlock from '/DeviceBlock/DeviceBlock'
import BackgroundImage from '../url-to-background.png'; // Path to BackgroundImage
import Logo from 'url-to-logo.png'; // Relative Path to Logo
const Play = {
init() {
const container = document.getElementById('blocker-container');
if (container) {
ReactDOM.render(
<DeviceBlock
backgroundImage={BackgroundImage}
logo={Logo}
/>,
container
)
}
}
}
document.addEventListener('DOMContentLoaded', () => {
Play.init()
})
import * as React from 'react'
import './DeviceBlock.scss'
function DeviceBlock (props) {
return (
<div
className='device-blocking'
style={{ backgroundImage: `url(${props.backgroundImage})` }}
>
<div className='container'>
<div className='logo'>
<img src={props.logo} />
</div>
<div className='message'>
<h1>Content</h1>
<a href='/' className='btn btn--primary btn--lg'>Link</a>
</div>
</div>
</div>
);
}
export default DeviceBlock
Note my solution only works if your images are from your own server and processed from webpack and not from another URL.
If the images are from another URL you shouldn't import the URLs and add them directly to the props
<DeviceBlock
backgroundImage="url-to-background.png",
logo="url-to-logo.png"
/>
Style is an object. Try it like this:
style={{backgroundImage: this.state.backgroundImage}}
Further, you cannot have a constructor and use state within a functional component. Either turn your component into a regular react component and keep the state, or have the state part in a regular react component and pass it down as props into the functional component. This is also mentioned in the answer by #Kenneth Truong
To turn it into a regular react component you can do the following:
class DeviceBlock extends React.Component {
constructor(props) {
super(props)
this.state = {
backgroundImage: "url('url-to-background.png')",
logo: "url('url-to-logo.png')";
}
}
render() {
return (
<div className='device-blocking' style={{backgroundImage: this.state.backgroundImage}}>
<div className='container'>
<div className='logo'>
<img src='{{this.state.logo}}' />
</div>
<div className='message'>
<h1>Content</h1>
<a href='/' className='btn btn--primary btn--lg'>Link</a>
</div>
</div>
</div>
)
}
}
You can see it working here:
https://codepen.io/anon/pen/zWexzO
UPDATE
Based on the updated question, you can also keep it as a functional component and pass down props:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
backgroundImage: "url('url-to-background.png')",
logo: "url('url-to-logo.png')";
}
}
render() {
return (
<DeviceBlock
backgroundImage={this.state.backgroundImage}
logo={this.state.logo}/>
)
}
}
const DeviceBlock = (props) => {
return (
<div className='device-blocking' style={{backgroundImage: props.backgroundImage}}>
<div className='container'>
<div className='logo'>
<img src='{{props.logo}}' />
</div>
<div className='message'>
<h1>Content</h1>
<a href='/' className='btn btn--primary btn--lg'>Link</a>
</div>
</div>
</div>
)
}
https://codepen.io/anon/pen/OvdPOQ
The app component is simply demonstrating that you can pass down some dynamic state. You can, of course, render the DeviceBlock component directly from your Play.init function like you do in your provided code (then you can just ignore the App component part).
I was able to get the images to show in the child component by passing the images into <DeviceBlock /> in the parent like so:
<DeviceBlock
backgroundImage="background-image-url.png"
logo="logo-image-url.png" />,
container
and then taking them as props in the child
<div className='device-blocking' style={{ backgroundImage: `url(${props.backgroundImage})` }}>
<div className='container'>
<div className='logo'>
<img src={props.logo} />
</div>
<div className='message'>
<h1>Content</h1>
<a href='/' className='btn btn--primary btn--lg'>Link text</a>
</div>
</div>
</div>

Communicating between independent components react

I have been facing a problem, I would like to write a div every time a specific button is clicked, but the button is in one component and the menu in which I want the div to be created is in another component as well , and both main components are in one component, is there a way of making this happen? So the structure of it all is something like this:
This is the root component with the smaller components in it:
export class Root extends React.Component {
render() {
return(
<div className="Body">
<div id="SideBar" ><SideMenu/></div>
<MenuCocktails/>
<MenuBeers/>
<MenuVines/>
<MenuLemonades/>
<MenuSoftDrinks/>
<MenuCollection/>
<MenuCharger/>
<div id="BottomMenu"><BottomMenu/></div>
</div>
)
}
}
this is the menu that I wanted to div to be created in:
export class BottomMenu extends React.Component{
constructor() {
super();
this.state = {
shown: ''
};
}
toggleMenu = () => {
this.setState({shown: this.state.shown ? '' : 'visible'});
}
render() {
return(
<div>
<button
id="button"
className={this.state.shown}
onClick={this.toggleMenu}
>
My Cart
</button>
<div id="Header" className={this.state.shown}>
<button id="CheckOutButton">Check Out</button>
</div>
</div>
);
}
}
and this is one of the components with the button:
import React from "react";
import styles from "./MenuCocktails.css";
import plus from "./plus.png";
export class NiğdeGazozu extends React.Component{
render() {
return (
<div id="menu-items-1">
<div id="item-name">NIĞDE GAZOZU</div>
<div id ="price-item">8 TL</div>
<button id="plusbutton"><img src={plus}/></button>
</div>
);
}
}

Passing state in React from two different components

I have a TopNab bar (component), which contains a SearchBar component. My Main component is rendering out TopNav, the main container component (Profile), and the Footer component. I want my SearchBar component to pass its state to the main container component so that the Profile component can use it.
What I am trying to build:
A user types a name into search bar and submits.
The profile matching the user name is displayed in the main container component.
Right now I have a component that can render out user profiles. I also have a component thats state updates to the user submitted value. What I need to do is pass this user submitted value to my profile component in order to render out the correct profile.
Is this possible or do I need to rebuild my components so the search is included in the Profile component?
SearchBar
import React, { Component, PropTypes } from 'react';
import Profile from './Profile';
class SearchBar extends Component {
constructor(props){
super(props)
this.state = {
name: ''
}
}
handleChange(e) {
this.setState({
name: e.target.value
});
}
handleSubmit(e) {
e.preventDefault();
console.log("searching for NAME " + this.state.name);
let profileName = this.state.name;
//PASS STATE TO PROFILE COMPONENT
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit.bind(this)}>
ARMORY BETA
<input type="text" placeholder="Enter Name"
name="name"
value={this.state.name}
onChange={this.handleChange.bind(this)} />
<button className="btn btn-success" type="submit">Search</button>
</form>
</div>
)
}
}
export default SearchBar;
Profile
import React, { Component, PropTypes } from 'react';
import SearchBar from './SearchBar';
import ProfileContainer from '../containers/ProfileContainer';
class Profile extends Component {
render() {
return (
<div className="cols2">
<div>[IMG]</div>
<div>
<ProfileContainer name={this.props.name}/>
</div>
</div>
)
}
}
Profile.PropTypes = {
name: PropTypes.string
}
Profile.defaultProps = {
name: ''
}
export default Profile;
Main
import React, { Component } from 'react';
import TopNav from './TopNav';
import Footer from './Footer';
import Profile from './Profile';
class Main extends Component {
render() {
return (
<div className="container-fluid">
<div className="row">
//TopNav calls SearchBar
<TopNav />
</div>
<div className="row">
<Profile />
</div>
<div className="row">
<Footer />
</div>
</div>
)
}
}
export default Main;
In Main, you should add a prop to <TopNav /> that points to a handler method that will propagate the profileName state change back to Main. This, in turn, will cause Profile to be re-rendered. The handler method takes one argument profileName and is called from the handleSubmit method in TopNav. Here's the code:
SearchBar
class SearchBar extends Component {
. . .
handleSubmit(e) {
e.preventDefault();
console.log("searching for NAME " + this.state.name);
let profileName = this.state.name;
this.props.handleProfileChange(profileName);
}
. . .
}
SearchBar.propTypes = {
handleProfileChange: React.PropTypes.func.isRequired,
}
Main
class Main extends Component {
constructor(props) {
super(props);
this.state = { profileName: '' }
handleProfileChange = this.handleProfileChange.bind(this);
}
handleProfileChange(profileName) {
// This state change will force Profile component to be re-rendered
this.setState( { profileName });
}
render() {
return (
<div className="container-fluid">
<div className="row">
//TopNav calls SearchBar
<TopNav handleProfileChange={this.handleProfileChange} />
</div>
<div className="row">
<Profile profileName={this.state.profileName} />
</div>
<div className="row">
<Footer />
</div>
</div>
)
}
You'll need to expose a property on SearchBar that accepts a callback that will be called to indicate to its parent that the form was submitted (e.g. onSubmit)...
handleSubmit(e) {
e.preventDefault();
console.log("searching for NAME " + this.state.name);
let profileName = this.state.name;
//PASS STATE TO PROFILE COMPONENT
this.props.onSubmit(yourFormData);
}
...TopNav won't handle onSubmit itself, but just pass it on up to its own parent (perhaps renaming to "onSearchBarSubmit" along the way to make the name clearer from the perspective of TopNav's parent):
class TopNav extends Component {
render() {
return (
<div className="container-fluid">
<SearchBar onSubmit={this.props.onSearchBarSubmit}
</div>
);
}
}
class Main extends Component {
render() {
return (
<div className="container-fluid">
<div className="row">
<TopNav onSearchBarSubmit={ (criteria) => this.searchForStuff(criteria) } />
</div>
<div className="row">
<Profile data={this.state.stuffYouGotBackFromSearch} />
</div>
<div className="row">
<Footer />
</div>
</div>
)
}
}
...OR, in some cases, it can be desirable to un-nest the components, allowing SearchBar as one of TopNav's props.children. This allows you to handle onSubmit directly within Main, and pass anything it receives onto Profile:
class Main extends Component {
render() {
return (
<div className="container-fluid">
<div className="row">
//TopNav calls SearchBar
<TopNav>
<SearchBar onSubmit={ (criteria) => this.searchForStuff(criteria) } />
</TopNav>
</div>
<div className="row">
<Profile data={this.state.stuffYouGotBackFromSearch} />
</div>
<div className="row">
<Footer />
</div>
</div>
)
}
}
...a side-benefit of un-nesting is that it would allow you to use TopNav and Searchbar independently.

Resources