I am now implementing the react-bootstrap Accordion with the Custom Toggle with the reference below.
https://react-bootstrap.github.io/components/accordion/
But if you have the componentDidUpdate() in your code, Accordion does not work.
You click the Accordion's Custom Toggle.
The Accordion Collapse expands.
But the componentDidUpdate() or componentDidMount() is kicked and it updates the screen.
They extract data from the server by using fetch.
This seems to be an issue.
The just expanded Accordion Collapse is immediately folded.
So you cannot expand the Accordion.
Anyone can provide me with any solution?
The entire code is as below.
import React from 'react';
import {Accordion, Card, useAccordionToggle, ListGroup} from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
class EMP extends React.Component {
constructor(props) {
super(props);
this.state = {employees: []}
}
componentDidMount() {
fetch('/SQLite/employees')
.then(response => response.json())
.then(employees => this.setState({ employees }));
}
componentDidUpdate() {
fetch('/SQLite/employees')
.then(response => response.json())
.then(employees => this.setState({ employees }));
}
render() {
return (
<div>
<EmployeeList employees={this.state.employees} />
</div>
)
}
}
class EmployeeList extends React.Component {
constructor(props) {
super(props);
}
render() {
const CustomToggle = ({ children, eventKey }) => {
const decoratedOnClick = useAccordionToggle(
eventKey,
(e) =>{
var item = e.target.parentNode.children[0];
if(item.innerText.includes('▶',0)){
item.innerText = item.innerText.replace('▶', '▼');
}
else{
item.innerText = item.innerText.replace('▼', '▶');
}
}
);
return (
<ListGroup.Item
onClick={decoratedOnClick}
style={{cursor: 'pointer', paddingBottom: '0', paddingTop: '0' }}
>
{children}
</ListGroup.Item>
);
}
return (
<Accordion defaultActiveKey='0'>
<Card>
<Card.Header>
<CustomToggle eventKey='0'>▶ Click me!</CustomToggle>
</Card.Header>
<Accordion.Collapse eventKey='0'>
<Card.Body>Hello! Im the body</Card.Body>
</Accordion.Collapse>
</Card>
<Card>
<Card.Header>
<CustomToggle eventKey='1'>▶ Click me!</CustomToggle>
</Card.Header>
<Accordion.Collapse eventKey='1'>
<Card.Body>Hello! Im the body</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
)
}
}
export default EMP;
I found the reason of the issue.
With the content of componentDidMount() and componentDidUpdate() without arrow function ( () => fetch() ), the app falls into an infinite loop.
render() is called.
this.setState() is called.
render() is called again.
To prevent this inifinite loop, you must write the arrow function in the componentDidmount().
So the correct and complete code is as below.
import React from 'react';
import {Accordion, Card, useAccordionToggle, ListGroup} from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
class EMP extends React.Component {
constructor(props) {
super(props);
this.state = {employees: []}
}
componentDidMount() {
() => fetch('/SQLite/employees')
.then(response => response.json())
.then(employees => this.setState({ employees }));
}
componentDidUpdate() {
() => fetch('/SQLite/employees')
.then(response => response.json())
.then(employees => this.setState({ employees }));
}
render() {
return (
<div>
<EmployeeList employees={this.state.employees} />
</div>
)
}
}
class EmployeeList extends React.Component {
constructor(props) {
super(props);
}
render() {
const CustomToggle = ({ children, eventKey }) => {
const decoratedOnClick = useAccordionToggle(
eventKey,
(e) =>{
var item = e.target.parentNode.children[0];
if(item.innerText.includes('▶',0)){
item.innerText = item.innerText.replace('▶', '▼');
}
else{
item.innerText = item.innerText.replace('▼', '▶');
}
}
);
return (
<ListGroup.Item
onClick={decoratedOnClick}
style={{cursor: 'pointer', paddingBottom: '0', paddingTop: '0' }}
>
{children}
</ListGroup.Item>
);
}
return (
<Accordion defaultActiveKey='0'>
<Card>
<Card.Header>
<CustomToggle eventKey='0'>▶ Click me!</CustomToggle>
</Card.Header>
<Accordion.Collapse eventKey='0'>
<Card.Body>Hello! Im the body</Card.Body>
</Accordion.Collapse>
</Card>
<Card>
<Card.Header>
<CustomToggle eventKey='1'>▶ Click me!</CustomToggle>
</Card.Header>
<Accordion.Collapse eventKey='1'>
<Card.Body>Hello! Im the body</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
)
}
}
export default EMP;
Related
I'm trying to open a modal dialog from a set of cards that has been looping from the data the component receives. I can't figure it out how to make the modal get the appropriate data from the clicked card. In my code below, I tried to put the modal outside the loop, but then I can't figure it out how to pass the id of the clicked card to a new function which would control the modal
Here is the Component that manages the cards loop and contains the modal
import React, {Component} from 'react';
import {Nav, NavItem, NavLink, Card, CardImg, CardText,
CardBody, CardTitle, CardSubtitle, FormGroup, Input, Col, Button, Modal, ModalHeader, ModalBody} from 'reactstrap';
import classnames from 'classnames';
class ProductCard extends Component {
constructor(props){
super(props);
this.state={
productList: this.props.products,
isModalOpen: false
}
this.toggleModal = this.toggleModal.bind(this)
}
toggleModal() {
this.setState({
isModalOpen: !this.state.isModalOpen
});
}
render(){
return(
this.state.productList.map(prod => (
<div key={prod.prod_id} className="col-12 col-md-3 mb-4 rowCard" onClick={this.toggleModal}>
<Card>
<CardImg top width="100%" src={prod.prod_image} alt={prod.prod_name_eng}/>
<CardBody>
<CardTitle>{prod.prod_name_eng}</CardTitle>
<CardSubtitle>{prod.prod_cost_total}</CardSubtitle>
<CardText>{prod.prod_description}</CardText>
</CardBody>
</Card>
<Modal isOpen={this.state.isModalOpen} toggle={this.toggleModal}>
<ModalHeader toggle={this.toggleModal}>{prod.prod_name_eng}</ModalHeader>
<ModalBody>{prod.prod_description}</ModalBody>
</Modal>
</div>
))
);
}
}
Any help is welcome! thanks
I would suggest moving the Modal outside of your map, since that makes things more complicated than they need to be. If you do this, then you toggleModal method is then responsible for accepting an index (supplied by the map function) and then you would just need to retrieve the correct text for the modal elements.
toggleModal(index) {
this.setState({
cardIndex: index,
isModalOpen: !this.state.isModalOpen
});
}
Then you're modal just needs to reference the productList in state, access the index and get the title and description:
class ProductCard extends Component {
constructor(props) {
super(props);
this.state = {
productList: this.props.products,
cardIndex: null,
isModalOpen: false
};
this.toggleModal = this.toggleModal.bind(this);
}
toggleModal(id) {
console.log(id);
this.setState({
cardIndex: id,
isModalOpen: !this.state.isModalOpen
});
}
render() {
const { productList, cardIndex } = this.state;
console.log("CardIndex: ", cardIndex);
console.log("Product: ", productList[cardIndex]);
return (
<Fragment>
{productList.map((prod, index) => {
return (
<div
key={prod.prod_id}
className="col-12 col-md-3 mb-4 rowCard"
onClick={e => this.toggleModal(index)}
>
<Card>
<CardImg top src={prod.prod_image} alt={prod.prod_name_eng} />
<CardBody>
<CardTitle>{prod.prod_name_eng}</CardTitle>
<CardSubtitle>{prod.prod_cost_total}</CardSubtitle>
<CardText>{prod.prod_description}</CardText>
</CardBody>
</Card>
</div>
);
})}
<Modal
isOpen={this.state.isModalOpen}
toggle={e => this.toggleModal(cardIndex)}
>
<ModalHeader toggle={e => this.toggleModal(cardIndex)}>
{cardIndex !== null && productList[cardIndex].prod_name_eng}
</ModalHeader>
<ModalBody>
{cardIndex !== null && productList[cardIndex].prod_description}
</ModalBody>
</Modal>
</Fragment>
);
}
}
Here is a codesandbox link to a working version:
I've this class which is a specific entry in a list.
I am trying to use the semantic-ui-react TextArea as a controlled component.
When an external event (changing the selected language) triggers the componentWillReceiveProps method, my data object in state is updated with the new data.
However the rendered value of the TextArea, which is set to this.state.value, is never changed.
I've verified that the state is in fact the new value, but I do not understand why the rendered value does not change.
import React, { Component } from "react";
import { Segment, Grid, Button, TextArea, Form } from 'semantic-ui-react'
const UNAVAILABLE = "Translation unavailable."
class Key extends Component {
constructor(props) {
super(props)
this.state = {
data: props.data[props.language]
}
}
componentWillReceiveProps = (props) => {
this.setState({
data: props.data[props.language]
})
}
handleEdit = (event) => {
this.setState({data: event.target.value})
this.props.edit(event.target.value)
}
render = () => {
let inverted = null;
let color = null;
if(this.props.hasChanged()){
inverted = true;
color = 'green'
} else if(!this.props.data[this.props.language]) {
inverted = true;
color = 'red'
}
return(
<Segment className='key' inverted={inverted} color={color}>
<Grid columns='equal' textAlign='left'>
<Grid.Row>
<Grid.Column className='keyField' width={3}>
{this.props.name}
</Grid.Column>
<Grid.Column width={5}>
{this.props.data.en}
</Grid.Column>
<Grid.Column width={5}>
<Form>
<TextArea
value={this.state.data}
placeholder={UNAVAILABLE}
onChange={this.handleEdit}>
</TextArea>
</Form>
</Grid.Column>
<Grid.Column>
<Button
className='button'
floated='right'
icon='trash alternate'
compact
onClick={this.props.delete}
/>
</Grid.Column>
</Grid.Row>
</Grid>
</Segment>
)
}
}
export default Key;
SOLUTION: The real problem was my data object has values of data[language] that end up being undefined. I was expecting it to take a null value and go back to the placeholder, but apparently when you give null to the value field of a textArea that has a value it does nothing, as shown in github.com/facebook/react/issues/2533. Adding a check whether the property was in the data object and using an empty string instead fixed my issue.
You can verify its working for me
import React, { Component } from "react";
import { Segment, Grid, Button, TextArea, Form } from 'semantic-ui-react'
class AboutPage extends React.Component {
constructor(){
super();
this.state={
data:"initial data"
}
}
componentDidMount(){
setTimeout(()=>{
this.setState({data: 'new Data'})
}, 5000)
}
render() {
return (
<div>
<h1>About</h1>
<Key data={this.state.data}/>
</div>
);
}
}
const UNAVAILABLE = "Translation unavailable."
class Key extends Component {
constructor(props) {
super(props)
this.state = {
data: props.data
}
}
componentWillReceiveProps = (props) => {
this.setState({
data: props.data
})
}
handleEdit = (event) => {
this.setState({data: event.target.value})
// this.props.edit(event.target.value)
}
render = () => {
let inverted = null;
let color = null;
if(true){
inverted = true;
color = 'green'
} else if(!this.props.data[this.props.language]) {
inverted = true;
color = 'red'
}
return(
<Segment className='key' inverted={inverted} color={color}>
<Grid columns='equal' textAlign='left'>
<Grid.Row>
<Grid.Column className='keyField' width={3}>
{'name'}
</Grid.Column>
<Grid.Column width={5}>
{'English'}
</Grid.Column>
<Grid.Column width={5}>
<Form>
<TextArea
value={this.state.data}
placeholder={UNAVAILABLE}
onChange={this.handleEdit}>
</TextArea>
</Form>
</Grid.Column>
<Grid.Column>
<Button
className='button'
floated='right'
icon='trash alternate'
compact
/>
</Grid.Column>
</Grid.Row>
</Grid>
</Segment>
)
}
}
export default AboutPage;
im new in react and well im trying to pass a value from a card click to a child component, i tried a lot of different options but is not working.... can please someone give me a hand with this.
Parent :
import React, { Component } from 'react';
import {
Card, CardImg, CardBody,
CardTitle
} from 'reactstrap'; import { Container, Dimmer, Loader } from 'semantic-ui-react'
import logo from './santa.png'
import Divisiones from './division';
class divisionesSisa extends Component {
constructor() {
super()
this.state = {
division: "1"
}
this.methodFetch = this.methodFetch.bind(this)
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
this.methodFetch()
}
fetch(endpoint) {
return window.fetch(endpoint)
.then(response => response.json())
.catch(error => console.log(error))
}
methodFetch() {
this.fetch('/####')
.then(results => {
return results;
}).then(data => {
this.setState(data)
})
}
handleClick = (e) =>{
this.setState({ division: e.target.id });
}
render() {
let { data } = this.state
return data
?
<div className="row">
{Object.keys(data).map((key) => {
return <div className="col-md-2">
<Card>
<CardImg style={{ justifyContent: 'center', alignItems: 'center', marginLeft: 0, marginRight: 1 }} src={logo} alt="Card image cap" />
<CardBody style={{ marginBottom: 10, marginLeft: 0, marginRight: 0 }}>
<a id={data[key]} href="sisa/division" onClick={() => this.handleClick}><CardTitle align="center" >{data[key]}</CardTitle></a>
</CardBody>
</Card>
</div>
})}
<Divisiones division={this.handleClick.bind(this)}/>
</div>
: <Container text>
<Dimmer active inverted>
<Loader content='Loading' />
</Dimmer>
</Container>
}
}
export default divisionesSisa;
Child component
import React, { Component } from 'react';
class division extends Component {
constructor(props){
super(props)
}
componentDidMount(){
console.log(this.props.division)
}
show(){
}
render() {
return(
<div>
<h2>{this.props.division}</h2>
</div>
);
}
}
export default division;
what im doing wrong?
i take any suggestions to fix this problem or change the way im using react
I believe you should use the state to set division prop in 'Divisiones'
const { division } = this.state
<Divisiones division={ division }
[Edit]
As you are receiving 'undefined', I have noticed that you don't pass event to your handleClick function.
You are expecting an event parameter here:
handleClick = (e) => {
this.setState({ division: e.target.id });
}
So you should pass the parameter like this:
onClick={(event) => this.handleClick(event)}
plus you could also add a onClick function on the CardImg
I am trying to toggle the info window in react-google-maps off and on through a custom component. The toggle method is being called as I checked if it's logged. Here's the code:
/**
* Created by.
*/
import * as React from 'react'
import {Col, Row, Card, CardHeader, CardBody, CardColumns, CardText, CardFooter} from 'reactstrap'
import {InfoWindow, Marker} from 'react-google-maps'
export default class home extends React.Component {
state = {
isOpen: false
}
toggleOpen = () => {
this.setState(({ isOpen }) => (
{
isOpen: !isOpen,
}
));
if(this.state.isOpen)
console.log("state is open")
else
console.log("state is not open")
}
render()
{
const { isOpen } = this.state;
return (
<Marker
position={this.props.position}
onClick={this.toggleOpen}>
<InfoWindow isOpen={isOpen}>
<Card className="hovercard">
<Row>
<CardColumns sm={6} lg={3}>a
<CardHeader>
{this.props.homestay}
</CardHeader>
<CardText className="avatar">
<img alt="profile img" src={this.props.profilePic}/>
</CardText>
<div className="info">
<CardText>{this.props.descrip}</CardText>
</div>
<CardFooter>
{this.props.price}
</CardFooter>
</CardColumns>
</Row>
</Card>
</InfoWindow>
</Marker>
)
}
}
The infowindow is not opening when I click it. Any ideas?
EDIT ----
I changed the toggle method as you mentioned but the toggle is still not responding. Here's my project in sandeditor:https://codesandbox.io/s/93258nn8m4
You will need to put state in constructor for first time initialization or to create instance of the class, However not necessary always but use it to keep best practice.
constructor() {
this.state = {
isOpen: false
}
}
Also, I don't know if it is the correct way to setState
change this
toggleOpen = () => {
this.setState(({ isOpen }) => (
{
isOpen: !isOpen,
}
));
if(this.state.isOpen)
console.log("state is open")
else
console.log("state is not open")
}
to this
toggleOpen = () => {
this.setState({isOpen: !this.state.isOpen},() => {
if(this.state.isOpen)
console.log("state is open")
else
console.log("state is not open")
}
);
}
P.S. Beware because React setState is asynchronous!
Update
Just noticed that if you don't define a constructor in the class that is ok. Because The constructor is dead.
However, Your transpiler still will generate it in a constructor form which is satisfying.
So that part of your question; setting state without a constructor is fine but to setState is definitely not.
This reset the isOpen value.Once you are setting to current value and in callback you are toggling it.
this.setState(({ isOpen }) => (
{
isOpen: !isOpen,
}
));
Correct way:
this.setState(prevState) => (
{
isOpen: !isOpen,
}
));
I need to render a modal/lightbox component dynamic into a list array component, but it only renders the last modal content.
How can I turn this modal component dynamic to call it from the main component and populate it with correct data from an object array?
My List component is:
import React, { Component } from 'react';
import LightBox from './LightBox';
class ListPrice extends Component {
constructor(props) {
super(props);
this.state = { isOpen: false };
}
toggleModal = () => {
this.setState({
isOpen: !this.state.isOpen
});
}
render() {
return (
<div>
{this.props.products.map(product => {
return(
<div>
<a key={product.id} onClick={this.toggleModal}>
<h3>{product.title}</h3>
<p>{product.description}</p>
</a>
<LightBox key={product.id} show={this.state.isOpen}
onClose={this.toggleModal}>
{product.modalContent}
</LightBox>
</div>
);
})}
</div>
);
}
}
export default ListPrice;
And my LightBox component is (I removed styles to display short code here):
import React from 'react';
import PropTypes from 'prop-types';
class LightBox extends React.Component {
render() {
if(!this.props.show) {
return null;
}
return (
<div>
<div>
{this.props.children}
<div>
<button onClick={this.props.onClose}>
Close
</button>
</div>
</div>
</div>
);
}
}
LightBox.propTypes = {
onClose: PropTypes.func.isRequired,
show: PropTypes.bool,
children: PropTypes.node
};
export default LightBox;
Thank you for any advice :)
With show={this.state.isOpen} you always display all the modals - only the last one is visible as other modals are displayed behind it.
In order to fix that you must show only the selected dialog. You can store opened dialog in state with construct like this.setState({ openedDialog: product.id }).
Then you can query if the dialog is open by using this.state.openedDialog === product.id. That should do the job.
openModal = (id) = () => {
this.setState({
openedDialog: id
});
}
closeModal = () => {
this.setState({
openedDialog: null
});
}
show={this.state.openedDialog === product.id}
onClick={this.openModal(product.id)}
onClose={this.closeModal}