useState with arrays not rerendering - reactjs

I am facing issue while using useState hook with array. I checked various resources on stackoverflow, but could not fix it.
my basic code snippet looks like :
const [users, setUsers] = useState([]);
function addNewContact(user) {
const newUsers = [...users,user];
console.log(newUsers);
setUsers(newUsers);
}
<CardContainer users={users}></CardContainer>
class CardContainer extends Component {
constructor(props) {
super(props);
console.log("this -> ");
console.log(this.props.users);
this.state = {
users: this.props.users
}
}
render() {
//console.log(this.state.users)
return (
<div class="row row-cols-1 row-cols-md-2 g-4">
{
this.state.users.map(user => {
return <Card id={user.phone} title={user.name} email={user.email} phone={user.phone}></Card>
})
}
</div>
)
}
}
export default CardContainer;
I am able to see updated array in the console, but the component using it is not rendering again. Can anyone please help me on this.

The issue is due to you're storing the prop in the state of the child component, which is assigned on component initialization and component initialization/constructor only run one, until its remounted. After that, whenever, the state changes in the parent component, the child component is not re-rendering, because it uses its own state for map.
This below code only runs once on the component initialization.
this.state = {
users: this.props.users
}
In the child component, you can directly use the props and the child component will always re-render on change in the parent component. Instead of this.state.users.map you can directly map the array from props like this this.props.users.map. This way,the component will re-render on state change in the parent compoenent.

As #Junaid said, constructor is only called once before component mounting. If you really need to set a separate state inside the child component, then you can use componentDidUpdate(prevProps) react life cycle method. Make sure to compare previous and current props in order to avoid infinite loop of re-rendering.
componentDidUpdate(prevProps) {
if (this.props.users !== prevProps.users) {
this.setState({ users: this.props.users });
}
};

Related

How to update the value of a prop upon a state change in React

I have a parent React component (MainComponent) that renders a child component (theKeyComponent) and passes a constant as a prop (myID). The parent component also tracks the state 'activeLink'.
import theKeyComponent from "../components/theKeyComponent.jsx";
export default class MainComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
activeLink: '#section1'
}
}
render(){
const myId = this.state.activeLink === '#section1' ? '0001' : '0002';
return(){
<theKeyComponent myID={myID} />
<otherComponent otherPropsHere={otherProps} />
}
}
}
Here's a bit from inside the KeyComponent
export default class HelpScoutBeacon extends React.Component {
static propTypes = {
myID: PropTypes.string.isRequired
}
componentDidMount() {
myAPImethod('init', this.props.myID);
}
render(){return(){}}
}
I want to change the value of the constant myID depending on the value of 'activeLink'. This is not a problem when both components are mounted for the first time. However, when the value of 'activeLink' changes 'myID' doesn't change since the child component is already mounted.
I'm struggling to see what would be the 'React way' of doing this. Should 'myID' be set as another state and the function that sets the state for activeLink should include another one to set the state of myID? Or is this overcomplicating things and there's an easier way to re-render only that particular child component so that it considers the new value of myID.
I'm new to React so I was hoping I could get some clarification form SO community.
This is not a problem when both components are mounted for the first
time. However, when the value of 'activeLink' changes 'myID' doesn't
change since the child component is already mounted.
The issue is with regards to how you handle the trigger of the API call.
componentDidMount will only trigger when the component was initially mounted. This will not be triggered if a state is updated. You are going to want to use componentDidUpdate React Lifecycle as well in augmentation to componentDidMount. componentDidUpdate will trigger when your activeLink state changes because you pass it as props to theKeyComponent
componentDidUpdate() {
myAPImethod('init', this.props.myID);
}
Reference: https://reactjs.org/docs/react-component.html#componentdidupdate
import theKeyComponent from "../components/theKeyComponent.jsx";
export default class MainComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
activeLink: '#section1'
}
}
static getDerivedStateFromProps(props, state) {
if (props.activeLink !== state.activeLink ) {
// updating the state here if there are any changes in props.
return {
activeLink: props.activeLink,
};
}
// Return null if the props hasn't changed
return null;
}
use getDerivedStateFromProps hook to find out changes of props. you need to update the changes of props in state.
render(){
// now, here your state is updated.
const myId = this.state.activeLink === '#section1' ? '0001' : '0002';
return (
<theKeyComponent myID={myID} />
<otherComponent otherPropsHere={otherProps} />
)
}
Any suggestions are welcome.

React functional component state change does not trigger child component to re-read its props

I'm having this React functional component. We know that since v16.8, functional component has kind of "state". It uses useEffect to "setState" to state variable products.
Then I pass product as prop for the child component *ProductGrid".
I have the ProductOfCategory as parent component, it will fetch data from an URL and set the result to state variable "products". I want the child component ProductGrid to be able to read that data (change), so I made it looks like this.
Parent:
export default function ProductOfCategory() {
let { categoryId } = useParams();
const [products, setProducts] = useState([]);
useEffect(() => {
fetch('/products/cat/' + categoryId)
.then(response => response.json())
.then(data => {
setProducts(data);
console.log("Fetch me: ", data);
});
}, [categoryId]);
return (
<div>
<h3>ID: {categoryId}</h3>
<ProductGrid products={products} />
</div>
);
}
Child:
class ProductGrid extends Component {
constructor(props) {
super(props);
this.state = { products: this.props.products};
console.log("ProductGrid.props.products: ", this.props);
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("Grid did update, products: ", this.state.products);
}
}
Result:
When the data in parent changes, I can see (in the console) that the child component (ProductGrid) is re-rendered also, but its prop variable "products" is NOT. It's always an empty array. What is the reason of this? Does it mean that functional component's state is different from legacy state?
Is there someway to overcome this?
Many thanks.
You problem is on the line
console.log("Grid did update, products: ", this.state.products);
Here you are logging the state of the child component. When you construct the child component you copy the prop.products to state.products. Then when prop.product changes you don't update the state to reflect that change, you continue to print the initial value of products that was passed to the component.
The solution here would be to use this.props.products instead of this.state.products.

Why aren't parent Props Equal to Child state when Child state actually reference props from parent

I am passing props from Parent component into Child's state but They are out of sync.
What I tried:
State Updates May Be Asynchronous, I have taken care of that using a call back instead of returning an object.
Objects are passed by reference, but the prop i used is a string.
I am using React 16 and es6 syntax
class Parent extends React.Component {
state = {
isHidden: false
}
render() {
console.log('PROPS from PARENT', this.state.isHidden)
return <div>
<Child isOpen={this.state.isHidden} />
<button onClick={this.toggleModal}>Toggle Modal</button>
</div>
}
toggleModal = () => this.setState(state => ({isHidden: !state.isHidden}))
}
class Child extends React.Component {
state = {
isHidden: this.props.isOpen
}
render() {
console.log('STATE of CHILD:',this.state.isHidden)
return <p hidden={this.state.isHidden}>Hidden:{this.state.isHidden}</p>
}
}
ReactDOM.render(<Parent/>, document.getElementById('app'));
Here a codepen PEN - notice the redered element is supposed to be hidden based on the state(state depends on props from parent)
Use componentWillReceiveProps which call when changes in props occur.
class Child extends React.Component {
state = {
isHidden: this.props.isOpen
}
componentWillReceiveProps(props) {
if (props.isOpen != this.state.isHidden)
this.setState({
isHidden: props.isOpen
})
}
render() {
console.log('STATE of CHILD:', this.state.isHidden)
return <p hidden = {
this.state.isHidden
} > Hidden: {
this.state.isHidden
} < /p>
}
}
If you remove the state definition from the child, which is not needed, and use only the props that is passed from the parent, I believe that the behaviour of the child will make sense.
If you want to use state in the child, the constructor setting is not enough, you need the set the child state when props changes.
Console.log is asynchronous, so you cannot rely on it here.
Your Child's state does not change with prop change since component does not know anything about state change in your constructor. This is a common pitfall when you depend on your props to construct your local state. You can use componentWillReceiveProps as shown in #Nishant Dixit's answer. But, starting with React 16.3 we have getDerivedStateFromProps function (lifecylce method) for this.
static getDerivedStateFromProps( props, state) {
if( props.isOpen === state.isHidden) {
return null;
}
return {
isHidden: props.isOpen,
}
}
Here, we are comparing our prop and state, if there is a change we are returning desired state. No need to use this.setState.
Related API change blog post including async rendering:
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
Although the other answers will make the code work, there is actually a more elegant solution :)
Your child component does not need any state as the state is managed by the Parent (which manages the isHidden property and passes it to the child). So the child component should only care about props.
Try writing the component like this and I believe it should work:
class Child extends React.Component {
render() {
return <p hidden={this.props.isHidden}>Hidden:{this.props.isHidden}</p>
}
}
Dan Abramov who works on the React team tweeted about this problem - essentially saying that you should think hard about whether you can just use props before using state in a component
https://twitter.com/dan_abramov/status/979520339968516097?lang=en

React / Redux Components not re-rendering on state change

I think this question has been answer several time but I can't find my specific case.
https://codesandbox.io/s/jjy9l3003
So basically I have an App component that trigger an action that change a state call "isSmall" to true if the screen is resized and less than 500px (and false if it is higher)
class App extends React.Component {
...
resizeHandeler(e) {
const { window, dispatch } = this.props;
if (window.innerWidth < 500 && !this.state.isSmall) {
dispatch(isSmallAction(true));
this.setState({ isSmall: true });
} else if (window.innerWidth >= 500 && this.state.isSmall) {
dispatch(isSmallAction(false));
console.log(isSmallAction(false));
this.setState({ isSmall: false })
}
};
componentDidMount() {
const { window } = this.props;
window.addEventListener('resize', this.resizeHandeler.bind(this));
}
...
I have an other component called HeaderContainer who is a child of App and connected to the Store and the state "isSmall", I want this component to rerender when the "isSmall" change state... but it is not
class Header extends React.Component {
constructor(props) {
super(props);
this.isSmall = props.isSmall;
this.isHome = props.isHome;
}
...
render() {
return (
<div>
{
this.isSmall
?
(<div>Is small</div>)
:
(<div>is BIG</div>)
}
</div>
);
}
...
even if I can see through the console that redux is actually updating the store the Header component is not re-rendering.
Can someone point out what I am missing ?
Am I misunderstanding the "connect()" redux-react function ?
Looking at your code on the link you posted your component is connected to the redux store via connect
const mapStateToProps = (state, ownProps) => {
return {
isHome: ownProps.isHome,
isSmall: state.get('isSmall')
}
}
export const HeaderContainer = connect(mapStateToProps)(Header);
That means that the props you are accessing in your mapStateToProps function (isHome and isSmall) are taken from the redux store and passed as props into your components.
To have React re-render your component you have to use 'this.props' inside the render function (as render is called every time a prop change):
render() {
return (
<div>
{
this.props.isSmall
?
(<div>Is small</div>)
:
(<div>is BIG</div>)
}
</div>
);
}
You are doing it well in the constructor but the constructor is only called once before the component is mounted. You should have a look at react lifecycle methods: https://reactjs.org/docs/react-component.html#constructor
You could remove entirely the constructor in your Header.js file.
You should also avoid using public class properties (e.g. this.isSmall = props.isSmall; ) in react when possible and make use of the React local state when your component needs it: https://reactjs.org/docs/state-and-lifecycle.html#adding-local-state-to-a-class
A component is only mounted once and then only being updated by getting passed new props. You constructor is therefore only being called once before mount. That means that the instance properties you set there will never change during the lifetime of your mounted component. You have to directly Access this.props in your render() function to make updating work. You can remove the constructor as he doesn't do anything useful in this case.

How to force dom re render in react

I am trying to force a child component to re-render. I have tried this.forceUpdate();, but it does not work. I put console.log statements in my <PostList /> component, and none of them are ever called--not componentDidMount, nor componentWillMount, componentWillReceiveProps, none of them. It's as if the <PostList /> component is never initialized. I am sure it is though, because I know for a fact items.count retrieves my items. Here is my render method:
render() {
const items = this.state.posts;
const postList = items.count > 0 ? (<PostList comingFromSearch={true} xyz={items} />) : (<div></div>)
const navBar = <NavigationBar />
return (
<div><br/>{navBar}
<div className="container">
<h3>Search Results for {this.state.searchTerm}</h3>
<div className="row">
<div className="col-x-12">{postList}</div>
</div>
</div>
</div>
)
}
And here is my api call:
retrieveSearch(term) {
Helpers.searchWithTerm(term).then((terms) => {
const postsWithTermsInTitle = terms.titleResults
this.setState({posts: postsWithTermsInTitle})
this.forceUpdate();
}).catch((error) => {
console.log("error searching: " + error);
})
}
I should note, on my previous page, i had another ` component, and maybe react is using that one instead of this one? I want to force it to use this instance.
If this.forceUpdate(); does not make the whole DOM re-render, how can I do that?
thanks
your PostList and NavigationBar Components might not update because they only update when their props are changed (shallow compare).
PostList might not update when changing the inner content of the array, because the component will shallow compare the new state with the previous one. Shallow comparing an array will basically checked against its length property. which does not change in this case.
Quick Solution
Sometimes you need to update a List, without changing any of its props or the length of the list. To achieve this, just pass a prop to the component and keep incrementing it instead of calling force update.
retrieveSearch(term) {
Helpers.searchWithTerm(term).then((terms) => {
const postsWithTermsInTitle = terms.titleResults
this.setState((curState) => ({posts: postsWithTermsInTitle, refreshCycle: curState.refreshCycle+1}))
this.forceUpdate();
}).catch((error) => {
console.log("error searching: " + error);
})
}
render() {
...
<PostList
...
refreshCycle={this.state.refreshCycle}
/>
...
}
Right solution
The right solution is to provide an itemRenderer which you is a function that returns the an individual item from the list. This function is passed as a prop to the component.
This way you have control over how the items inside the list will appear, also changes inside the itemRenderer function will cause a component update.
itemRenderer(itemIndex) {
return <div>{this.props.item[itemIndex]}</div>;
}
render() {
...
<PostList
itemRenderer={this.itemRenderer.bind(this)}
itemsLength={items.length}
/>
...
}
The itemRenderer will be called inside the PostList in a loop (of length itemsLength). each loop will be passed the index of the current iteration, so you can know which item of the list to return from the function.
This way you can also make your list more scalable and more accommodating.
You can check an implementation of such solution on a list package like this one: https://www.npmjs.com/package/react-list
You can force a re-render of a component and all its children by changing the state of the component. In the constructor add a state object:
constructor(props) {
super(props)
this.state = {
someComponentState: 'someValue'
}
}
Now whenever you do:
this.setState(someComponentState, 'newValue')
It will re-render the component and all its children.
This of course assumes your component is a class based component, not a functional component. However, if your component is a functional component you can easily transform it to a class based component as follows:
class ComponentName {
constructor() {
// constructor code
}
render() {
// render code
}
}
export default ComponentName
Understand that componenet level state is not the same as redux state but is exposed only inside the component itself.

Resources