PureRenderMixin & state management design - javascript

I have some top-level component (RegisrationPage) with state, which pass it state/props to dump bottom-level components (InputField, Dropdown, Datepicker).
Bottom-level components change RegistrationPage's state with help of callbacks.
Problem: PureRenderMixin doesn't work since I have to bind state-change-callback that are passed to bottom-level components.
Question: how to make PureRenderMixin works in the most elegant way?
Let's explain it with code:
InputBlock:
const React = require('react'),
PureRenderMixin = require('react-addons-pure-render-mixin');
module.exports = React.createClass({
mixins: [PureRenderMixin],
propTypes: {
input: React.PropTypes.string,
onChange: React.PropTypes.func
},
render() {
//PROBLEM - is re-rendered each time , since onChange callback each time is an different object due bind method call
}
});
RegistrationPage:
RegistrationPage = React.createClass({
/**
* Since all state is held by `RegistrationPage` and bottom-level components are dump,
* I do _onFieldChange.bind - _onFieldChange should know which field should be changed
*/
render() {
return CreateFragment({
email: <InputBlock
input={this.state.email}
onChange={this._onFieldChange.bind(self, 'email')}/>,
firstName: <InputBlock
input={this.state.firstName}
onChange={this._onFieldChange.bind(self, 'firstName')}/>,
.........
});
},
_onFieldChange(key, event) {
//Save registered progress to store and re-render the component
AppActionCreators.saveRegisterProgress(this.state);
}
})
My workaround: just pass inputFieldName as extra property and do binding inside bottom-level component.

The problem is .bind() returns a new function every time you invokes it. To avoid it, you should create functions outside rendering. For example:
RegistrationPage = React.createClass({
render() {
return CreateFragment({
email: <InputBlock
input={this.state.email}
onChange={this._onEmailChange}/>,
firstName: <InputBlock
input={this.state.firstName}
onChange={this._onFirstNameChange}/>,
.........
});
},
_onFieldChange(key, event) {
//Save registered progress to store and re-render the component
AppActionCreators.saveRegisterProgress(this.state);
}
_onEmailChange() {
this._onFieldChange('email')
}
_onFirstNameChange() {
this._onFieldChange('firstName')
}
})

Related

convert react class to react hooks useState?

Can someone help me convert this to react hooks I believe it would be react useState()
https://github.com/react-grid-layout/react-grid-layout/blob/master/test/examples/7-localstorage.jsx
import React from "react";
import RGL, { WidthProvider } from "react-grid-layout";
const ReactGridLayout = WidthProvider(RGL);
const originalLayout = getFromLS("layout") || [];
/**
* This layout demonstrates how to sync to localstorage.
*/
export default class LocalStorageLayout extends React.PureComponent {
static defaultProps = {
className: "layout",
cols: 12,
rowHeight: 30,
onLayoutChange: function() {}
};
constructor(props) {
super(props);
this.state = {
layout: JSON.parse(JSON.stringify(originalLayout))
};
this.onLayoutChange = this.onLayoutChange.bind(this);
this.resetLayout = this.resetLayout.bind(this);
}
resetLayout() {
this.setState({
layout: []
});
}
onLayoutChange(layout) {
/*eslint no-console: 0*/
saveToLS("layout", layout);
this.setState({ layout });
this.props.onLayoutChange(layout); // updates status display
}
render() {
return (
<div>
<button onClick={this.resetLayout}>Reset Layout</button>
<ReactGridLayout
{...this.props}
layout={this.state.layout}
onLayoutChange={this.onLayoutChange}
>
<div key="2" data-grid={{ w: 2, h: 3, x: 8, y: 0 }}>
<span className="text">5</span>
</div>
</ReactGridLayout>
</div>
);
}
}
function getFromLS(key) {
let ls = {};
if (global.localStorage) {
try {
ls = JSON.parse(global.localStorage.getItem("rgl-7")) || {};
} catch (e) {
/*Ignore*/
}
}
return ls[key];
}
function saveToLS(key, value) {
if (global.localStorage) {
global.localStorage.setItem(
"rgl-7",
JSON.stringify({
[key]: value
})
);
}
}
if (process.env.STATIC_EXAMPLES === true) {
import("../test-hook.jsx").then(fn => fn.default(LocalStorageLayout));
}
trying my best to understand react classes since I only know react hooks so any patince help would be so amazing and helpful please and thank you
React classes are simple if you understand the lifecycle of a component.
A component is instantiated, then mounted (associated with DOM) & then it renders.
After every update (state or props) it's re-rendered.
Constructor
The instantiation of a component or any JS class happens in the constructor. It is run only once.
Since class components inherit from React.Component, they pass the props to React.Component by calling super(props). This initializes the props field.
super calls should be the 1st line in a constructor.
You cannot access the following in the constructor: window, document, fetch, localStorage, sessionStorage.
render
The render method returns the rendered Element/Fragment. It corresponds to the returned part of a function component. It runs every time the component is rendered.
Event listener binding
The hardest part of classes is the event method binding.
The implicit this object, is necessary to access state, props & other component methods. However, inside an event listener method*. it refers to the event target. Hence the bind calls. Nevertheless, this problem, can be bypassed by using arrow methods as the arrow functions do not have their own this (ie. this does not refer to event target).
Function components don't have this problem, as they don't need this.
State
The state is initialized in the constructor.
Unlike function components, classes treat the state as a single object.
Instead of,
let [x, setX] = useState(0); let [y, setY] = useState(1);
In classes it's:
this.state = {x:0, y:1};
And to update state, we use the setState method,
this.setState({x: 3}); // updates only x, & y is unchanged: setX(3)
this.setState({x: 3, y: 4}); // updates both x & y: setX(3); setY(4)
this.setState((state)=>({ y: state.y - 1)) // updates y using previous value: setY(y=>y-1)
An advantage of classes is that we can update a state property using another state property's previous value.
this.setState((state)=>({x: (y%2)? 100: 50})); // updates x using y's value
Also, the state can be updated with respect to props:
this.setState((state,props)=>({x: state.x + props.dx}));
In addition, setState has an optional 2nd callback argument. This callback is run immediately after the state update.
this.setState(state=>({x: state.x+1}), ()=> {
// called after x is updated
sessionStorage.setItem('x', this.state.x);
});
// function version
setX(x=>x+1);
useEffect(()=> {
localStorage.setItem('x', x);
}, [x]); // runs every time x is updated
Mounting
After mounting, componentDidMount() is called. Now, component can access the window & document objects.
Here here, you can
Call setInterval/setTimeout
Update state with API calls (fetch) or storage (localStorage & sessionStorage)
It's equivalent to useEffect(()=> {}, [])
Unmounting
Before unmounting componentWillUnmount() is called. Typically, clearTimeout/clearInterval are called here to clean up the component.
It's equivalent to the returned method of useEffect.
useEffect(()=>{
//....
return ()=> {
// componentWillUnmount code
};
}, [])

Pass a prop from state and do not trigger update in connect

I am currently grabbing a prop from state and using it on an event listener. i.e.,
import * as React from 'react';
import { getDetails } from './actions';
interface Props {
selecting: boolean;
getDetails(): Action<void>;
}
#connect((state) => ({
selecting: state.items.selecting,
}), {
getDetails,
})
export default class Grid extends React.PureComponent<Props> {
onMouseEnter = () => {
if (!this.props.selecting) {
this.props.getDetails();
}
}
render() {
return (
<div onMouseEnter={this.onMouseEnter} />
);
}
}
However, whenever the selecting property changes, it causes a re-render to my component.
Is there a way to pass a variable from state through connect and NOT have it trigger this update to my component? I want it almost as if it were an instance-bound variable rather than a state variable.
Try overriding the shouldComponentUpdate() lifecycle function. This gives you much more granular control over when your component should or shouldn't re-render (at the cost of added code complexity).
shouldComponentUpdate(nextProps, nextState) {
if(nextProps.someLogic !== this.props.someLogic)
return false; // Don't re-render
return true;
}
Documentation: Here
Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior.

React Component - debounce

Trying to create a delay on react component that has input field that updates on change
Here is my onChange method
handleOrderQtyKeyPress (e) {
var regex = /[^0-9]/
if (e.key.match(regex)) {
e.preventDefault();
}
if (this.state.orderQtyValue.toString().length == 3) {
e.preventDefault();
}
}
and the react-bootstrap component:
<FormControl
type='number'
min='0'
value={this.state.orderQtyValue}
onChange={this.handleOrderQtyChange}
onKeyPress={this.handleOrderQtyKeyPress}
style={styles.orderQtyValue}
/>
so I tried importing lodash _.debounce and applying at the constructor
import debounce from 'lodash/debounce';
this.handleOrderQtyKeyPress = _.debounce(this.handleOrderQtyKeyPress.bind(this),1000);
I am not getting a debounce. What am I missing here?
I see that you use this, so I assume that FormControl is inside of a render function of your stateful component. In this case make sure that your binding and debouncing is happening in constructor of this stateful component.
```
const Component extends React.Component {
constructor(props) {
super(props);
this.handleOrderQtyKeyPress = _.debounce(this.handleOrderQtyKeyPress.bind(this), 1000);
}
}
```
Please, read comment which explains how this works
class Input extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
value: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
]),
}
state = {
value: '',
}
// When component receives updated `value` from outside, update internal `value` state.
componentWillReceiveProps(nextProps) {
this.setState({ value: nextProps.value });
}
// Store updated value localy.
onChange = (event) => {
this.setState({ value: event.target.value });
}
onBlur = (event) => {
// Trigger change to external env.
this.props.onChange(this.state.value);
// Also, don't forget to call `onBlur` if received from parent.
if (this.props.onBlur) {
this.props.onBlur(event);
}
}
render() {
return <input {...this.props} value={this.state.value} onChange={this.onChange} onBlur={this.onBlur} />
}
}
If you want to automatically debounce (or throttle) a component easily, when the props change often (as opposed to internal state changed),
I've authored a tiny wrapper (1kb minified) for that, called React-Bouncer:
import bouncer from '#yaireo/react-bouncer'
// uses 300ms `debounce` by default
const DebouncedYourComponent = bouncer(YourComponent)
This is useful when you do not have much of a control on the rate which the props sent to the component are updated, or the root cause of the often updates is unknown.
(Obviously, using debounce on the root-cause is the first thing to try)

Reactjs how to change the state of a component from a different component

I have a react component, lets call it as component 1
define([..., /path/component_2.jsx], function(..., Component2) {
var Component1 = React.createClass({
getInitialState: function() {
return {.......};
},
componentDidMount: function() {
.......
dates = ....;
Component2.setState({dates: dates});
}
render: function() { return (<div ...></div>) }
});
}
As you can see, I am calling the Component2.setState in this component. But I am getting an error like setState is not a function. I tried this with defining a custom function instead of setState function in component 2 and calling this function from component 1, but I am getting the same error, 'is not a function'.
And component 2:
define([..., ], function(...) {
var Component2 = React.createClass({
getInitialState: function() {
return {.......};
},
render: function() { return (<div ...></div>) }
});
}
So I guess in reactjs we calls a component only for rendering something (React DOM elements)? and cannot change the component state?
If so how can I change a state of a component from a different component which is not a child or parent of first?
Components don't publicly expose their state. It's also important to remember that the state is scoped to the instance of components, not their definition.
To communicate between components, you could roll your own event subscription service.
var events = new EventEmitter();
// inside Component1
events.emit('change-state', newState);
// inside Component2
events.on('change-state', function(state) {
this.setState(state);
});
However, this will quickly become difficult to manage.
A more sensible solution is to use Flux. It allows you to explicitly manage the state of your entire application and subscribe to changes in different bits of the state, within your components. It's worth trying to wrap your head around it.
The component that wants to communicate should dispatch an action and this action will be responsible for changing something in the stores, your other component should subscribe to that store and can update its state based on the change.
You can use a shared state between the two component.
You can build a "mixin" like that
app.mixins.DateMixin = {
getInitialState: function ()
return {
dates: []
}
}
};
and then in you components you can include this mixins using the mixins array
define([..., /path/component_2.jsx], function(..., Component2) {
var Component1 = React.createClass({
mixins: [app.mixins.DateMixin],
getInitialState: function() {
return {.......};
},
componentDidMount: function() {
.......
dates = ....;
this.setState({dates: dates});
}
render: function() { return (<div ...></div>) }
});
}
define([..., ], function(...) {
mixins: [app.mixins.DateMixin],
var Component2 = React.createClass({
getInitialState: function() {
return {.......};
},
render: function() { return (<div ...></div>) }
});
}
Now your components share the "dates" state and you can change/check it in both of them.
NB: you can also share methods with in the same way by writing into a mixin component.
Edit: I suggest you to visit this website http://simblestudios.com/blog/development/react-mixins-by-example.html

Clone a component that has already been mounted

I am trying to build an app that uses drag-and-drop behaviour, and the component being dragged needs to be cloned elsewhere in the DOM. Since the component is already mounted, trying to mount it again causes the browser to hang.
Trying to use cloneWithProps results in a Cannot read property 'defaultProps' of undefined error.
Here's a testcase:
var TestCase = React.createClass({
getInitialState () {
return {
draggingItem: null
}
},
render () {
return <div>
<ExampleComponent onClick={this.setDraggingItem} />
{this.state.draggingItem}
</div>
},
setDraggingItem (component) {
// This gives `Cannot read property 'defaultProps' of undefined`
//React.addons.cloneWithProps(component)
// This crashes the browser
//this.setState({ draggingItem: component })
}
})
var ExampleComponent = React.createClass({
render () {
return <div onClick={this.handleOnClick}>Hello World</div>
},
handleOnClick (event) {
this.props.onClick(this)
}
})
React.render(<TestCase />, document.body)
Of course I could simply clone component.getDOMNode() in setDraggingItem, but it really seems like rendering the component or calling cloneWithProps should work?
The two things you need to create an element is: the component class (e.g. ExampleComponent) and its props. cloneWithProps is only to be used in render and only with an element coming from props which was created in another component's render. You shouldn't save elements, or pass them around other than to other components in render. Instead, you pass around objects (props) and component classes.
Since you need to know the props and component class to render it in the first place, you can handle all of this in TestCase.
var TestCase = React.createClass({
getInitialState () {
return {
draggingItem: null,
draggingItemProps: null
}
},
render () {
return <div>
<ExampleComponent onClick={this.setDraggingItem.bind(null,
/* the component class */ ExampleComponent,
/* the props to render it with */ null
)} />
{
this.state.draggingItem && React.createElement(
this.state.draggingItem,
this.state.draggingItemProps
)
}
</div>
},
setDraggingItem (component, props, event) {
this.setState({ draggingItem: component, draggingItemProps: props })
}
});
var ExampleComponent = React.createClass({
render () {
return <div onClick={this.handleOnClick}>Hello World</div>
},
// just defer the event
handleOnClick (event) {
this.props.onClick(event)
}
});
If you wish to make these valid outside this TestCase component, ensure there aren't any functions bound to TestCase in the props. Also ensure there's no children prop with react elements in it. If children are relevant, provide the {componentClass,props} structure needed to recreate them.
It's hard to tell what your actual requirements are, but hopefully this is enough to get you started.
You need be sure you're creating a new component with the same props, not mount the same one multiple times. First, setup a function that returns an instantiated components (easier to drop JSX here):
function getComponent(props) {
return ExampleComponent(props);
}
Then in your TestCase render:
return (<div>
{ getComponent({ onClick: this.setDraggingItem }) }
{ this.state.draggingItem }
</div>);
That will create the first component. Then to create a clone:
setDraggingItem(component) {
var clone = getComponent(component.props);
}
This deals with the cloning part. You still have some dragging and rendering to figure out.

Categories