React component opens all child states - reactjs

I have a list of courses with student icon on them. When clicked on student icon it opens a modal and displays all assigned students on the course.
The problem I am having is when I click on one of the course to open the modal, it opens the modal for all the other courses. I know its to do with the state behaviour but I can't seem to figure out how best to tackle this problem.
Following is my code:
class CourseDetails extends React.Component {
constructor(props) {
super(props);
autobind(this);
this.state = { openStudentsAssignedToCourseModal: false };
}
closeStudentsAssignedToCourseModal() {
this.setState({ openStudentsAssignedToCourseModal: false });
}
render() {
const { store } = this.props;
const { openStudentsAssignedToCourseModal } = this.state;
return store.allCourses.map((course) => {
return (
<Container key={course.id}>
<p>{course.name}</p>
<UsersIcon
size={25}
onClick={() => {
if (course.listOfStudents.length > 0)
this.setState({
openStudentsAssignedToCourseModal: true
});
}}
/>
{openStudentsAssignedToCourseModal && (
<StudentsOnCourseModal
course={course}
isOpen
close={() => {
this.closeEmployeesAssignedModal();
}}
/>
)}
</Container>
);
});
}
}
Modal:
class StudentsOnCourseModal extends React.Component {
constructor() {
super();
autobind(this);
}
render() {
const { course, isOpen, close } = this.props;
const s = course.listOfStudents.length === 1 ? '' : 's';
return (
<Modal
isOpen={isOpen}
close={close}
width="large"
bgDismiss={false}
>
<ModalHeader>
<h2>Assigned students</h2>
</ModalHeader>
<ModalBody>
<p>
There {s === '' ? 'is' : 'are'}{' '}
<b>{course.listOfStudents.length}</b> student{s}{' '}
currently assigned to the course <b>{course.name}</b>.
</p>
<StudentsContainer>
{course.listOfStudents.map(student => (
<StudentItem key={student.id}>
<StudentCard
name={student.name}
link={`/student-profile/${
student.id
}/personaldetails`}
imageHref={
student._links.image
? student._links.image.href
: undefined
}
/>
</StudentItem>
))}
</StudentsContainer>
<OutlineButton
onClick={e => {
e.preventDefault();
close();
}}
>
Close
</OutlineButton>
</ModalBody>
</Modal>
);
}
}

I wasn't capturing the selected course so it was opening all of them. Fixed it by introducing a new state to capture the selected value and passed that into the modal.

Related

Calling state hook from outside its component

When I run my current code it produces a button for each post in a local JSON file and if I click on the button it expands/contracts. I want to have a button that opens or closes all the posts but I'm unsure how to achieve this, my logic is that since externalData is an array of Post components, handleClick should simply be calling its own function but I get the error element.setOpenStatus is not a function when I press the Open All button.
function Post(props) {
const [openStatus, setOpenStatus] = useState(true);
if (openStatus) {
return (
<button onClick={() => setOpenStatus(false)}>
<p>Title: {props.post.title}</p>
<p>User: {props.post.user}</p>
<p>Time: {props.post.time}</p>
<p>Content: {props.post.content}</p>
<hr />
</button>
);
}
return (
<button onClick={() => setOpenStatus(true)}>
<p>Title: {props.post.title}</p>
<p>User: {props.post.user}</p>
<p>Time: {props.post.time}</p>
<hr />
</button>
);
}
class AllPosts extends React.Component {
constructor(props) {
super(props);
this.state = {
externalData: [],
};
this.handleClick = this.handleClick.bind(this);
this.renderPost = this.renderPost.bind(this);
}
renderPost(element) {
return (
<Post
key={element.title}
post={element}
/>
)
}
checkIfExisting(post, key, value) {
return post[key] === value;
}
// HERE things are going wrong
handleClick() {
this.state.externalData.forEach(element => {
element.setOpenStatus({ openStatus: false });
});
}
render() {
this.props.data.forEach((element) => {
if (!this.state.externalData.some(
(post) => this.checkIfExisting(post, "key", element.title)
)) {
this.state.externalData.push(this.renderPost(element))
}
})
return (
<div>
<h1>using local json file</h1>
<button onClick={this.handleClick}>Open All</button>
<div>
{this.state.externalData}
</div>
</div>
);
}
}
export default AllPosts;

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>
}
}

Multiple buttons do the same

I am building an app in React, that is connected to an API I have written before. Buttons are renderizing but all of them change at the same time. I need advice about how can I write my code in order to separate the functionality.
My app renderize with a .map the same number of Buttons as appointments which is an array. All of them change when this.state.shown change but I need to separate all the buttons in order to only show the one that I clicked. Right now, when I clicked in one of them, this.state.shown change its value so all the buttons change because all depends of the same variable. I am looking for advices about how I can separate this.
class AppointmentsList extends Component {
constructor(props) {
super(props);
this.state = {
appointments: [],
isLoading: false,
shown: false, //Variable to know if a button need to change and render the component
customerUp: false
}
this.toggleCustomer = this.toggleCustomer.bind(this);
//this.showCustomer = this.showCustomer.bind(this);
}
toggleCustomer() {
this.setState({
shown: !this.state.shown
})
} //This function change the value of shown when a Button is clicked.
render() {
const {appointments, isLoading} = this.state;
if(isLoading) {
return <p>Loading...</p>;
}
return(
<div>
<h2>Lista de citas</h2>
{appointments.map((app) =>
<div key={app.id}>
<p>Fecha: {app.appointment}</p>
<p>Cliente: {app.customer.name}</p>
<p>Id: {app.customer.id}</p>
{ this.state.shown ? <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer() }>Ocultar cliente</Button> : <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer() }>Ver cliente</Button> }
{ this.state.shown ? <CustomerView id={app.customer.id} /> : null }
</div>
)}
</div>
)
}
How can I reorganize my code in order to render the Buttons separately?
Thanks in advance.
Method 1: You can make shown state a object like:
state = {
shown:{}
}
toggleCustomer(id) {
const updatedShownState = {...this.state.shown};
updatedShownState[id] = updatedShownState[id] ? false : true;
this.setState({
shown: updatedShownState,
})
} //This function change the value of shown when a Button is clicked.
render() {
const {appointments, isLoading} = this.state;
if(isLoading) {
return <p>Loading...</p>;
}
return(
<div>
<h2>Lista de citas</h2>
{appointments.map((app) =>
<div key={app.id}>
<p>Fecha: {app.appointment}</p>
<p>Cliente: {app.customer.name}</p>
<p>Id: {app.customer.id}</p>
{ this.state.shown[app.customer.id] ? <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer(app.customer.id) }>Ocultar cliente</Button> : <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer() }>Ver cliente</Button> }
{ this.state.shown[app.customer.id] ? <CustomerView id={app.customer.id} /> : null }
</div>
)}
</div>
)
}
Method 2: Make a separate component for Button and Customer
return(
<div>
<h2>Lista de citas</h2>
{appointments.map((app) =>
<Appointment key = {app.id} app = {app} />
)}
</div>
)
}
class Appointment extends Component {
state = {
shown: false,
}
toggleCustomer() {
this.setState({
shown: !this.state.shown
})
}
render() {
const { app } = this.props;
return (
<div key={app.id}>
<p>Fecha: {app.appointment}</p>
<p>Cliente: {app.customer.name}</p>
<p>Id: {app.customer.id}</p>
{ this.state.shown ? <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer() }>Ocultar cliente</Button> : <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer() }>Ver cliente</Button> }
{ this.state.shown ? <CustomerView id={app.customer.id} /> : null }
</div>
)
}
}
Let me know if it works and the method you prefer.
You can create a separate component for your button (buttonComponent) inside your AppointmentsList component and pass the shown has props and the in componentDidMount of buttonComponent copy the props to the state of buttonComponent.
This way each button will have its own state, which manages shown.
Button component:
import react from 'react';
interface buttonComponentProps{
shown: boolean;
}
interface buttonComponentState{
shown: boolean;
}
class buttonComponent extends react.Component<buttonComponentProps,{}>{
constructor(props:buttonComponentProps){
super();
this.state{
shown:props.shown
}
}
....
}
export default buttonComponent;

Bootstrap dropdowns in ReactJS, only one open at a time

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;

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