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.
Related
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 });
}
};
Suppose I have 3 components in a react App. App component, Items component and Item component.
My state is in the App component. Now I want to delete an item from the state via id. The onClick function is in Item component. The id from Item component goes throw Items component to App component.
I have tried it using .bind method and manage to 2 pass data Item to Items.
You don't need redux for this. Context is nice but not necessary either. I prefer context tbh. But you can do it without them the following way:
// App.js
class App extends Component {
state = { items: ...etc };
handleItemClick = (id) => whatever
render() {
return <Items items={this.state.items} onItemClick={this.handleItemClick} />
}
}
// Items.js
class Items extends Component {
render() {
return this.props.items(item => <Item key={item.id} id={item.id} onClick={this.props.onItemClick} />);
}
}
// Item.js
class Item extends Component {
handleClick = () => {
this.props.onClick(this.props.id);
}
render() {
return <whatever onClick={this.handleClick} />
}
}
You can do the following thing - pass the onClick function as a prop from the 'App' component to the 'Item' component this way you have access to the state in the App component, and you pass an id parameter to it from the component that uses the function.
Generally speaking, I think that the Items component should hold the state and also the functionality not the App component nor the Item component which is just a UI component (stateless component). this way whenever you remove an item, the Items component will re-render itself because of the change in the state.
I am building a gallery app where I need to create multiple HTTP requests to pull gallery entries(images & videos).
As gallery will be auto scrolling entries, I am trying to prevent re-rendering component when I make subsequent HTTP requests and update the state.
Thanks
Here's an example of only re-rendering when a particular condition is fulfilled (e.g. finished fetching).
For example, here we only re-render if the value reaches 3.
import React, { Component } from 'react';
import { render } from 'react-dom';
class App extends React.Component {
state = {
value: 0,
}
add = () => {
this.setState({ value: this.state.value + 1});
}
shouldComponentUpdate(nextProps, nextState) {
if (nextState.value !== 3) {
return false;
}
return true;
}
render() {
return (
<React.Fragment>
<p>Value is: {this.state.value}</p>
<button onClick={this.add}>add</button>
</React.Fragment>
)
}
}
render(<App />, document.getElementById('root'));
Live example here.
All data types
useState returns a pair - an array with two elements. The first element is the current value and the second is a function that allows us to update it. If we update the current value, then no rendering is called. If we use a function, then the rendering is called.
const stateVariable = React.useState("value");
stateVariable[0]="newValue"; //update without rendering
stateVariable[1]("newValue");//update with rendering
Object
If a state variable is declared as an object, then we can change its first element. In this case, rendering is not called.
const [myVariable, setMyVariable] = React.useState({ key1: "value" });
myVariable.key1 = "newValue"; //update without rendering
setMyVariable({ key1:"newValue"}); //update with rendering
Array
If a state variable is declared as an array, then we can change its first element. In this case, rendering is not called.
const [myVariable, setMyVariable] = React.useState(["value"]);
myVariable[0] = "newValue"; //update without rendering
setMyVariable(["newValue"]); //update with rendering
None of the answers work for TypeScript, so I'll add this. One method is to instead use the useRef hook and edit the value directly by accessing the 'current' property. See here:
const [myState, setMyState] = useState<string>("");
becomes
let myState = useRef<string>("");
and you can access it via:
myState.current = "foobar";
So far I'm not seeing any drawbacks. However, if this is to prevent a child component from updating, you should consider using the useMemo hook instead for readability. The useMemo hook is essentially a component that's given an explicit dependency array.
It's as easy as using this.state.stateName = value. This will change the state without re-rendering, unlike using this.setState({stateName:value}), which will re-render. For example;
class Button extends React.Component {
constructor( props ){
super(props);
this.state = {
message:"Hello World!"
};
this.method = this.method.bind(this);
}
method(e){
e.preventDefault();
this.state.message = "This message would be stored but not rendered";
}
render() {
return (
<div >
{this.state.message}
<form onSubmit={this.method}>
<button type="submit">change state</button>
</form>
</div>
)
}
}
ReactDOM.render(<Button />, document.getElementById('myDiv'));
If you just need a container to store the values, try useRef. Changing the value of ref.current doesn't lead to re-rendering.
const [ loading,setLoading] = useState(false)
loading=true //does not rerender
setLoading(true) //will rerender
In functional component refer above code, for class use componentShouldUpdate lifecycle
I have some data loaded in the store after initial Axios call.
Then I render two components match (parent component) and player (child component).
This is the way to show the two components in a related way (this is a simplified example from my original code, in this example I could solve my problem in another way, but in my complex real code it is essential to do an operations in children component first):
match.js
class Match extends Component {
constructor(props) {
super(props);
}
render() {
return (
Object.values(this.props.matchs).map(( match_id ) => {
let match = this.props.matchs[match_id];
return (
<div key={match_id}>
<p>{match.tournament}</p>
<p>{match.color}</p> {/* this color depends of children condition*/ }
<div className="players">
{match.array_players.map ( ( player_id ) => {
let player = this.props.players[player_id];
return (
<Player key={odd_id} ownPlayer={player} />
)
})
</div>
</div>
)
});
)
}
}
const mapStateToProps = state => {
return {
matchs: state.matchs.matchs,
players: state.players.players
};
}
const mapDispatchToProps = dispatch => {
return {
// actions
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Matchs);
player.js
class Player extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<p>{this.props.ownPlayer.name}</p>
<p>{this.props.player_color}</p>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
// I need to make some previous operations before render
let player_color;
if (ownProps.ownPlayer.name == "paul")
player_color = 'yellow';
else
player_color = 'blue';
// Then I Need to update parent component with children color condition
// if (player_color == 'yellow')
// match_color = 'yellow'
//
// Call some action here to update parent component???
// things like these do not work:
// let id_p = ownProps.player.id_player;
// state.players.players[id_p].color = 'blue'; This does not work
return {
player_color
};
};
const mapDispatchToProps = dispatch => {
return {
//
}
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Player);
Then I need to update a prop in a parent component after some conditions in children component.
I've read this article:
https://redux.js.org/docs/recipes/ComputingDerivedData.html
But I don't know how to send data to store and refresh parent component before render.
I thought about calling like an action in componentWillMount or componentWillUpdate to send data to store, but I don't know if it's correct way.
There is nothing wrong with calling an action inside the lifecycle, it is not recommended to do it inside the render method because it my trigger infinite actions, but in your situation if you indeed have to do this calculation inside the child component I believe you should dispatch this action inside componentWillReceiveProps or ComponentDidMount, in some situations you actually have to do it in both places.
go for it!
The docs are pretty clear:
You can either do one-time ops in constructor / ComponentWillMount / ComponentDidMount or repetitive ops in recurring life-cycle methods like ComponentWillReceiveProps.
If you need a way for the child component to update the store, than you have to dispatch an action that will go and do so, and put it in ComponentWillMount or ComponentWillReceiveProps depending on the need, sometimes you need to put it in both.
But, on a side note, like Bruno Braga said, it does seem like the wrong place to put logic in.
I would suggest to put this logic in the reducer, as Component really shouldn't handle store logic, just notify (dispatch) state changes.
Also, I don't think that you need to connect the Player component to the redux store, since it seems like each player has it's own independent instance.
What I would suggest is passing the Player Component a function from the Match Component, something like
<Player ... onGoalScored={()=> this.handleGoalScored()} />
and on the Match component do:
handleGoalScore() {
this.props.dispatch(updateGoalsAction())
}
and have the logic in the reducer, the let's say will figure out what the color of Match should be, and, on the next state update to Match, because of the binding to store.matchs.color will be rendered as Red
I have CompetitionSection which repeats all the competitions from database. When user clicks on one, it redirects him to a Competition Page, loads for a second and renders the page with all the details in it. So far, so good.
But when users goes back to the Competition Section and then click on the second competition, it instantly loads up the previous competition, 0 loading time.
From my point of view, what is failing is that the props of the component are not updating when I render the component (from the second time). Is not a router problem, which was my first instinct because I'm seeing the route.params changing acordingly, but the actions I dispatch to change the props are not dispatching. Here's a bit of code of said component.
class CompetitionPage extends React.Component {
componentWillMount() {
let id = getIdByName(this.props.params.shortname)
this.props.dispatch(getCompAction(id));
this.props.dispatch(getCompMatches(id));
this.props.dispatch(getCompParticipants(id));
this.props.dispatch(getCompBracket(id));
}
render() {
let { comp, compMatches, compBracket, compParticipants } = this.props
...
I tried every lifecycle method I know. component Will/Did Mount, component Will/Did update and I even set shouldUpdate to true and didn't do the trick. As I understand, the problem will be solved with a lifecycle method to dispatch the actions everytime an user enters Competition Page and not just for the first time. I'm running out of options here, so any help will be appreciated.
NOTE: I'm a newbie at React/Redux so I KNOW there are a couple of things there are anti-pattern/poorly done.
UPDATE: Added CompetitionsSection
class CompetitionsSection extends React.Component {
render() {
const {competitions} = this.props;
return (
...
{ Object.keys(competitions).map(function(comp, i) {
return (
<div key={i} className={competitions[comp].status ===
undefined? 'hide-it':'col-xs-12 col-md-6'}>
...
<Link to={"/competitions/"+competitions[comp].shortName}>
<RaisedButton label="Ver Torneo" primary={true} />
</Link>
...
It helps to better understand the lifecycle hooks. Mounting a component is when it is placed on the DOM. That can only happen once until it is removed from the DOM. An UPDATE occurs when new props are passed or setState is called. There are a few methods to troubleshoot when updates are not happening when you think they should:
Ensure that you are changing state in componentDidMount or componentDidUpdate. You cannot trigger an update in componentWillMount.
Make sure that the new props or state are completely new objects. If you are passing an object down in props and you are just mutating the object, it will not trigger an update. For instance, this would not trigger a update:
class CompetitionPage extends React.Component {
constructor(props) {
super(props)
this.state = {
competitions: [ compA, compB ]
}
}
triggerUpdate() {
this.setState({
competitions: competitions.push(compC)
})
}
componentDidMount() {
triggerUpdate()
}
render() {
return(
<div>
Hello
</div>
)
}
This is due to the fact that a new competition is being appended to the array in state. The correct way is to completly create a new state object and change what needs to be changed:
const newCompetitions = this.state.competitions.concat(compC)
this.setState(Object.assign({}, this.state, { competitions: newCompetitions }))
Use ComponentWillRecieveProps on an update to compare previous and current prop values. You can setState here if clean up needs to be done:
Read more about this method in the React documentation:
https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops