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;
Related
I want to display/hide the chat body when on/off the switch. That means when switch on I want to display the chat body and when to switch off I want to hide it. Below is an image of toggle switch that I have used. Can you give me help to do that?
class MyApp extends Component {
render() {
return (
<FormControlLabel
control=
{
<Switch
name="sector"
color="primary"
style={{paddingRight: "30px"}}
onClick={this.handleClick.bind(this)}
/>
}
label="Sector 1"
/>
<div className="chatBody">
This is my chat body
</div>
);
}
}
export default MyApp;
You can show/hide the div content using the React state handlers, your code then could look like this:
class MyApp extends Component {
constructor(props) {
super(props);
this.state = {showBody: false};
this.handleClick = this.handleClick.bind(this);
}
handleClick () {
// toggle the showBody state to hide and show the body
this.setState({ showBody: !this.state.showBody })
}
render() {
return (
<FormControlLabel
control=
{
<Switch
name="sector"
color="primary"
style={{paddingRight: "30px"}}
onClick={this.handleClick}
/>
}
label="Sector 1"
/>
{this.state.showBody && (
<div className="chatBody">
This is my chat body
</div>
)}
);
}
}
export default MyApp;
As you can see on the this.state.showBody && we are declaring that the body should only display if the showBody state is true.
Then in some scenarios for "controlled inputs" there is probably a property in your Switch for the "checked" state (it usually depends on the library) and then you can use the state in the Switch to a controlled value: checked={this.state.showBody}.
Just add a state to control this:
class MyApp extends Component {
constructor(props) {
super(props);
this.state = {
checked1: false,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
this.setState({
checked1: e.target.checked,
});
}
render() {
return (
<>
<FormControlLabel
control={
<Switch
checked={this.state.checked1}
name="sector"
color="primary"
style={{ paddingRight: "30px" }}
onClick={this.handleClick.bind(this)}
/>
}
label="Sector 1"
/>
{this.state.checked1 && <div className="chatBody">This is my chat body</div>}
</>
);
}
}
export default MyApp;
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;
The popup show up after 1 sec. But I need to show it only to user who doesn't subscribe yet, by using localStorage. I did try use local storage like this code below, but then all I've got is a blank white page when it's time to show/not show popup. Is the localStorage I coded was totally wrong? Please help
import React from 'react'
import styled from 'react-emotion'
import Rodal from 'rodal'
import '../styles/rodal.css'
import Delayed from '../components/Delayed'
const Signup = () => (
<Containers>
<SubsribtionForm
action="https://subscribe/post?/....."
method="post"
>
<SubscribeInput type="email" name="EMAIL" placeholder="Subscribe to Updates!" required />
<Button type="submit" onClick={this.submit}>Add Me</Button>
</SubsribtionForm>
</Containers>
)
export default class Popup extends React.Component {
constructor(props) {
super(props)
this.state = { visible: true }
if (localStorage.getItem('submit')) {
this.setState({ visible: false })
}
this.submits = this.submits.bind(this)
}
submits() {
const newsub = this.state.submit
localStorage.setItem('submit', newsub)
}
show() {
this.setState({ visible: true })
}
hide() {
this.setState({ visible: false })
}
render() {
return (
<div>
<Container>
<Delayed waitBeforeShow={1000}>
<Rodal
visible={this.state.visible}
onClose={this.hide.bind(this)}
width={500}
height="100%"
customStyles={customStyles}
>
<Box>
<Banner />
<ContainerContent>
<Header>Subscribe to our mailing list</Header>
<Words>
We will organize and send regular updates Stay informed!
</Words>
</ContainerContent>
<ContainerForm>
<Signup />
</ContainerForm>
</Box>
</Rodal>
</Delayed>
</Container>
</div>
)
}
}
constructor(props) {
super(props)
this.state = {
visible: !(localStorage.getItem('submit') !== '' && localStorage.getItem('submit') !== null),
}
this.submits = this.submits.bind(this)
}
Just check if submit is not empty, like above.
Another approach would be to do the following in componentDidMount life cycle
componentDidMount() {
if (localStorage.getItem('submit')) {
this.setState({ visible: false })
}
}
You are calling this.setState inside the class constructor, you can use a simple conditional in this.state to assign the value directly and please use the bind in the constructor :D. I use the length because if the string is '' the length is 0 then that value in the conditional is false
import React from 'react'
import styled from 'react-emotion'
import Rodal from 'rodal'
import '../styles/rodal.css'
import Delayed from '../components/Delayed'
const Signup = () => (
<Containers>
<SubsribtionForm
action="https://subscribe/post?/....."
method="post"
>
<SubscribeInput type="email" name="EMAIL" placeholder="Subscribe to Updates!" required />
<Button type="submit" onClick={this.submit}>Add Me</Button>
</SubsribtionForm>
</Containers>
)
export default class Popup extends React.Component {
constructor(props) {
super(props)
const submit = localStorage.getItem('submit')
this.state = { visible: !submit && !submit.length }
this.submits = this.submits.bind(this)
this.show = this.show.bind(this)
this.hide = this.hide.bind(this)
}
submits() {
const newsub = this.state.submit
localStorage.setItem('submit', newsub)
}
show() {
this.setState({ visible: true })
}
hide() {
this.setState({ visible: false })
}
render() {
return (
<div>
<Container>
<Delayed waitBeforeShow={1000}>
<Rodal
visible={this.state.visible}
onClose={this.hide}
width={500}
height="100%"
customStyles={customStyles}
>
<Box>
<Banner />
<ContainerContent>
<Header>Subscribe to our mailing list</Header>
<Words>
We will organize and send regular updates Stay informed!
</Words>
</ContainerContent>
<ContainerForm>
<Signup />
</ContainerForm>
</Box>
</Rodal>
</Delayed>
</Container>
</div>
)
}
}
I'm trying to set up an "edit profile" function in Meteor + React. The actual update works fine, but when I load the page, the current data is not displayed. A few console.log() calls reveal that it is initially undefined, probably when the state is set, and a moment later is correctly updated, but then the state has been set already.
For normal subscriptions, I would use $subReady - what do I use for the automatically published user data ?
Here is my code:
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { withTracker } from 'meteor/react-meteor-data';
import { Card, Loader, Form } from 'semantic-ui-react';
// React component
class ProfileCard extends React.Component {
state = { username: this.props.currentUser ? this.props.currentUser.username : '' }
handleChange = (e, { name, value }) => this.setState({ [name]: value })
handleSubmit = () => {
const { username } = this.state
Meteor.call('user.update', username);
}
render() {
const { username } = this.state
return (
<Card fluid>
{ this.props.currentUser ? (
<Card.Content>
<Card.Header>Account Data</Card.Header>
<Form onSubmit={this.handleSubmit}>
<Form.Input
label="Username"
name="username"
type="text"
placeholder="set user name"
value={username}
onChange={this.handleChange}
/>
<Form.Button primary content="Submit" />
</Form>
</Card.Content>
) : (
<Card.Content>
<Loader />
</Card.Content>
)}
</Card>
);
}
}
export default withTracker(props => {
return {
currentUser: Meteor.user(),
};
})(ProfileCard);
I created a custom Accordion component which again consist of two child components called AccordionTitle and AccordionContent:
The AccordionTitle component has a button. When clicked, the AccordionContent part toggles its style from display:none to block and back when clicked again.
AccordionTitle.js
class AccordionTitle extends Component {
constructor() {
super();
this.show = false;
}
toggle() {
this.show = !this.show;
if (this.props.onToggled) this.props.onToggled(this.show);
}
render() {
return (
<div style={this.props.style}>
<Button onClick={e => this.toggle(e)} />
{this.props.children}
</div>
);
}
}
export default AccordionTitle;
AccordionContent.js
class AccordionContent extends Component {
render() {
let style = this.props.style ? this.props.style : {};
style = JSON.parse(JSON.stringify(style));
style.display = this.props.show ? 'block' : 'none';
return (
<div style={style}>
{this.props.children}
</div>
);
}
}
export default AccordionContent;
Also, I use the following parent component:
Accordion.js
class Accordion extends Component {
render() {
return (
<div>
{this.props.children}
</div>
);
}
}
Accordion.Title = AccordionTitle;
Accordion.Content = AccordionContent;
export default Accordion;
Now, when I use the Accordion component, it's possible that I might need multiple accordions in a row which would look like this:
ProductAccordion.js
import React, { Component } from 'react';
import Accordion from '../Accordion/Accordion';
class ProductAccordion extends Component {
constructor() {
super();
this.state = {
show: false,
};
}
toggled() {
this.setState({
show: !this.state.show,
});
}
render() {
this.productsJsx = [];
const products = this.props.products;
for (let i = 0; i < products.length; i += 1) {
this.productsJsx.push(
<Accordion.Title onToggled={e => this.toggled(e, this)}>
{products[i].name}
<img src="{products[i].imgsrc}" />
</Accordion.Title>,
<Accordion.Content show={this.state.show}>
{products[i].name}<br />
{products[i].grossprice} {products[i].currency}<br />
<hr />
</Accordion.Content>,
);
}
return (
<Accordion style={styles.container}>
{this.productsJsx}
</Accordion>
);
}
}
export default ProductAccordion;
As you can see, I am grabbing the toggled Event from Accordion.Title and I bind it to the prop show of Accordion.Content via the toggled() method.
Now, this works perfectly fine as long as there is just one product, but if there are more of them, clicking on the button will toggle all AccordionContent instances.
How can I change this so that only the content-part which belongs to the title that contains the clicked button will be toggled?
I also have the feeling that the component Accordion should take care of this (rather than ProductAccordion) by allowing Accordion.Title to delegate the toggled event directly to its sibling Accordion.Content. How can I achieve this?
I would suggest storing the index of the open item in state, instead of a boolean. Then in your render, show={this.state.show} would be something like show={this.state.show === i}.
Full example:
import React, { Component } from 'react';
import Accordion from '../Accordion/Accordion';
class ProductAccordion extends Component {
constructor() {
super();
this.state = {
show: null,
};
}
toggled(event, ind) {
const index = this.state.index;
this.setState({ show:ind === index ? null : ind });
}
render() {
this.productsJsx = [];
const products = this.props.products;
for (let i = 0; i < products.length; i += 1) {
this.productsJsx.push(
<Accordion.Title onToggled={e => this.toggled(e, i)}>
{products[i].name}
<img src="{products[i].imgsrc}" />
</Accordion.Title>,
<Accordion.Content show={this.state.show === i}>
{products[i].name}<br />
{products[i].grossprice} {products[i].currency}<br />
<hr />
</Accordion.Content>,
);
}
return (
<Accordion style={styles.container}>
{this.productsJsx}
</Accordion>
);
}
}
export default ProductAccordion;
and this
class AccordionTitle extends Component {
constructor() {
super();
}
render() {
return (
<div style={this.props.style}>
<Button onClick={this.props.onToggled} />
{this.props.children}
</div>
);
}
}
export default AccordionTitle;