React Native ProgressBarAndroid setState not working - javascript

I'm having trouble figuring out the problem with the following code. I'm trying to change the prop animating on a ProgressBarAndroid, and make it toggle every second. The code works as intended if I set loading to true in my constructor, but not if it's set to false (which is what I want, I don't want it to start animating right away). When it's set to false, the progressbar stays invisible all the time. Any ideas?
import React, { Component } from 'react';
import { ProgressBarAndroid } from 'react-native';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {loading: false}; // works if it is set to true here instead
// Toggle the state every second
setInterval(() => {
this.setState({loading: !this.state.loading});
}, 1000);
}
render() {
return (
<ProgressBarAndroid animating={this.state.loading}></ProgressBarAndroid>
);
}
}

This is actually an issue I had with ProgressBarAndroid a few weeks back. Once initiated as false, I was never able to set it back true.
The quick and dirty solution I had at the time was to just to move the state change outside of the animating prop.
In your case, changing this:
<ProgressBarAndroid animating={this.state.loading}></ProgressBarAndroid>
to this:
<View>
{ this.state.loading ? <ProgressBarAndroid/> : null }
</View>
and make sure to also include View from react-native.

Related

skip re-render using shouldComponentUpdate and nextState

I have currently a drop-down select to filter some charts after 'Apply'. It works fine.(See screenshot below).
The problem is that when another timespan gets selected, React does a re-render to all charts before I click 'Apply' button.
I want to avoid this unnecessary re-render by implementingshouldComponentUpdate, but I can't figure out how.
Below what I tried but it did not work(still a re-render):
shouldComponentUpdate(nextState) {
if (this.state.timespanState !== nextState.timespanState) {
return true;
}
return false;
}
But it always return true, because nextState.timespanState is undefined. Why?
Drop-down Select
<Select value={this.state.timespanState} onChange={this.handleTimeSpanChange}>
handleTimeSpanChange = (event) => {
this.setState({ timespanState: event.target.value });
};
constructor(props) {
super(props);
this.state = { timespanState: 'Today'};
this.handleTimeSpanChange = this.handleTimeSpanChange.bind(this);
}
You're on the right track with using shouldComponentUpdate, it's just that the first parameter is nextProps and the second is nextState, so in your case, the undefined value is actually nextProps with the wrong name.
Change your code to this,
shouldComponentUpdate(nextProps,nextState) { // <-- tweak this line
if (this.state.timespanState !== nextState.timespanState) {
return true;
}
return false;
}
Finally, I solve the problem by separating drop-down selectbox and charts into two apart components and made the drop-down component as a child component from its parent component, charts components.
The reason is the following statement
React components automatically re-render whenever there is a change in their state or props.
Therefore, React will re-render everything in render() method of this component. So keeping them in two separate components will let them re-render without side effect. In my case, any state changes in drop-down or other states in Filter component, will only cause a re-render inside this component. Then passing the updated states to charts component with a callback function.
Something like below:
Child component
export class Filter extends Component {
handleApplyChanges = () => {
this.props.renderPieChart(data);
}
render(){
return (
...
<Button onClick={this.handleApplyChanges} />
);
}
}
Parent component
export class Charts extends Component{
constructor(props){
this.state = { dataForPieChart: []};
this.renderPieChart = this.renderPieChart.bind(this);
}
renderPieChart = (data) => {
this.setState({ dataForPieChart: data });
}
render(){
return (
<Filter renderPieChart={this.renderPieChart} />
<Chart>
...data={this.state.dataForPieChart}
</Chart>
);
}
}
If still any question, disagreement or suggestions, pls let me know:)

React - How do I make an element disappear after animation ends?

Background
I am trying to make an element disappear after the animation ends (I am using animate.css to create the animations).
The above 'copied' text uses animated fadeOut upon clicking the 'Copy to Journal Link'. Additionally, the above demo shows that it takes two clicks on the link to toggle the span containing the text 'copied' from displayed to not displayed.
According to the animate.css docs, one can also detect when an animation ends using:
const element = document.querySelector('.my-element')
element.classList.add('animated', 'bounceOutLeft')
element.addEventListener('animationend', function() { doSomething() })
My Problem
However, within the componentDidMount() tooltip is null when attempting to integrate what animate.css docs suggest.
What am I doing wrong? Is there a better way to handle this behavior?
ClipboardBtn.js
import React, { Component } from 'react'
import CopyToClipboard from 'react-copy-to-clipboard'
class ClipboardBtn extends Component {
constructor(props) {
super(props)
this.state = {
copied: false,
isShown: true,
}
}
componentDidMount() {
const tooltip = document.querySelector('#clipboard-tooltip')
tooltip.addEventListener('animationend', this.handleAnimationEnd)
}
handleAnimationEnd() {
this.setState({
isShown: false,
})
}
render() {
const { isShown, copied } = this.state
const { title, value } = this.props
return (
<span>
<CopyToClipboard onCopy={() => this.setState({ copied: !copied })} text={value}>
<span className="clipboard-btn">{title}</span>
</CopyToClipboard>
{this.state.copied ? (
<span
id="clipboard-tooltip"
className="animated fadeOut"
style={{
display: isShown ? 'inline' : 'none',
marginLeft: 15,
color: '#e0dbda',
}}
>
Copied!
</span>
) : null}
</span>
)
}
}
export default ClipboardBtn
Using query selectors in React is a big NO. You should NEVER do it. (not that that's the problem in this case)
But even though it's not the problem, it will fix your problem:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
https://reactjs.org/docs/refs-and-the-dom.html
componentDidMount gets called only once during the inital mount. I can see that in the inital component state, copied is false, hence #clipboard-tooltip never gets rendered. That is why tooltip is null.
Instead try this :
componentDidUpdate(prevProps, prevState) {
if(this.state.copied === true && prevState.copied === false) {
const tooltip = document.querySelector('#clipboard-tooltip')
tooltip.addEventListener('animationend', this.handleAnimationEnd)
}
if(this.state.copied === false && prevState.copied === true) {
const tooltip = document.querySelector('#clipboard-tooltip')
tooltip.removeEventListener('animationend', this.handleAnimationEnd)
}
}
componentDidUpdate gets called for every prop/state change and hence as soon as copied is set to true, the event handler is set inside componentDidUpdate. I have added a condition based on your requirement, so that it doesn't get executed everytime. Feel free to tweak it as needed.

react - force componentDidUpdate() when props are same value

In my component im trying to sync the received props with the current state in order to make it visible from outside (I know this is an anti-pattern, but I haven't figured out another solution to this yet. Im very open to suggestions!).
Anyways, this is what I've got:
export class PopupContainer extends React.Component {
state = {
show: false,
};
shouldComponentUpdate(nextProps, nextState) {
if (this.props.show === nextProps.show) return true;
return true;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// if popup shown, fade it out in 500ms
if (this.props.show !== prevProps.show)
this.setState({ show: this.props.show });
if (this.state.show) {
setTimeout(() => this.setState({ show: false }), 2000);
}
}
render() {
return <Popup {...{ ...this.props, show: this.state.show }} />;
}
}
And in my external component I'm rendering the container :
<PopupContainer
show={this.state.popup.show}
message={this.state.popup.message}
level={this.state.popup.level}
/>
Now when I initially set this.state.show to true it works, but every successive assignment which is also true without any false assignment inbetween doesn't work. How do I force componentdidUpdate() to fire anyways even if the props are the same value? shouldComponentUpdate() didn't seem to solve the problem.
Thank you!
Edit: I noticed that the render() method is only called in the parent element. It seems like as there is no change in properties for the child, react doesn't even bother rerendering the childern which somehow makes sense. But how can I force them to rerender anyways?
This is kind of a hack, but it works for me.
In the child class
Add a property to state in constructor - let's call it myChildTrigger, and set it to an empty string:
this.state = {
...
myChildTrigger: ''
}
then add this to componentDidUpdate:
componentDidUpdate(prevProps) {
if(this.state.myChildTrigger !== this.props.myParentTrigger) {
// Do what you want here
this.setState({myChildTrigger: this.props.myParentTrigger});
}
}
In the parent class
Add a myParentTrigger to state in constructor:
this.state = {
...
myParentTrigger: ''
}
In the render method, add it as a prop, like this:
<ChildClass ... myParentTrigger={this.state.myParentTrigger} />
Now you can trigger a call to componentDidUpdate to execute whatever is inside the if-statement, just by setting myParentTrigger to a new value, like:
this.setState({ myParentTrigger: this.state.myParentTrigger + 'a' });

Display Component based on another component lifecycle

I have recently encountered an issue regarding the usage of one of my costum components. I have created a "Chargement" (Loading in French) Component for a project I am working on.
This component is a simple circular spinner with a dark background that when displayed, informs the user that an action is going on.
import React, {Fragment} from 'react';
import { CircularProgress } from 'material-ui/Progress';
import blue from 'material-ui/colors/blue';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
const styles = theme => ({
chargement: {
position: 'fixed',
left: '50%',
top: '50%',
zIndex: 1
}
});
class Chargement extends React.Component {
render () {
const { classes } = this.props;
if (this.props.chargement) {
return (
<Fragment>
<div className='loadingicon'>
<CircularProgress size={80} style={{ color: blue[500] }}/>
</div>
<div className='loadingBackground'/>
</Fragment>
);
} else {
return null;
}
}
}
const mapStateToProps = (state) => {
return {
chargement: state.App.chargement
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
}, dispatch);
};
Chargement.propTypes = {
classes: PropTypes.object.isRequired
};
let ChargementWrapped = withStyles(styles)(Chargement);
export default connect(mapStateToProps, mapDispatchToProps)(ChargementWrapped);
This component is displayed based on a boolean variable in my redux Store called "chargement".
It works like a charm whenever I am using it to make api call and load data. However, one of the components in my Web App takes quite a bit of time to render (1-2 seconds). This component renders a pretty big list of data with expansion panels. I tried to set my display variable based on the componentWillMount and componentDidMount functions.
class ListView extends React.Component {
componentWillMount () {
this.props.setChargement(true);
}
componentDidMount () {
this.props.setChargement(false);
}
However with this particular case the "chargement" component never displays.
I also tried to create a "Wrapper Component" in case the issue came from my "chargement" component being somewhat related to the re-rendered component as a children. :
export default class AppWrapper extends React.Component {
render () {
return (
<Fragment>
<Reboot />
<EnTete />
<Chargement />
<App />
</Fragment>
);
}
}
The "App " component is the one that takes a few seconds to render and that I am trying to implement my "chargement" component for. I am pretty sure this as to do with the component lifecycle but everything I tried so far failed.
My current stack is : React with Redux and MaterialUi
What am I missing ?
Thanks for your help!
Ps: You might want to check the explanation and precision I added on the main answer comments as they provide further context.
Not sure if I understood correctly, but I think the problem is simply your API call takes more time than your component mounting cycle, which is totally normal. You can solve the problem by rearranging a bit the places where to put the IO.
Assuming you are making the API call from AppWrapper, dispatch the Redux action in componentDidMount i.e. fetchListItems(). When the API call resolves, the reducer should change its internal loading value from true to false. Then, AppWrapper will receive chargement as a prop and its value will be false. Therefore, you should check what this value is in AppWrapper's render method. If the prop is true, you render the Chargement component or else, render ListView.
Also, try always to decouple the IO from the view. It's quite likely that you'll need to reuse Chargement in other situations, right? Then, make it a simple, generic component by just rendering the view. Otherwise, if you need to reuse the component, it will be coupled to one endpoint already. For this, you can use a Stateless Functional Component as follows:
const Chargement = () =>
<Fragment>
<div className='loadingicon'>
<CircularProgress size={80} style={{ color: blue[500] }}/>
</div>
<div className='loadingBackground'/>
</Fragment>
I found a way to fix my issue that does not involve the use of the "chargement" component like I had initially planned. The issue revolved around the usage of Expansion Panels from the Material-Ui-Next librairy.
The solution I found is the following :
Instead of trying to show a Loading component while my list rendered, I reduced the render time of the list by not rendering the ExpansionDetail Component unless the user clicked to expand it.
This way, the list renders well under 0.2 seconds on any devices I've tested. I set the state to collapsed: false on every panel inside the constructor.
class ListItem extends React.Component {
constructor (props) {
super(props);
this.state = {
collapsed: false
};
this.managePanelState = this.managePanelState.bind(this);
}
managePanelState () {
if (this.state.collapsed) {
this.setState({collapsed: false});
} else {
this.setState({collapsed: true});
}
}
Then I use the onChange event of the expansion panel to switch the state between collapsed and not on every ListItemDetail element.
<ExpansionPanel onChange={() => this.managePanelState()}>
I guess sometimes the solution isn't where you had initially planned.
Thanks to everyone who took time to look into my problem!

ES6 react state change doesn't affect render inline if clause

i have this subcomponent which renders a "report bugg" button and should display the report bugg form when it is being pressed.
e.g : button pressed -> state update to report_toggle = true
As seen in this code:
import React from 'react';
import ReportBox from './ReportBox';
class ReportBugButton extends React.Component {
constructor(){
super();
this.toggleReportBox = this.toggleReportBox.bind(this);
this.reportSubmit = this.reportSubmit.bind(this);
this.state = {
report_toggle: true
}
}
toggleReportBox(e){
e.preventDefault();
this.state.report_toggle ? this.state.report_toggle = false : this.state.report_toggle = true;
console.log("State ist: ", this.state.report_toggle);
}
reportSubmit(e){
e.preventDefault();
}
render() {
return(
<div>
<i className="fa fa-bug"></i>
{ this.state.report_toggle ? <ReportBox toggleReport={this.toggleReportBox} submitReport={this.reportSubmit} /> : '' }
</div>
);
}
}
export default ReportBugButton;
When the Report Button is clicked the console log perfectly shows that the state is being updated since it always changes between "State is: true" and "State is: false".
Unfortunately the inline if in the render method doesn't seem to care much since it doesn't display the component if it the state is true.
If I set the state true by default it is being displayed , but not being hidden when its set to false by clicking.
Any ideas ? ... :)
You are changing state the wrong way. You should NEVER modify the state variable directly.
Use this.setState.
e.g
this.setState({ report_toggle: !this.state.report_toggle });
Only when you call this function, is the render function triggered and re-rendering is done (only if some state variables changed).

Categories