Updating a D3 Chart within React/Redux - reactjs

I have a D3(v3) chart within a React Component that is used within a Redux application. What is the best way to handle my D3 chart updating to reflect my Redux store change?
Right now, I have a function within the React component that calls the drawing of the chart and a function that calls the removal of the previous chart as soon as componentWillUpdate is called as so:
export default class Chart extends Component {
componentWillUpdate(nextProps) {
this.removePreviousChart();
this.drawChart(nextProps.chartData);
}
removePreviousChart(){
const chart = document.getElementById('chart');
while(chart.hasChildNodes())
chart.removeChild(chart.lastChild);
}
}
drawChart() {
//appends an svg to #chart html element and draws a d3 Chart
}
render(){
this.drawChart();
return(<div id='chart' />)
}
}
Any alternative approaches, pseudocode, ideas, or feedback on how to improve this question would be appreciated.

The approach you followed seems fine to go with.
componentWillUpdate() is invoked immediately before rendering when new
props or state are being received. Use this as an opportunity to
perform preparation before an update occurs. This method is not called
for the initial render.
Note that you cannot call this.setState() here. If you need to update
state in response to a prop change, use componentWillReceiveProps()
instead.
Note
componentWillUpdate() will not be invoked if shouldComponentUpdate()
returns false.
You can read more from here
If you want to setState() on receiving newProps use componentWillReceiveProps() which is fired for every new props.
Use your Chart API to draw everytime you have new props.
export default class Chart extends Component {
componentWillReceiveProps(nextProps) {
this.removePreviousChart();
this.drawChart(nextProps.chartData);
}
removePreviousChart(){
const chart = document.getElementById('chart');
while(chart.hasChildNodes())
chart.removeChild(chart.lastChild);
}
}
drawChart(chartData) {
const chart = document.getElementById('chart'); //fails if DOM not rendered
//appends an svg to #chart html element and draws a d3 Chart
//assuming you chart function as Chart(element, data);
if(chart && chartData){ //draw only if DOM rendered and have chartData
new Chart(chart, chartData); //calls to draw graph
}
}
render(){
return(<div id='chart' />)
}
}

Related

How to use state in dynamically created components at runtime for React Native

I have created a function that creates and builds a tree of components at run time.
This tree is different every time based on some parameters coming from a webservice.
I am able to render the tree successfully, but the state values don't get updated when the state is changed.
For example:
export default class App extends React.Component {
state={
loading:true,
}
componentDidMount() {
this.buildComponent();
}
buildComponent() {
var comp = <View style={{flex:1,justifyContent:'center',alignItems:'center'}}>
<Text>{this.state.name}</Text>
<Button title="Change State" onPress={()=>this.setState({name:"Changed"})} />
</View>
this.comp = comp;
this.setState({loading:false});
}
render() {
if (this.state.loading) {
return <Text style={{alignSelf:'center',marginTop:50}}>Loading ...</Text>
}
return (
this.comp
);
}
}
In this code sample, I know it is not best practice to do it this way, but I just have a use case in which I need to code it in such way.
As you see in the sample, when I click on the button, I want the state name to be changed to "Changed", but it doesn't get changed.
Sounds like React doesn't map the state.name inside the text and renders it once it's changed.
I don't want to place the buildComponent inside render method because buildComponent does a lot of expensive calculations and calling it every time would affect the performance big time, therefore I wanted a way to build the components one time and render it, but state values don't get updated at all.
Thanks in advance for your valuable input.
u are using componentDidMount so the comp only renders once when the state is updated componentDidMount is not called, so if u want to reRender comp again u def componentDidUpdate instead
componentDidUpdate() {
this.buildComponent();
}

Using React hooks to prevent React re-rendering of a D3 chart

I have been using React and D3 separately and now have a project where I need low level control over the plotting function of an application. Basically, I need to be able to go and fetch higher resolution data from a database as the user zooms in, and vice versa as the user zooms out, on a plot.
I have found a few methods to use D3 and React together. I wanted to try and keep all of my React code based around the hooks API as that is what is used for the rest of the code base. I am struggling to get the hooks equivalent for the specific cases that I am facing. The documentation on React hooks is great but I think my situation is more of an edge case and I have not seen any discussion relating to similar use cases to what I have.
The logic of the code is fairly straight forward:
I have a main container, call it App.js, that hold some state. App.js renders a Wrapper component (which is where the challenge is occurring) and then the Wrapper.js file simply creates the D3 Plot. The D3 plot is just typical D3 code for a line plot with some zoom events.
The Wrapper code:
import React, { Component } from 'react';
import Plot from './Plot'; // D3 plot
class Wrapper extends Component {
// Sets up plot when Wrapper first added to DOM tree
componentDidMount(){
this.setState({
plot: new Plot(this.refs.plot, this.props.data, this.props.updateData)
});
};
// Do not allow React to re-render the Wrapper and therefore the Plot
shouldComponentUpdate(){
return false;
};
// When the data prop changes, execute the update() method in the plot file
componentWillReceiveProps(nextProps){
this.state.plot.update(nextProps.data) // the .update() method calls props.updateData()
}
render(){
return <div ref="plot"></div>
}
}
export default Wrapper;
I have started putting together the hooks version below but I cannot come up with suitable emthods that meet the specific requirements of what I am after for the cases of the shouldComponentUpdate and componentWIllReceiveProps blocks.
hooks version:
import React, { useEffect, useRef } from 'react';
import Plot from './Plot'; // D3 plot
const Wrapper = props => {
// destruct props
const {
data = props.data,
fields = props.fields,
click = props.click
} = props
// initialise empty ref
const plotRef= useRef(null);
// ComponentDidMount translated to hooks
useEffect(() => {
new Plot(plotRef, data, updateData)
},[]) // empty array ensures the plot object is only created once on initial mounting
// shouldComponentUpdate
useEffect(...) // This won't work because render has already occurred before useEffect dependency array diffed?
// ComponentWIllReceiveProps
useEffect(() => {
plot.update(data)
}, [data]) // This won't work because component needs to have re-rendered
return (
<div ref= {plotRef}></div>
)
};
export default Wrapper;
What I am trying to achieve is blocking any rendering of the D3 chart after the initial mount, but then if the data props changes in the parent component, execute a method in the D3 class without allowing React to re-render the Wrapper component.
Is this at all possible or am I better off leaving this as a class based component?
I have been banging my head against a wall trying to get the logic without any success so any input would be greatly appreciated.
you can wrap your return value in React useMemo with suitable dependencies.
https://reactjs.org/docs/hooks-reference.html#usememo

React-Redux Dispatch design

Overview
We have a page with a header (Blue color) and content section (Green color) that can be seen in image below. The requirement is when a user selects a year in header, then the content page will show data as per the selected year.
What is happening right now Technically
When user selects a year in header, we dispatch the selected value and the active container's mapStateToProps function is triggered and the selected year is passed to the component.
class Page1Content extends Component {
}
function mapStateToProps(state) {
return { selectedYear : state.userSelectedValue };
}
export default connect(mapStateToProps, null)(Page1Content);
Question 1
How will data on Page1Content will refresh? Few approaches:
In ComponentDidUpdate react life cycle method of the Page1Content API to fetch data can be called. However have seen opinions where we should avoid React hooks and life cycle methods with Redux.
In mapStateToProps function API can be called.
Can any one suggest what is the better place to call the API?
Question 2
Data on Page1Content will be used only by this page. This data will not be used by any other component and hence need not be shared by any other Component. Now question 2 is
In case we decide to use ComponentDidUpdate should we again dispatch the API call using Thunk or any other library and then catch the response in mapStatesToProps again?
Or we should make the API call and resolve it in the component itself as a promise. Then the response will be set in State and respective Template will be refreshed.
ComponentDidUpdate is a lifecycle method not a hook. Hooks is functionality that allows functional components to have class based functionality such as state.
You are using a class based component in your example so you are not using hooks.
https://reactjs.org/docs/hooks-intro.html
Yes Redux shouldnt be used with hooks since context is a better option.
You can lift state up so to speak and update the local state in the parent component getting rid of redux completely.
Just pass down the setState function and the state itself to the appropriate children.
class App extends Component {
constructor(props) {
super(props);
this.state = {
some_prop: false
}
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({some_prop: true})
console.log('Click happened');
}
render() {
return (
<Header onClick={this.handleClick } />
<Page1Component props={this.state.some_prop} />
}
}
Edit:
Question 1
How will data on Page1Content will refresh?
best option is with a ternary expression in your render method, there is no need to check if the state updated. In react if the state is changed your component will automatically re render.
render() {
return (
<div>
{this.props.selectedYear
? <p> {this.props.selectedYear}</p>
: null
}
</div>
}
}
Question 2
Data on Page1Content will be used only by this page. This data will not be used by any other component and hence need not be shared by any other Component. Now question 2 is
If I understand this correctly you will need to use an action creators, redux thunk is overkill here.
class Header extends Component {
constructor(props) {
super(props);
}
handleClick() {
this.props.dispatchActionCreator({some_value})
console.log('Click happened');
}
render() {
return (
<button onClick={(some_value) => this.handleClick(some_value)}>Click </button>
}
}
function mapDispatchToProps(state) {
return {
dispatchActioNCreator: (some_value) => dispatch(ACTIONS.action_creator(some_value) };
}
This will save your value from your header to the global redux state and then you can just access with mapStateToProps in your Page1Component.

Make react redraw all component, or part of it

I have this line of code
render() {
return <img class="animated bounceIn" src={this.state.img_source}></img>
}
Animation is played. When I change img_source by setState(), react detects changes only at source of the image, and changes the picture. So animation is not played again.
How can I make animation played again?
First, you have to use "className", not "class" in react JSX. Next, be pragmatic: animation is probably triggered by css and render is redrawn every time you change the state.
export class MyComponent extends React.Component {
//... code
render() {
return (
<img
className={this.state.className}
src={this.state.img_source}
></img>)
}
It's enough to manage class names in state and them will be applied to your render automagically with the same effects that you would get with other well known libraries as jQuery.
You can use the React component lifecycle methods.
componentDidUpdate will serve the purpose.
componentDidUpdate (prevProps, prevState) {
if (prevState.img_source !== this.state.img_source) {
// Redo the animation
}
}
Since the animation is triggered by css, then you must remove and add the classes when updating the state of the img src:
render() {
return <img ref={i => {this.img = i}} className={"animated bounceIn"} src={this.state.img_source}></img>
}
componentDidUpdate (pps, pst) {
if (prevState.img_source !== this.state.img_source) {
this.img.classList.remove("animated");
this.img.classList.remove("bounceIn");
this.img.classList.add("animated");
this.img.classList.add("bounceIn");
}
}
Using ref you can reference the Dom element and add / remove de classes manually. It's not elegant but it works

Trying to render a new instance in ReactJS

As an example (real tried code)
I have a component of which I want to initiate a NEW instance for rendering.
import React, { Component } from 'react';
export default class TinyObject extends Component {
constructor(props) {
super(props);
console.log("TinyObject constructor");
}
render() {
console.log("TinyObject render");
return (
<div>HEY THIS IS MY TINY OBJECT</div>
);
}
}
Then in main App constructor I do the following:
var myTinyObject = new TinyObject();
var myArray = [];
myArray.push(myTinyObject);
this.state = {testing: myArray};
Then a created a function to render this:
renderTest()
{
const {testing} = this.state;
const result = testing.map((test, i) => {
console.log(test);
return {test};
});
}
And I call this from the App render function like this:
render() {
const { gametables, tableActive } = this.state;
console.log("render");
return <div><div>{this.renderTest()}</div></div>;
}
It runs, no errors.
I see console log of the following:
console.log("TinyObject constructor");
console.log(test);
But I don't see console log of the TinyObject render nor do I see the render output.
Thanks to lustoykov answer I got a little further
JSX: var myTinyObject = <TinyObject />;
works!
but in the real app I add a little more and don't know how to do it here.
return <GameTable key={'gt'+index} data={table} settings={this.settingsData} sendTableNetworkMessage={this.sendTableNetworkMessage} />
this is the way I was rendering; and I needed more instances of GameTable
now the question is; how do I add the arguments like data & settings to myTinyObject.
thanks for helping so far.
You don't manually instantiate react component, use JSX or createElement. For instance
via JSX
var myTinyObject = <TinyObject prop1={prop1} prop2={prop2} />;
via React.createElement
var myTinyObject = React.createElement(TinyObject, { prop1, prop2 }, null);
I would definitely check out some tutorials and how React works at a basic level. You aren't really going to call your react components like you would normally do in javascript since the render function returns jsx.
Fundamentally, React is what is called a single page application. That means that your browser will load up a single html file with a div. Now that div will be where React performs its magic by using Javascript to change stuff around.
It is easiest for me to think of React as a tree. You create these components that you place on the DOM or in your HTML and React will add and remove them downwards. For instance, take a look at this picture of twitter.
So first the Feed component is going to be put on the DOM. Then the Feed component will render the Tweet components. So as you can see the rendering goes in one direction, downwards.
Now, as you can see your render methods are not returning javascript. It is returning something that looks like HTML but we call it JSX. That means we want to render it a little differently with our react classes.
If we have a child component:
class Child extends React.Component {
render() {
return <h1>Hello, I am inside the parent component</h1>;
}
}
We can call the render method like this:
class Parent extends React.Component {
render() {
<Child /> //This is how I use the Child class
}
}
Now the reason why react is so performant is that the child cannot be re-rendered unless we do 1 of two things:
It is a component with a state and we call a method setState()
We pass down new props to a child component from the parent component
You can read about it here
Now the only way to get React to call that render function again is by doing those two things.

Resources