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

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

Related

Simple animation with react and gsap

I have been struggling with this for days and am just having trouble wrapping my head around the varied documentation and examples out there, so I'm hoping someone can help with the specific example I'm working on and that might in turn help me understand this a bit better. This seems like it's far more convoluted than it should be, but I'm sure that's just my lack of understanding of how it all works.
The goal: make a very simple animation of a red ball moving back and forth. Hell, make any kind of animation at all (once I can confirm that gsap is animating anything at all, I should be able to take it from there).
The problem: Nothing happens. No errors, nothing clear to go on, just nothing. I still don't have a strong understanding of how this should work; a lot of the guides I've checked seem to be using different methods and don't go into a lot of detail as to why, so it's made it difficult to extend that knowledge to my specific scenario.
The code. I've simplified this greatly because, as I mentioned, all I really need is to just get any kind of gsap animation to work at all and from there I'm confident I can do with it what I need. If anyone feels like it would make a difference, I'm happy to update with the full code:
import React, { useRef, useEffect } from 'react';
import { gsap } from 'gsap';
const tl = gsap.timeline({paused: true, repeat: 0});
function App() {
const waitingAnimationRef = useRef(null);
useEffect(() => {
tl.set(waitingAnimationRef, {autoAlpha: 0});
tl.play();
}, []);
return (
<div className="App">
<div id="red-circle" ref={waitingAnimationRef}></div>
</div>
);
}
export default App;
Here's a working example and some tips to help you make some sense of it:
Create a timeline with gasp.timeline and store it in a ref. You can do this as you've done, creating the timeline outside your component, but then you need to pass that timeline to the ref in your component. In this example, I did that by passing the variable name for the timeline to the useRef hook directly: const tl = useRef(timeline);.
I used the timeline options { repeat: -1, yoyo: true } so that the animation would loop infinitely in alternating directions because you said you wanted to make a ball "moving back and forth".
You'll also need a DOM node ref so you can pass that to the gsap.context() method. Create the ref in your component and pass it to the wrapping element of the component. Here, I called mine app (const app = useRef(null)) and then passed it to the top level div in the App component with ref={app}. Make sure you're passing the ref to a DOM node and not a React component (or you'll have to forward the ref down to a node child within it). Why are we using refs? Because refs are stable between rerenders of your components like state, but unlike state, modifying refs don't cause rerenders. The useRef hook returns an object with a single property called current. Whatever you put in the ref is accessed via the current property.
Use a useLayoutEffect() hook instead of useEffect. React guarantees that the code inside useLayoutEffect and any state updates scheduled inside it will be processed before the browser repaints the screen. The useEffect hook doesn't prevent the browser from repainting. Most of the time we want this to be the case so our app doesn't slow-down. However, in this case the useLayoutEffect hook ensures that React has performed all DOM mutations, and the elements are accessible to gsap to animate them.
Inside the useLayoutEffect, is where you'll use the gsap context method. The gsap context method takes two arguments: a callback function and a reference to the component. The callback is where you can access your timeline (don't forget to access via the current property of the ref object) and run your animations.
There are two ways to target the elements that you're going to animate on your timeline: either use a ref to store the DOM node or via a selector. I used a selector with the ".box" class for the box element. This is easy and it's nice because it will only select matching elements which are children of the current component. I used a ref for the circle component. I included this as an example, so you could see how to use forwardRefs to pass the ref from the App component through Circle component to the child div DOM node. Even though this is a more "React-like" approach, it's harder and less flexible if you have a lot of elements to animate.
Just like useEffect, useLayoutEffect returns a clean up function. Conveniently, the gsap context object has a clean up method called revert.
import { useLayoutEffect, useRef } from "react";
import gsap from "gsap";
const timeline = gsap.timeline({ repeat: -1, yoyo: true });
function App() {
const tl = useRef(timeline);
const app = useRef(null);
const circle = useRef(null);
useLayoutEffect(() => {
const ctx = gsap.context(() => {
tl.current
// use scoped selectors
// i.e., selects matching children only
.to(".box", {
rotation: 360,
borderRadius: 0,
x: 100,
y: 100,
scale: 1.5,
duration: 1
})
// or refs
.to(circle.current, {
rotation: 360,
borderRadius: 50,
x: -100,
y: -100,
scale: 1.5,
duration: 1
});
}, app.current);
return () => ctx.revert();
}, []);
return (
<div ref={app} className="App">
<Box />
<Circle ref={circle} />
</div>
);
}

Should we change class component to functional component when we put state into Redux?

I was learning React and created two class components having respective states. Then, I learned about Redux and decided to transfer states into redux store. The question is "Is it best practice to change class componenents into functional components since we get state via props from redux store?"
Functional components with react hooks is the new standard of coding on React. For store management(f.e. redux) you may use as classes as functional components, but most of the libs moved to functional components and you may not use all benefits of last versions of them.
Why I prefer functional components and hooks over classes:
Cleaner render tree. No wrapper components
More flexible code. You
can use useEffect on different state changes, in classes you have
only componentDidUpdate for ANY state/props change
You can define your custom hooks to keep your code clean and shiny
IMHO, yes, I suggest that you should switch from class-based component to functional component as soon as possible.You might not want to know how the class-based components have bugged me so hurt before I decided to go with Hooks. The number of components in my large project is now over 400 (including both smart and dumb components) and keep increasing. Hooks keep my life easier to continue developing and maintaining.
Have a look at this useful article: https://blog.bitsrc.io/why-we-switched-to-react-hooks-48798c42c7f
Basically, this is how we manage state with class-based:
It can be simplified to half the lines of code, achieving the same results with functional component and useState, useEffect:
Please also take a look at this super useful site: https://usehooks.com/
There are many useful custom hooks from the community that you can utilize. Below are the ones that I have been using all the time:
useRouter: Make your life easier with react-router. For example:
import { useRouter } from "./myCustomHooks";
const ShowMeTheLocation = () => {
const router = useRouter();
return <div>Show me my param: {router.match.params.myDesiredParam}</div>;
}
useEventListener: simplify your event handler without using componentDidMount and componentWillUnmount to subscribe/unsubscribe. For example, I have a button that needs to bind a keypress event:
import { useEventListener } from "./myCustomHooks";
const FunctionButton = () => {
const keydownHandler = event => { // handle some keydown actions };
const keyupHandler = event => { // handle some keyup actions };
// just simple like this
useEventListener("keydown", keydownHandler);
useEventListener("keyup", keyupHandler);
}
useAuth: authenticate your user.
import { useAuth } from "./use-auth.js";
const Navbar = (props) => {
// Get auth state and re-render anytime it changes
const auth = useAuth();
// if user is authenticated, then show user email, else show Login
return <div>{auth.user? auth.user.email: "Login"}</div>;
}
useRequireAuth: handle redirect your user if they are signed out and trying to view a page that should require them to be authenticated. This is composed by useRouter and useAuth above.
import { useRequireAuth } from "./myCustomHooks";
// Dashboard is a page that need authentication to view
const Dashboard = () => {
const isAuth = useRequireAuth();
// If isAuth is null (still fetching data)
// or false (logged out, above hook will redirect)
// then show loading indicator.
if (isAuth) {
return <div>Fetching data, please wait!</div>
}
// {...{ isAuth }} is similar to:
// isAuth={isAuth}
return <Dashboard {...{ isAuth }} />
}
Hope this helps!
First of All, States can be used only in Class Component. In React's latest version there's a huge update that allows functional components to declare and use state using React-Hooks. So, the best practice I would personally suggest you is to use Class Component when you use the Redux Store. As you're a beginner, Please use a functional component where you don't use any state or props and just render DOM elements (Note: Functional components can accept props). Once you learn the differences properly, go with React-Hooks.
I hope it helps!! Happy Coding!!

Using constructor items within a function-based component

I'm following along to this PubNub Push Notifications in React-JS tutorial and everything is going fine up until I add the Constructor to the code as it states in the tutorial. I (think I) know enough about react to know that you cannot use a constructor in a non-class based component.
I can't seem to figure out how I would implement this. It appears that when the tutorial was made, the boilerplate code has since been updated and this constructor is no longer supposed to be used in this way.
Below is an image of where I am with the code and in the tutorial:
Should I just convert this to a class-based component? or will that limit me from using hooks in the future?
(Apologies for lack of react knowledge)
You need to use useRef and useEffect hooks:
Why useRef?
The useRef() Hook isn’t just for DOM refs. The “ref” object is a generic container whose current property is mutable and can hold any value, similar to an instance property on a class. more
this.pubnub is an instance property of a class, so you have to add it to useRef hook.
Why useEffect?
useEffect, adds the ability to perform side effects from a function component more
Initialization of PubNubReact - is a side effect, so you need to add it to useEffect hook.
UPDATE
Bad news: It looks like that pubnub-react can't be used with functional components, they have very weird API and they even agree with this (You could read about that in this issue)
Good news: You could use an official pubnub package in your application and it will work great:
import React, { useRef, useEffect } from 'react';
import PubNub from 'pubnub';
function App() {
const pubnub = useRef(null);
useEffect(() => {
pubnub.current = new PubNub({...});
pubnub.current.addListener({
message(msg) { console.log(msg) }
});
pubnub.current.subscribe({ channels: ['channel1'] });
pubnub.current.publish({ message: 'hello world from react', channel: 'channel1' });
}, []);
return (...);
}

Updating a D3 Chart within React/Redux

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' />)
}
}

Should updating props re-render the entire component?

Let's say I have a CookingClass component that gets initialized like this.
let teachers = makeTeachers(["Amber", "Jason", "Lily"])
let students = makeStudents(["Hopper"])
<CookingClass
teachers={teachers}
students={students}
/>
One of the teachers dropped out:
let newTeachers = makeTeachers(["Amber", "Jason"])
<CookingClass
teachers={newTeachers}
/>
It will make the entire component re-render. I am not sure whether React only calculates the diff and efficiently re-renders it or I must use shouldComponentUpdate to take care of it myself.
More real-world example might be implementing a Google map where there are a million markers and you want to replace one of the markers.
You're changing a so called Virtual DOM node. For every change in a virtual node shouldComponentUpdate() gets called. If you don't implement it yourself it will always return true;
So if you only want to reload your CookingClass in specific cases you would have to implement it yourself.
The pro of React is that it will only re-render Native DOM nodes when they will get changed in the Virtual DOM. This is the "render" which makes React so fast.
Based on your sample code, the component will re-render everytime.
You should use the react-redux (documentation) bindings to "connect" the component to the store.
// ConnectedCookingClass.js
import { connect } from 'react-redux';
import CookingClass from './CookingClass';
const mapStateToProps = (state) => {
return {
teachers: state.teachers,
students: state.students
};
};
const ConnectedCookingClass = connect(mapStateToProps)(CookingClass);
export default ConnectedCookingClass;
Then use this component elsewhere like so:
// OtherComponent.js
import ConnectedCookingClass from './ConnectedCookingClass';
const OtherComponent = React.createElement({
render() {
return (
<div>
<ConnectedCookingClass />
</div>
);
}
});
The react-redux bindings will do some smart things for you, like only re-rendering the component when the props returned by mapStateToProps are actually different than their previous value (via a shallowEqual comparison), so you should try to only return values here, no functions. Functions should be returned in mapDispatchToProps.
The default implementation of shouldComponentUpdate in react-redux will return true when:
ALWAYS if the component is a "pure" component (aka stateless-function)
When the props have been updated manually (after componentWillReceiveProps called)
When the store has changed and the new props are different than the old props.
Here's what that looks like from the source code:
shouldComponentUpdate() {
return !pure || this.haveOwnPropsChanged || this.hasStoreStateChanged
}
The real DOM Rendering is completely handled by React with very efficient innerHTML inserts and only for changes in the new data structure of your application VirtualDomTree.
shouldComponentUpdate() controls if the component should be recalculated or not. You should use it, when you are rendering statical data, for example. The output of the component will not change, so you could just return false and the first version of the component will be used for ever ;)

Resources