Show or Hide a particular element in react - reactjs

I have to show list of faqs and I need to hide the answers of the questions. When I click on the question, the answer for that particular question need to be shown. My problem is, I have a bunch of questions and when i click the button, it will show all of the answer instead of the specific answer to that question.
class Faqs extends Component {
constructor(props){
super(props);
this.state = {
isHidden: true
}
}
toggleHidden () {
this.setState({
isHidden: !this.state.isHidden
})
}
render() {
return (
<div>
<span onClick={() => this.toggleHidden()}><strong>This is the question</strong></span>
{!this.state.isHidden && <p>Answer for the question</p>} <br/>
<span onClick={() => this.toggleHidden()}><strong>Question2</strong></span>
{!this.state.isHidden && <p>Answer2</p>} <br/>
<hr></hr>
</div >
)
}
}

You can break your component to one more level to have a sub component which renders only the question and corresponding answer. Pass the question and answers as props. In that way you can use the same component for all questions and yet every question/answer pair will have their own state.
class Faq extends Component{
state = {isHidden: true}
toggleHidden = ()=>this.setState((prevState)=>({isHidden: !prevState.isHidden}))
render(){
return(
<div>
<span onClick={this.toggleHidden}>
<strong>{props.question}</strong></span>
{!this.state.isHidden && <p>{props.answer}</p>}
</div>
)
}
}
class Faqs extends Component {
render() {
return (
<div>
<Faq question={"Question 1"} answer={"answer 1"} />
<Faq question={"Question 2"} answer={"answer 2"} />
</div >
)
}
}

Ideally you would list FAQs in some kind of list - then as you iterate over them, each will have an index number assigned to it - then as you toggle individual answers, you store that index in the state and operate on DOM via that number.
edit. In current day and age, it's only appropriate to show example using hooks:
const {useState} = React;
const FaqApp = () => {
const [ selectedQuestion, toggleQuestion ] = useState(-1);
function openQuestion(index) {
toggleQuestion(selectedQuestion === index ? -1 : index);
}
const faqs = getFaqs();
return (
<div>
<h2>FAQs:</h2>
{faqs.map(( { question, answer}, index) => (
<div key={`item-${index}`} className={`item ${selectedQuestion === index ? 'open' : ''}`}>
<p className='question' onClick={() => openQuestion(index)}>{question}</p>
<p className='answer'>{answer}</p>
</div>
))}
</div>
)
}
function getFaqs() {
const faqs = [
{
question: 'Question 1',
answer: 'answer 1'
},
{
question: 'Question 2',
answer: 'answer 2'
}
];
return faqs;
}
ReactDOM.render(
<FaqApp />,
document.getElementById("react")
);
body {
background: #fff;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
h2 {
margin-bottom: 11px;
}
.item + .item {
margin-top: 11px;
}
.question {
font-weight: bold;
cursor: pointer;
}
.answer {
display: none;
}
.open .answer {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
older version of this post:
I've written a quick example that allows you to have multiple questions:
class FaqApp extends React.Component {
constructor(props) {
super(props)
this.state = {
// start the page with all questions closed
selectedQuestion: -1
};
this.openQuestion = this.openQuestion.bind(this);
}
getFaqs() {
// some service returning a list of FAQs
const faqs = [
{
question: 'Question 1',
answer: 'answer 1'
},
{
question: 'Question 2',
answer: 'answer 2'
}
];
return faqs;
}
openQuestion(index) {
// when a question is opened, compare what was clicked and if we got a match, change state to show the desired question.
this.setState({
selectedQuestion: (this.state.selectedQuestion === index ? -1 : index)
});
}
render() {
// get a list of FAQs
const faqs = this.getFaqs();
return (
<div>
<h2>FAQs:</h2>
{faqs.length && faqs.map((item, index) => (
<div key={`item-${index}`} className={`item ${this.state.selectedQuestion === index ? 'open' : ''}`}>
<p className='question' onClick={() => this.openQuestion(index)}>
{item.question}
</p>
<p className='answer'>
{item.answer}
</p>
</div>
))}
</div>
)
}
}
ReactDOM.render(<FaqApp />, document.querySelector("#app"))
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
h2 {
margin-bottom: 11px;
}
.item + .item {
margin-top: 11px;
}
.question {
font-weight: bold;
cursor: pointer;
}
.answer {
display: none;
}
.open .answer {
display: block;
}
<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>

The issue is that you're using one boolean piece of state to control the logic for multiple pieces. This is a classic scenario to use separate components.
Create a new component ToggleQuestion that encapsulates the mechanic of show/reveal.
The Faqs component instead manages a list of ToggleQuestion components.
const QUESTIONS = [
{ title: 'q1', answer: 'a1' },
{ title: 'q2', answer: 'a2' }
]
class ToggleQuestion extends React.Component {
constructor (props) {
super(props)
this.state = { isHidden: true }
}
toggleHidden () {
this.setState({ isHidden: !this.state.isHidden })
}
render () {
const { question, answer } = this.props
const { isHidden } = this.state
return (
<div>
<span>{question}</span>
{ !isHidden && <span>{answer}</span> }
<button onClick={this.toggleHidden.bind(this)}>
Reveal Answer
</button>
</div>
)
}
}
class Faqs extends React.Component {
render () {
return (
<div>
{ QUESTIONS.map(question => (
<ToggleQuestion
question={question.title}
answer={question.answer}
/>
))}
</div>
)
}
}
ReactDOM.render(<Faqs />, document.getElementById('container'))
<div id='container'></div>
<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>

I would write a different handler for the answer. In the future if you need more logic for the answer will be scalable. Notice renderAnswer
class Faqs extends Component {
constructor(props){
super(props);
this.state = {
isHidden: true
}
}
toggleHidden () {
this.setState({
isHidden: !this.state.isHidden
})
}
renderAnswer() {
if (this.state.isHidden) {
return;
}
return (
<p>Answer</p>
);
}
render() {
return (
<div>
<span onClick={() => this.toggleHidden()}><strong>This is the question</strong></span>
{ this.renderAnswer() } <br/>
<span onClick={() => this.toggleHidden()}><strong>Question2</strong></span>
{ this.renderAnswer() } <br/>
<hr></hr>
</div >
)
}
}

This is another way to do what you want. (This one will only make it possible to have one open at a time)
class Faqs extends Component {
constructor(props){
super(props);
this.state = {
hiddenId: null,
}
}
setHiddenId(id) {
this.setState({
hiddenId: id
})
}
render() {
return (
<div>
<span onClick={() => this.setHiddenId('one')}><strong>This is the question</strong></span>
{this.state.hiddenId === 'one' && <p>Answer for the question</p>} <br/>
<span onClick={() => this.setHiddenId('two')}><strong>Question2</strong></span>
{this.state.hiddenId === 'two' && <p>Answer2</p>} <br/>
<hr></hr>
</div >
)
}
}

Related

React Star Widget - why do all stars update on single click?

I am trying to create a star widget. I have a state array for each star, but when I click one of the stars, ALL of the stars set themselves to that state. I am very lost on this, please halp. I have added a lot of debugging logs. The moment I set newStars[i] = currentStar;, the entire newStars array gets updated, but I'm failing to see why.
Also, here is the code pen link: https://codepen.io/trismi/pen/zYZpvQq?editors=1111
HTML:
<div id="root">
</div>
CSS (plus the awesome fonts stylesheet linked in the codepen)
.star {
display: inline-block;
width: 30px;
text-align: center;
color: #ddd;
font-size: 20px;
transform: scale(.8);
transition: transform 50ms ease;
&:hover,
&.semi-active {
color: gold;
transform: scale(1);
}
&.selected {
color: orange;
transform: scale(1);
}
}
JAVASCRIPT
function Star(props) {
console.log(props);
console.log(props.index);
let classes = 'star' + (props.selected ? ' selected' : '') + (props.hover ? ' semi-active' : '');
return (
<div className={classes} onClick={props.onClick}>
<i className="fas fa-star"></i>
</div>
);
}
class RatingWidget extends React.Component {
constructor(props){
super(props);
this.state = {
stars: Array(5).fill({
selected: false,
hover: false,
}),
}
}
handleClick(currentStar, index) {
console.log('\n\n\n******CLICK');
console.log("star state on click", currentStar);
console.log("index", index);
let newStars = this.state.stars.slice();
let newStar = newStars[index];
console.log("new star ", newStar);
newStar.selected = !newStar.selected;
newStars[index] = newStar;
console.log("stars", newStars);
this.setState({
stars: newStars
});
}
render() {
let stars = this.state.stars.map((rating, index) => {
return (
<Star
key={index}
index={index}
onClick={() => this.handleClick(rating, index)}
selected={rating.selected}
hover={rating.hover}
/>);
});
return (
<div className="RatingWidget">
Future rating widget
{stars}
</div>
);
}
}
ReactDOM.render(<RatingWidget />, document.getElementById('root'));
The problem is here:
Array(5).fill({
selected: false,
hover: false,
})
you are filling the same object (same reference) to each element of the array.
Try using:
Array(5).fill(null).map(() => ({
selected: false,
hover: false,
}))
Or use Array.from():
Array.from({length: 5}, () => ({ selected: false, hover: false}))
You can have the below handleClick function
I updated let newStar = newStars[index]; to let newStar = {...newStars[index]};
handleClick(currentStar, index) {
console.log('\n\n\n******CLICK');
console.log("star state on click", currentStar);
console.log("index", index);
let newStars = this.state.stars.slice();
let newStar = {...newStars[index]};
console.log("new star ", newStar);
newStar.selected = !newStar.selected;
newStars[index] = newStar;
console.log("stars", newStars);
this.setState({
stars: newStars
});
}

Written a Flexi React Component that is handed a JSON object.buting having logic error

const flexiConfig = {
items: [
{
"name": "person_name",
"label": "Person's Name",
"type": 'TextField',
},
{
"name": "states",
"label": "Person's state",
"type": "DropDown",
"values": [
"Maharashtra",
"Kerala",
"Tamil Nadu"
]
}
]
};
class Flexi extends React.Component {
constructor(props) {
super(props)
this.state = {
}
}
onFlexSubmit(e) {
e.preventDefault();
console.log(props.config.items)
}
render() {
return (
<div>
<form action="">
<button type="submit">Submit</button>
</form>
</div>
)
}
}
ReactDOM.render(<Flexi onSubmit={this.onFlexiSubmit} config={flexiConfig}/>, document.querySelector("#app"))
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
.done {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
input {
margin-right: 5px;
}
<div id="app"></div>
Tried to do like this,
the component creates the appropriate TextField and DropDown HTML elements to allow the user to enter the necessary information. The component also renders a submit button that calls the onSubmit function supplied in the props with a JSON object as an argument that contains the user entered values for each of the items in flexiConfig.
what changes i need to make in order to get above one.
Not sure, but my understanding from your questions is that
you're trying to render a form based on input from that json
pass values back to the function which does something? (I just printed those)
relevant flexi component:
import React, { useState, useEffect } from "react";
const Flexi = ({ onSubmitFn, config }) => {
const [inputVal, setInputVal] = useState("");
const [ddVal, setDdVal] = useState("");
useEffect(() => {
}, []);
const changeHandler = e => {
setInputVal(e.target.value);
};
const changeHandlerDD = e => {
setDdVal(e.target.value);
};
const formSubmitHandler = e => {
e.preventDefault();
onSubmitFn({ name: inputVal, dd: ddVal });
};
return (
<>
<h1>Flex-ish form</h1>
<form>
{(config.length > 0) ?config.map((val, index) => {
if (val.type === "TextField") {
return (
<div>
<label htmlFor={val.label}>{val.label}:</label>
<input type="text" id={val.label} onChange={changeHandler} />
</div>
);
}
if (val.type === "DropDown") {
return (
<div>
<label htmlFor={val.label}>{val.label}:</label>
<select type="text" id={val.label} onChange={changeHandlerDD}>
<option value="" />
{val.values.map(ddOption => {
return <option value={ddOption}>{ddOption}</option>;
})}
</select>
</div>
);
}
}): <p>No data received</p> }
<button type="submit" onClick={formSubmitHandler}>
Submit
</button>
</form>
</>
);
};
export default Flexi;
relevant section in the parent component:
constructor() {
super();
this.state = {
name: "React",
place: 'somewhere',
};
}
onFlexSubmit = (val) => {
this.setState({ name: val.name, place: val.dd });
}
render() {
return (
<div>
<Flexi onSubmitFn={this.onFlexSubmit} config={flexiConfig.items} />
<Hello name={this.state.name} place={this.state.place} />
</div>
);
}
}
complete working stackblitz here

REACT.JS: How to loop over all NavBar buttons and remove their class and add "active" class to the clicked button

I am trying to make a simple NavBar with React.js. The problem I found myself in is the looping over all nav buttons and remove the "active" className and then add "active" to just that one clicked button.
I managed to make a state that toggles "active" to true on the clicked element which then in the className attribute does this If statement:
className={this.state.active ? "nav-item nav-link active" : "nav-item nav-link"}
Here is the full code:
import React, { Component } from 'react';
class NavButton extends Component {
state = {
active: false
}
setActive = () => {
this.setState({
active: !this.state.active
})
}
render() {
return (
<a
className={this.state.active ? "nav-item nav-link active" : "nav-item nav-link"}
href={this.props.href}
onClick={this.setActive}> {this.props.title}
</a>
)
}
}
class NavBar extends Component {
buttons = [
{
title: "Home",
key: 0
},
{
title: "Team",
key: 1
},
{
title: "Discord",
key: 2
},
{
title: "Gallery",
key: 3
},
{
title: "Download",
key: 4
}
]
render() {
return (
<nav className="navbar" id="navbarMain">
<div></div>
<div className="navbar-nav flex-row">
{this.buttons.map(button => <NavButton title={button.title} key={button.key} />)}
</div>
<div></div>
</nav>
)
}
}
export default NavBar
This works, for just one element (don't mind that the active state goes false when it's true. The problem is, how would I do it in the React way to remove the active className in all other buttons?
With plain JS i have no issues to do that, i just loop over all elements that have the className "navbar-item" and set their classnames to be without the "active" one then add " active" to the pressed element like in this example https://www.w3schools.com/howto/howto_js_tabs.asp
Would you guys be able to help and tell me what would be the best react way to do this?
Much appreciated!
A common pattern for these use-cases is to keep the relevant state in the parent, so that it is the parent (NavBar) that keeps track of which child (NavButton) is "active". The NavButton can then become a stateless component which takes "active" as a prop.
const NavButton = ({active, title, href, onSetActive}) => {
return (
<button
className={active ? "nav-item nav-link active" : "nav-item nav-link"}
href={href}
onClick={onSetActive} >
{title}
</button>
)
}
class NavBar extends React.Component {
constructor(props) {
super(props);
this.state = {
activeIndex: 0, // keep the active index in state
buttons: [
{
title: "Home",
key: 0
},
{
title: "Team",
key: 1
},
{
title: "Discord",
key: 2
},
{
title: "Gallery",
key: 3
},
{
title: "Download",
key: 4
}
]
}
}
handleChangeActive(newActiveIndex) {
this.setState({activeIndex: newActiveIndex});
}
render() {
const {activeIndex} = this.state;
return (
<nav className="navbar" id="navbarMain">
<div></div>
<div className="navbar-nav flex-row">
{this.state.buttons.map((button, buttonIndex) =>
/* determine which nav button is active depending on the activeIndex state */
<NavButton onSetActive={ () => this.handleChangeActive(buttonIndex)} active={buttonIndex === activeIndex } title={button.title} key={button.key} />)}
</div>
<div></div>
</nav>
)
}
}
ReactDOM.render(<NavBar />, document.querySelector("#app"));
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
.done {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
input {
margin-right: 5px;
}
.nav-item.nav-link {
background: grey;
}
.nav-item.nav-link.active {
background: red;
}
<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>
<body>
<div id="app"></div>
</body>
I would move your state and logic to the NavBar component. It would be responsible to store and set the current active button, and pass it as prop to all buttons.
class NavBar extends Component {
state = {
activeButtonIndex: null;
}
buttons = [
{
title: "Home",
key: 0
},
{
title: "Team",
key: 1
},
];
renderButton = (button, index) => (
<NavButton
{...button}
isActive={this.state.activeButtonIndex === index}
setActive={() => this.setState({ activeButtonIndex: index })}
/>
);
render() {
return (
<nav className="navbar" id="navbarMain">
<div className="navbar-nav flex-row">
{this.buttons.map((button, index) => this.renderButton(button, index)}
</div>
</nav>
);
}
}
const NavButton = ({ isActive, setActive, href, title }) => (
<a
className={isActive ? "nav-item nav-link active" : "nav-item nav-link"}
href={href}
onClick={setActive}
>
{title}
</a>
);

Navigation menu not working in React

Please help me to fix this navigation menu.Something here is not working.It has to change the clicked cell after click. I would be very grateful if you show me where is the problem
class MenuExample extends React.Component{
constructor(props) {
super(props);
this.state = {focused: 0};
}
clicked(index){
this.setState({focused: index});
};
render: {
return (
<div>
<ul>{ this.props.items.map(function(m, index){
var style = '';
if(this.state.focused == index){ style = 'focused'; }
return <li className={style} onClick={this.clicked.bind(this)}>{m}</li>;
}) }
</ul>
<p>Selected: {this.props.items[this.state.focused]}</p>
</div>
);
}
};
ReactDOM.render(
<MenuExample items={ ['Home', 'Services', 'About', 'Contact us'] } />,
document.getElementById('root')
);
Its a binding issue, you forgot to bind the map callback method, here:
this.props.items.map(function(m, index){.....})
Use arrow function to maintain the context, like this:
this.props.items.map((m, index) => {.....})
Check the working code:
class MenuExample extends React.Component{
constructor(){
super();
this.state = { focused: 0 };
}
clicked(index){
this.setState({focused: index});
}
render() {
return (
<div>
<ul>{ this.props.items.map((m, index) => {
var style = '';
if(this.state.focused == index){
style = 'focused';
}
return <li className={style} onClick={this.clicked.bind(this, index)}>{m}</li>
}) }
</ul>
<p>Selected: {this.props.items[this.state.focused]}</p>
</div>
);
}
}
ReactDOM.render(
<MenuExample items={ ['Home', 'Services', 'About', 'Contact us'] } />,
document.getElementById('container')
);
* {
padding:0;
margin:0;
}
html{
font:14px normal Arial, sans-serif;
color:#626771;
background-color:#fff;
}
body{
padding:60px;
text-align: center;
}
ul{
list-style:none;
display: inline-block;
}
ul li{
display: inline-block;
padding: 10px 20px;
cursor:pointer;
background-color:#eee;
color:#7B8585;
transition:0.3s;
}
ul li:hover{
background-color:#beecea;
}
ul li.focused{
color:#fff;
background-color:#41c7c2;
}
p{
padding-top:15px;
font-size:12px;
}
<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='container'/>
Working Fiddle.

ReactJS ReactCSSTransitionGroup why does it work one component but not another?

Trying to figure out why this ReactCSSTransitionGroup animation works:
class SlideExample extends React.Component{
constructor(props) {
super(props);
this.state = { visible: false };
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState({ visible: ! this.state.visible });
}
render() {
return <div>
<button onClick={this.handleClick}>{this.state.visible ? 'Slide up' : 'Slide down'}</button>
<ReactCSSTransitionGroup
transitionName="example"
transitionEnterTimeout={300}
transitionLeaveTimeout={300}>
{
this.state.visible
? <div className='panel'>
<ul className="project-list">
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</div>
: null
}
</ReactCSSTransitionGroup>
</div>
}
}
const ProjectList = (props) => {
return(
<div className="ProjectList">
<SlideExample />
</div>
);
}
But not like this:
class App extends Component {
constructor() {
super();
this.state = {
_isProjectNavOpen: true,
}
}
_toggleProjectNav() {
this.setState(prevState => ({
_isProjectNavOpen: !prevState._isProjectNavOpen,
}));
}
render() {
return(
<div className="App">
<Router>
<div className="main-content">
<Route path="/projects" component={(props, state, params) =>
<ProjectList
_toggleProjectNav={this._toggleProjectNav}
_isProjectNavOpen={this.state._isProjectNavOpen}
{...props} />} />
</div>
</Router>
</div>
);
}
}
const ProjectList = (props) => {
return(
<div className="ProjectList">
<div className="center title" onClick={props._toggleProjectNav} id="Menu">Menu</div>
<ReactCSSTransitionGroup
transitionName="example"
transitionEnterTimeout={300}
transitionLeaveTimeout={300}>
{
props._isProjectNavOpen
? <div className='panel'>
<ul className="project-list">
<li>xx one</li>
<li>xx two</li>
<li>xx three</li>
</ul>
</div>
: null
}
</ReactCSSTransitionGroup>
</div>
);
}
The CSS:
.panel {
width: 200px;
height: 100px;
background: green;
margin-top: 10px;
}
.example-enter {
height: 0px;
}
.example-enter.example-enter-active {
height: 100px;
-webkit-transition: height .3s ease;
}
.example-leave.example-leave-active {
height: 0px;
-webkit-transition: height .3s ease;
}
_toggleProjectNav is a prop passed down from a parent component that toggles the _isProjectNavOpen state true/false; it works in that the panel does hide/show, but without the animation... does it have to do with the state being passed from the parent? Trying to understand how ReactCSSTransitionGroup works.
Thanks!
This code works for me. In this case this is not a ReactCSSTransitionGroup issue. The issue is most probably related to the CSS. Pay attention, some browsers require initial height value before the transition. So you need something like this:
.example-leave {
height: 100px;
}

Resources