Let's say I have a series of static divs that create rows with a handful of children. One of those children has a click handler, which should fire an event specific to its parent div. The event needs to target the parent div because we're changing some styling only in the parent.
Am I correct in how I've targeted each child's parentNode, in my code below? (Basically, is this best practice?) Just curious.
Thanks!
class ClickExample extends Component {
handleClick = (e) => {
const parentDiv = e.target.parentNode;
parentDiv.classList.toggle('someClass');
}
render() {
return (
<div>
<div>
<h1 onClick={(e) => { this.handleClick(e) }}>Click Me!</h1>
</div>
<div>
<h1 onClick={(e) => { this.handleClick(e) }}>No, Click Me!</h1>
</div>
</div>
);
}
}
export default ClickExample
This approach is against React philosophy. You should define a component for this purpose. You should read a little bit more of React philosophy but the correct approach would be something like this:
class ClickableComponent extends React.Component {
render() {
return (
<div onClick={this.props.onClick}>
{ this.props.children }
</div>
);
}
}
class ClickExample extends React.Component {
handleClick() {
this.setState({
active: null
});
}
render() {
return (
<div>
<div className={this.state.active === 1 ? 'someClass' : null}>
<ClickableComponent onClick={() => this.setState({ active: 1 })}>
ClickMe
</ClickableComponent>
</div>
<div className={this.state.active === 2 ? 'someOtherClass' : null}>
<ClickableComponent onClick={() => this.setState({ active: 2 })}>
No, ClickMe!
</ClickableComponent>
</div>
</div>
);
}
}
export default ClickExample
Related
I'm building my first React project. I have a list that drops down on-hover on my main homepage, inside the drop-down, I'm trying to show specific list details with my child Component. I got the click-to-show function to work, but can't seem to make it work with showing the right corresponding detail (black&white parent should show black&white text description). My current code below:
class Style extends React.Component {
constructor(props) {
super(props);
this.state = { showDetail: false };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState((state) => ({
showDetail: !state.showDetail,
}));
}
render() {
return (
<section className="style">
<div className="style-list">
<p onClick={this.handleClick}>
Black & White</p>
<p onClick={this.handleClick}>
Modern</p>
<p onClick={this.handleClick}>
Old School</p>
</div>
<div className="style-description">
{this.state.showDetail ? <StyleDetail /> : null}
</div>
</section>
class StyleDetail extends React.Component {
render() {
return (
<div>
<p id="blackwhite">black&white description text</p>
<p id="modern">modern description text</p>
<p id="oldschool">oldschool description text</p>
</div>
);
}
}
Do I need key values to make this work? Or do I need to declare my corresponding elements as variables? Can't seem to wrap my head around it. Thanks in advance for any help!
From a newbie developer xx
Create a new state to store current style and pass it to the StyleDetail component.
import React from "react";
class Style extends React.Component {
constructor(props) {
super(props);
this.state = {
showDetail: false,
currentStyle: ""
};
this.changeStyle= this.changeStyle.bind(this);
}
changeStyle(clickedStyle) {
let newState = {
currentStyle: clickedStyle
};
if (clickedStyle === this.state.currentStyle) {
newState.showDetail = !this.state.showDetail;
}
this.setState(newState);
}
render() {
return (
<section className="style">
<div className="style-list">
<p onClick={e => this.changeStyle("B&W")}>Black & White</p>
<p onClick={e => this.changeStyle("modern")}>Modern</p>
<p onClick={e => this.changeStyle("old-school")}>Old School</p>
</div>
<div className="style-description">
{this.state.showDetail && (
<StyleDetail currentStyle={this.state.currentStyle} />
)}
</div>
</section>
);
}
}
export default Style;
import React from "react";
class StyleDetail extends React.Component {
render() {
return (
<div>
{this.props.currentStyle === "B&W" && (
<p id="blackwhite">black&white description text</p>
)}
{this.props.currentStyle === "modern" && (
<p id="modern">modern description text</p>
)}
{this.props.currentStyle === "old-school" && (
<p id="oldschool">oldschool description text</p>
)}
</div>
);
}
}
export default StyleDetail;
Hope it helps :)
I am new to both coding as well as React.js, so any assistance in learning what I am doing incorrectly is greatly appreciated! I am creating multiple cards on a page with riddles where the answer is hidden via css. I am using an onClick function ("toggleAnswer") to toggle the state of each answer to change the className so that the answer will either be visible or hidden. Currently, the onClick event is changing the state for all the answers. I realize this is because my code is not targeting a particular element, but I am unsure how this can be done. How can this be achieved? My code is currently like this:
// RiddlesPage where toggleAnswer function is defined
class RiddlesPage extends Component {
constructor(props) {
super(props);
this.state = {
questionData: [],
isHidden: true
};
this.getPageData = this.getPageData.bind(this);
this.toggleAnswer = this.toggleAnswer.bind(this);
}
getPageData() {
console.log("we hit getPageData function starting --");
helpers.getRiddlesPage().then(data => {
console.log("this is the result", data);
this.setState({
questionData: data[0].questionData,
});
});
}
toggleAnswer(e) {
this.setState({ isHidden: !this.state.isHidden });
}
componentWillMount() {
this.getPageData();
}
render() {
const answerClass = this.state.isHidden ? "answer-hide" : "answer";
return (
<div>
<Riddles>
{this.state.questionData.map((data, index) => {
return (
<RiddlesItem
key={index}
id={index}
question={data.question}
answer={data.answer}
button={data.buttonURL}
answerClass={answerClass}
onClick={this.toggleAnswer}
/>
);
})}
</Riddles>
</div>
);
}
}
export default RiddlesPage;
// Riddles Component
import React from "react";
import "./riddles.css";
const Riddles = props => (
<div id="riddles-row">
<div className="container">
<div className="row">
<div className="col-12">
<div>{props.children}</div>
</div>
</div>
</div>
</div>
);
export default Riddles;
// RiddlesItem Component where onClick function is set as a prop
import React from "react";
import "./riddles.css";
const RiddlesItem = props => (
<div>
<div className="card-body">
<p id="question">{props.question}</p>
<img
className="img-fluid"
id={props.id}
src={props.button}
onClick={props.onClick}
alt="answer button"
/>
<p className={props.answerClass}> {props.answer} </p>
</div>
</div>
);
export default RiddlesItem;
You'd have to keep track of each answer that has been shown in state (in an array or something).
First
Send the index of the answer up in the onclick function. In that function, check if it exists in the "shownAnswers" array and either add or remove it.
onClick={e => props.onClick(e, props.id)}
and
toggleAnswer(e, index) {
if (this.state.shownAnswers.indexOf(index) > -1) {
this.setState({
shownAnswers: this.state.shownAnswers.filter(val => val !== index)
});
} else {
this.setState({
shownAnswers: this.state.shownAnswers.concat(index)
});
}
}
Then
When you're passing the class name down to the child component, check if its index is in the "shownAnswers" array to decide which class name to pass.
answerClass={this.state.shownAnswers.indexOf(index) > -1 ? "answer" : "answer-hide"}
Building off your example, it could look something like this (untested):
// RiddlesPage where toggleAnswer function is defined
class RiddlesPage extends Component {
constructor(props) {
super(props);
this.state = {
questionData: [],
shownAnswers: []
};
this.getPageData = this.getPageData.bind(this);
this.toggleAnswer = this.toggleAnswer.bind(this);
}
getPageData() {
console.log("we hit getPageData function starting --");
helpers.getRiddlesPage().then(data => {
console.log("this is the result", data);
this.setState({
questionData: data[0].questionData,
});
});
}
toggleAnswer(e, index) {
if (this.state.shownAnswers.indexOf(index) > -1) {
this.setState({ shownAnswers: this.state.shownAnswers.filter(val => val !== index) });
} else {
this.setState({ shownAnswers: this.state.shownAnswers.concat(index) });
}
}
componentWillMount() {
this.getPageData();
}
render() {
return (
<div>
<Riddles>
{this.state.questionData.map((data, index) => {
return (
<RiddlesItem
key={index}
id={index}
question={data.question}
answer={data.answer}
button={data.buttonURL}
answerClass={this.state.shownAnswers.indexOf(index) > -1 ? "answer" : "answer-hide"}
onClick={this.toggleAnswer}
/>
);
})}
</Riddles>
</div>
);
}
}
export default RiddlesPage;
// Riddles Component
import React from "react";
import "./riddles.css";
const Riddles = props => (
<div id="riddles-row">
<div className="container">
<div className="row">
<div className="col-12">
<div>{props.children}</div>
</div>
</div>
</div>
</div>
);
export default Riddles;
// RiddlesItem Component where onClick function is set as a prop
import React from "react";
import "./riddles.css";
const RiddlesItem = props => (
<div>
<div className="card-body">
<p id="question">{props.question}</p>
<img
className="img-fluid"
id={props.id}
src={props.button}
onClick={e => props.onClick(e, props.id)}
alt="answer button"
/>
<p className={props.answerClass}> {props.answer} </p>
</div>
</div>
);
export default RiddlesItem;
In my React component, I'm displaying a list of items -- each in its own DIV element with a unique id i.e. <div id="abc-123">.
I'm also using react-perfect-scrollbar to make the whole thing nicer looking.
I keep a variable in my reducer named activeElementId and when the value of activeElementId changes, I want to automatically scroll to that item on the screen.
Setting the activeElementId is the easy part but I'm not sure how to scroll to that element and would appreciate some pointers.
This is the parent component that contains the ListComponent.
class MyComponent extends Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
{this.props.items.length > 0
?
<PerfectScrollBar>
<ListComponent items={this.props.items} />
</PerfectScrollBar>
: null}
</div>
);
}
}
My ListComponent is a presentational component:
const ListComponent = ({ items }) => {
return(
<ul className="pretty-list">
{items.map(item => <ItemComponents item={item} />)}
</ul>
);
}
export default ListComponent;
And the ItemComponent is a presentational component as well:
const ItemComponent = ({ Item }) => {
return(
<li>
<div id={item.id}>
{item.someProperty}
</div>
</li>
);
}
export default ItemComponent;
I really like the idea of keeping ListComponent and ItemComponent separate and as presentational components as that helps keep the code simpler and easier to manage. Not sure if that would make it difficult to implement the auto scroll logic though.
The library you use has a method called setScrollTop. You can use it with getBoundingClientRect. To use getBoundingClientRect you need to have the dom-element. You didn't give any code about how you are setting or getting the active element but I'll try to give you an example. Maybe it will help you to implement on your code.
Example
class MyComponent extends Component {
constructor(props) {
super(props);
}
_onListItemChange = (itemsPosition) => {
this.scrollbar.setScrollTop(itemsPosition.top);
}
render() {
return (
<div>
{this.props.items.length > 0 ?
<PerfectScrollBar ref={(scrollbar) => { this.scrollbar = scrollbar; }}>
<ListComponent
items={this.props.items}
onListItemChange={this._onListItemChange} />
</PerfectScrollBar>
: null}
</div>
);
}
const ListComponent = ({ items, onListItemChange }) => {
return(
<ul className="pretty-list">
{items.map(item => (
<ItemComponents
item={item}
onListItemClick={onListItemChange} />
))}
</ul>
);
}
export default ListComponent;
import { render, findDOMNode } from 'react-dom';
class ListItem extends React.Component {
_onClick = () => {
let domElement = findDOMNode(this.item);
this.props.onListItemClick(domElement.getBoundingClientRect());
}
render() {
const { item } = this.props;
return(
<li>
<div id={item.id} ref={(item) => { this.item = item; }} onClick={this._onClick}>
{item.someProperty}
</div>
</li>
);
}
}
I have a component built using the below code. The aim is to add a class on the card to highlight it when the button inside it is clicked. However, the below code works on the first click but doesn't work for the subsequent clicks.
I understood that I have to set the clicked state of other elements to false when I remove the class. How can this be done?
import React, { Component } from 'react';
import './PricingCard.css';
class PricingCard extends Component {
constructor(){
super();
this.state = {
clicked : false
}
}
makeSelection(){
let elems = document.getElementsByClassName('Card');
for(var i=0;i<elems.length;i++){
elems[i].classList.remove("active");
}
this.setState({clicked: true});
}
render() {
var activeClass = this.state.clicked ? 'active' : '';
return (
<div className= {"categoryItem Card " + this.props.planName + " " +activeClass}>
<div className="cardDetails">
<div> {this.props.planName} </div>
<div className="pricing"> {this.props.price} </div>
<button onClick={this.makeSelection.bind(this)} className="buttonPrimary"> Select this plan </button>
<div className="subtitle"> {this.props.footerText} </div>
</div>
</div>
);
}
}
export default PricingCard;
Wouldn't it be easier to have the logic in a parent component? Since it is "aware" of all the child Card components.
Have something like...
this.state = { selectedComponent: null };
onClick(card_id) {
this.setState({ selectedComponent: card_id });
}
...in render:
const cards = smth.map((card) =>
<Card onClick={this.onClick.bind(this, card.id)}
isActive={map.id === this.state.selectedComponent} />
Would this work?
Best way will be to lift lift the state up. Like this:
class PricingCardContainer extends React.Component {
constructor(props){
super(props);
this.state = {
selectedCard: NaN,
}
}
handleCardClick(selectedCard){ this.setState({ selectedCard }); }
render() {
return (
<div>{
this.props.dataArray.map((data, i) =>
<PricingCard
key={i}
className={this.state.selectedCard === i ? 'active': ''}
price={data.price}
onClick={() => this.handleCardClick(i)}
footerText={data.footerText}
planName={data.planName}
plan={data.plan}
/>
)
}</div>
)
}
}
const PricingCard = ({ className = '', planName, price, onClick, footerText }) => (
<div className= {`categoryItem Card ${planName} ${className}`}>
<div className="cardDetails">
<div> {planName} </div>
<div className="pricing"> {price} </div>
<button onClick={onClick} className="buttonPrimary"> Select this plan </button>
<div className="subtitle"> {footerText} </div>
</div>
</div>
);
export default PricingCard;
Although it would be better to use some data id than index value.
I'm new to React and I'm puzzled on something kind of basic.
I need to append a component to the DOM after the DOM is rendered, on a click event.
My initial attempt is as follows, and it doesn't work. But it's the best thing I've thought to try. (Apologies in advance for mixing jQuery with React.)
ParentComponent = class ParentComponent extends React.Component {
constructor () {
this.addChild = this.addChild.bind(this);
}
addChild (event) {
event.preventDefault();
$("#children-pane").append(<ChildComponent/>);
}
render () {
return (
<div className="card calculator">
<p><a href="#" onClick={this.addChild}>Add Another Child Component</a></p>
<div id="children-pane">
<ChildComponent/>
</div>
</div>
);
}
};
Hopefully it's clear what I need to do, and I hope you can help me attain an appropriate solution.
Don't use jQuery to manipulate the DOM when you're using React. React components should render a representation of what they should look like given a certain state; what DOM that translates to is taken care of by React itself.
What you want to do is store the "state which determines what gets rendered" higher up the chain, and pass it down. If you are rendering n children, that state should be "owned" by whatever contains your component. eg:
class AppComponent extends React.Component {
state = {
numChildren: 0
}
render () {
const children = [];
for (var i = 0; i < this.state.numChildren; i += 1) {
children.push(<ChildComponent key={i} number={i} />);
};
return (
<ParentComponent addChild={this.onAddChild}>
{children}
</ParentComponent>
);
}
onAddChild = () => {
this.setState({
numChildren: this.state.numChildren + 1
});
}
}
const ParentComponent = props => (
<div className="card calculator">
<p><a href="#" onClick={props.addChild}>Add Another Child Component</a></p>
<div id="children-pane">
{props.children}
</div>
</div>
);
const ChildComponent = props => <div>{"I am child " + props.number}</div>;
As #Alex McMillan mentioned, use state to dictate what should be rendered in the dom.
In the example below I have an input field and I want to add a second one when the user clicks the button, the onClick event handler calls handleAddSecondInput( ) which changes inputLinkClicked to true. I am using a ternary operator to check for the truthy state, which renders the second input field
class HealthConditions extends React.Component {
constructor(props) {
super(props);
this.state = {
inputLinkClicked: false
}
}
handleAddSecondInput() {
this.setState({
inputLinkClicked: true
})
}
render() {
return(
<main id="wrapper" className="" data-reset-cookie-tab>
<div id="content" role="main">
<div className="inner-block">
<H1Heading title="Tell us about any disabilities, illnesses or ongoing conditions"/>
<InputField label="Name of condition"
InputType="text"
InputId="id-condition"
InputName="condition"
/>
{
this.state.inputLinkClicked?
<InputField label=""
InputType="text"
InputId="id-condition2"
InputName="condition2"
/>
:
<div></div>
}
<button
type="button"
className="make-button-link"
data-add-button=""
href="#"
onClick={this.handleAddSecondInput}
>
Add a condition
</button>
<FormButton buttonLabel="Next"
handleSubmit={this.handleSubmit}
linkto={
this.state.illnessOrDisability === 'true' ?
"/404"
:
"/add-your-details"
}
/>
<BackLink backLink="/add-your-details" />
</div>
</div>
</main>
);
}
}