Bootstrap dropdowns in ReactJS, only one open at a time - reactjs

I have a page contains multiple Bootstrap Cards and each card is a component and each card footer is also a component. Card Footer contains buttons. When you click on a button, drop down will be opened like below
At any point of time when I click on a button, other drop downs should be in closed state. But its happening like this...
Requirement: One more thing is when I click on the same button, the respective drop down should be closed.
Requirement: When I click on any item inside drop down the respective drop down should be closed
My Architecture is like below
HOME PAGE COMPONENT CODE -START
class HomePage extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [],
activatedIdStoredInParent: ""
};
}
toggleCountersMenu = (name) => {
var name1 = name;
this.setState((prevState) => {
return {
activatedIdStoredInParent: name1
}
});
}
render() {
const products = this.state.items.map((item, index) => {
return <div>
<Card
product={item}
activatedIdStoredInParent={this.state.activatedIdStoredInParent}
toggleCountersMenu={this.toggleCountersMenu}
>
</Card>;
</div>
});
return (
<div>
<div className="card-columns">
{products}
</div>
</div >
);
}
}
export default HomePage;
HOME PAGE COMPONENT CODE - END
CARD COMPONENT CODE - START
class Card extends React.Component {
handleActionClick = (name) => {
this.props.toggleCountersMenu(name);
}
render() {
return (
<div key={this.props.product.name}>
<CardHeader product={this.props.product} />
<CardBody product={this.props.product} />
<CardFooter
product={this.props.product}
onActionItemClick={this.handleActionClick}
activatedIdStoredInParent={this.props.activatedIdStoredInParent}
/>
</div>
);
}
}
export default Card;
CARD FOOTER COMPONENT CODE - START
class CardFooter extends React.Component {
handleActionItemClick = (name) => {
this.props.onActionItemClick(name);
}
render() {
console.log('Card Footer Drop Down comp rendered');
return (
<div className=" card-footer text-center">
<ButtonDropdown text="F" className="danger"
product={this.props.product}
onActionItemClick={this.handleActionItemClick}
activatedIdStoredInParent={this.props.activatedIdStoredInParent}
></ButtonDropdown>
</div>
);
}
}
export default CardFooter;
ButtonDropdown COMPONENT CODE - START
class ButtonDropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
show: ' none',
localActivatedId: 'none'
}
}
toggleOpen = (e) => {
var name = e.target.name;
this.setState((prevState, props) => {
var item = {
localActivatedId: name
}
if (props.activatedIdStoredInParent === name) {
if (prevState.show === ' show') {
item.show = ' none';
}
else {
item.show = ' show';
}
}
return item;
});
this.props.onActionItemClick(name);
}
numberClick = (e) => {
var qty = e.target.innerText;
this.setState((prevState, props) => {
var item = {
show: ' none'
}
return item;
});
}
render() {
return (
<div className="btn-group" >
<button type="button" className={`btn btn-${this.props.className} mr-1`} name={this.props.product.name + '$$' + this.props.text} onClick={this.toggleOpen}>
{this.props.text} (classAdded={this.state.show})
</button>
<div className={`dropdown-menu ${this.state.show}`}>
<span className="dropdown-item cursor-pointer " onClick={this.numberClick}>
-1
</span>
<span className="dropdown-item cursor-pointer" onClick={this.numberClick}>
-2
</span>
</div>
</div>
);
}
}
export default ButtonDropdown;
When I add multiple buttonDropdown components in Card Footer the end product is like this. How can I close other dropdowns.
I would like to know is my architecture is correct.. I am not using Redux/Flux etc..

You can use the componentDidUpdate lifecycle, in order to update your state's property that is opening the dropdown.
I don't know if it's the open or show property that displays the content of the dropdown but here's my logic.
class ButtonDropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
//
};
}
componentDidUpdate(prevProps) {
const name = this.props.product.name + '$$' + this.props.text;
if (prevProps.activatedIdStoredInParent !== this.props.activatedIdStoredInParent && this.props.activatedIdStoredInParent !== name) {
this.closeDropDown();
}
}
closeDropDown = () => this.setState({ isOpen: false });
toggleOpen = (e) => {
//
}
numberClick = (e) => {
//
}
render() {
//
}
}
export default ButtonDropdown;

Related

Change **child element** className with parent onClick [duplicate]

I am trying to figure out how to toggle an active class on click to change CSS properties.
My code is below. Can anyone advise how I should do this? Without creating a new component for each item is it possible to do this?
class Test extends Component(){
constructor(props) {
super(props);
this.addActiveClass= this.addActiveClass.bind(this);
}
addActiveClass() {
//not sure what to do here
}
render() {
<div>
<div onClick={this.addActiveClass}>
<p>1</p>
</div>
<div onClick={this.addActiveClass}>
<p>2</p>
</div>
<div onClick={this.addActiveClass}>
<p>3</p>
</div>
</div>
}
}
Use state. See the React docs.
class MyComponent extends Component {
constructor(props) {
super(props);
this.addActiveClass= this.addActiveClass.bind(this);
this.state = {
active: false,
};
}
toggleClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
};
render() {
return (
<div
className={this.state.active ? 'your_className': null}
onClick={this.toggleClass}
>
<p>{this.props.text}</p>
</div>
)
}
}
class Test extends Component {
render() {
return (
<div>
<MyComponent text={'1'} />
<MyComponent text={'2'} />
</div>
);
}
}
You can also do this with hooks.
function MyComponent (props) {
const [isActive, setActive] = useState(false);
const toggleClass = () => {
setActive(!isActive);
};
return (
<div
className={isActive ? 'your_className': null}
onClick={toggleClass}
>
<p>{props.text}</p>
</div>
);
}
I would prefer using the && operator in an inline if statement. In my opinion it gives cleaner codebase this way.
Generally you could be doing something like this:
render(){
return(
<div>
<button className={this.state.active && 'active'}
onClick={ () => this.setState({active: !this.state.active}) }>Click me</button>
</div>
)
}
Just keep in mind that arrow functions are and ES6 feature and remember to set this.state.active value in the class constructor.
this.state = { active: false }
Or if you want to inject CSS in JSX you are able to do it this way:
<button style={this.state.active && style.button} >button</button>
And you can declare style json variable:
const style = { button: { background:'red' } }
Remember to use camelCase on JSX stylesheets.
Well, your addActiveClass needs to know what was clicked. Something like this could work (notice that I've added the information which divs are active as a state array, and that onClick now passes the information what was clicked as a parameter after which the state is accordingly updated - there are certainly smarter ways to do it, but you get the idea).
class Test extends Component(){
constructor(props) {
super(props);
this.state = {activeClasses: [false, false, false]};
this.addActiveClass= this.addActiveClass.bind(this);
}
addActiveClass(index) {
const activeClasses = [...this.state.activeClasses.slice(0, index), !this.state.activeClasses[index], this.state.activeClasses.slice(index + 1)].flat();
this.setState({activeClasses});
}
render() {
const activeClasses = this.state.activeClasses.slice();
return (
<div>
<div className={activeClasses[0]? "active" : "inactive"} onClick={() => this.addActiveClass(0)}>
<p>0</p>
</div>
<div className={activeClasses[1]? "active" : "inactive"} onClick={() => this.addActiveClass(1)}>
<p>1</p>
</div>
<div onClick={() => this.addActiveClass(2)}>
<p>2</p>
</div>
</div>
);
}
}
You can simply access the element classList which received the click event using event.target then by using toggle method on the classList object to add or remove the intended class
<div onClick={({target}) => target.classList.toggle('active')}>
....
....
....
</div>
Equevelent
<div onClick={e=> e.target.classList.toggle('active')}>
....
....
....
</div>
OR by declaring a function that handle the click and does extra work
function handleClick(el){
.... Do more stuff
el.classList.toggle('active');
}
<div onClick={({target})=> handleClick(target)}>
....
....
....
</div>
React has a concept of components state, so if you want to switch it, do a setState:
constructor(props) {
super(props);
this.addActiveClass= this.addActiveClass.bind(this);
this.state = {
isActive: false
}
}
addActiveClass() {
this.setState({
isActive: true
})
}
In your component use this.state.isActive to render what you need.
This gets more complicated when you want to set state in component#1 and use it in component#2. Just dig more into react unidirectional data flow and possibly redux that will help you handle it.
using React you can add toggle class to any id/element, try
style.css
.hide-text{
display: none !important;
/* transition: 2s all ease-in 0.9s; */
}
.left-menu-main-link{
transition: all ease-in 0.4s;
}
.leftbar-open{
width: 240px;
min-width: 240px;
/* transition: all ease-in 0.4s; */
}
.leftbar-close{
width: 88px;
min-width:88px;
transition: all ease-in 0.4s;
}
fileName.js
......
ToggleMenu=()=>{
this.setState({
isActive: !this.state.isActive
})
console.log(this.state.isActive)
}
render() {
return (
<div className={this.state.isActive===true ? "left-panel leftbar-open" : "left-panel leftbar-close"} id="leftPanel">
<div className="top-logo-container" onClick={this.ToggleMenu}>
<span className={this.state.isActive===true ? "left-menu-main-link hide-from-menu" : "hide-text"}>Welcome!</span>
</div>
<div className="welcome-member">
<span className={this.state.isActive===true ? "left-menu-main-link hide-from-menu" : "hide-text"}>Welcome<br/>SDO Rizwan</span>
</div>
)
}
......
The above answers will work, but just in case you want a different approach, try classname: https://github.com/JedWatson/classnames
A good sample would help to understand things better:
HTML
<div id="root">
</div>
CSS
.box {
display: block;
width: 200px;
height: 200px;
background-color: gray;
color: white;
text-align: center;
vertical-align: middle;
cursor: pointer;
}
.box.green {
background-color: green;
}
React code
class App extends React.Component {
constructor(props) {
super(props);
this.state = {addClass: false}
}
toggle() {
this.setState({addClass: !this.state.addClass});
}
render() {
let boxClass = ["box"];
if(this.state.addClass) {
boxClass.push('green');
}
return(
<div className={boxClass.join(' ')} onClick={this.toggle.bind(this)}>{this.state.addClass ? "Remove a class" : "Add a class (click the box)"}<br />Read the tutorial here.</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
you can add toggle class or toggle state on click
class Test extends Component(){
state={
active:false,
}
toggleClass() {
console.log(this.state.active)
this.setState=({
active:true,
})
}
render() {
<div>
<div onClick={this.toggleClass.bind(this)}>
<p>1</p>
</div>
</div>
}
}
Thanks to #cssko for providing the correct answer, but if you tried it yourself you will realise it does not work. A suggestion has been made by #Matei Radu, but was rejected by #cssko, so the code remains unrunnable (it will throw error 'Cannot read property bind of undefined'). Below is the working correct answer:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.addActiveClass = this.addActiveClass.bind(this);
this.state = {
active: false,
};
}
addActiveClass() {
const currentState = this.state.active;
this.setState({
active: !currentState
});
};
render() {
return ( <
div className = {
this.state.active ? 'your_className' : null
}
onClick = {
this.addActiveClass
} >
<
p > {
this.props.text
} < /p> < /
div >
)
}
}
class Test extends React.Component {
render() {
return ( <
div >
<
MyComponent text = {
'Clicking this will toggle the opacity through css class'
}
/> < /
div >
);
}
}
ReactDOM.render( <
Test / > ,
document.body
);
.your_className {
opacity: 0.3
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
React has a concept of components state, so if you want to Toggle, use setState:
App.js
import React from 'react';
import TestState from './components/TestState';
class App extends React.Component {
render() {
return (
<div className="App">
<h1>React State Example</h1>
<TestState/>
</div>
);
}
}
export default App;
components/TestState.js
import React from 'react';
class TestState extends React.Component
{
constructor()
{
super();
this.state = {
message: 'Please subscribe',
status: "Subscribe"
}
}
changeMessage()
{
if (this.state.status === 'Subscribe')
{
this.setState({message : 'Thank You For Scubscribing.', status: 'Unsubscribe'})
}
else
{
this.setState({ message: 'Please subscribe', status: 'Subscribe' })
}
}
render()
{
return (
<div>
<h1>{this.state.message}</h1>
<button onClick={()=> this.changeMessage() } >{this.state.status}</button>
</div>
)
}
}
export default TestState;
Output
I started learning React recently and wanted to build a tab just to see how far my knowledge has gone. I came across this and decided to implement something without redux. I kind of feel the answers don't reflect what op wants to achieve. He wants only one active component but the answers here will set all components active. I have given it a shot.
Below is a tab file
import React, { Component } from 'react';
class Tab extends Component {
render(){
const tabClassName = "col-xs-3 tab-bar";
const activeTab = this.props.activeKey === this.props.keyNumber ? "active-tab" : null;
return (
<div
className = {`${tabClassName} ${activeTab}`}
onClick={()=>this.props.onClick(this.props.keyNumber)}
>
I am here
</div>
);
}
}
export default Tab;
The tabs file...
import React, { Component } from 'react';
import Tab from './tab';
class Tabs extends Component {
constructor(props){
super(props);
this.state = {
currentActiveKey: 0,
tabNumber: 2
};
this.setActive = this.setActive.bind(this);
this.setTabNumber = this.setTabNumber.bind(this);
}
setTabNumber(number){
this.setState({
tabNumber: number
});
}
setActive (key){
this.setState({
currentActiveKey: key
});
}
render(){
let tabs = [];
for(let i = 0; i <= this.state.tabNumber; i++){
let tab = <Tab key={i} keyNumber={i} onClick={this.setActive} activeKey={this.state.currentActiveKey}/>;
tabs.push(tab);
}
return (
<div className="row">
{tabs}
</div>
);
}
}
export default Tabs;
your index file...
import React from 'react';
import ReactDOM from 'react-dom';
import Tabs from './components/tabs';
ReactDOM.render(
<Tabs />
, document.querySelector('.container'));
and the css
.tab-bar {
margin: 10px 10px;
border: 1px solid grey;
}
.active-tab {
border-top: 1px solid red;
}
This is a skeleton of something I want to improve on so increasing the tabNumber beyond 4 will break the css.
Here is a code I came Up with:
import React, {Component} from "react";
import './header.css'
export default class Header extends Component{
state = {
active : false
};
toggleMenuSwitch = () => {
this.setState((state)=>{
return{
active: !state.active
}
})
};
render() {
//destructuring
const {active} = this.state;
let className = 'toggle__sidebar';
if(active){
className += ' active';
}
return(
<header className="header">
<div className="header__wrapper">
<div className="header__cell header__cell--logo opened">
<a href="#" className="logo">
<img src="https://www.nrgcrm.olezzek.id.lv/images/logo.svg" alt=""/>
</a>
<a href="#" className={className}
onClick={ this.toggleMenuSwitch }
data-toggle="sidebar">
<i></i>
</a>
</div>
<div className="header__cell">
</div>
</div>
</header>
);
};
};
Just wanted to add my approach. Using hooks and context provider.
Nav.js
function NavBar() {
const filterDispatch = useDispatchFilter()
const {filter} = useStateFilter()
const activeRef = useRef(null)
const completeRef = useRef(null)
const cancelRef = useRef(null)
useEffect(() => {
let activeClass = '';
let completeClass = '';
let cancelClass = '';
if(filter === ACTIVE_ORDERS){
activeClass='is-active'
}else if ( filter === COMPLETE_ORDERS ){
completeClass='is-active'
}else if(filter === CANCEL_ORDERS ) {
cancelClass='is-active'
}
activeRef.current.className = activeClass
completeRef.current.className = completeClass
cancelRef.current.className = cancelClass
}, [filter])
return (
<div className="tabs is-centered">
<ul>
<li ref={activeRef}>
<button
className="button-base"
onClick={() => filterDispatch({type: 'FILTER_ACTIVE'})}
>
Active
</button>
</li>
<li ref={completeRef}>
<button
className="button-base"
onClick={() => filterDispatch({type: 'FILTER_COMPLETE'})}
>
Complete
</button>
</li>
<li ref={cancelRef}>
<button
className={'button-base'}
onClick={() => filterDispatch({type: 'FILTER_CANCEL'})}
>
Cancel
</button>
</li>
</ul>
</div>
)
}
export default NavBar
filterContext.js
export const ACTIVE_ORDERS = [
"pending",
"assigned",
"pickup",
"warning",
"arrived",
]
export const COMPLETE_ORDERS = ["complete"]
export const CANCEL_ORDERS = ["cancel"]
const FilterStateContext = createContext()
const FilterDispatchContext = createContext()
export const FilterProvider = ({ children }) => {
const [state, dispatch] = useReducer(FilterReducer, { filter: ACTIVE_ORDERS })
return (
<FilterStateContext.Provider value={state}>
<FilterDispatchContext.Provider value={dispatch}>
{children}
</FilterDispatchContext.Provider>
</FilterStateContext.Provider>
)
}
export const useStateFilter = () => {
const context = useContext(FilterStateContext)
if (context === undefined) {
throw new Error("place useStateMap within FilterProvider")
}
return context
}
export const useDispatchFilter = () => {
const context = useContext(FilterDispatchContext)
if (context === undefined) {
throw new Error("place useDispatchMap within FilterProvider")
}
return context
}
export const FilterReducer = (state, action) => {
switch (action.type) {
case "FILTER_ACTIVE":
return {
...state,
filter: ACTIVE_ORDERS,
}
case "FILTER_COMPLETE":
return {
...state,
filter: COMPLETE_ORDERS,
}
case "FILTER_CANCEL":
return {
...state,
filter: CANCEL_ORDERS,
}
}
return state
}
Works fast, and replaces redux.
const aDiv = useRef(null);
function app(){
const [isDark, setIsDark] = useState();
useEffect(()=>{
if(isDark){
aDiv.current.classList.add('dark-mode')
}else{
aDiv.current.classList.remove('dark-mode')
}
},[isDark]}
return <div className = "app" ref = {aDiv}> </div>
useRef to id the element to toggle the class, then a boolean useState to track switching, on true, we get the ref's current classList then add a className else we remove the className.
All this happen in the useEffect with our useState as dependency array.
import { useState } from "react";
import "./App.css";
export default function App() {
const [isActive, setIsActive] = useState(false);
const handleClick = (event) => {
// ️ toggle isActive state on click
setIsActive((current) => !current);
};
return (
<div>
<button className={isActive ? "bg-salmon" : ""} onClick={handleClick}>
Click
</button>
</div>
);
}

React show/hide content

I'm trying to make a toggle content button with React. But I can only get it to open, not to close when I click on it again. Can someone please take a look and let me know what I need to change within the code to accomplish it?
Here's what I have so far:
class Test extends React.Component {
constructor(props) {
super(props)
this.state = {
activeLocation: 0,
}
}
changeActiveLocation = (activeLocation) => {
this.setState({
activeLocation: activeLocation,
});
}
render() {
const activeLocation = company.locations[this.state.activeLocation];
return (
{company.locations.map((location, index) => (
<div className="test-item">
<div className="test-item-container" onClick={() => {this.changeActiveLocation(index)}}>
<div className="test-item-header">
<h3>Text goes here!</h3>
<a><FontAwesomeIcon icon={(this.state.activeLocation === index) ? 'times' : 'chevron-right'} /></a>
</div>
</div>
</div>
))}
)
}
}
Thank you!
You're setting the active location to be the same location that you've clicked already so the this.state.activeLocation === index is always true. I would refactor the locations to their own component with an isOpen state value that gets updated when the location is clicked. So like the following:
// test class
class Test extends React.Component {
constructor(props) {
super(props)
this.state = {
activeLocation: 0,
}
}
changeActiveLocation = (activeLocation) => {
this.setState({
activeLocation: activeLocation,
});
}
render() {
const activeLocation = company.locations[this.state.activeLocation];
return (
{company.locations.map((location, index) => (
<LocationItem location={location} onClick={() => this.changeActiveLocation(index)} />
))}
)
}
}
// LocationItem
class LocationItem extends React.Component {
state = { isOpen: false };
handleClick = () => {
this.setState(prevState => { isOpen: !prevState.isOpen});
// call parent click to set new active location if that's still needed
if(this.props.onClick) this.props.onClick;
}
render() {
return <div className="test-item">
<div className="test-item-container" onClick={this.handleClick}>
<div className="test-item-header">
<h3>Text goes here!</h3>
<a><FontAwesomeIcon icon={(this.state.isOpen ? 'times' : 'chevron-right'} /></a>
</div>
</div>
</div>
}
}

Toggle modal in React

I have a parent container which has a couple of child components. When the user clicks onClick={props.toggleReviewForm}, the function
toggleReviewForm () {
this.setState(prevState => ({
reviewFormActive: !prevState.reviewFormActive,
displayNameModalActive: !prevState.displayNameModalActive
}))
}
toggles the reviewForm state to visible. It's visibility is set with reviewFormActive={reviewFormActive} in the child component and the parent has `this.state = {reviewFormActive: false} set in the constructor. I am passing
displayNameModalActive={displayNameModalActive}
into the child component for the modal, but getting the error
Uncaught TypeError: Cannot read property 'displayNameModalActive' of undefined at DisplayNameModal.render
Parent Container
class ReviewsContainer extends React.Component {
constructor (props) {
super(props)
this.state = {
reviewFormActive: false,
displayNameModalActive: false
}
this.config = this.props.config
this.toggleReviewForm = this.toggleReviewForm.bind(this)
}
toggleReviewForm () {
this.setState(prevState => ({
reviewFormActive: !prevState.reviewFormActive,
displayNameModalActive: !prevState.displayNameModalActive
}))
}
render () {
const {
reviewFormActive,
displayNameModalActive
} = this.state
return (
<div className='reviews-container'>
<ReviewForm
config={this.config}
reviewFormActive={reviewFormActive}
toggleReviewForm={this.toggleReviewForm}
/>
{this.state.displayName &&
<div className='modal-container'>
<DisplayNameModal
bgImgUrl={this.props.imageUrl('displaynamebg.png', 'w_1800')}
config={this.config}
displayNameModalActive={displayNameModalActive}
displayName={this.state.displayName}
email={this.state.email} />
</div>
}
</div>
)
}
}
export default ReviewsContainer
Child Component (modal)
class DisplayNameModal extends React.Component {
constructor (props){
super(props)
this.state = {
displayName: this.props.displayName,
email: this.props.email.split('#')[0]
}
}
render (props) {
const {contentStrings} = this.props.config
return (
<div>
//Should only allow the modal to show if the username is the same as the email or there is no username available
{ props.displayNameModalActive && this.state.displayName === this.state.email || !this.state.displayName &&
<div className='display-name-container' style={{ backgroundImage: `url(${this.props.bgImgUrl})` }}>
<div className='display-name-content'>
<h2 className='heading'>{contentStrings.displayNameModal.heading}</h2>
<p>{contentStrings.displayNameModal.subHeading}</p>
<input type="text"
defaultValue={this.state.displayName}
placeholder={this.state.displayName}
minLength="3"
maxLength="15"/>
<button
onClick={this.updateDisplayName}
className='btn btn--primary btn--md'>
<span>{contentStrings.displayNameModal.button}</span>
</button>
<p className='cancel'>{contentStrings.displayNameModal.cancel}</p>
</div>
</div>
}
</div>
)
}
}
export default DisplayNameModal
Whyt this:
props.displayNameModalActive
and not this:
this.props.displayNameModalActive
?
Correct me if I'm wrong but render doesn't get props as argument.

React Passing state to sibling component and up to parent class

Very very new to React and I seem to be stuck. This is a simple Todo app, I basically have 3 components, the base component, an input component and a task component. I have figured out how to edit the state within each component but I am having trouble passing state from component to component.
class App extends Component {
render() {
return (
<div id="appContainer">
<HeaderTitle />
<TaskInput />
<Task taskState={true} text="task one" />
<Task taskState={true} text="task two" />
<Task taskState={true} text="task three" />
</div>
);
}
}
class TaskInput extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
update(e) {
this.setState({inputValue: e.target.value});
console.log(this.state);
}
taskCreate(e) {
this.setState({text: this.state.inputValue, completeState: false});
console.log('button clicked');
console.log(this.state);
}
render () {
return (
<div className="taskInputContainer">
<TaskInputField update={this.update.bind(this)} taskCreate={this.taskCreate.bind(this)} />
</div>
)
}
}
class Task extends Component {
constructor(props) {
super();
this.state = {
completeState: false
}
}
toggleTask (e) {
this.setState({
completeState: !this.state.completeState
});
}
delete (item) {
}
render() {
return (
<div className="taskContainer" onClick={this.toggleTask.bind(this)}>
<div className={"taskState " + this.state.completeState}></div>
<div className={"taskText " + this.state.completeState }>{this.props.text}</div>
<div className="taskDelete"><i className="fa fa-times-circle-o" aria-hidden="true"></i></div>
</div>
);
}
}
const TaskInputField = (props) =>
<div className="taskInputContainer">
<input type="text" className="taskInputField" onChange={props.update}/>
<i className="fa fa-plus-circle" aria-hidden="true" onClick={props.taskCreate}></i>
</div>;
Task.propTypes = {
text: PropTypes.string.isRequired,
completeState: PropTypes.bool
};
Task.defaultProps = {
text: 'Task',
completeState: false
};
const HeaderTitle = () => (
<h1>Davids Todo List</h1>
);
export default App;
So in the TaskInput has its own state that I can update but how do I pass that up to the parent component to update and add a Task component? Also how do I add a Task component without re-rendering the whole thing?
This issue is documented in detail in the article 'lifting the state up' in React's documentation.
TLDR, you create a handler that updates the state of the current component and pass it to children as props. In the example below (a modified version of your code), I passed down the methods that changes the state of component App, into its children components (TaskInput and Tasks).
class App extends React.Component {
constructor() {
super();
this.state = {
tasks: [],
}
}
addTask = (e, text) => {
e.preventDefault();
const newTask = {
id: new Date().getTime(),
done: false,
text
};
const newTasks = this.state.tasks.concat([newTask]);
this.setState({
tasks: newTasks
})
}
toggleTask = (id) => {
const updatedTask = this.state.tasks.filter(task => task.id === id);
updatedTask[0].done = !updatedTask[0].done;
const newTasks = this.state.tasks.map(task => {
if (task.id === id) {
return updatedTask[0];
}
return task;
});
this.setState({
tasks: newTasks
});
}
render() {
return (
<div id="appContainer">
<HeaderTitle />
<TaskInput addTask={this.addTask} />
{
this.state.tasks.length > 0 ? <Tasks tasks={this.state.tasks} toggleTask={this.toggleTask}/> : <div>no tasks yet</div>
}
</div>
);
}
}
class TaskInput extends React.Component {
constructor(props) {
super(props);
this.state = {
currentInput: ''
}
}
handleChangeText = (e) => {
this.setState({
currentInput: e.target.value,
})
}
render() {
return (<form>
<input type="text" value={this.state.currenInput} onChange={this.handleChangeText}/><input type="submit" onClick={(e) => this.props.addTask(e, this.state.currentInput)} value="Add Task"/></form>)
}
}
const Tasks = (props) => (
<div>
{
props.tasks.map(task => (
<div
style={ task.done ? { textDecoration: 'line-through'} : {} }
onClick={() => props.toggleTask(task.id)}
>{task.text}</div>
))
}
</div>
);
const HeaderTitle = () => (
<h1>Davids Todo List</h1>
);
ReactDOM.render(<App />, document.getElementById('app'))
<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>

Toggle show/hide to a specific element

I have this particular code that shows a list of questions and buttons for each of it. When I click the button, it will show the specific answer to the question. 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.
Here is the code
class App extends React.Component {
constructor(){
super()
this.state = {
answer: [],
isHidden: true
}
this.toggleHidden = this.toggleHidden.bind(this)
}
componentWillMount(){
fetch('http://www.reddit.com/r/DrunkOrAKid/hot.json?sort=hot')
.then(res => res.json())
.then( (data) => {
const answer = data.data.children.map(obj => obj.data);
this.setState({answer});
})
}
toggleHidden(){
this.setState({isHidden: !this.state.isHidden})
}
render(){
const answer = this.state.answer.slice(2)
return <div>
<h1>Drunk or Kid</h1>
{answer.map(answer =>
<div key={answer.id}>
<p className="title">{answer.title}</p>
<button onClick={this.toggleHidden}>Answer</button>
{!this.state.isHidden && <Show>{answer.selftext}</Show>}
</div>
)}
</div>
}
}
const Show = (props) => <p className="answer">{props.children}</p>
And here is the link to the codepen
Here is a Codepen based on my suggestion:
The basics of the child component would be:
class Question extends React.Component {
// Set initial state of isHidden to false
constructor() {
super();
this.state = {
isHidden: false
}
}
// Toggle the visibility
toggleHidden() {
this.setState({
isHidden: !this.state.isHidden
});
}
// Render the component
render() {
const { answer } = this.props;
return (
<div key={answer.id}>
<p className="title">{answer.title}</p>
<button onClick={ () => this.toggleHidden() }>Answer</button>
{this.state.isHidden && <Show>{answer.selftext}</Show>}
</div>
);
}
}
Then you would map over it within the parent component as:
answer.map(answer =>
<Question answer={answer} key={answer.id} />
)
Another option is to add a state that save opened answer id, then checking if specific answer in that state or not.
let's see in action
class SomeComponent extends React.Component {
constructor(props){
super(props)
this.state = {
opened: []
}
this.toggleShowHide = this.toggleShowHide.bind(this)
}
toggleShowHide(e){
const id = parseInt(e.currentTarget.dataset.id)
if (this.state.opened.indexOf(id) != -1){
// remove from array
this.setState({opened: this.state.opened.filter(o => o !== id)})
} else {
this.setState({opened: [...this.state.opened, id]})
}
}
render(){
return <ul>
{ this.state.answers.map(ans => (
<li key={ans.id} data-id={ans.id}>
question
<button onClick={this.toggleShowHide}>show answer</button>
<span
style={{ display: this.state.opened.indexOf(ans.id) !== -1 ? 'block' : 'none' }}>answer</span>
</li>
))}
</ul>
}
}
Here is video in action https://www.youtube.com/watch?v=GJsPEsckB4w

Resources