setState is not changing view - javascript

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

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

What lifecycle hook i need to use? - react

The situation is this: i have a database, where the data is. The data is structured with an array of objects. The objects have three properties (which are relevant now). These are: id, parentId, and status. The components build up with these properties, and the component clones itself recursively, so they are nested within each other. It looks like this:
class Task extends React.Component{
constructor(props){
super(props);
this.state = {
status: this.props.status
}
}
//gets fired right after the state changed by removeTask
static getDerivedStateFromProps(props){
console.log("state from props")
return{status: props.status}
}
componentDidUpdate(){
console.log("update");
this.checkDeleted()
}
checkDeleted = () => {
if (this.state.status === 'deleted'){
deleteTask(this.props.id) //a function which deletes from database
}
}
tasks = () => {
console.log("childs rendered")
return this.props.tasks
.filter(task => task.pId === this.props.id)
.map(task => {
return <Task
id={task.id}
pId={task.pId}
status={this.state.status}
/>
})
}
removeTask = () => {
console.log("state changed")
this.setState({status: 'deleted'})
}
render(){
console.log("render")
return(
<div
<button onClick={this.removeTask} />
{this.tasks()}
</div>
)
}
}
What happens: the order of the logs are the next:
state changed (removeTask())
state from props (gDSFP())
render
childs rendered (tasks() fired inside render())
update (componentDidUpdate())
This isn't good, because when the state changed from removeTask(), it gets right back from the props with gDSFP, before the component can pass the changed state to it's childs. But i want to set the state from the props, because the childs need to get it. What could happen here is: the removeTask() fired, sets the new state, rerender, the childs get the new status as a prop, and when the update happens, deletes all the component, and it's child from the database. So what will be good:
click happened, set new state
render
render childs, set they status prop to the state
check if the status is "deleted"
set state from props if it's changed, and rerender
How to earn this?
I have problem with your order to begin with. Your data depends on whats in the DB. You might delete from the state and the DB task failed. So why bother updating the state manually. Just listen and load your state from props that come from DB. when you delete from the DB, your props will be updated and re-render will occur. So basically if i were you , i would stick with
class Task extends React.Component{
constructor(props){
super(props);
}
//gets fired right after the state changed by removeTask
static getDerivedStateFromProps(props){
console.log("state from props")
return{status: props.status}
}
componentDidUpdate(){
console.log("update");
}
tasks = () => {
console.log("childs rendered")
return this.props.tasks
.filter(task => task.pId === this.props.id)
.map(task => {
return <Task
id={task.id}
pId={task.pId}
status={this.props.status}
/>
})
}
removeTask = () => {
console.log("state changed");
deleteTask(this.props.id) //a function which deletes from database
}
render(){
console.log("render")
return(
<div
<button onClick={this.removeTask} />
{this.tasks()}
</div>
)
}
}
from the above, you can notice i removed checkDeleted because i don't need to update my state. I can just rely on props. Remove set state status because i can just rely on props sttaus which btw tally or is in sync with DB.

Initialize state with dynamic key based on props in reactJS

How to initialize state with dynamic key based on props? The props is a data fetched from external source (async). So the props will change when the data is succesfully downloaded. Consider a component like this.
edit: I want to make the state dynamic because I want to generate a dialog (pop up) based on the item that is clicked. the DialogContainer is basically that. visible prop will make that dialog visible, while onHide prop will hide that dialog. I use react-md library.
class SomeComponent extends React.Component {
constructor() {
super();
this.state = {};
// the key and value will be dynamically generated, with a loop on the props
// something like:
for (const item of this.props.data) {
this.state[`dialog-visible-${this.props.item.id}`] = false}
}
}
show(id) {
this.setState({ [`dialog-visible-${id}`]: true });
}
hide(id) {
this.setState({ [`dialog-visible-${id}`]: false });
}
render() {
return (
<div>
{this.props.data.map((item) => {
return (
<div>
<div key={item.id} onClick={this.show(item.id)}>
<h2> Show Dialog on item-{item.id}</h2>
</div>
<DialogContainer
visible={this.state[`dialog-visible-${item.id}`]}
onHide={this.hide(item.id)}
>
<div>
<h1> A Dialog that will pop up </h1>
</div>
</DialogContainer>
</div>
);
})}
</div>
)
}
}
// the data is fetched by other component.
class OtherComponent extends React.Component {
componentDidMount() {
// fetchData come from redux container (mapDispatchToProps)
this.props.fetchData('https://someUrlToFetchJSONData/')
}
}
The data then is shared via Redux.
However, based on my understanding so far, state can be updated based on props with componentWillReceiveProps or the new getDerivedStateFromProps (not on the constructor as above). But, how to do that on either method?
The example here only explains when the state is initialized on the constructor, and call setState on either cWRP or gDSFP. But, I want the key value pair to be initialized dynamically.
Any help/hint will be greatly appreciated. Please do tell if my question is not clear enough.
import React from 'react';
import {connect} from 'react-redux';
import {yourAction} from '../your/action/path';
class YourClass extends React.Component {
state = {};
constructor(props){
super(props);
}
componentDidMount(){
this.props.yourAction()
}
render() {
const {data} = this.props; //your data state from redux is supplied as props.
return (
<div>
{!data ? '' : data.map(item => (
<div>{item}</div>
))}
</div>
)
}
}
function mapStateToProps(state) {
return{
data:state.data //state.data if that is how it is referred to in the redux. Make sure you apply the correct path of state within redux
}
}
export default connect(mapStateToProps, {yourAction})(YourClass)
If you do this, <div>{item}</div> will change as you change the data state. The idea is to just map the redux state to your class props - you don't have to map the props back to the state. The render() automatically listens to changes in props supplied by redux. However, if you do want to somehow know redux state change in events, you can add the following functions.
componentWillReceiveProps(newProps){
console.log(newProps)
}
getDerivedStateFromProps(nextProps, prevState){
console.log(nextProps);
console.log(prevState);
}

Pass a prop from state and do not trigger update in connect

I am currently grabbing a prop from state and using it on an event listener. i.e.,
import * as React from 'react';
import { getDetails } from './actions';
interface Props {
selecting: boolean;
getDetails(): Action<void>;
}
#connect((state) => ({
selecting: state.items.selecting,
}), {
getDetails,
})
export default class Grid extends React.PureComponent<Props> {
onMouseEnter = () => {
if (!this.props.selecting) {
this.props.getDetails();
}
}
render() {
return (
<div onMouseEnter={this.onMouseEnter} />
);
}
}
However, whenever the selecting property changes, it causes a re-render to my component.
Is there a way to pass a variable from state through connect and NOT have it trigger this update to my component? I want it almost as if it were an instance-bound variable rather than a state variable.
Try overriding the shouldComponentUpdate() lifecycle function. This gives you much more granular control over when your component should or shouldn't re-render (at the cost of added code complexity).
shouldComponentUpdate(nextProps, nextState) {
if(nextProps.someLogic !== this.props.someLogic)
return false; // Don't re-render
return true;
}
Documentation: Here
Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior.

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