React Smart and Dumb Components - reactjs

Have been working on React and would like to know, best practices for seperating smart and dumb componenents. Example below Parent controls state, but i have put button ui in render, should these go into child and implemented back to parent via callback or is that overkill? thoughts..here is my code
class Child extends React.Component {
constructor(props){
super(props);
}
render() {
return (
<div><p>I said {this.props.greeting} {this.props.count} times</p>
</div>
);
}
}
class Parent extends React.Component {
constructor() {
super();
this.state = {
count: 0,
greeting: "Hello"
};
}
sayHello() {
this.setState((prevState, props) => {
return {
count: prevState.count + 1,
greeting: "Hello"
}
}
)};
sayGoodBye() {
this.setState((prevState, props) => {
return {
count: this.count = 1,
greeting: "Goodbye"
}
}
)};
render() {
return (
<div>
<button onClick={() => this.sayHello() }>Say Hello</button>
<button onClick={() => this.sayGoodBye() }>Say Goodbye</button>
<Child count={this.state.count} greeting={this.state.greeting} />
</div>
)
}
}
ReactDOM.render(<Parent />, document.getElementById('app'));

I think the separation in this case is good. The buttons are directly involved with the state of Parent so creating a child just for them will be an overkill. In general the "dump" components are only about visually showing data/state. Sometimes they contain elements like buttons but the only one thing that they do is to notify the outside world that X thing happened.
Also the Child could be a stateless function:
const Child = ({ greeting, count }) => (
<div>
<p>I said { greeting } { count } times</p>
</div>
);
You could try making a component to be just a function. If you can't then it is probably not as dump as you think it is :)

Related

Migration from componentWillReceiveProps to getDerivedStateFromProps

I am learning reactjs and I wrote component with the method componentWillReceiveProps (cWRP) but I read that it is deprecated and it must replace with getDerivedStateFromProps (gDSFP) - https://en.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html.
Please note that the following code has the sole purpose of illustrating my problem and questions. It is not a full code.
App.js file :
import React from 'react';
import './App.css';
import Display from './component.js'
class App extends React.Component {
state={resetCounter:false}
resetCounter= () => this.setState( {resetCounter: true} );
render() {
return (
<div className="App">
<header className="App-header">
<Display resetCounter={this.state.resetCounter}></Display>
<div>
<p></p><p></p>
<button onClick={this.resetCounter}>Reset</button>
</div>
</header>
</div>
);
}
componentDidUpdate () {
if (this.state.resetCounter!==false)
this.setState( {resetCounter: false} );
}
}
export default App;
component.js file
import React from 'react'
class Display extends React.Component {
constructor() {
super();
this.state = this.resetState();
this.state.generalCounter=0;
}
/* method to avoid code duplication in constructor and cWRP
could not be used with getDerivedStateFromProps */
resetState = () => ({resettableCounter: 0,});
componentWillReceiveProps(nextProps) {
if (nextProps.resetCounter===true)
this.setState(this.resetState())
}
render() {
return (
<>
<div>
<div>general counter : {this.state.generalCounter}</div>
<div>resettable counter : {this.state.resettableCounter}</div>
</div>
<div>
<button onClick={this.incCounters}>+</button>
<button onClick={this.decCounters}>-</button>
</div>
</>
)
}
incCounters= () => this.setState(
{
resettableCounter: this.state.resettableCounter+1,
generalCounter: this.state.generalCounter+1
}
)
decCounters= () => this.setState(
{
resettableCounter: this.state.resettableCounter-1,
generalCounter: this.state.generalCounter-1
}
)
}
export default Display
In the state of the component, there is a resettable part and a non resettable one. A method resetState is used to avoid code duplication in the constructor and in cWRP.
To replace cWRP by gDSFP, I wrote a class method because instance method could NOT be called in gDSFP (this is not usable)
...
constructor() {
super();
this.state = Display.resetState();
this.state.generalCounter=0;
}
static resetState () {
return ({resettableCounter: 0,});
}
static getDerivedStateFromProps(nextProps) {
if (nextProps.resetCounter === true) {
return Display.resetState();
} else {
return null;
}
}
...
With this solution, it is very easy to modify all my components but I am not sure that it is a good mean.
I wonder if I have a misconception and if I should rewrite my components to separate them into Fully controlled components and Fully uncontrolled components with a key ( https://en.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#preferred-solutions).
For example, in this case, do I have to write :
One Fully uncontrolled components for the resettable counter
One Fully controlled one for the non resettable counter
A parent component with the +/- buttons to render them.
I ask this question because in some cases, it will be much work, so I want to be sure before continuing.
You would want to keep the gdsfp version in your post if your component depends on some outside props, which you don't have controll over (such as JSON returned or 3rd party render props component, etc).
It looks like you have a full control over what's passed down to the Display. You can pass down an initial resettableCounter value down to Display.
The advantage is two-folds.
Your Display props shows what the Display does - Making it more descriptivie/readable.
It's easier to maintain, as you don't have to massage the data.
For your particular case, Fully uncontrolled component with a key seems to make more sense, as Display should accept the initial value to show, but is responsible for managing the reseetableCounter.
Unless it's absolutely unavoidable, don't create components which control their siblings (or parents). Instead, lift state to a common ancestor:
const Display = ({
generalCounter,
resettableCounter,
incrementCounters,
decrementCounters,
}) => (
<div>
<div>General Counter: {generalCounter}</div>
<div>Resettable Counter: {resettableCounter}</div>
<button onClick={incrementCounters}>Increment</button>
<button onClick={decrementCounters}>Decrement</button>
</div>
);
class DisplayContainer extends React.Component {
state = {
generalCounter: 0,
resettableCounter: 0,
};
incrementCounters = () => this.setState(prevState => ({
generalCounter: prevState.generalCounter + 1,
resettableCounter: prevState.resettableCounter + 1,
}));
decrementCounters = () => this.setState(prevState => ({
generalCounter: prevState.generalCounter - 1,
resettableCounter: prevState.resettableCounter - 1,
}));
resetResettableCounter = () => this.setState({
resettableCounter: 0,
});
render() {
return (
<React.Fragment>
<Display
{...this.state}
incrementCounters={this.incrementCounters}
decrementCounters={this.decrementCounters}
/>
<button onClick={this.resetResettableCounter}>
Reset Resettable Counter
</button>
</React.Fragment>
);
}
}
const App = () => (
<div>
<DisplayContainer />
</div>
);
An alternative approach would be something like Redux (which effectively lifts state out of React).

Child not updating after Parent State changed

I am quite new with React and I have problem bellow
I have a parent component like this:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {count:1};
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
return false;
}
setCount = () => {
this.setState({
count: 2
});
};
render() {
const {name, running, onRun, onStop} = this.props;
return (
<div>
<Test count={this.state.count}/>
<p><a href="#" onClick={this.setCount}>SetCount</a></p>
</div>
);
}
}
And here is Test component
class Test extends React.Component {
constructor(props) {
super(props);
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
return true;
}
render() {
const {count} = this.props;
return (
<div>
{console.log("Counting")}
<p>{count}</p>
</div>
);
}
}
I have method "shouldComponentUpdate" returns "false" in Parent component because I don't want to re-render it.
My understanding is React know which part of DOM need to be re-rendered. And in this case, the state of Parent changes will re-render "Test" component
But when I run above code, "Test" component does not redender.
Is there anything wrong in my code?
Thanks a lot for your help
You need to return true from your parent's shouldComponentUpdate method.
If you return false, after the initial render it won't update, even if you call a function that calls setState.
Is the refresh of the whole page are you talking about? If thats the case, probably you wanna change your <a> tag to button or use e.preventDefault();.
If not, I am not sure if that is possible. If you setState in the parent, it will rerender parent as well as the children. If you dont want to render the parent then you have to manage individual state management in the child level.
For example,
class Parent extends React.Component {
constructor(props) {
super(props);
}
render() {
const {name, running, onRun, onStop} = this.props;
return (
<div>
<Test/>
</div>
);
}
}
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {count:1};
}
setCount = (e) => {
e.preventDefault();
this.setState({
count: 2
});
};
render() {
const {count} = this.state;
return (
<div>
{console.log("Counting")}
<p>{count}</p>
<p><a href="#" onClick={this.setCount}>SetCount</a></p>
</div>
);
}
}

fat arrow componentDidMount

I found in legacy code
componentDidMount = () => {
...
}
I know it is not valid, but it works. Now, I am curious what is the difference between this, and the right way
componentDidMount() {
...
}
Hi basically you can do it, but it's unnecessary and can also hurt performance. Because each time your function does an arrow operation, it has to create a new function object. So it's just an optimization choice.
Good threads and articles:
https://github.com/facebook/react/issues/10810
Pros and cons of using react's life cycle methods in arrow function format
https://reactarmory.com/answers/when-to-use-arrow-functions
https://medium.com/#charpeni/arrow-functions-in-class-properties-might-not-be-as-great-as-we-think-3b3551c440b1
I don't think there is much difference.
But () => {} returns something (implicitly), and I don't think componentDidMount() returns something nor would it be 'better'
So I would write this (as in the docs)
componentDidMount() {
...
}
Arrow functions and function declarations in React work the same way as how they work in vanilla JS.
componentDidMount = () => { // arrow function
and
componentDidMount() { // function declaration
mean the same as these functions are class specific in React. But things get interesting when you see how arrow functions and function declarations behave when you create them and pass them as handlers to other components.
Take a look at this example
export default class Parent extends Component {
constructor() {
super();
this.state = {
time: new Date().toLocaleTimeString()
};
// no function binding necessary
}
updateTime = () => { // arrow function used here
this.setState({ // `this` here will be bound to Parent implicitely
time: new Date().toLocaleTimeString()
});
};
render() {
return (
<div>
<div>Parent: {this.state.time}</div>
<button onClick={() => this.updateTime()}>Button in Parent</button>
<Child
time={new Date().toLocaleTimeString()}
updateTimeHandler={this.updateTime}
/>
</div>
);
}
}
// updateTimeHandler will be implicitly bound
// to Parent's context
const Child = ({ time, updateTimeHandler }) => {
return (
<div>
<div>Child: {time}</div>
<button onClick={() => updateTimeHandler()}>Button in Child</button>
</div>
);
};
export default Child;
Now when you don't use arrow,
export default class Parent extends Component {
constructor() {
super();
this.state = {
time: new Date().toLocaleTimeString()
};
// you have to bind the function to this class
// if you don't, then `this.` in the method
// will execute in child's context and throw an error
this.updateTime = this.updateTime.bind(this);
}
updateTime() { // function declaration
this.setState({
time: new Date().toLocaleTimeString()
});
}
render() {
return (
<div>
<div>Parent: {this.state.time}</div>
<button onClick={() => this.updateTime()}>Button in Parent</button>
<Child
time={new Date().toLocaleTimeString()}
updateTimeHandler={this.updateTime}
/>
</div>
);
}
}
// updateTimeHandler will execute in Parent's context
// as we explicitly told so
const Child = ({ time, updateTimeHandler }) => {
return (
<div>
<div>Child: {time}</div>
<button onClick={() => updateTimeHandler()}>Button in Child</button>
</div>
);
};
export default Child;
You can play around in this code sandbox and see it for yourself.
https://codesandbox.io/s/j78y87npkv
In addition to this behavior, there are obvious performance differences as quoted in other answers.

Sending onMouseDown from parent to child

Is there a way to send the clickevent from the parent to the child?
This is my parent component:
<Component {...props}>
<Child />
{props.children}
</Component>
This is the child component:
<Component onMouseDown={e => this.handleClick(e, props)}></Component>
Whenever the parent component is clicked I want to trigger the handleclick component of my child.
Thanks in advance!
You can use a reference to your child component:
// parent.js
constructor(props) {
super(props);
this.child = React.createRef();
}
handleMouseDown = e => {
this.child.current.handleClick(e, this.props);
}
render() {
return (
<Component onMouseDown={this.handleMouseDown} {...props}>
<Child ref={this.child}/>
{props.children}
</Component>
)
}
You can do this using rxjs with Observable and Subscriptions. Here is a working example and I'll explain what's going on https://codesandbox.io/s/7wjwnznk3j
Relevant reading:
fromEvent: https://rxjs-dev.firebaseapp.com/api/index/function/fromEvent
subscription: https://rxjs-dev.firebaseapp.com/api/index/class/Subscription
I used Typescript since I prefer it, but is absolutely not a requirement. You parent class will look like this:
interface State {
obs$?: Observable;
}
class App extends React.Component<null, State> {
public readonly state: State = {};
public ref: React.Ref<React.ReactHTMLElement>;
componentDidMount() {
this.setState({
obs$: fromEvent(this.ref, 'click')
});
}
#Bind()
setParentRef(el: HTMLElement) {
this.ref = el;
}
render() {
return (
<div style={parentStyles} ref={this.setParentRef}>
<Child parentClick={this.state.obs$} />
</div>
);
}
}
We have our ref this.ref and set it through the function, we need this since it is the target of a fromEvent and click is the event. This automatically creates an observable that will emit to any subscribers when it is clicked. You will want to pass this as a prop to your child component. Then in that component you can subscribe to it and do whatever you want when there is a click in the parent.
interface Props {
parentClick?: Observable;
}
interface State {
onClick$?: Subscription;
numClicks: number;
}
class Child extends React.Component<Props, State> {
public readonly state: State = { numClicks: 0 };
componentDidMount() {
if (this.props.parentclick) {
this.handle();
}
}
componentDidUpdate(prevProps: Props) {
if (
this.props.parentClick !== undefined &&
this.state.onClick$ === undefined
) {
this.handleSubscribe();
}
}
componentWillUnmount() {
if (this.state.onClick$) {
this.state.onClick$.unsubscribe();
}
}
handleSubscribe() {
this.setState({
onClick$: this.props.parentClick.subscribe(this.onParentClick)
});
}
#Bind()
onParentClick() {
this.setState((prevState: State) => ({
numClicks: prevState.numClicks + 1
}));
}
render() {
return (
<div style={childStyles}>
Parent clicked {this.state.numClicks} time(s)
</div>
);
}
}
So in this instance, when the parent is clicked the subscription invokes the onParentClick method. Then in that method we implement a simple counter and display it in the HTML.
One thing important thing is to ALWAYS make sure you unsubscribe from subscriptions. If you don't this will create a memory leak and will be really tricky to track down, since it is easy to overlook.

ReactJs Calling parent function

I am sorry if it is duplicate. I am passing parent function to child but when I use this method in child it give me this error
_this2.props.changeAppMode is not a function
I tried stackover flow already answered questions but can't able to resolve it. I am a newbie so might be I am missing some other concept
Following are my components
Parent Component
class Users extends React.Component {
constructor(props) {
super(props);
this.state = {
currentMode: 'read',
userId: null
};
this.changeAppMode = this.changeAppMode.bind(this);
}
changeAppMode(newMode, userId) {
this.setState({currentMode: newMode});
if (userId !== undefined) {
this.setState({userId: userId});
}
}
render() {
var modeComponent =
<ReadUserComponent
changeAppMode={this.changeAppMode}/>;
switch (this.state.currentMode) {
case 'read':
break;
case 'readOne':
modeComponent = <ViewUser />;
break;
default:
break;
}
return modeComponent;
}
}
export default Users;
Child
class ReadUserComponent extends React.Component {
constructor(props) {
super(props);
console.log(props);
};
componentDidMount() {
this.props.fetchUsers();
}
render(){
const users = this.props.users;
return (
<div className='overflow-hidden'>
<h1>Users List </h1>
<TopActionsComponent changeAppMode={this.props.changeAppMode} />
<UsersTable
users={users}
changeAppMode={this.props.changeAppMode} />
</div>
);
}
}
ReadUserComponent.propTypes = {
users: React.PropTypes.array.isRequired,
fetchUsers: React.PropTypes.func.isRequired
}
function mapStateToProps(state) {
return {
users: state.users
}
}
export default connect(mapStateToProps, { fetchUsers })(ReadUserComponent);
Child of Child [This component calling parent function]
class TopActionsComponent extends React.Component {
render() {
return (
<div>
<a href='#'
onClick={() => this.props.changeAppMode('create')}
className='btn btn-primary margin-bottom-1em'> Create product
</a>
</div>
);
}
}
export default TopActionsComponent;
Thanking you in anticipation. Really appreciate your help
Sorry if it is duplicate but I am kind of a stuck in it
I think it's related to binding in child component. Could you try below piece of code while passing props into child component.
<TopActionsComponent changeAppMode={::this.props.changeAppMode} />
okay. try this. I think it should work.
<ReadUserComponent changeAppMode={() => this.changeAppMode}/>;
<UsersTable
users={users}
changeAppMode={() => this.props.changeAppMode} />
Try this, it solved the same issue i was facing.
In ReadUserComponent Component:
<TopActionsComponent changeAppMode={this.changeAppMode.bind(this)} />
Define this function in ReadUserComponent:
changeAppMode(type){
this.props.changeAppMode(type);
}
In TopActionsComponent Component:
<a href='#' onClick={this.changeAppMode.bind(this,'create')}
className='btn btn-primary margin-bottom-1em'> Create product
</a>
Define this function in TopActionsComponent Component:
changeAppMode(type){
this.props.changeAppMode(type);
}

Resources