react-highcharts: canvas size changes when changing tabs - reactjs

Description
I have a Highcharts line chart in a "progress" tab (the tab is a react-bootstrap Tab). When I open the "progress" tab, the chart appears at 100% width, which is good. When I toggle to a different tab, then come back to the "progress" tab, the chart's width changes from 100% to a fixed 600px. I went through the docs, both the official Highcharts and React-Highcharts, and could not find a solution.
I imagine there is a problem with the re-render of the component.
Code
highchart-line.js:
export default class HighChartLine extends Component {
constructor(props) {
super(props);
this.state = { config: undefined }; // or use null
}
getScoreData(eqId){
const self = this;
let url = 'src/data/dermatology/user_scores-eq' + eqId + ".json";
axios.get(url)
.then((response) => {
... //response informs
let config = {...};
self.setState({ config: config });
});
}
componentWillMount(){
this.getScoreData(this.props.equation.eqId);
}
componentWillReceiveProps(nextProps){
this.getScoreData(nextProps.equation.eqId);
}
render(){
// Handle case where the response is not here yet
if ( !this.state.config ) {
return <div>Loading...</div>
}
if ( this.state.config.length === 0 ) {
return <div>Sorry, no data was found for this equation.</div>;
} return (
<ReactHighcharts config={this.state.config} />
)
}
}
highchart-line gets imported into progress-tab.js:
const ProgressTab = ({equation, user}) => {
return(
<HighChartLine equation={equation} user={user} />
)
}; export default ProgressTab;
progress-tab gets imported into modal-main.js:
export default class ModalMain extends Component {
constructor(props) {
super(props);
this.state = {
tabKey: this.props.tabKey
};
}
handleSelect = (tabKey) => {
this.setState({ tabKey: tabKey });
};
render() {
return (
<Tabs activeKey={this.state.tabKey}
onSelect={this.handleSelect}
>
<Tab tabClassName="main-tab" eventKey={"progress"} >
<ProgressTab {...this.props} />
</Tab>
<Tab tabClassName="main-tab" eventKey={"peer-compare"}>
<PeerCompareTab {...this.props}/>
</Tab>
</Tabs>
)
}
}
Browser render
Width is correctly set to 100%
Width changes itself to 600px

Although this could hurt performance somewhat, I found that if I do not pass in the isPureConfig={true} prop into the ReactHighcharts component, it will re-render on tab select. Being that ReactHighcharts sets isPureConfig={false} by default and Highcharts itself reflows by default, it will all resize accordingly. The downside is you have to work with your charts re-rendering every time: https://github.com/kirjs/react-highcharts#limiting-highchart-rerenders
Of course, I am not certain it's any better to reflow the chart on every tab select anyway...

Related

Changing state in React JS is not re-rendering parent

My react app is a multi-page form. It goes to next page after clicking 'Next'. Currently I have some text that should have a css class when current page is page 1, and when user goes to next page, the css class should be removed for that text (the text is still displayed for all pages).
My actual code is much larger so I'm only posting all the important parts(I think) that are required for this questions.
import ChildComponent from '....';
class Parent extends React.Component {
state = {
page: 1, //default start page
currentPageis1: true,
currentPageis2: false,
currentPageis3: false,
}
change = () => {
const = {page, currentPageis1} = this.state;
this.setState({
page: page + 1 //to go to next page
});
this.setState({
currentPageis1: !currentPageis1
});
}
showPage = () =>{
const {page, currentPageis1} = this.state;
if(page === 1)
return (<ChildComponent
change={this.change}
currentPageis1={currentPageis1}
/>)
}
render(){
return (
<p className={this.currentPageis1 ? '': 'some-css-class'}>Some Text</p>
<form>{this.showPage()}
)
}
}
class ChildComponent extends React.Component {
someFunction = e =>{
e.preventDefault();
this.props.change();
}
render(){
return (
<Button onClick={this.someFunction}>Next</Button>
)
}
}
Currently, when I click Next button, the currentPageis1 updates to false. I checked it using Firefox React extension. But it does not re-render the page. Which means "Some Text" still has the CSS class.
My guess is className={this.currentPageis1 ? '': 'css-class'} in Parent class is only being run once (when the page is first loaded). Do I have to use lifecycle method? How do I make react re-render everytime currentPageis1 is changed?
You are doing <p className={this.currentPageis1 ? '': 'some-css-class'}>Some Text</p>. In order to apply styles to only page 1, you should revert the values in your condition. When currentPageis1 is false '' value is picked up.
Also this.currentPageis1 is wrong. You should use state i.e. this.state.currentPageis1
Working demo
Like this
<p className={this.state.currentPageis1 ? "some-css-class" : ""}>
Some Text
</p>
To get your style to render, you'll need to add the props keyword.
Return Child component inside of Parent and pass the change method as
a prop
Also, updated your setState so you only call it once instead of twice
in the change method
class Parent extends React.Component {
state = {
page: 1, //default start page
currentPageis1: true,
currentPageis2: false,
currentPageis3: false,
}
change = () => {
const = {page, currentPageis1} = this.state;
this.setState({
...this.state,
page: page + 1,
currentPageis1: !currentPageis1
});
}
render(){
return (
<div>
<p className={this.props.currentPageis1 ? '': 'some-css-class'}>Some Text</p>
<Child change={this.change} />
</div>
)
}
}

Button functionality is not working when clicked for 2nd time in React

In component A ,I've a btn with search filters , After clicking btn im sending the selected values from search filters into component B where it is getting data from api and rendering the info by using table.
So the problem im facing is, At 1st time btn click all selected values are passed to component B and also data is rendering but when im clicking the btn for 2nd time after changing search filter values , the btn func is not working. I've also checked in console by displaying logs in component B. Im able to see only 1st call logs but not 2nd call.
Im new to React & also rarely develop front-end. Can anyone tell where am i doing mistake ?
Component A:
class Hook extends React.Component<any ,any > {
constructor(props) {
super(props);
this._onButtonClick = this._onButtonClick.bind(this);
this.state = {
showComponent: false,
};
};
_onButtonClick() {
this.setState({
showComponent: true,
});
}
render() {
const {classes} = this.props;
return (
<div>
<Box border={1} className={classes.root} display="flex" >
<div>
<Button variant="contained" color="primary"
onClick={this._onButtonClick}>
Search
</Button>
</div>
</Box>
{this.state.showComponent ?
<BComponent a = {this.state.dropdown1value} b = {this.state.dropdown2value} c = {this.state.dropdown3value} d = {this.state.dropdown4value}/>
: null }
</div>
);
}
}
Component B:
class BComponent extends React.Component<any ,any > {
constructor(props) {
super(props);
this.getAPIInfo = this.getAPIInfo.bind(this);
this.state = {
result: { allInfo: [] },
};
}
componentDidMount() {
this.getAPIInfo(this.props.a,this.props.b,this.props.c,this.props.d).then(Response => {this.setState({result: Response })});
this.setState({loaderFlag: true});
}
async getAPIInfo(a, b, c, d) {
let res;
try {
calling API
});
} catch (error) {
console.log("API call error", error);
} this.setState({loaderFlag: false});
return res;
}
render() {
const {classes} = this.props;
if(this.state.loaderFlag) {
return (
<Loader /> );
}
else
return (
Rendering API data through material UI table
);
}
}
export default withStyles(useStyles)(BComponent)
Ideally the ComponentB should be a dumb/presentation component and Component A should fetch the data and send it over to ComponentB as props.
this._onButtonClick
should then fetch the data from API and pass it over to componentB for display.

ReactJS Assigning Value inside a class component method to props

I'm trying to access the index of the selected tab in a React component so as to map it to props as follows:
class AttendanceDetail extends React.Component {
handleSelect(key, props) {
console.log(key)
props.index = key;
}
render(){
const {single_class, courses, attendances} = this.props;
// console.log(this.state);
if(single_class) {
return(
<div className='container content-section'>
// Some irrelevant Code
<Tabs defaultActiveKey={0} onSelect={this.handleSelect} id="uncontrolled-tab-example">
{ courses.map((course, index) => {
return (
<Tab eventKey={index} title={course.course + " year " + course.yearofstudy}>
//Other irrelevant code...
</Tab>
)
})}
</Tabs>
</div>
)
} else {
return (
<div className='container content-section'>
Loading Unit details...
</div>
);
}
}
}
So basically the handleSelect method is what determines the index of the selected tab and logs it to the console. The problem is, I'm tring to map that key (index) to props so as to access it else where but to no avail. Could someone help me out? What am I missing?
You're not supposed to set the component's props, only read. You can use state within the component:
export class Wrapper extends React.Component {
constructor() {
this.state = {
index: 0 //initial state
}
}
handleSelect(index, props) {
this.setState({index})
}
render() {
return (
<span>{this.state.index}</span>
)
}
}
you can read more on the official docs.
if i understood the scenario correctly, you need to log index value of the currently active tab. try using onFocus event handler to get the index value of the currently visible tab and set the state that will be used by handleSelect
constructor(props){
super(props);
this.state = {
index:''
}
}
the handler definition
setIndex = (index) => {
this.setState({index})
}
update handleSelect
handleSelect(index) {
console.log(index)
// call event handler of parent component eg: this.props.getIndex(index);
}
update tabs component handler
<Tabs defaultActiveKey={0} onSelect={() => {this.handleSelect(this.state.index)}} id="uncontrolled-tab-example">
call handler on focus of tab
<Tab
onFocus={() => {this.setIndex(index)}}
eventKey={index}
title={course.course + " year " + course.yearofstudy}>
//Other irrelevant code...
</Tab>

How to reload the current component in react?

I've have an component "A" with a button. When the user press the button I'm showing a modal(react-responsive-modal) with bunch of filed and an update button. When the user presses the update button on the modal I want to reload the component "A" with the updated data.
I tried redirecting using this.props.history.push('dashboard/componentA') but it didn't work. Then I tried redirecting to the dashboard and again redirecting to the component like this
this.props.history.push('/dashboard');
this.props.history.push('/dashboard/componentA');
It worked but I'm not seeing any loader that I've used on 'componentWillMount' and the component just freezes up. I couldn't scroll up or down.
Try not to use the browser history as a way to update react (as much as you can). React is designed to re-render components when the props or state for that component change. As an example, this should trigger an update in ComponentA without needing to update the browser's history:
class ComponentA extends Component {
handleModalClick = (event) => {
this.setState({
componentData: event.data,
});
}
render() {
return (
<ReactModal onClick={this.handleClick} />
)
}
}
EDIT: Updated to show a data fetching parent component:
class DataFetcher extends Component {
saveAndFetchData = (saveData) => {
FetchDataPromise(saveData).then((updatedData) => {
this.setState({ data: updatedData });
}
}
render() {
const { data } = this.state;
return (
<div>
<ComponentA data={data} />
<ReactModalComponent handleClick={saveAndFetchData} />
</div>
);
}
}
class ComponentA extends Component {
render() {
const { data } = this.props;
return (
<div>
...render data...
</div>
)
}
}

React onClick doesn't stop browser from setting anchor tag

I'm a React newbie and ran into a problem with paging controls using link tags. My basic paging control renders as something like this:
Next
The JSX definition that renders it looks like this:
<a href={"#page"+(this.props.pageIndex+1)} onClick={this.handleClick}>
{this.props.name}
</a>
The problem is that when you click on the Next link to go to Page 2, the browser ends up showing #page3 in the URL bar, even though the code properly renders page 2. (The code does nothing to modify the URL.) Tracing following the JavaScript in the debugger, I see that window.location.href stays at #page1, then jumps to #page3.
I believe what is happening is that React is intercepting the click event, and re-renders the page properly, then the browser's default link handling fires after the Next link has changed to point to #page3 instead of #page2.
Is my analysis correct? If so, what is the proper way to make this work so the browser shows #page2 in the URL bar?
EDIT: Here is the simplified code in context:
class RecordList extends React.Component {
changePage(pageIndex) {
console.log("change page selected: "+pageIndex);
this.props.changePage(pageIndex);
return false;
}
render() {
...
nextLink = (<PagingLink name=" Next> "pageIndex={this.props.pageIndex+1} handleClick={() =>
this.changePage(this.props.pageIndex+1)}/>)
return (
...
<div className="paging-control">
<span>Page {this.props.pageIndex+1}</span>
{nextLink}
</div>
);
}
}
class PagingLink extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.handleClick(this.props.pageIndex)
}
render() {
return (
<span className="pageLink">
<a href={"#page"+(this.props.pageIndex+1)} onClick={this.handleClick}>
{this.props.name}
</a>
</span>
);
}
}
class App extends Component {
constructor() {
super();
this.state = {
pageSize: 20,
pageIndex: 0,
...
};
}
componentDidMount() {
var pageIndex = this.state.pageIndex;
if (window.location.hash.indexOf("#page") === 0) {
pageIndex = Number(window.location.hash.substring(5))-1;
this.setState((prevState) => {
return { pageIndex: pageIndex };
}, () => {
this.fetchRecords(pageIndex);
});
}
else {
this.fetchRecords(pageIndex);
}
}
fetchRecords(pageIndex) {
...
}
changePage(pageIndex) {
console.log("change page selected: "+pageIndex);
this.setState({pageIndex: pageIndex});
this.fetchRecords(pageIndex);
}
render() {
var content = (
<RecordList
records={this.state.records}
pageSize={this.state.pageSize}
pageIndex={this.state.pageIndex}
changePage={(pageIndex) => this.changePage(pageIndex)}
/>
)
return (
<div className="App">
...
{content}
</div>
);
}
prevent the default event on the anchor :
handleClick(event) {
event.preventDefault()
this.props.handleClick(this.props.pageIndex)
}
I could not get this working reliably with my own event handling and url rewriting, so I decided to simply rebuild it using React Router V4. When the going gets tough, it's always a good idea to not reinvent the wheel and let somebody else do the hard work for you.

Resources