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

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).

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:)

Propogating Events to Child Components in React

I want to send events down to my React child.
I feel like this is kind of an easy thing to do, so maybe i just have a mental block, and there is something obvious that is staring me in the face.
Anyway, I have a little Test app which illustrates the problem:
export class Test extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
let {buttonClicked, textFieldChanged} = this.state
return (
<div>
<button onClick={()=>this.handleClick()}>
Click
</button>
<input type={"text"} onChange={()=>this.handleTextChange()}/>
<Inner buttonClicked={buttonClicked} textFieldChanged={textFieldChanged}/>
</div>
);
}
handleClick(e) {
this.setState({ buttonClicked: true })
}
handleTextChange(e) {
this.setState({textFieldChanged:true})
}
}
class Inner extends React.Component {
render() {
let {buttonClicked, textFieldChanged} = this.props;
return (
<React.Fragment>
<div>Clicked : {buttonClicked ? "CLICKED!" : " "}</div>
<div>Text input : {textFieldChanged ? "TYPED!" : " "}</div>
</React.Fragment>
);
}
}
A button and a textfield live in the parent. Both these widgets can fire off events and change the child component.
This is simply achieved by passing a state value as a property down to the child. Very easy stuff.
However I would like an either/or situation. When I click the button this removes the text event, and vice versa. Ie. I do not want to see a situation like this :
Now there is a very obvious way to fix this by changing the state value to "false" of the other value.
handleClick(e) {
this.setState({ buttonClicked: true, textFieldChanged: false })
}
handleTextChange(e) {
this.setState({textFieldChanged:true, buttonClicked: false})
}
Is there any OTHER way of doing this?
The problem is that I have LOTS and LOTS of even handlers in my component and I don't want to negate the other state properties of the other values.
if i understood you correctly just one function will help - pass the attribute name into it
handleClick(propName) {
this.setState({
...this.state,
[propName]: !this.state[propName]
})
}
Create property lastEventType in parent component state , whenever you click or type - update it. And pass only this property to Inner component

setState is not changing view

I have a component "BulkActionPanel" that renders some buttons. Buttons are enabled or disabled based on the array property "selectedJobIds" passed as a props from its parent component "Grid". Precisely, if length of props "selectedJobIds" is greater than 0 then buttons are enabled else they are disabled.
I have a callback on "onClick" of all the buttons inside BulkActionPanel component, that sets the selectedJobIds to '0' by calling actionCreator "this.props.removeSelectedJobIds([rowData.id])" and it ensures that buttons are disabled.
Since action creator takes a lot of time (does heavy processing on grid), I am maintaining a local state "disable" inside BulkActionPanel to ensure button gets disabled first and then selectedJobIds state is updated in redux store.
I wrote the code below but buttons are not getting disabled until action creator " this.props.removeSelectedJobIds([rowData.id]);" finishes.
export default class Grid extends Component {
render() {
<BulkActionPanel
actions={this.bulkActions}
selectedJobIds={this.getFromConfig(this.props.config, [SELECTED_ROWS_PATH_IN_GRID_CONFIG])}
/>
<SlickGrid/>
}
}
export default class BulkActionPanel extends Component {
constructor() {
super();
this.state = {
disable: true
}
}
componentWillReceiveProps(nextProps){
if(nextProps.selectedJobIds && nextProps.selectedJobIds.length > 0){
this.setState({disable:false});
}
}
shouldComponentUpdate(nextProps) {
return nextProps.selectedJobIds !== undefined && nextProps.selectedJobIds.length
}
#autobind
onActionButtonClick(action) {
this.setState({disable:true}
, () => {
// Action creator that takes a lots of time
this.props.removeSelectedJobIds([rowData.id]);
}
);
}
#autobind
renderFrequentActions() {
return this.props.actions.frequentActions.map((frequentAction) => (
<button
className="btn btn-default"
key={frequentAction.DISPLAY_NAME}
onClick={() => this.onActionButtonClick(frequentAction)}
disabled={this.state.disable}
>
{frequentAction.DISPLAY_NAME}
</button>
));
}
render() {
const frequentActions = this.renderFrequentActions();
return (
<div className="btn-toolbar bulk-action-panel">
{frequentActions}
</div>
);
}
}
Does it has something to do with parent child relation of Grid and BulkActionPanel component? Leads here is appreciated.
Thanks!
I think your component is not passing this
if(nextProps.selectedJobIds && nextProps.selectedJobIds.length > 0){
this.setState({disable:false});
}
you have in your componentWillReceiveProps
if callback from removeSelectedJobIds isn't fired, state won't be changed, try set state of button like you did, and use reducer to dispatch action when removeSelectedJobIds finished, catch that action and rerender or change what you need.
OR
Use reducer for everything. onclick call actin type that let's you know data in table is rendering, use initail state in reducer to disable btn, when data in table finishes calucating fire action in reducer that send new data to component state

React Native ProgressBarAndroid setState not working

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.

React: Controlling input value with both props and state

Given a React component with a controlled input, I would like to be able to:
Set the value of the input from the parent's state
Allow the user to change the input to any value
Update the parent's state only after the user submits and input passes validation.
I can accomplish 1 and 2 with the snippet below, but since the value came into the ChildComponent via props, I'm not sure how I can change the input value without changing the value of myInput on the parent.
class ChildComponent extends React.Component
{
render(){
return <input type="text" value={this.props.inputValue} onChange={this.handleChange.bind(this)} />
}
handleChange(e){
this.props.onInputChange(e.target.value);
}
handleSubmit(){
// do some validation here, it it passes...
this.props.handleSubmit();
}
}
class ParentComponent extends React.Component{
constructor(){
super();
this.state = {myInput: ""};
}
render(){
return <ChildComponent inputValue={this.state.myInput} onInputChange={this.handleChange.bind(this)} />
}
handleChange(newValue){
this.setState({myInput: newValue});
}
handleSubmit(){
// do something on the server
}
}
Then you just need to move the state to the child component, instead of rendering from props.inputValue directly. Basically you'd just move handleChange to the child.
Set the initial value from props.inputValue in getInitialState, then make sure to update the child state in componentWillReceiveProps.
componentWillReceiveProps is deprecated
Source: https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops
This lifecycle was previously named componentWillReceiveProps. That
name will continue to work until version 17. Use the
rename-unsafe-lifecycles codemod to automatically update your
components.
Use something like this instead:
componentDidUpdate(prevProps) {
if (this.props.yourObj != null && prevProps.yourObj !== this.props.yourObj) {
this.setState({
yourStateObj = this.props.yourObj
});
}
}

Categories