Is it okay to connect a PureComponent? - reactjs

I was wondering if this is okay:
import React, { PureComponent } from "react";
import { Text, TouchableOpacity } from "react-native";
import { connect } from "react-redux";
import PropTypes from "prop-types";
class ListItem extends PureComponent {
render() {
return (
<TouchableOpacity>
<Text style={{ color: "red" }}>Some Text</Text>
<TouchableOpacity />
</TouchableOpacity>
);
}
}
export default connect()(ListItem);
And than maybe add mapStateToProps(). Or is this an Anti-pattern? I've heard that PureComponents can slow down performance...

Actually connect() function makes the wrapped component pure by default (see docs). That is, the wrapped component will be rerendered only when the properties change (state or own props). So there's no point in inheriting from PureComponent, because shouldComponentUpdate logic is already implemented in HOC produced by connect().
I've heard that PureComponents can slow down performance...
Shallow props comparison performed by PureComponent is relatively cheap operation. I don't think it's going to be an issue.

There is no issue in using connect and PureComponent. PureComponent renders if the props have changed and connect() maps the redux state to props. See in this example by the redux team. I have replaced the TodoList component with a Purecomponent:
class TodoList extends React.PureComponent {
render() {
const { todos, toggleTodo } = this.props;
return (
<ul>
{todos.map(todo => (
<Todo key={todo.id} {...todo} onClick={() => toggleTodo(todo.id)} />
))}
</ul>
);
}
}
/*
const TodoList = ({ todos, toggleTodo }) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => toggleTodo(todo.id)}
/>
)}
</ul>
)
*/
It works just the same.

I had a problem with list items that were connected components and ended up here after googling for it.
I will add the description of the problem and my solution here:
The mapStateToProps looks something like this
import { defaultMemoize } from 'reselect';
const mapStateToProps = () => {
const createMergedItem = defaultMemoize((item, edit) =>
edit
? { ...item, edit: true }
: { ...item, edit: false }
);
return (state, { item, edits }) => {
//returning same ref when item and edits[item.id] didn't change
return createMergedItem(item, Boolean(edits[item.id]));
};
};
export default connect(
mapStateToProps,
)(Item);
In the List component
items.map(item=>(<Item key={item.id} item={item} edit={edit} />)
The code is a bit simplified but what it does is that List passes an item and edit to each Item component as props, edit is an object that has members with an item.id as key. If I have an item with id 1 and edits is {1:anythingTruthy} then the item 1 is in edit mode.
When I'd change an item in the list from or to edit mode then all the items in the list that weren't in changed would re render even though mapStateToProps would return the same reference as last time.
I always thought that connect would return a pure component but I was mistaken, the solution is to make Item a pure component and with React.memo this is very simple:
import { memo } from 'react';
//mapStateToProps is the same
export default connect(
mapStateToProps,
)(memo(Item));//wrap Item in memo
Where Item is a functional component (props=>jsx).
when you change edit mode of one item in the list the edit prop will change for all items but thanks to defaultMemoize and returning a function from mapStateToProps that crates a memoized createMergedItem function it'll return props that has the same reference as the last one. This was not enough because the Item function was still called. I had to also use React.memo to make Item a pure component.

Pure component does the shallow comparison of props and re-renders only if props are changes.
connect(HOC) also does the shallow comparison and re-rendering bases on comparison.
Both do the same job. so there is no harm in using both in same component, but while using both, post connect does the comparison, pure component will also do it's shallow comparison. which is repetitive and can be time taking.
Avoid using pure component while using connect.

Related

using React.memo in functional component to renderItem FlatList to minimize re-render existing items

I'm using functional components, and using Flatlist to render list of datas,
it's working ok, but each time state get additional data it always re-rendering existing
and it will cause performance problem, I have read these articles from SO but still has no clues
https://stackoverflow.com/a/57405307/938947
https://stackoverflow.com/a/46349156/938947
here is my code that using main Flatlist
<FlatList
horizontal={false}
showsHorizontalScrollIndicator={false}
data={users}
keyExtractor={(item, index) => String(index)}
renderItem={RenderUser}
onEndReachedThreshold={0.7}
onEndReached={callBackMoreData}
/>
and here is working RenderUser but the problem it re render existing item
if state has additional data, what I would like to achieve is only render additional
data
import React from 'react';
import { ListItem } from 'react-native-elements';
const RenderUser = ({ item, index }) => {
return (
<React.Fragment>
{ console.log('index: ', index)}
<ListItem
title={item.attributes.name}
/>
</React.Fragment>
);
};
export default RenderUser;
and I did tried using this code below (but I get an error message saying
TypeError: renderItem is not a function. (in 'renderItem(props)', 'renderItem' is an
instance of object))
import React, { memo } from 'react';
import { ListItem } from 'react-native-elements';
const RenderUser = ({ item, index }) => {
return (
<React.Fragment>
{ console.log('index: ', index)}
<ListItem
title={item.attributes.name}
/>
</React.Fragment>
);
};
export default memo(RenderUser);
According to react official documentation:
By default it will only shallowly compare complex objects in the props object. If you want control over the comparison, you can also provide a custom comparison function as the second argument
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);
In your case you need to add condtion:
import React, { memo } from 'react';
import { ListItem } from 'react-native-elements';
const RenderUser = ({ item, index }) => {
return (
<React.Fragment>
{ console.log('index: ', index)}
<ListItem
title={item.attributes.name}
/>
</React.Fragment>
);
};
function arePropsEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
return nextProps.item.attribute.name===prevProps.item.attribute.name
}
export default memo(RenderUser,arePropsEqual);
Note : Not sure how many props you are getting and what are unique. Basically you need to compare that last one is equal to this one return true in that way react does not re-render your component

Child component won't re-render

I am using react with redux and I have a todo-list.
I have a 'Todos' component which acts like a container and a 'Todoitem' component which holds every todo.
Everything works fine - reducers change the state and it is updating with new data, but the child component (aka 'Todoitem' component) won't re-render.
Todos.js:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import TodoItem from "./TodoItem";
class Todos extends Component {
render() {
return (
<div className="Todos">
<div className="todos_title"> {this.props.title} </div>
{this.props.todos.map(todo => {
console.log(todo); // this line prints updated data from state just fine!
return <TodoItem todo={todo} key={todo.id}></TodoItem>;
})}
</div>
);
}
}
// PropTypes
Todos.propTypes = {
todos: PropTypes.array.isRequired
};
const mapStateToProps = state => ({
todos: state.todosReducer.todos
});
export default connect(mapStateToProps)(Todos);
TodoItem.js:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { checkTodo } from "../actions/todosAction";
class TodoItem extends Component {
onChange = (e, id) => {
console.log(this.props.todo.completed.toString()); // this also prints fine the updated data
this.props.checkTodo(id); // dispatches an action to the reducer to toggle the todo.completed with the right todo.id
};
render() {
console.log('rendering'); // this is the problem - this line calls only in the first rendering, but not when state changes
let { id, title, completed } = this.props.todo;
return (
<div className={completed ? "TodoItemDone" : "TodoItem"}>
<p>
<input
className="todo_cb"
type="checkbox"
onChange={e => this.onChange(e, id)}
checked={completed ? "checked" : ""}
/>
{id}) {title}
</p>
</div>
);
}
}
// PropTypes
TodoItem.propTypes = {
todo: PropTypes.object.isRequired
};
const mapDispatchToProps = dispatch => ({
checkTodo: todo => dispatch(checkTodo(todo))
});
const mapStateToProps = state => ({});
export default connect(
null,
mapDispatchToProps
)(TodoItem);
I noticed that if I do pass a mapStateToProps in child comp it is re-rendering, like this:
const mapStateToProps = state => ({
some_prop: state
});
I understand the if I use mapStateToProps in the child it re-renders but I don't need anything directly from the state in child, the parent does this.
It makes some sense but my todos are stored in an Array in the state and I am mapping over them as you see in the parent component, so I can't map a specific todo from this array to the component props (how could I distinguish each element in the array to map to the prop?).
I am very confused.
I read that component re-renders when state or his props change. Inside the child component the props do change because the parent re-renders and it iterates the todos again and return the component with new props.
Maybe it's not the way to pass the todos to the components but I still don't understand how come the props changes and render() is not called.
Thank you very much!
Edit 1:
I connected the checkTodo action to the parent component and passed the function with props and it works just fine.
Still I don't understand why before the child component haven't re-rendered with the previous code...
Edit 2:
Actually I just lied, it does not work. I forgot to remove mapStateToProps which I said worked, so I am back to square one.
Edit 3:
Solved with by calling forceUpdate(). Still can't understand why it happened.

React Native. Getting value from ref attribute

I am trying to get value from a component but keep getting undefined refs.
Here is my code. From a function onClickSave(), I have tried to get this.refs to get a value of ref "input" in TextInputCell component but it's undefined. Is my code incorrect?
import React from 'react';
import { View, Text } from 'react-native';
import { Form, Section, TextInputCell } from 'react-native-forms';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import ActionBar3 from '../components/ActionBar3';
import * as profileActions from '../actions/profileActions';
const GLOBAL = require('../GlobalConstants');
class ProfileViewEdit extends React.Component {
constructor(props) {
super(props);
this.onClickSave.bind(this);
}
componentDidMount() {
console.log('componentDidMount');
}
onClickSave() {
console.log('aaabd');
console.log(this.refs);
}
render() {
const title = this.props.navigation.state.params.title;
let value = this.props.navigation.state.params.value;
return (
<View style={{ flex: 1, backgroundColor: '#EFEFF4' }}>
<ActionBar3
barTitle={title} navigation={this.props.navigation} onClickSave={this.onClickSave}
/>
<Section
title={title}
//helpText={'The helpText prop allows you to place text at the section bottom.'}
>
<TextInputCell
value={value}
ref="input"
/>
</Section>
</View>
);
}
}
const mapStateToProps = (state) => ({
stateProfile: state.profile
});
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators(profileActions, dispatch)
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProfileViewEdit);
First thing that you are not handling events correctly. To use this in your events you need to bind this. Arrow functions bind it itself but you can bind manually to. More information is here.
You have to be careful about the meaning of this in JSX callbacks. In
JavaScript, class methods are not bound by default. If you forget to
bind this.handleClick and pass it to onClick, this will be undefined
when the function is actually called.
Second thing string refs are not suggested anymore. You should use functional ones. More info about that here.
Legacy API: String Refs
If you worked with React before, you might be familiar with an older
API where the ref attribute is a string, like "textInput", and the DOM
node is accessed as this.refs.textInput. We advise against it because
string refs have some issues, are considered legacy, and are likely to
be removed in one of the future releases. If you’re currently using
this.refs.textInput to access refs, we recommend the callback pattern
instead.
Example
<ActionBar3 barTitle={title} navigation={this.props.navigation} onClickSave={ () => this.onClickSave()} />
<TextInputCell value={value} ref={(ref) => { this.inputRef = ref; }} />

use react-redux with connect() and {...this.props}

I cannot figure out, how to make right solution, when I want to call action in my container from other component, by the way I want to use spread operator because I need to pass too many parametrs in my component and don't want describe all of them.
I know I can pass all props from redux store via props, like this example in Menu, but my component too nested, and I have to send props in eighter component in nest
render() {
return (
<div className="wrapper">
<Menu {...this.props} />
</div>
);
}
}
const mapStateToProps = reduxStore => (
{
app: reduxStore.app
}),
mapDispatchToProps = dispatch => ({appActions: bindActionCreators(appActions, dispatch)});
export default connect(mapStateToProps, mapDispatchToProps)(App);
So, I decided to connect my nested component with redux store, because I need to work from my nested component with store and actions in main container component. But this solution doesn't work, because i use spread operator to my nested component.
render() {
return <Link activeClassName='active' onClick={this.props.appActions.closeMenu} {...this.props} />;
}
And using spread operator is really important because component get too much different parameters from its parent component, and if i don't use {...this.props}, I have to write like this:
render() {
const { to, onlyActiveOnIndex, className, specialIcons } = this.props;
return <Link activeClassName='active' onClick={this.props.appActions.closeMenu} to={to} specialIcons={specialIcons} onlyActiveOnIndex={onlyActiveOnIndex} className={className} >{this.props.children}</Link>;
}
But also, I have to connect to common redux store, and when I connected, occurs an Error, because of my component use {...this.props} and it get all props, including actions from container and component doesn't know what do with them. I find one solution of this proplem, but I'm not sure that it is right variant. I clone props with spread operators, but delete property that contain new functions (actions) from common store.
render() {
let oldProps = {...this.props};
delete oldProps.appActions;
delete oldProps.app;
return <Link activeClassName='active' onClick={this.props.appActions.closeMenu} {...oldProps} >{this.props.children}</Link>;
}
}
const mapState = reduxStore => ({app: reduxStore.app}),
mapDispatchToProps = dispatch => ({appActions: bindActionCreators(appActions, dispatch)});
export default connect(mapState, mapDispatchToProps)(NavLink);
I'm guessing that I don't understand something basic and global in react-redux or I use bad practice. May be I should use higher order components in React? but now I don't know how to make it better.
Here is a functional example. I made it for a personal project. I removed the useless code for the purpose of the example.
Something you might want to get is eslint, it will show you basic mistake people are making while coding.
For example, it will say that you having declared your PropTypes. In your code, where does it say what app is? Sure it's coming from reduxStore.app but what kind of PropTypes is it?
Also, you shouldn't link all the reduxStore state to your component. You should just import what you really need. In my example, I import only users from state.app.users. If I had more, or want all elements of the reducer state, I would import all of them individually and then declare the props like this:
Home.propTypes = {
users: PropTypes.object.isRequired,
actions: {
load: PropTypes.func.isRequired,
},
};
Because JavaScript isn't a typed language, the PropTypes like above help you make typed validation. You can also see the props actions which contains all the functions you import in AppActions in your case.
To see how to use the function from the action afterward, look at my componentWillMount()
import React, { Component, PropTypes } from 'react';
import { ListView} from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as app from '../../actions/appActions';
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
class Home extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: ds.cloneWithRows(props.users.toJS()),
};
}
componentWillMount() {
this.props.actions.load();
}
componentWillReceiveProps(nextProps) {
if (this.props.users !== nextProps.users) {
this.setState({
dataSource: ds.cloneWithRows(nextProps.users),
});
}
}
render() {
return (
<ListView
dataSource={this.state.dataSource}
enableEmptySections
renderRow={
(rowData) => <User haveLunch={rowData.haveLunch} name={rowData.name} />
}
/>
);
}
}
Home.propTypes = {
users: PropTypes.object.isRequired,
actions: {
load: PropTypes.func.isRequired,
},
};
function mapStateToProps(state) {
return {
users: state.app.users,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(app, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Hope this will help ya ;)

React + Redux: Separating the presentation from the data

I am building a weather app with React & Redux. I've decided to venture into uncharted waters as a noob to React & Redux. I'm splitting things up into presentational components and their respective container that will handle the data. I'm having some problems wrapping my head around this though. It might come down to how I'm trying to do it I'm just really unsure.
Right now I have SearchBar, CurrentWeather, & Forecast components and an AppContainer that I'm trying to integrate those components into. I have the SearchBar component integrated into the AppContainer so far and it is working with no problems. Here is where I am getting confused. So I have provided the needed actions and components to the container and the container has been connected so when the user does a search the api call will be made and the state will update through the reducers.
That data should be available through mapStateToProps now correct?
How can I go about using that data after the user has performed the action but have it not be used upon the initial render? If AppContainer is rendering these three components I will obviously be passing props to them so they render and function as they are expected to. I'm thinking this is where a lifecycle could be used I'm just unsure of which or how to use them. My code for the AppContainer, SearcBar, & CurrentWeather are below. CurrentWeather & Forecast are nearly identical (only providing different data from different endpoints for the api) so I did not provide it. I also didn't provide the actions or reducers because I know they work fine before I decided to attempt this refactor. Maybe I need more than one container to pull this off? Any advice or direction would be greatly appreciated, thanks all and have a good night.
** Do have a side question: on _weatherSearch I have event.preventDefault(); because the SearchBar is a form element. Do I even need to provide this? If event is not what is being passed but the term I think no. The event is being used as seen below in the form element of SearchBar:
onSubmit={event => getWeather(event.target.value)}
App Container:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchCurrentWeather, fetchForecast } from '../actions/actions';
import SearchBar from '../components/SearchBar';
import CurrentWeather from '../components/CurrentWeather';
class AppContainer extends Component {
_weatherSearch(term) {
event.preventDefault();
// Here is where we go to fetch weather data.
this.props.fetchCurrentWeather(term);
this.props.fetchForecast(term);
}
render() {
const getWeather = term => {this._weatherSearch(term);};
return (
<div className="application">
<SearchBar getWeather={getWeather}/>
<CurrentWeather />
</div>
);
}
}
const mapStateToProps = ({ current, forecast }) => {
return {
current,
forecast
}
}
export default connect(mapStateToProps,
{ fetchCurrentWeather, fetchForecast })(AppContainer);
SearchBar:
import React from 'react';
const SearchBar = ({ getWeather }) => {
return(
<form className='input-group' onSubmit={event => getWeather(event.target.value)}>
<input
className='form-control'
placeholder='Search a US City' />
<span className='input-group-btn'>
<button className='btn btn-secondary' type='submit'>Submit</button>
</span>
</form>
);
}
export default SearchBar;
CurrentWeather: *NOTE: I have not removed any of the logic or data processing from CurrentWeather yet so it has not been refactored to a presentational only component yet.
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {unitConverter} from '../conversions/conversions_2.0';
export class CurrentWeather extends Component {
_renderCurrentWeather(cityData) {
const name = cityData.name;
const {temp, pressure, humidity} = cityData.main;
const {speed, deg} = cityData.wind;
const {sunrise, sunset} = cityData.sys;
return (
<tr key={name}>
<td>{unitConverter.toFarenheit(temp)} F</td>
<td>{unitConverter.toInchesHG(pressure)}"</td>
<td>{humidity}%</td>
<td>{unitConverter.toMPH(speed)}mph {unitConverter.toCardinal(deg)}</td>
</tr>
);
}
render() {
let currentWeatherData = [];
if (this.props.current) {
currentWeatherData = this.props.current.map(this._renderCurrentWeather);
}
return (
<table className="table table-reflow">
<thead>
<tr>
<th>Temperature</th>
<th>Pressure</th>
<th>Humidity</th>
<th>Wind</th>
</tr>
</thead>
<tbody>
{currentWeatherData}
</tbody>
</table>
);
}
}
function mapStateToProps({current}) {
return {current};
}
export default connect(mapStateToProps)(CurrentWeather);
Your render function is very dynamic. You can omit anything you like:
class AppContainer extends Component {
_weatherSearch(term) {
// event.preventDefault(); We can't do this because we don't have an event here...
this.props.fetchCurrentWeather(term);
this.props.fetchForecast(term);
}
render() {
const getWeather = term => { this._weatherSearch(term); };
return (
<div className="application">
<SearchBar getWeather={getWeather}/>
{ Boolean(this.props.current) && <CurrentWeather /> }
</div>
);
}
}
const mapStateToProps = ({ current }) => ({ current });
export default connect(mapStateToProps,
{ fetchCurrentWeather, fetchForecast })(AppContainer);
This is how you deal with missing data. You just either show nothing, or a message to search first, or if it's loading,you can show a spinner or throbber.
The technique used above to hide CurrentWeather is to pass a Boolean to React if we're wanting to hide the component. React ignores true, false, null and undefined.
Note that it's a good idea to only ever pass data in mapStateToProps that you'll actually be using inside the component itself. In your code you're passing current and forecast but you don't use them.
Redux will rerender when any of the mapStateToProps, mapDispatchToProps or props data changes. By returning data you'll never use you instruct Redux to rerender when it's not necessary.
I'm a react-redux noob myself :-) and I've come across similar issues.
As far as I can tell, the container/presentational separation you've made looks good, but you can go even a step further and separate the container's fetching and mounting.
The solution I'm referring to is what people variously call "higher-order components" and "wrapper components": (the code below isn't tested - it's just for illustration purposes)
import {connect} from blah;
const AppWrap = (Wrapped) => {
class AppWrapper extends Component {
constructor(props) {
super(props);
this.state = {foo: false};
}
componentWillMount() {
this.props.actions.fooAction()
.then(() => this.setState({foo: false}));
}
render() {
return (<Wrapped {...this.props} foo={this.state.foo}/>);
}
}
function mapState(state) { blah }
function mapDispatch(dispatch) { blah }
return connect(mapState, mapDispatch)(AppWrapper);
}
export default AppWrap;
Notice the = (Wrapped) => { part at the top. That is what's doing the actual "wrapping", and the argument can be named anything so long as you refer to it in the render hook.
Now inside your AppContainer, you get a this.props.foo which acts as a flag telling you that fooAction() has completed, and you can use it to render your presentational components accordingly. Until fooAction completes, you can be sure that the foo passed into AppContainer will be false.
To put what I just said into code, your AppContainer might look something like this:
import AppWrapper from './AppWrapper';
class AppContainer extends Component {
constructor(props) {
super(props);
}
render() {
return (!this.props.foo) ? <div>bar</div> : (
<div blah>
<SearchBar blah/>
<CurrentWeather blah/>
</div>
);
}
}
export default AppWrapper(AppContainer);
The benefit of using a wrapper component like this is that
you can take more control over how and when exactly the data gets rendered
account for "loading" mechanisms and logic
avoid quirky problems like having to make dispatches within componentWillMount hooks and having to deal with the consequences.
Take a look at this blog post for more information about HoCs: https://medium.com/#dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750

Resources