Destructuring in a React Class Component - javascript

Is there another way of using ES6 destructuring in a React Class component without having to do it in each method?
I am using the same prop (this.prop.page) in the constructor, componentDidMount(), componentDidUpdate() and render() methods:
class SinglePage extends React.Component {
constructor(props) {
super(props);
const { page } = this.props;
//...
}
componentDidMount() {
const { page } = this.props;
//...
}
componentDidUpdate() {
const { page } = this.props;
//...
}
render() {
const { page } = this.props;
return (
//...
);
}
}
exports default SinglePage;
Is there a way of do it just once?

There is if you can use latest react version with hooks. UseEffect will replace didMount and didUpdate and also no constructor with functional component. I recommend to read this article about useEffect hook. https://overreacted.io/a-complete-guide-to-useeffect/

useEffect is there to handle the cases you would use lifecycle methods for in class components. You can use one or more, depending on your needs.
import React, { useEffect } from React;
function SinglePage({ page }) {
useEffect(() => {
// componentDidMount() {
}, []); // empty array here means it'll only run after the first render
useEffect(() => {
// componentDidMount() {
// componentDidUpdate() {
}); // no second are means it runs after every render
useEffect(() => {
// componentDidMount() {
// componentDidUpdate() {
}, [page]); // runs on initial render and whenever `page` changes
useEffect(() => {
return () => cancelTheThings(); // componentWillUnMount() {
}); // return a function from your useEffect function to have it run before unmount
return {
//...
}
}
export default SinglePage;

Related

How to make useEffect react on props change as fast as ComponentDidUpdate?

rotateLogo() fires immediately when isLoading prop gets updated in the following case:
class Logo extends Component {
constructor(props) {
super(props);
};
}
componentDidUpdate() {
if (this.props.isLoading) {
this.rotateLogo();
}
}
...
However, when trying to implement the same in a functional component with useEffect, there is a considerable delay between the update of isLoading and
the activation of rotateLogo()
const Logo = ({ isLoading }) => {
useEffect(() => {
if (isLoading) {
rotateLogo();
}
}, [isLoading])
...
You are probably looking to synchronously re-render in this case so you should use useLayoutEffect instead of useEffect: https://reactjs.org/docs/hooks-reference.html#uselayouteffect

Set state of a component from another function

I have a stateful component which has a scroll event listener like this
import React, { Component } from 'react'
import { withRouter } from 'react-router'
import AppDetailPageUI from './AppDetailPageUI.js'
class AppDetailPageSF extends Component {
constructor(props) {
super(props);
this.state = {
scrolledDown:false,
};
this.handleScroll = this.handleScroll.bind(this);
}
render() {
return (
<AppDetailPageUI
scrolledDown={this.state.scrolledDown}
/>
);
}
componentDidMount() {
window.addEventListener('scroll', this.handleScroll);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll);
}
handleScroll(event) {
if (window.scrollY === 0 && this.state.scrolledDown === true) {
this.setState({scrolledDown: false});
}
else if (window.scrollY !== 0 && this.state.scrolledDown !== true) {
this.setState({scrolledDown: true});
}
}
}
export default withRouter(AppDetailPageSF)
This works perfectly fine.But i want to use handleScroll method in many statefull components, and including the same method in every component is not a good practice.
so this is what i tried instead, I created another HandleScrollUtil function something like this
const HandleScrollUtil = {
handleScroll: function(component) {
if (window.scrollY === 0 && component.state.scrolledDown === true) {
component.setState({scrolledDown: false});
}
else if (window.scrollY !== 0 && component.state.scrolledDown !== true) {
component.setState({scrolledDown: true});
}
}
}
export default HandleScrollUtil
and then i tried calling this method by passing this reference something like this
componentDidMount() {
window.addEventListener('scroll', HandleScrollUtil.handleScroll(this));
}
componentWillUnmount() {
window.removeEventListener('scroll', HandleScrollUtil.handleScroll(this));
}
But it does not seem to work now.
Only respective component can deal with its state, you can create handler inside the
AppDetailPageSF component something like
setScrollDownHandler = (event,scrollValue) =>{
this.setState({scrolledDown: scrollValue});
}
then you can pass this handler down to any component has a prop, this is the right way to it.
Code to update the State must be in the same component, all we could do it is create a hander to deal with it and pass that handler to places where we would like to update it from.
Solution is a Higher Order Component
I would recommend using a HOC like this, it can be wrapped to any component you are going to use.
import React, { Component } from 'react';
const withScrollHandler = (WrappedComponent) => {
return class extends Component {
componentDidMount() {
this.props.setScrollDownHandler();
}
componentWillUnmount() {
this.props.setScrollDownHandler();
}
render () {
return (
<div>
<WrappedComponent {...this.props} />
</div>
);
}
}
}
export default withScrollHandler;

Trigger setState function in parent from promise.then in child

I am trying to find a solution to setState from a parent within child promise.
The parent component is
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
transition: false
};
}
handleTransition = () => {
this.setState(state => ({ transition: !state.transition }));
};
render() {
return <Child handleTransition={this.handleTransition} />;
}
}
of which this.props.handleTransition is to be triggered from a child component as
class Child extends Component {
constructor(props) {
super(props);
this.state = {};
}
onSubmit = event => {
firebase
.doCreateUserWithEmailAndPassword(email, password)
.then(() => {
// Trigger this.props.handleTransition here
})
...
Where this.props.handleTransition is wanting to be triggered with then of onSubmit
Please let me know if you require more detail? I would prefer not to use a library or package to achieve this but if it makes life easier I may consider. Redux is likely the best option but I would prefer not to unless necessary.
Note: this.props.handleTransition(); does the job but esLint returns an error of Must use destructuring props assignmenteslint(react/destructuring-assignment) and I am considering that this method is not the correct method.
// --- parent.js
import React, { Component, Fragment } from "react";
import { ChildComponent } from './containers/child'
class ParentContainer extends Component {
handleUpdate = () => {
// whatever you want to do here
}
render(){
return (
<Fragment>
<ChildComponent onUpdate={this.handleUpdate} />
</Fragment>
);
}
}
export default ParentContainer;
// --- child.js
import React, { Component, Fragment } from "react";
export class ChildComponent extends Component {
this.someAsyncFunction = () => {
fetch('/just/for/example')
.then(res =>
// Do whatever you need here, then hit your function on parent by bubbling the request up the chain
this.props.onUpdate();
)
}
render(){
return (
// whatever you want to do with this data
);
}
}

React forwardRef HoC not giving reference to container element

I am trying to build a generic HOC for a closing element on click of outside its space(generic close on outside solution).
As I see it ,this could be achieved with forwardRef and HOC implementation and although there is an example in official docs I cannot seem to get it right.
So I want my HOC to create a reference to the container of component. It is wrapping because it has handlers to track clicks and act upon them. For instance, lets say we have a generic Dropdown component, one would expect that I can close it on any click outside the area of this component.
The code I currently have:
import React from 'react';
function withClose(Component) {
class ClickContainer extends React.Component {
constructor() {
super();
this.handleClose = this.handleClose.bind(this);
}
componentDidMount() {
document.addEventListener('click', this.handleClose);
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClose);
}
handleClose(e) {
// I expect having here context of container of wrapped component to do something like
const { forwardedRef } = this.props; // <- I expect having context in forwardedRef variable
}
render() {
const { forwardedRef, ...rest } = this.props;
return <Component ref={forwardedRef} {...rest} />;
}
}
return React.forwardRef((props, ref) => {
return <ClickContainer {...props} forwardedRef={ref} />;
});
}
export default withClose;
What am I missing here? I cannot make it work, I only get context of wrapped component not the element itself.
Thanks a bunch!
Ref should be passed down to the element
Checkout https://codesandbox.io/s/7yzoqm747x
Assuming
export const Popup = (props,) => {
const { name, forwardRef } = props;
return (
<div ref={forwardRef}> // You need to pass it down from props
{name}
</div>
)
}
And the HOC
export function withClose(Component) {
class ClickContainer extends React.Component {
constructor() {
super();
this.handleClose = this.handleClose.bind(this);
}
componentDidMount() {
document.addEventListener('click', this.handleClose);
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClose);
}
handleClose(e) {
const { forwardRef } = this.props;
console.log(forwardRef);
}
render() {
const { forwardRef, ...rest } = this.props;
return <Component forwardRef={forwardRef} {...rest} />;
}
}
return React.forwardRef((props, ref) => {
return <ClickContainer {...props} forwardRef={ref} />;
});
}
And expect
const CloseablePopup = withClose(Popup);
class App extends Component {
popupRef = React.createRef();
render() {
return (<CloseablePopup ref={popupRef} name="Closable Popup" />);
}
}
I Implemented the same thing few days ago.
I wanted a HOC that handle onClickoutside
I wanted that component to check on each click if the child was click or not and if it did to invoke a function on the child.
this was my solution:
import React from 'react';
export default function withClickOutside(WrappedComponent) {
class WithClickOutside extends WrappedComponent {
constructor(props) {
super(props);
this.handleClickOutside = this.handleClickOutside.bind(this);
this.wrappedElement = React.createRef();
}
componentDidMount() {
document.addEventListener('click', this.handleClickOutside, true);
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClickOutside, true);
}
handleClickOutside(event) {
if (event.type !== 'click') return;
if (!this.wrappedElement.current) {
throw new Error(`No ref for element ${WrappedComponent.name}. Please create ref when using withClickOutside`);
}
if (!this.wrappedElement.current.contains(event.target)) {
if (!this.onClickOutside) {
throw new Error(`${WrappedComponent.name} does not implement onClickOutside function. Please create onClickOutside function when using withClickOutside`);
}
this.onClickOutside(event);
}
}
render() {
const wrapped = super.render();
const element = React.cloneElement(
wrapped,
{ ref: this.wrappedElement },
);
return element;
}
}
return WithClickOutside;
}
Then the component you wrap must implement a function called onClickOutside .

How to get state via React-Loadable? React-Redux

I written a custom logic for handling async route loading bundles in react-router-dom .v4. It's work perfectly. But also I heard about useful package with nice API to do the same, like React-Loadable. It has one problem, I cannot get the props/state pushed from Redux on the mount of the component throw this package.
My code is rewritten from the custom style to react-loadable style in two examples below. The last one is react-loadable version, that does not throw state/props.
My personal code:
const asyncComponent = getComponent => {
return class AsyncComponent extends React.Component {
static Component = null;
state = { Component: AsyncComponent.Component };
componentWillMount() {
const { Component } = this.state
if (!Component) {
getComponent().then(({ default: Component }) => {
const { store } = this.props // CAN GET THE REDUX STORE
AsyncComponent.Component = Component;
this.setState({ Component });
});
}
}
render() {
const { Component } = this.state;
if (Component) {
return <Component {...this.props} />
}
return null;
}
};
};
export default withRouter(asyncComponent(() => import(/* webpackChunkName: "chunk_1" */ './containers/Component')))
The same code, but with React-Loadable:
const Loading = () => {
return <div>Loading...</div>;
}
const asyncComponent = Loadable({
loader: () => import(/* webpackChunkName: "" */ './containers/Component')
.then(state => {
const { store } = this.props // CANNOT GET THE REDUX STORE!!
}),
loading: Loading
})
export default withRouter(asyncComponent)
To get the state from Redux store via Provider you should place your asyncComponent in Stateful Component wrapper, like you do in your custom async logic (1st case).
It because Loadable library returns you asyncComponent like a function, not a constructor, that way he cannot get access to current Redux store. So, the working solution will be the next:
const Loading = () => {
return <div>Loading...</div>;
}
const asyncComponent = Loadable({
loader: () => import(/* webpackChunkName: "" */ './containers/Component')
.then(state => {
const { store } = this.props // YOU WILL GET THE REDUX STORE!!
}),
loading: Loading
})
class asyncComponentWrapper extends Component{ // Component wrapper for asyncComponent
render() {
return <asyncComponent {...this.props} />
}
}
export default withRouter(asyncComponentWrapper)
P.S.
I do not know what you try to do, but in case how to make reducer injection inside the current store (probably it's exactly what you trying to do), you need to include you Redux store explicitly by import, not from the Provider state.

Categories

Resources