I have a ParentComponent looking like this:
const ParentComponent = () => {
[product, setProduct] = useState({
name: "Test",
description: "Information goes here..."
})
return(
<ChildComponent product={product} updateProduct={setProduct}/>
);
}
and a ChildComponent as follows:
const ChildComponent = ({product, updateProduct}) => {
// some code here
return(
<RichTextEditor content={product.description} update={updateProduct}/>
)
}
const isEqual = () => {
return true; // I want to force the component to never re-render
}
export default React.memo(ChildComponent, isEqual)
What's happening here is that I have a product object with a description that is updated by the ChildComponent. The value for the description gets set via updateProduct every time the input changes in the RichTextEditor component. I understand that because of the state change of product the ChildComponent gets re-rendered everytime which is unfortunate because that can cause unwanted effects like making the input field inside RichTextEditor lose focus for example. So I tried forcing the ChildComponent to never re-render with my isEqual function that always returns true. It keeps re-rendering though, why is that?
I realize that there are other designs available to avoid this issue completely, and I'm probably going to do that anyways but before I do that I want to understand why I can not force ChildComponent to not re-render.
If you are using useState, useContext or useReducer then the component will still re-render.
This is straight from the docs.
Related
I am trying to build an ecommerce website, and I hit a problem I cannot seem to resolve. I am very new to react and JS so have some patience please :)
I declared 4 useStates in my app.js:
const [elementeDinState, setElementeDinState] = useState([]);
const [currentCategorie, setCurrentCategorie] = useState("Acasa");
const [subCategorie, setSubcategorie] = useState([]);
const [cartContents, setCartContents] = useState([]);
const fetchData = useCallback(async () => {
const data = await getCategories();
setElementeDinState(data);
}, []);
useEffect(() => {
fetchData().catch(console.error);
}, [fetchData]);
const changeHeader = (dataFromMenuItem) => {
setCurrentCategorie(dataFromMenuItem);
};
const changeCopiiContent = (data1FromThere) => {
setSubcategorie(data1FromThere);
};
const changeCart = (dataFromCart) => {
setCartContents(dataFromCart);
};
I am passing the functions to change those states to different child components as props. my problem is, when I add items to cart it triggers a re render of my component (products listing component) that should not be affected by cartContents and that resets the state of said component to the initial value that changes the items being shown. does useState hook create a single global state comprised of all those states?
If these useState are defined in the app.js and then passed down, when a child will use them chasing the state will happen in the app.js so all the children of <App /> will be re-rendered.
I guess that your app.js looks similar:
function App() {
const [elementeDinState, setElementeDinState] = useState([]);
// ...and the other hooks and methods
return (
<cartContents setElementDinState={setElementeDinState} />
<ProductList />
)
}
In this case the state is in the component so when <CartContents /> changes it, it will trigger a re-render of the and all its children <ProductList /> included.
To avoid this problem think better when each piece of state needs to be and put the state as near as possibile to that component. For example, if the state of the cart does not influence the Product list. Move the useState in the <Cart /> component.
From what I understand, your problem is that you're simply resetting the cartContents state every time you call the changeCart function, correct?
What you probably want, is to add (or remove ?) the item to the cart, like this?
const changeCart = (dataFromCart) => {
setCartContents(oldContents => [...oldContents, dataFromCart]);
};
Here is a description of useState from the oficial site:
useState is a Hook (...). We call it inside a function component to add some local state to it
So it creates just a local state.
About your problem, We need more information, but I believe that some parent component of that widget is trying to render other component instead of your the component that you wanted (let's call it "ProblemComponent") and rendering you ProblemComponent from scratch again, before you can see it.
it's something like that:
function ParentComponent(props: any) {
const isLoading = useState(false);
// Some logic...
if(isLoading) {
return <LoadingComponent/>;
}
return <ProblemComponent/>;
}
If that doesn't work you can also try to use React.memo() to prevent the ProblemComponent to update when it props change.
well, seems like I wanted to change the way react works so I figured out a work around, based on what you guys told me. I declared the state of the productsComponent in the parent component and adding to cart now doesn't force a refresh of the items being shown. thank you!
I have a simple React component and I set the same value each time that I click on the button:
import React, { useState } from 'react';
import './style.css';
let data = { title: 'ABC' };
export default function App() {
const [foo, setFoo] = useState();
console.log('Rendered');
return (
<div>
<button onClick={() => setFoo(data)}>Set Data</button>
<h1>Data: {JSON.stringify(foo)}</h1>
</div>
);
}
But renders are a little bit strange because at the second button click React renders the component but I set the same value.
Why React re-render the component although I set the same value?
Demo Here
The question is about why the component renders although the new state equals the previous state (shallow comparison)
// On second button click
const prevState = data
// State trigger
setFoo(data)
// Same state, it doesn't triggers a render
data === prevState
So, the component didn't trigger render due to state change.
But it happened due to another reason, as mentioned in docs "between the lines" under Hooks API Bailing out of a state update section:
Note that React may still need to render that specific component again before bailing out.
Unlike in class component, for function components, after setting the same state like in our case, sometimes, React will need another render to validate its equality. Its unfortunate edge case.
But it should not consider you as "performance issue" since it does not effect the React.Node tree, it won't continue in the reconciliation process if the state didn't change. It even won't make unnecessary hooks calls.
Another Simplified Example
Same goes here, there is another render for bail out, another log of "A".
Although there is a "bail out" render, notice that the useEffect does not run.
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
const App = () => {
const [state, setState] = React.useState(0);
useEffect(() => {
console.log("B");
});
console.log("A");
return (
<>
<h1>{state}</h1>
<button onClick={() => setState(42)}>Click</button>
</>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
If you wondering on logs order ("Why 'A' logged before 'B'?"), try deep diving another question: React useEffect in depth / use of useEffect?
In functional component, a component isn't re-rendered if it's the same value, i.e. a value that passes === comparison:
const [state, setState] = useState({});
...
setState(state => state); // no re-render
Otherwise a component is re-rendered:
setState(state => ({...state})); // re-render
As commented already, it may be "rerendered" by react, but the DOM will likely not change.
Checkout https://github.com/facebook/react/issues/17474
I have a basic e-commerce app for practice. There's a functional component called "Shop" which has two states:
[products, setProducts] = useState([10ProductObjects])
and [cart, setCart] = useState([])
On the first render cycle, the 10 products are loaded and each Product component has an "add to cart" button that adds a product to the cart array.
When I click the button, the cart array gets populated and its length is displayed. However, doing so re-renders the 10 products again even though nothing has been changed on them.
Now as I see it since one of the states changes i.e. the cart array, the whole Shop component is rendered again. Which in turn renders its child components, including those which were not changed.
I've tried to use React.memo but it doesn't help since no props are being changed on "Shop", rather the state is changing. I've also used the useMemo hook and it had some interesting results.
Using the products array as a dependency solves the extra re-rendering problem, but the new products are not added to the cart anymore. Using both [products, cart] as the dependencies works but brings back the original problem.
I know it could be done using shouldComponentUpdate but I need that kind of flexibility in functional components.
N.B: This is my first ever question and I'm all ears for any kind of feedback.
import React, { useState, useMemo } from 'react';
import fakeData from '../../fakeData';
import Product from '../Product/Product';
const Shop = () => {
console.log('[Shop.js] rendered')
const first10 = fakeData.slice(0, 10);
const [products, setProducts] = useState(first10);
const [cart, setCart] = useState([]);
const addProductHandler = (product) => {
console.log(cart, product);
const newCart = [...cart, product];
console.log(newCart);
setCart(newCart);
}
let productsOnScreen = useMemo(() => {
return products.map( prod => {
return <Product product={prod} addProductHandler={addProductHandler} />
});
}, [products])
return (
<div className="shop-container">
<div className="product-container">
{productsOnScreen}
</div>
<div className="cart-container">
<h3>this is cart</h3>
<h5>Order Summary: {cart.length}</h5>
</div>
</div>
);
};
export default Shop;
Memoize addProductHandler with React.useCallback so that the reference to it does not change between renders:
const addProductHandler = React.useCallback((product) => {
setCart(oldCart => {
return [...oldCart, product];
});
}, [setCart]);
Then, memoize <Product> with React.memo. You didn't post your code for that component but it would look something like this:
export const Product = React.memo((props) => {
// normal functional component stuff
return <>product component or whatever</>;
});
These are both necessary for a component to avoid unnecessary rerenders. Why?
React.useCallback allows for comparison-by-reference to work for a callback function between renders. This does not work if you declare the callback function every render, as you have currently.
React.memo wraps a component to enable it to render or not render depending on a shallow comparison of its props. React components do NOT do this by default, you have to explicitly enable it with React.memo.
As the docs mention:
This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs.
In other words, you should only use this as a performance optimization if you are experiencing slowdowns, NOT to prevent renders for any other reason, and ONLY if you are actually experiencing performance issues.
If this doesn't work, it's likely that one of your <Product> props is still changing (in the context of shallow comparison-by-reference) between renders. Keep playing with and memoizing props until you figure out which ones are changing between renders. One way to test this is a simple React.useEffect (for debug purposes only) inside of <Product> which will alert you when a prop has changed:
React.useEffect(() => {
console.log('product prop changed');
}, [product]);
React.useEffect(() => {
console.log('addProductHandler prop changed');
}, [addProductHandler]);
// etc...
I have a route (using React-Router) with component which it renders. Every time this route opened and its component created I need to reset some part of Redux state (one reducer's state in fact), used in this component. This reducer is shared in some other parts of the app, so I use Redux state and not local component's state. So how can I reset the reducer's state every time my component created? I am wondering about best practice to do this.
I think if I'll dispatch actions in componentDidMount method, there will be blinking of previous state for some second.
Can I dispatch action to reset some reducer's state in component's constructor?
Is there any better approach? Can I somehow to set initial state in connect() function, so component will have resetted state each time it created? I check the docs, but I cannot find some argument for this.
Yes, you can dispatch action in constructor to change reducer state
constructor(prop){
super(prop);
prop.dispatch(action);
}
Another approach you can try is setting default props so that you don't need to call reducer(dispatch action)
ButtonComponent.defaultProps = {
message: defaultValue,
};
One possible solution I can think of...
If you could go with the first approach, you can try to stop the previous state being shown while component is being re-rendered with reset state.
The only phase during which you would see the prevState is during the initial render. How about an instance variable to track the render count.
A rough draft.
import React from "react";
import { connect } from "react-redux";
import { add, reset } from "./actions";
class Topics extends React.Component {
renderCount = 0;
componentDidMount() {
// Dispatch actions to reset the redux state
// When the connected props change, component should re-render
this.props.reset();
}
render() {
this.renderCount++;
if (this.renderCount > 1) {
return (
<div>
{this.props.topics.map(topic => (
<h3 id={topic}>{topic}</h3>
))}
</div>
);
} else {
return "Initializing"; // You can return even null
}
}
}
const mapStateToProps = state => ({ topics: state });
const mapDispatchToProps = (dispatch) => {
return {
add(value){
dispatch(add(value));
},
reset(){
dispatch(reset());
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Topics);
Here renderCount is a class variable, that keeps incrementing on component render. Show a fallback UI on first render to avoid previous state from being shown and on second render (due to redux store update), you could display the store data.
A working example added below. I have added an approach to avoid the fallback UI as well. Have a look if it helps.
https://stackblitz.com/edit/react-router-starter-fwxgnl?file=components%2FTopics.js
I'm trying to understand the connect method of react-redux, and the functions it takes as parameters. In particular mapStateToProps().
The way I understand it, the return value of mapStateToProps will be an object derived from state (as it lives in the store), whose keys will be passed to your target component (the component connect is applied to) as props.
This means that the state as consumed by your target component can have a wildly different structure from the state as it is stored on your store.
Q: Is this OK?
Q: Is this expected?
Q: Is this an anti-pattern?
Yes, it is correct. Its just a helper function to have a simpler way to access your state properties
Imagine you have a posts key in your App state.posts
state.posts //
/*
{
currentPostId: "",
isFetching: false,
allPosts: {}
}
*/
And component Posts
By default connect()(Posts) will make all state props available for the connected Component
const Posts = ({posts}) => (
<div>
{/* access posts.isFetching, access posts.allPosts */}
</div>
)
Now when you map the state.posts to your component it gets a bit nicer
const Posts = ({isFetching, allPosts}) => (
<div>
{/* access isFetching, allPosts directly */}
</div>
)
connect(
state => state.posts
)(Posts)
mapDispatchToProps
normally you have to write dispatch(anActionCreator())
with bindActionCreators you can do it also more easily like
connect(
state => state.posts,
dispatch => bindActionCreators({fetchPosts, deletePost}, dispatch)
)(Posts)
Now you can use it in your Component
const Posts = ({isFetching, allPosts, fetchPosts, deletePost }) => (
<div>
<button onClick={() => fetchPosts()} />Fetch posts</button>
{/* access isFetching, allPosts directly */}
</div>
)
Update on actionCreators..
An example of an actionCreator: deletePost
const deletePostAction = (id) => ({
action: 'DELETE_POST',
payload: { id },
})
So, bindActionCreators will just take your actions, wrap them into dispatch call. (I didn't read the source code of redux, but the implementation might look something like this:
const bindActionCreators = (actions, dispatch) => {
return Object.keys(actions).reduce(actionsMap, actionNameInProps => {
actionsMap[actionNameInProps] = (...args) => dispatch(actions[actionNameInProps].call(null, ...args))
return actionsMap;
}, {})
}
Q: Is this ok?
A: yes
Q: Is this expected?
Yes, this is expected (if you are using react-redux).
Q: Is this an anti-pattern?
A: No, this is not an anti-pattern.
It's called "connecting" your component or "making it smart". It's by design.
It allows you to decouple your component from your state an additional time which increases the modularity of your code. It also allows you to simplify your component state as a subset of your application state which, in fact, helps you comply with the Redux pattern.
Think about it this way: a store is supposed to contain the entire state of your application.
For large applications, this could contain dozens of properties nested many layers deep.
You don't want to haul all that around on each call (expensive).
Without mapStateToProps or some analog thereof, you would be tempted to carve up your state another way to improve performance/simplify.
You got the first part right:
Yes mapStateToProps has the Store state as an argument/param (provided by react-redux::connect) and its used to link the component with certain part of the store state.
By linking I mean the object returned by mapStateToProps will be provided at construction time as props and any subsequent change will be available through componentWillReceiveProps.
If you know the Observer design pattern it's exactly that or small variation of it.
An example would help make things clearer:
import React, {
Component,
} from 'react-native';
class ItemsContainer extends Component {
constructor(props) {
super(props);
this.state = {
items: props.items, //provided by connect#mapStateToProps
filteredItems: this.filterItems(props.items, props.filters),
};
}
componentWillReceiveProps(nextProps) {
this.setState({
filteredItems: this.filterItems(this.state.items, nextProps.filters),
});
}
filterItems = (items, filters) => { /* return filtered list */ }
render() {
return (
<View>
// display the filtered items
</View>
);
}
}
module.exports = connect(
//mapStateToProps,
(state) => ({
items: state.App.Items.List,
filters: state.App.Items.Filters,
//the State.App & state.App.Items.List/Filters are reducers used as an example.
})
// mapDispatchToProps, that's another subject
)(ItemsContainer);
There can be another react component called itemsFilters that handle the display and persisting the filter state into Redux Store state, the Demo component is "listening" or "subscribed" to Redux Store state filters so whenever filters store state changes (with the help of filtersComponent) react-redux detect that there was a change and notify or "publish" all the listening/subscribed components by sending the changes to their componentWillReceiveProps which in this example will trigger a refilter of the items and refresh the display due to the fact that react state has changed.
Let me know if the example is confusing or not clear enough to provide a better explanation.
As for: This means that the state as consumed by your target component can have a wildly different structure from the state as it is stored on your store.
I didn't get the question, but just know that the react state (this.setState) is totally different from the Redux Store state!
The react state is used to handle the redraw and behavior of the react component. The react state is contained to the component exclusively.
The Redux Store state is a combination of Redux reducers states, each is responsible of managing a small portion app logic. Those reducers attributes can be accessed with the help of react-redux::connect#mapStateToProps by any component! Which make the Redux store state accessible app wide while component state is exclusive to itself.
This react & redux example is based off Mohamed Mellouki's example.
But validates using prettify and linting rules. Note that we define our props
and dispatch methods using PropTypes so that our compiler doesn't scream at us.
This example also included some lines of code that had been missing in Mohamed's
example. To use connect you will need to import it from react-redux. This
example also binds the method filterItems this will prevent scope problems in
the component. This source code has been auto formatted using JavaScript Prettify.
import React, { Component } from 'react-native';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
class ItemsContainer extends Component {
constructor(props) {
super(props);
const { items, filters } = props;
this.state = {
items,
filteredItems: filterItems(items, filters),
};
this.filterItems = this.filterItems.bind(this);
}
componentWillReceiveProps(nextProps) {
const { itmes } = this.state;
const { filters } = nextProps;
this.setState({ filteredItems: filterItems(items, filters) });
}
filterItems = (items, filters) => {
/* return filtered list */
};
render() {
return <View>/*display the filtered items */</View>;
}
}
/*
define dispatch methods in propTypes so that they are validated.
*/
ItemsContainer.propTypes = {
items: PropTypes.array.isRequired,
filters: PropTypes.array.isRequired,
onMyAction: PropTypes.func.isRequired,
};
/*
map state to props
*/
const mapStateToProps = state => ({
items: state.App.Items.List,
filters: state.App.Items.Filters,
});
/*
connect dispatch to props so that you can call the methods from the active props scope.
The defined method `onMyAction` can be called in the scope of the componets props.
*/
const mapDispatchToProps = dispatch => ({
onMyAction: value => {
dispatch(() => console.log(`${value}`));
},
});
/* clean way of setting up the connect. */
export default connect(mapStateToProps, mapDispatchToProps)(ItemsContainer);
This example code is a good template for a starting place for your component.
React-Redux connect is used to update store for every actions.
import { connect } from 'react-redux';
const AppContainer = connect(
mapStateToProps,
mapDispatchToProps
)(App);
export default AppContainer;
It's very simply and clearly explained in this blog.
You can clone github project or copy paste the code from that blog to understand the Redux connect.
It's a simple concept. Redux creates a ubiquitous state object (a store) from the actions in the reducers. Like a React component, this state doesn't have to be explicitly coded anywhere, but it helps developers to see a default state object in the reducer file to visualise what is happening. You import the reducer in the component to access the file. Then mapStateToProps selects only the key/value pairs in the store that its component needs. Think of it like Redux creating a global version of a React component's
this.state = ({
cats = [],
dogs = []
})
It is impossible to change the structure of the state by using mapStateToProps(). What you are doing is choosing only the store's key/value pairs that the component needs and passing in the values (from a list of key/values in the store) to the props (local keys) in your component. You do this one value at a time in a list. No structure changes can occur in the process.
P.S. The store is local state. Reducers usually also pass state along to the database with Action Creators getting into the mix, but understand this simple concept first for this specific posting.
P.P.S. It is good practice to separate the reducers into separate files for each one and only import the reducer that the component needs.
Here's an outline/boilerplate for describing the behavior of mapStateToProps:
(This is a vastly simplified implementation of what a Redux container does.)
class MyComponentContainer extends Component {
mapStateToProps(state) {
// this function is specific to this particular container
return state.foo.bar;
}
render() {
// This is how you get the current state from Redux,
// and would be identical, no mater what mapStateToProps does
const { state } = this.context.store.getState();
const props = this.mapStateToProps(state);
return <MyComponent {...this.props} {...props} />;
}
}
and next
function buildReduxContainer(ChildComponentClass, mapStateToProps) {
return class Container extends Component {
render() {
const { state } = this.context.store.getState();
const props = mapStateToProps(state);
return <ChildComponentClass {...this.props} {...props} />;
}
}
}
Yes, you can do this. You can also even process the state and return the object.
function mapStateToProps(state){
let completed = someFunction (state);
return {
completed : completed,
}
}
This would be useful if you want to shift the logic related to state from render function to outside of it.
I would like to re-structure the statement that you mentioned which is:
This means that the state as consumed by your target component can
have a wildly different structure from the state as it is stored on
your store
You can say that the state consumed by your target component has a small portion of the state that is stored on the redux store. In other words, the state consumed by your component would be the sub-set of the state of the redux store.
As far as understanding the connect() method is concerned, it's fairly simple! connect() method has the power to add new props to your component and even override existing props. It is through this connect method that we can access the state of the redux store as well which is thrown to us by the Provider. A combination of which works in your favor and you get to add the state of your redux store to the props of your component.
Above is some theory and I would suggest you look at this video once to understand the syntax better.
import React from 'react';
import {connect} from 'react-redux';
import Userlist from './Userlist';
class Userdetails extends React.Component{
render(){
return(
<div>
<p>Name : <span>{this.props.user.name}</span></p>
<p>ID : <span>{this.props.user.id}</span></p>
<p>Working : <span>{this.props.user.Working}</span></p>
<p>Age : <span>{this.props.user.age}</span></p>
</div>
);
}
}
function mapStateToProps(state){
return {
user:state.activeUser
}
}
export default connect(mapStateToProps, null)(Userdetails);