I'm attempting to have an icon's state change depending on whether or not there's data present. For example, if the 'Likes' counter is greater than 0 than the circle will be filled in, otherwise, it will be an empty circle.
I have not been able to figure out how to render this via useState
import React, { useState } from "react";
import ActivityIconEngaged from "./ActivityIconEngaged";
import { Modal, Button } from "react-bootstrap";
import PropTypes from "prop-types";
import data from "../Assets/ActivityData";
import { faCircle } from "#fortawesome/pro-regular-svg-icons";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
class ActivityIcon extends React.Component {
constructor(props) {
super(props);
this.state = {
activities: data.activities,
activity: data.activities[0]
};
}
render() {
const { activities, activity } = this.state;
return (
<div>
{activities.map(activity => (
<ActivityIconData key={activity._id} activity={activity} />
))}
</div>
);
}
}
function ActivityIconData({ activity }) {
const { index, likeCount } = activity;
const [show, setShow] = useState(false);
const handleClose = () => {
setShow(false);
};
const [trigger, setTrigger] = useState({ showContent: true });
const showContent = trigger;
const openModal = e => {
e.preventDefault();
setShow(true);
};
if ({ likeCount } > 0) {
setTrigger(!trigger);
}
return (
<>
<div id={`activity-${index}`}>
<span onClick={openModal} className="mr-1">
{showContent ? (
<FontAwesomeIcon
icon={faCircle}
size="2x"
className="ActivityIconDefault"
/>
) : (
<ActivityIconEngaged />
)}
</span>
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Activity</Modal.Title>
</Modal.Header>
<Modal.Body>likes: {likeCount}</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
<Button variant="primary" onClick={handleClose}>
Save Changes
</Button>
</Modal.Footer>
</Modal>
</div>
</>
);
}
ActivityIconData.propTypes = {
activity: PropTypes.object.isRequired
};
export default ActivityIcon;
The result that I'm currently getting is that the modal successfully opens when the circle is clicked, HOWEVER, the circle is not filled when the number of likes exceeds 0.
I think you're declaring too many pieces of state to handle this. You don't need a likeCount as well as a trigger variable with a showContent property.
If you want to use a ternary operator to show the icon, you can just replace showContent with an expression for likeCount:
{likeCount > 0 ? <FontAwesomeIcon icon={faCircle} size="2x" className="ActivityIconDefault" /> : <ActivityIconEngaged />}
In first look, your condition is always false because you making a comparison between object and a number.
Two distinct objects are never equal for either strict or abstract comparisons.
// instead of { likeCount } > 0, which always results false
if (likeCount > 0) {
setTrigger(!trigger);
}
Related
I'm a beginner in React and I've seen for the moment only the basics. I have a task at work which consists in making the component of the Terms Of Services whis is in OptInPage.jsx reusable. I tried several things without really understanding what I was doing, I looked at the documentation but the examples seems to be more basic than my problem.
I was thinking of extracting the page and creating a new TermsOfServices component to integrate between the jsx tags .
The problem here is that on the one hand, there should be a "read only" property to prevent the user from modifying the content, and on the other hand, I don't really understand how to pass props from my OptinPage component to my new TermsOfServices component.
Sorry in advance if my problem may seem stupid or something like that, I'm really stuck with that. Thank you in advance for your help.
OptinPage.jsx
import API from 'api';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, intlShape } from 'react-intl';
import {
Block,
BlockTitle,
Col,
Fab,
Icon,
Link,
NavRight,
Navbar,
Page,
Popup,
Preloader,
} from 'framework7-react';
import { connect } from 'react-refetch';
import ReactHtmlParser from 'react-html-parser';
import './OptInPage.scss';
class OptInPage extends PureComponent {
static propTypes = {
agreeTosFunc: PropTypes.func.isRequired,
agreeTos: PropTypes.object,
logout: PropTypes.func.isRequired,
onSucceeded: PropTypes.func,
opened: PropTypes.bool.isRequired,
tos: PropTypes.object.isRequired,
};
static contextTypes = {
apiURL: PropTypes.string,
intl: intlShape,
loginToken: PropTypes.string,
logout: PropTypes.func,
userId: PropTypes.string,
};
static defaultProps = {
agreeTos: {},
onSucceeded: () => {},
};
state = {
currentTos: -1,
};
componentDidUpdate(prevProps) {
const {
agreeTos,
onSucceeded,
opened,
tos,
} = this.props;
const { currentTos } = this.state;
/* Reset currentTos on opened */
if (!prevProps.opened && opened) {
this.setState({ currentTos: -1 });
}
/* Prepare for first tos after receiving all of them */
if (
prevProps.tos.pending &&
tos.fulfilled &&
tos.value.length &&
currentTos < 0
) {
this.setState({ currentTos: 0 });
}
/* When sending ToS agreement is done */
if (
prevProps.agreeTos.pending &&
agreeTos.fulfilled
) {
onSucceeded();
}
}
handleNext = () => {
const { agreeTosFunc, tos } = this.props;
const { currentTos: currentTosId } = this.state;
const termsOfServices = tos.value;
const done = currentTosId + 1 === termsOfServices.length;
this.setState({ currentTos: currentTosId + 1 });
if (done) {
agreeTosFunc(termsOfServices.map((v) => v._id));
}
};
render() {
const { logout, opened, tos } = this.props;
const { intl } = this.context;
const { formatMessage } = intl;
const { currentTos: currentTosId } = this.state;
const termsOfServices = tos.value;
const currentTermsOfServices = termsOfServices && termsOfServices[currentTosId];
const loaded = termsOfServices && !tos.pending && tos.fulfilled;
const htmlTransformCallback = (node) => {
if (node.type === 'tag' && node.name === 'a') {
// eslint-disable-next-line no-param-reassign
node.attribs.class = 'external';
}
return undefined;
};
return (
<Popup opened={opened} className="demo-popup-swipe" tabletFullscreen>
<Page id="optin_page">
<Navbar title={formatMessage({ id: 'press_yui_tos_title' })}>
<NavRight>
<Link onClick={() => logout()}>
<FormattedMessage id="press_yui_comments_popup_edit_close" />
</Link>
</NavRight>
</Navbar>
{ (!loaded || !currentTermsOfServices) && (
<div id="optin_page_content" className="text-align-center">
<Block className="row align-items-stretch text-align-center">
<Col><Preloader size={50} /></Col>
</Block>
</div>
)}
{ loaded && currentTermsOfServices && (
<div id="optin_page_content" className="text-align-center">
<h1>
<FormattedMessage id="press_yui_tos_subtitle" values={{ from: currentTosId + 1, to: termsOfServices.length }} />
</h1>
<BlockTitle>
{ReactHtmlParser(
currentTermsOfServices.title,
{ transform: htmlTransformCallback },
)}
</BlockTitle>
<Block strong inset>
<div className="tos_content">
{ReactHtmlParser(
currentTermsOfServices.html,
{ transform: htmlTransformCallback },
)}
</div>
</Block>
<Fab position="right-bottom" slot="fixed" color="pink" onClick={() => this.handleNext()}>
{currentTosId + 1 === termsOfServices.length &&
<Icon ios="f7:check" aurora="f7:check" md="material:check" />}
{currentTosId !== termsOfServices.length &&
<Icon ios="f7:chevron_right" aurora="f7:chevron_right" md="material:chevron_right" />}
</Fab>
{currentTosId > 0 && (
<Fab position="left-bottom" slot="fixed" color="pink" onClick={() => this.setState({ currentTos: currentTosId - 1 })}>
<Icon ios="f7:chevron_left" aurora="f7:chevron_left" md="material:chevron_left" />
</Fab>
)}
</div>
)}
</Page>
</Popup>
);
}
}
export default connect.defaults(new API())((props, context) => {
const { apiURL, userId } = context;
return {
tos: {
url: new URL(`${apiURL}/tos?outdated=false&required=true`),
},
agreeTosFunc: (tos) => ({
agreeTos: {
body: JSON.stringify({ optIn: tos }),
context,
force: true,
method: 'PUT',
url: new URL(`${apiURL}/users/${userId}/optin`),
},
}),
};
})(OptInPage);
If I've understood you right, you need smth like this.
First of all, you need to create a new file with you component and put the next code there
// don't forget to clean the unused imports
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, intlShape } from 'react-intl';
import {Block, BlockTitle, Col, Fab, Icon, Link, NavRight, Navbar, Page, Popup, Preloader } from 'framework7-react';
import ReactHtmlParser from 'react-html-parser';
export default class YourCLassName extends PureComponent<Props, State> {
static propTypes = {
currentTosId: PropTypes.object, // write correct type here
termsOfServices: PropTypes.object, // write correct type here
currentTermsOfServices: PropTypes.string, // write correct type here
handleNext: PropTypes.func, // write correct type here
handlePrev: PropTypes.func, // write correct type here
};
function htmlTransformCallback (node) => {
if (node.type === 'tag' && node.name === 'a') {
// eslint-disable-next-line no-param-reassign
node.attribs.class = 'external';
}
return undefined;
};
render() {
const { currentTosId, termsOfServices, currentTermsOfServices } = this.props;
return (
<div id="optin_page_content" className="text-align-center">
<h1>
<FormattedMessage id="press_yui_tos_subtitle" values={{ from: currentTosId + 1, to: termsOfServices.length }} />
</h1>
<BlockTitle>
{ReactHtmlParser(
currentTermsOfServices.title,
{ transform: htmlTransformCallback },
)}
</BlockTitle>
<Block strong inset>
<div className="tos_content">
{ReactHtmlParser(
currentTermsOfServices.html,
{ transform: htmlTransformCallback },
)}
</div>
</Block>
<Fab position="right-bottom" slot="fixed" color="pink" onClick={() => this.props.handleNext()}>
{currentTosId + 1 === termsOfServices.length &&
<Icon ios="f7:check" aurora="f7:check" md="material:check" />}
{currentTosId !== termsOfServices.length &&
<Icon ios="f7:chevron_right" aurora="f7:chevron_right" md="material:chevron_right" />}
</Fab>
{currentTosId > 0 && (
<Fab position="left-bottom" slot="fixed" color="pink" onClick={() => this.props.handlePrev())}>
<Icon ios="f7:chevron_left" aurora="f7:chevron_left" md="material:chevron_left" />
</Fab>
)}
</div>
);
}
}
And in the OptinPage.jsx you need to replace all this code that concerns your newComponent with
<YourComponentName currentTosId={this.state.currentTosId} termsOfServices={termsOfServices} currentTermsOfServices={currentTermsOfServices} handleNext={this.handleNext} handlePrev={this.handlePrev} />
So the main point is to cut off the code, that you think can be performed by one component, and send it through props
You can also save necessary state in component itself, not in a parent component, if no other component needs these props
I am currently creating a React todo application. So basically I have two component TodoList and TodoItem
TodoList component will receive an array of objects consisting title as the property and map through this with TodoItem component
In my TodoItem component, the user can choose to edit or delete the item. If the user chose to edit, a modal will show up with the existing title using a textarea. However, I have trouble implementing this function as the modal will always show up with the last element of the array.
TodoList Component
import React, { Component } from 'react'
import TodoItem from './TodoItem'
import { connect } from 'react-redux'
import { clear_todo } from '../store/actions/todoActions'
class Todolist extends Component {
clearList = (e) => {
e.preventDefault()
this.props.clearList(clear_todo());
}
handleChange = (index, title) => {
this.setState({
[index]: title
})
}
render() {
const { items, editItem } = this.props.todo
return (
<ul className="list-group my-5">
<h3 className="text-capitalize text-center">
Todo List
</h3>
{
items.map((item, index) => {
const editedTitle = item.title
return (<TodoItem key={item.id} title={item.title}
id={item.id}
editedTitle={editedTitle}
onChange={this.handleChange}
editItem={editItem} />)
})
}
<button className="btn btn-danger btn-block text-capitalize mt-5" onClick={this.clearList}> Clear List </button>
</ul>
)
}
}
const mapStateToProps = state => {
return {
todo: state.todo
}
}
const mapDispatchToProps = dispatch => {
return {
clearList: (clear_todo) => { dispatch(clear_todo) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Todolist)
TodoItem Component
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { delete_todo, edit_todo, toggle_edit } from '../store/actions/todoActions'
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Input } from 'reactstrap';
import TodoEditItem from './TodoEditItem'
class Todoitem extends Component {
// constructor(props) {
// super(props)
// this.state = {
// [props.id]: props.title
// }
// }
handleEdit = (id, title) => {
this.props.editTodo(edit_todo(id, title))
}
toggleEdit = (editItem, title) => {
this.props.toggleEdit(toggle_edit(!editItem))
// this.initializeTitle(title)
}
handleDelete = (id) => {
this.props.deleteTodo(delete_todo(id))
}
// onChange = (e, id) => {
// this.setState({
// [id]: e.target.value
// })
// }
componentDidMount() {
// console.log(this.props)
// this.initializeTitle(this.props.title)
this.setState({
[this.props.id]: this.props.editedTitle
})
console.log(this.state)
}
render() {
// console.log(this.state)
let { id, title, editItem, editedTitle, index } = this.props
console.log(id)
// console.log(index)
// let { item } = this.state
return (
<div>
<li className="list-group-item text-capitlize d-flex justify-content-between my-2">
<h6>{title}</h6>
<div className="todo-icon">
<span className="mx-2 text-success" onClick={this.toggleEdit.bind(this, editItem)} >
<i className="fas fa-pen"></i>
</span>
<span className="mx-2 text-danger" onClick={this.handleDelete.bind(this, id)}>
<i className="fas fa-trash"></i>
</span>
</div>
<Modal isOpen={editItem}>
<ModalHeader>Edit Todo Item</ModalHeader>
<ModalBody>
<Form>
<FormGroup row>
<Input type="textarea" name="text" value={this.state ? this.state[id] : ""} onChange={this.props.onChange} />
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.handleEdit.bind(this, id, editedTitle)}>Save</Button>{' '}
<Button color="secondary" onClick={this.toggleEdit.bind(this, editItem)}> Cancel</Button>
</ModalFooter>
</Modal>
</li>
{/* {editItem ? <TodoEditItem title={title} editItem={editItem} /> : ''} */}
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
deleteTodo: (delete_todo) => { dispatch(delete_todo) },
editTodo: (edit_todo) => { dispatch(edit_todo) },
toggleEdit: (toggle_edit) => { dispatch(toggle_edit) },
}
}
export default connect(null, mapDispatchToProps)(Todoitem)
Sample Image
Image of TodoItem
Image of Modal
As you can see from the image of TodoItem, I am trying to edit "Take out the garbage" but my textarea has been prepopulated as the last element.
You're using the same variable to determine all of the TodoItem's open state. The result is it appears that it is only taking the last value from the array, but really each modal is opening at once and the last one is the only visible modal.
// editItem here is used to determine the open state of both modals
const { items, editItem } = this.props.todo
...
{
items.map((item, index) => {
const editedTitle = item.title
return (
<TodoItem
key={item.id}
title={item.title}
id={item.id}
editedTitle={editedTitle}
onChange={this.handleChange}
editItem={editItem} // Same value
/>
)
})
}
...
let { editItem } = this.props
<Modal isOpen={editItem}> // All will open and close at the same time
Instead, use a different flag, or just manage the open state in each child like this:
<span className="mx-2 text-success" onClick={() => this.setState({open: true})} >
<i className="fas fa-pen"></i>
</span>
...
<Modal isOpen={this.state.open}>
I change the renderer with one click and then directly query an item (ref). setTimeout a good solution?
(I don't know if I change the renderer in a single click, then in the event I can do anything in the fresh renderer. setTimeout good solution? Someone else has a different solution because I feel like I didn't do it well.)
import React from 'react';
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
con1: false,
con2: false
};
}
handleClick = (e) => {
parseFloat(e.currentTarget.getAttribute('data-id')) === 1 ?
(this.setState({
con1: true,
con2: false
}))
:
(this.setState({
con2: true,
con1: false
}));
/* Good, but this is valid??? */
setTimeout(()=>{
console.log(this.buttonContainer.childNodes[0])
},0)
/* Not good
console.log(this.buttonContainer.childNodes[0]);
*/
}
render() {
const { con1, con2 } = this.state;
return (
<div className="app-container">
<button
data-id="1"
onClick = {(e) => this.handleClick(e)}
>
Button
</button>
<button
data-id="2"
onClick = {(e) => this.handleClick(e)}
>
Button
</button>
<div
className="button-conteiner"
ref={(ref) => this.buttonContainer = ref}
>
{ con1 ?
(<div className="container1">container1</div>)
:
(null)
}
{ con2 ?
(<div className="container2">container2</div>)
:
(null)
}
</div>
</div>
)}
}
export default Test;
I would suggest to use useEffect hook with function components in this case. the logic will be more readable
import React, { useState, useEffect, useRef } from "react";
const Test = props => {
const [con1, setCon1] = useState(false);
const [con2, setCon2] = useState(false);
const buttonContainer = useRef(null);
const handleClick = e => {
const b = parseFloat(e.currentTarget.getAttribute("data-id")) === 1;
setCon1(b);
setCon2(!b);
};
useEffect(() => {
if(buttonContainer.current) {
console.log(buttonContainer.current.childNodes[0])
}
}, [buttonContainer.current, con1, con2])
return (
<div className="app-container">
<button data-id="1" onClick={e => this.handleClick(e)}>
Button
</button>
<button data-id="2" onClick={e => this.handleClick(e)}>
Button
</button>
<div
className="button-conteiner"
ref={buttonContainer}
>
{con1 ? <div className="container1">container1</div> : null}
{con2 ? <div className="container2">container2</div> : null}
</div>
</div>
);
};
export default Test;
When I create a custom toolbar in the same file as the Calendar I can use the onView('day') method totally fine. It changes the views. However, when I put the same CalendarToolbar in a different file, and import it in the Calendar file it, doesn't update or change the view. I get the methods as props but it doesn't change anything.
// CustomToolbar
const CalendarToolbar = ({ onView, label, views }) => (
<div>
<h2>
{label}
</h2>
{views.map(view => (
<button
key={view}
type="button"
onClick={() => onView(view)}
>
{view}
</button>
))}
</div>
);
<Calendar
localizer={localizer}
defaultDate={new Date()}
defaultView="day"
events={mockEvents}
style={{ height: '100vh' }}
onSelectEvent={this.handleSelectEvent}
components={{
toolbar: CalendarToolbar,
}}
/>
Just wondered what I'm doing wrong?
I recently wrote my own custom Toolbar component. I copied the original Toolbar, from the repository, and then replaced the render() method with my own, copying what they had done and including my own stuff. My implementation stuff isn't completely important, but if you look at the onClick bits below, it may help you in doing what you want to do:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import ToolbarDateHeader from './ToolbarDateHeader.component';
import { Icon, Button, ButtonGroup, ButtonToolbar } from '../app';
const navigate = {
PREVIOUS: 'PREV',
NEXT: 'NEXT',
TODAY: 'TODAY',
DATE: 'DATE'
};
const propTypes = {
view: PropTypes.string.isRequired,
views: PropTypes.arrayOf(PropTypes.string).isRequired,
label: PropTypes.node.isRequired,
localizer: PropTypes.object,
onNavigate: PropTypes.func.isRequired,
onView: PropTypes.func.isRequired
};
export default class Toolbar extends Component {
static propTypes = propTypes;
render() {
let {
localizer: { messages },
label,
date
} = this.props;
return (
<ButtonToolbar>
<ButtonGroup>
<Button onClick={this.navigate.bind(null, navigate.TODAY)}>
{messages.today}
</Button>
<Button onClick={this.navigate.bind(null, navigate.PREVIOUS)}>
<Icon glyph="caret-left" />
</Button>
<Button onClick={this.navigate.bind(null, navigate.NEXT)}>
<Icon glyph="caret-right" />
</Button>
</ButtonGroup>
<ToolbarDateHeader date={date} onChange={this.toThisDay}>
{label}
</ToolbarDateHeader>
<ButtonGroup className="pull-right">
{this.viewNamesGroup(messages)}
</ButtonGroup>
</ButtonToolbar>
);
}
toThisDay = date => {
this.props.onView('day');
// give it just a tick to 'set' the view, prior to navigating to the proper date
setTimeout(() => {
this.props.onNavigate(navigate.DATE, date);
}, 100);
};
navigate = action => {
this.props.onNavigate(action);
};
view = view => {
this.props.onView(view);
};
viewNamesGroup(messages) {
let viewNames = this.props.views;
const view = this.props.view;
if (viewNames.length > 1) {
return viewNames.map(name => (
<Button
key={name}
className={cn({
active: view === name,
'btn-primary': view === name
})}
onClick={this.view.bind(null, name)}
>
{messages[name]}
</Button>
));
}
}
}
import React, { useState, useEffect } from "react";
import clsx from "clsx";
import moment from "moment";
function RBCToolbar(props) {
const { label, date, view, views, onView, onNavigate } = props;
const [month, setMonth] = useState("January");
const mMonth = moment(date).format("MMMM");
const months = moment.months();
useEffect(() => {
setMonth(mMonth);
}, [mMonth]);
const onChange = (event) => {
const current = event.target.value;
onNavigate("DATE", moment().month(current).toDate());
setMonth(current);
};
const goToView = (view) => {
onView(view);
};
const goToBack = () => {
onNavigate("PREV");
};
const goToNext = () => {
onNavigate("NEXT");
};
const goToToday = () => {
onNavigate("TODAY");
};
return (
<div className="rbc-toolbar">
<div className="rbc-btn-group">
<button onClick={goToToday}>Today</button>
<button onClick={goToBack}>Back</button>
<button onClick={goToNext}>Next</button>
</div>
<div className="rbc-toolbar-label">
{view === "month" ? (
<>
<select className="rbc-dropdown" value={month} onChange={onChange}>
{months?.map((month) => (
<option
value={month}
className="rbc-dropdown-option" //custom class
key={month}
>
{month}
</option>
))}
</select>
<span className="rbc-year"> {moment(date).format("YYYY")}</span>
</>
) : (
label
)}
</div>
<div className="rbc-btn-group">
{views?.map((item) => (
<button
onClick={() => goToView(item)}
key={item}
className={clsx({ "rbc-active": view === item })}
>
{item}
</button>
))}
</div>
</div>
);
}
export default RBCToolbar;
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;