I am working with Chartjs and trying to create the chart object once within a function. Then skipping that piece of code in the function so it will just update the chart on future calls.
I am not not sure how to structure the conditional chart object creation. Below is my code approach ( it does not run atm).
class BtcOverview extends React.Component {
constructor(props) {
super(props);
this.canvasRef = React.createRef();
}
componentDidMount () {
this.callApi();
}
callApi = () => {
fetch(getApi())
.then(results => results.json())
.then(marketData => {
//API FETCH STUFF
const chartOptions = {
...{}
...this.props.chartOptions
};
const BtcChart = new Chart(this.canvasRef.current, {
type: "LineWithLine",
data: this.props.chartData,
options: chartOptions
});
BtcChart.render();
// I want to create the BtcChart and render() on the first run. But then after that only call update() on the chart object
})
}
render() {
return (
<>
<CardBody className="pt-2">
<canvas
height="160"
ref={this.canvasRef}
style={{ maxWidth: "100% !important" }} className="mb-1" />
</CardBody>
</Card>
</>
);
}
}
Update: I should explain a bit more clearly. I want to be be able to create a 'new Chart' once and then on subsequent runs rather call BtcChart update given the chart object already exists.
When I tried to use an if-else statement to achieve that it would not compile because I guess because the chart BtcChart has not been created at the time of compiling and possibly a scope issue. So I am have been trying hacks that are probably wrong in their approach.
Related
I'm relatively new to React and am stumped trying to integrate third party plotting libraries into my application, in this instance chartjs. (note: I have looked at similar questions on here but was unable to glean a solution from them)
I need to create an plot instance that targets the context of a HTML canvas element. I'm using React's ref prop to try to implement this behaviour but I think I'm being tripped up by the asynchronicity of React's methods. It seems that ctx.current is always null in the render() method. I've also tried placing the chart() call in componentDidMount but then this.ctx is undefined, presumably because the conditional rendering hasn't occurred yet so the ref doesn't exist?
Any help would be much appreciated!
class Hello extends React.Component {
constructor(props) {
super(props);
this.state = {data: [], labels: []}
this.ctx = React.createRef();
this.fakeAPICall();
}
// retrieve fascinating data...
fakeAPICall = () => {
setTimeout(() => {
console.log('Data fetched');
this.setState({
data: [1,2,3],
labels: ['A', 'B', 'C']
})
}, 1500);
}
chart = () => {
console.log('creating chart')
const myChart = new Chart(this.ctx, {
type: 'bar',
data: {
labels: this.state.labels,
datasets: [{
label: 'Series 1',
data: this.state.data
}]
}
})
}
render() {
if(this.ctx && this.ctx.current) this.chart();
return (
<div>
<h1>Fascinating Chart</h1>
{
this.state.data.length
?
<canvas
ref={c => {
this.ctx = c.getContext('2d')}
}
style={{ width: 400, height: 200 }}
/>
:
<p>Loading ...</p>
}
</div>
);
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
My current best attempt is attached here in a fiddle: https://jsfiddle.net/69z2wepo/327056/
It doesn't work because there are no further state updates after you assign the ref to the canvas; that means no more re-renderings (calls to render method) so you never get to execute this.chart(). Instead of calling that last method in render, add it to the setState callback in fakeAPICall:
this.setState({
data: [1,2,3],
labels: ['A', 'B', 'C']
}, () => this.chart());
The callback will be executed once setState is completed and the component is re-rendered. You can read more about state callbacks here.
I'm trying to render dynamically a collection of component using componentDidUpdate.
This is my scenario:
var index = 0;
class myComponent extends Component {
constructor(props) {
super(props);
this.state = {
componentList: [<ComponentToRender key={index} id={index} />]
};
this.addPeriodHandler = this.addPeriodHandler.bind(this);
}
componentDidUpdate = () => {
var container = document.getElementById("container");
this.state.componentList.length !== 0
? ReactDOM.render(this.state.componentList, container)
: ReactDOM.unmountComponentAtNode(container);
};
addHandler = () => {
var array = this.state.componentList;
index++;
array.push(<ComponentToRender key={index} id={index} />);
this.setState = {
componentList: array
};
};
render() {
return (
<div id="Wrapper">
<button id="addPeriod" onClick={this.addHandler}>
Add Component
</button>
<div id="container" />
</div>
);
}
}
The problem is that componentDidUpdate work only one time, but it should work every time that component's state change.
Thank you in advance.
This is not how to use react. With ReactDOM.render() you are creating an entirely new component tree. Usually you only do that once to initially render your app. Everything else will be rendered by the render() functions of your components. If you do it with ReactDOM.render() you are basically throwing away everything react has already rendered every time you update your data and recreate it from scratch when in reality you may only need to add a single node somewhere.
Also what you actually store in the component state should be plain data and not components. Then use this data to render your components in the render() function.
Example for a valid use case:
class MyComponent extends Component{
state = {
periods: []
};
handleAddPeriod = () => {
this.setState(oldState => ({
periods: [
...oldState.periods,
{/*new period data here*/}
],
});
};
render() {
return (
<div id="Wrapper">
<button id="addPeriod" onClick={this.handleAddPeriod}>
Add Component
</button>
<div id="container">
{periods.map((period, index) => (
<ComponentToRender id={index} key={index}>
{/* render period data here */}
</ComponentToRender>
))}
</div>
</div>
);
}
}
}
Also you should not work with global variables like you did with index. If you have data that changes during using your application this is an indicator that is should be component state.
try
addHandler = () =>{
var array = this.state.componentList.slice();
index++;
array.push(<ComponentToRender key={index} id={index}/>);
this.setState=({
componentList: array
});
}
if that works, this is an issue with the state holding an Array reference that isn't changing. When you're calling setState even though you've added to the Array, it still sees the same reference because push doesn't create a new Array. You might be able to get by using the same array if you also implement shouldComponentUpdate and check the array length of the new state in there to see if it's changed.
What's the difference between stateless function component and common component when rending in React ?I couldn't understand very well! How can I get details about the difference between them! Thank you!
// Stateless function components
const renderIcon = result => {
return <Icon type="icon-fail"/>;
};
const Page = props => {
// ...
return (
<div>
...
{renderIcon(props.result)}
...
</div>
);
};
const ResultIcon = ({result}) => {
return <Icon type="icon-success"/>;
};
const Page = props => {
// ...
return (
<div>
...
<ResultIcon result={props.result} />
...
</div>
);
};
some codes I added some codes in project, there two methods to render component but perfect-scrollbar display differently. I don't know why!
// parts of codes
const data = [...data] // ...some dates used
return <div className="parts_codes">
{
[
{
title: 'Table',
components: [
{
dataType: 'basiclist',
DataComponent: ({mainTableData}, index) =>
<MainTable
loading={mainTableDataStatus}
// ...
/>,
}
]
},
{
title: 'String',
components: [
{
dataType: 'Object',
DataComponent: ({type}, index) => (!type || status)
?
<Spin key={index} />
:
<div key={index}>
<PlotComponent />
</div>
}, {
dataType: 'Object',
DataComponent: ({type}, index) => (!type || status)
?
<Spin key={index} />
:
<div key={index}>
key={index}
>
<ColumnComponent />
</div>
}
]
},
].map((item) => <div key={item.title} className="map_parts">
<span>{item.title}</span>
{
item.components.map((details, index) => {
const {dataType, DataComponent} = details;
return <DataComponent index={index} data={data}/>
// one component perferc-scrollbar instance failed
// return DataComponent(data, index);
// another functional component perferc-scrollbar instance success
})
}
</div>)
}
</div>
class MainTable extends Component {
componentDidUpdate() {
// ... do perferc-scrollbar instance
}
render() {
return <Table />
}
}
After Question edit
From the comments I gather your actual question is why is that exact snippet of code you provided failing.
It looks like this is the part you're having trouble with. Though I'm not sure. The comments around it are unclear. But I think it doesn't work as it is now.
item.components.map((details, index) => {
const {dataType, DataComponent} = details;
return <DataComponent index={index} data={data}/>
// one component perferc-scrollbar instance failed
// return DataComponent(data, index);
// another functional component perferc-scrollbar instance success
})
details.DataComponent appears to be a component generating function. You could try just changing your return <DataComponent... to return details.DataComponent({type: "Whatever type value is"}, index);
However, that code seems to be mixing data and components. Overall I'm not sure what you're doing or trying to do but with my limited knowledge I'd expect something more like:
const DataComponent = ({type,index, status, data}) => {
if(!type || status) {
return <Spin key={index} />
}
else {
// I assume plot component needs the 'data'
return <div key={index}><PlotComponent data={data}/></div>
}
}
Then each of your DataComponent: () => ... assignments would become: data:"The data object for the graph".
And finally, your return statement in your inner map statement would be return <DataComponent type={"Whatever type is"} status={"Whatever status is"} index={index} data={details.data} />;.
Old answer
Those all are stateless functional components. Where did you hear of 'common components'? That's not a thing I've heard of. If you could provide some links to the material you are reading it would be helpful.
The other way of defining components is to inherit from React.Component:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
Functional components, like the ones in your examples, are just a function; the same as would be in the render() of a component built with inheritance.
Regarding 'stateless': In programming, 'state' is a concept that refers to data that is carried over from one execution to the next. Stateless components don't carry any data. They can't because they are just a function not a class.
This makes them simpler but more predictable. Send the same parameters to the stateless component multiple times, you will get the same result.
This is more clear with examples. Many are provided here in the official documentation.
basically we use stateless functional Components when we doesn't need to use a life cycle method and neither the state, so that's why it's called stateless(without state). So we do not need to inherit from React.Component class, all we need is to return JSX directly with props data.
I recommend that you take a look at the react docs.
I am trying to use from React-virtualized.
In the following component I am trying to call public methods. The problem is, these methods are called (I see them called while debugging, but they have no visible effect.
import React, {Component} from "react";
import {List, AutoSizer, CellMeasurer, CellMeasurerCache} from "react-virtualized";
class InfiniteScroller extends Component {
constructor(props) {
super(props);
this.cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 50
});
this.state = {
currentLineSt: 50,
currentLineEnd: 100,
}
}
renderRow = ({index, parent, key, style}) => {
let className = "code-line";
if (this.state.currentLineSt <= index && this.state.currentLineEnd >= index) {
className += " code-line-focus";
}
return (
<CellMeasurer
key={key}
cache={this.cache}
parent={parent}
columnIndex={0}
rowIndex={index}
>
<div style={style} className={className}>
<span className={"code-index"}><b>{index}: </b></span>
<span className={"code"} style={{whiteSpace: "pre-wrap"}}>{this.props.data[index]}</span>
</div>
</CellMeasurer>
)
};
componentDidUpdate() {
// these methods are called but do nothing visible
this.myInfiniteList.forceUpdateGrid();
this.myInfiniteList.scrollToRow(100);
}
componentDidMount() {
// these methods are called but do nothing visible
this.myInfiniteList.forceUpdateGrid();
this.myInfiniteList.scrollToRow(100);
}
render() {
return (
<AutoSizer>
{
({width, height}) => {
return <List
ref={(ref) => this.myInfiniteList = ref}
forceUpdateGrid
rowCount={this.props.data.length}
width={width}
height={height}
deferredMeasurementCache={this.cache}
rowHeight={this.cache.rowHeight}
rowRenderer={this.renderRow}
/>
}
}
</AutoSizer>
);
}
}
export default InfiniteScroller;
I need to call them since:
1) After data change, line size does not change
2) Need a way to scroll to line on click.
Any ideas why it doesn't work or how I could do this differently would be greatly appreciated.
You'll have to talk more Brian to get a real understanding, but it appears your list isn't fully initialized on componentDidMount.
Note in this sandbox (took yours and tweaked): https://codesandbox.io/s/lro6358jr9
The log of the element in componentDidMount has an array of 0 for children, whereas the log when I click to do the same thing later has 26 children (and works fine).
I've noticed a lot of weird first load issues in react-virtualized usages (like your list not loading initially). Keep in mind that if you're giving react-virtualized data you expect it to update on, make sure that data is changing a prop somewhere. Otherwise nothing inside will re-render.
Not sure what I'm doing wrong but my component wrapped in setTimeout is not being rendered to the DOM:
const ContentMain = Component({
getInitialState() {
return {rendered: false};
},
componentDidMount() {
this.setState({rendered: true});
},
render(){
var company = this.props.company;
return (
<div id="ft-content">
{this.state.rendered && setTimeout(() => <Content company={company}/>,3000)}
</div>
)
}
})
I'd bet this isn't working because the render method needs all of its input to be consumed at the same time and it can't render other components in retrospect, there's a certain flow to React. I'd suggest to separate the timeout from render method anyway for logic's sake, and do it in componentDidMount like this:
const ContentMain = Component({
getInitialState() {
return {rendered: false};
},
componentDidMount() {
setTimeout(() => {
this.setState({rendered: true});
}, 3000);
},
render(){
if (!this.state.rendered) {
return null;
}
var company = this.props.company;
return (
<div id="ft-content">
<Content company={company}/>
</div>
)
}
})
Changing the state triggers the render method.
On a side note - even if your original approach worked, you'd see the component flicker for 3 seconds every time it got rendered after the initial load. Guessing you wouldn't want that :)