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;
Related
Please help. I am new to react and I'm trying to toggle an accordion by using state but I'm getting an error on the onClick portion. Cannot read properties of undefined (reading 'toggleAccordion'). I'm confused to what I should do.
Any help would be appreciated. Thank you!
export class Accordion extends Component {
constructor(props) {
super(props);
this.state = {
isActive: false
}
this.toggleAccordion = this.toggleAccordion.bind(this);
this.renderAccordion = this.renderAccordion.bind(this);
}
toggleAccordion = () => {
this.setState({ isActive: !this.state.isActive})
}
renderAccordion = () =>
{
const { items } = this.props;
const { isActive } = this.state;
const accordionItems = items.map(function(item) {
return (
<div>
<button className={styles.accordion} onClick={this.toggleAccordion()}>
{item.title}
<span style={{float: "right"}}>{isActive ? '-' : '+'}</span>
</button>
{isActive && <div className={styles.content} dangerouslySetInnerHTML={{__html: item.content}}></div>}
<br/><br/>
</div>
)
})
return (
{accordionItems}
)
}
render() {
return(
<React.Fragment>
{this.renderAccordion()}
</React.Fragment>
)
}
}
In React you need to provide a reference to the function in event handlers, instead of calling the function. Try (note the parenthesis) :
<button className={styles.accordion} onClick={() => this.toggleAccordion()}>
or
<button className={styles.accordion} onClick={this.toggleAccordion}>
instead of
<button className={styles.accordion} onClick={this.toggleAccordion()}>
See https://reactjs.org/docs/handling-events.html#gatsby-focus-wrapper for more details on how React handle events.
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;
I'm trying to return a component when I click on a button.
(a delete button with a confirmation popup) I've tried some examples I found online but none really worked for me.
Here is my code:
class DeleteTask extends React.Component {
handleClick() {
return <TaskDeleteModal />
}
render() {
return (
<div>
<button type="button" className={styles.deletetask} onClick={() => this.handleClick()}>Verwijderen</button>;
</div>
)
}
}
Thanks for reading, I hope you can assist me.
It did not work because the return <TaskDeleteModal /> of handleClick does not add <TaskDeleteModal /> to your render function.
You need to add <TaskDeleteModal /> to render function and control it visibility by a state:
Try the following code:
class DeleteTask extends React.Component {
this.state = {
showModal: false
}
handleClick() {
this.setState({showModal: true})
}
render() {
return (
<div>
<button type="button" className={styles.deletetask} onClick={() => this.handleClick()}>Verwijderen</button>;
{/* In TaskDeleteModal you might have a button, when you click on that button call this.setState({showModal: false}) to hide the modal */}
{this.state.showModal && <TaskDeleteModal />}
</div>
)
}
}
class DeleteTask extends React.Component {
this.state = {
showModal: false
}
handleClick() {
this.setState({showModal: true})
}
render() {
return (
<div>
<button type="button" className={styles.deletetask} onClick={() => this.handleClick()}>Verwijderen</button>;
{/* In TaskDeleteModal you might have a button, when you click on that button call this.setState({showModal: false}) to hide the modal */}
{this.state.showModal && <TaskDeleteModal />}
</div>
)
}
}
I am newbie in react and I have some trouble that I'd like to solve.
I would like to know how can I show and hide react components before and after to do a rest call.
I have the follow component:
class Loading {
render(){
return (
<div >
<Modal isOpen={true} centered >
<ModalHeader>Loading...</ModalHeader>
<ModalBody >
<div align='center' className="mt-2 mb-2">
<Spinner style={{ width: '4rem', height: '4rem' }} color="primary" />
</div>
</ModalBody>
</Modal>
</div>
)
}
}export default Loading;
And I would like to show this component in other module before to call a rest api and hide this component after the data come. The ideia is some like this:
class List extends Component {
constructor(props) {
super(props)
this.state = {
show : false
}
}
// HERE I WOULD LIKE TO SHOW THE LOADING COMPONENT
callRestApi = () => {
axiosAuth.get(url, getConfig())
.then(response => {
console.log(response.data)
this.setState({
eventos: response.data
})
}).catch(error => {
console.log(error);
return null
});
// HERE I WOULD LIKE TO HIDE THE LOADING COMPONENT
}
render() {
return(
<div>
<Button className="mr-2" color="primary" size="sm" onClick={this.callRestApi}>List All</Button>
</div>
How can I do it?
You can create state that dictates whether the loading spinner is visible or not. And append one last .then in the promise chain to modify it.
class List extends Component {
constructor(props) {
super(props)
this.state = {
show : false,
loaderVisible: true
}
}
// HERE I WOULD LIKE TO SHOW THE LOADING COMPONENT
callRestApi = () => {
axiosAuth.get(url, getConfig())
.then(response => {
console.log(response.data)
this.setState({
eventos: response.data
})
}).catch(error => {
console.log(error);
return null
}).then(() => {
this.setState({loaderVisible: false });
});
}
render() {
return(
<div>
{
this.state.loaderVisible? <Loading /> : ''
}
<Button className="mr-2" color="primary" size="sm" onClick={this.callRestApi}>List All</Button>
</div>
Then utilize ternary syntax on the spinner to determine visibility.
We use state to implement this. Here is the pseudo code.
class List extends Component {
state = { loading: false }
callRestApi = async () => {
this.setState({ loading: true });
await fetch(...);
this.setState({ loading: false });
}
render() {
<div>
{this.state.loading && <Loading />}
<button onClick={this.callRestApi}>List All</button>
</div>
}
}
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.