I'm currently learning React, and trying to get a sense of how components re-render. I have this parent component which renders three items. Each item just renders an <li>
function App() {
console.log("Parent Rerendered");
return (
<div>
<ul>
<Item1 />
<Item2 />
<Item3 />
</ul>
</div>
);
}
Item2 is a bit different because it also renders an "x" that will un-render the component when it's clicked:
function Item2() {
const [visible, setVisible] = useState(true);
const makeInvisible = () => {
setVisible(false);
};
console.log("Item 2 Rerendered");
return visible ? (
<div>
<li>
Second Item <span onClick={makeInvisible}>X</span>
</li>
</div>
) : null;
}
When I test this in my browser and click the "x", I can see from the console that Item2 gets re-rendered. However, none of the other components get re-rendered including the parent component. However the parent component does change, so how does this happen without re-rendering it.
If that's a bit confusing, here's an illustration of the initial state, my expectations, and reality. What am I misunderstanding about how React re-renders components?
A component rerenders when it sets state, or when its parent rerenders1. App has no state and no parent, so it will never rerender. It doesn't need to though. React saves the virtual DOM from the previous render, so it still knows that App is supposed to be a div surrounding a ul surrounding an Item1, Item2, and Item3. If the Item2 rerenders, and returns a null instead of a div, react will update the part of the real DOM that the Item2 is responsible for, by removing the div. The rest of the page remains intact
1) or if a context it consumes changes, or in a class component when you call forceUpdate. But for most cases, it's just state and parent that matter.
Instead of clicking onto <Item2 /> go to the Dev-tools -> Explorer -> select the Element and press delete. The view will also change, the gap will close, without react being involved at all.
React is responsible to update the DOM, the layout is done by the browser. So when <Item2 /> decides it wants to be rendered as null instead of a div>li (??? invalid markup ) and therefore removes the respective DOM-nodes, the browser will update the layout.
And the parent component has nothing to do with all that.
Related
I have a list of components that are added upon button click, so every button click adds one of the components. Each component has a button that can be clicked to delete the component from the list.
Each pair of text "New Item" and button is created as one component in the insertNewCollectorFields function. This function is called when the maroon button is clicked. Each component is added to a list of components "collectorsList" to be displayed. For each component, I use the current length of this collectorsList as the id and key.
This is my code:
import {useEffect, useState} from "react";
export default function Test(){
const [collectorsList, setCollectorsList] = useState([])
const insertNewCollectorFields = () => {
setCollectorsList(collectorsList.concat(
<div id={collectorsList.length} key={collectorsList.length}>
<p>New Item</p>
<button onClick={()=>deleteNewCollectorFields(collectorsList.length)}>Delete New Item</button>
</div>
))
}
const deleteNewCollectorFields = (id) => {
console.log(id)
setCollectorsList(collectorsList.filter((item) => item.id!==id))
}
return <div>
{collectorsList}
<button type={"button"} className={`btn col-auto maroonButton`} onClick={insertNewCollectorFields}>Add Another Collector</button>
</div>
}
I am able to add the components, and I can delete components that were added last without an issue. However, if I delete one of the components that was added first, any component that was added after it is deleted too, and I don't understand why.
This is an example of the issue:
Here I add three components:
Now I delete that third component, and no issue:
If I however delete the first component, all three components disappear, and this is the issue:
I created the list of components as a state. This is my first big project using React. What I know is that on creating a state, the value is updated and should be seen by all the components with the updated value? But apparently on debugging, I found that the "collectorsList" seen by every component in the deleteNewCollectorFields function is limited to the list that was present before creating that component. For example, if collectorsList contained 6 components, component number 3 only sees that there is component 1 and 2 in the collectorsList, and does not see that the collectorsList actually contains 6 components. So on deleting component number 3, the filter process in the delete function results in only component 1 and 2, and everything else disappears from the screen.
I don't understand why this happens and why the components are not able to see the updated list with all the components that were added to it. I also searched for other examples were such a delete function was created and it seems that my method should work, and I couldn't find any case were this problem was faced.
What am I doing wrong? Is there something I understood incorrectly about the use of states?
Your problem is that you are using id={collectorsList.length}, which is wrong, because the .length is changing every time you delete an item and this give wrong id to the deleteNewCollectorFields, just make unique id
Let's say I've got a Tooltip component that shows & hides depending on whether there is text data for it or not:
{this.state.tooltipText && (
<Tooltip
text={this.state.tooltipText} />
)}
If I wanted to transition this component in and out, I might instead include a boolean on prop and let the component manage transitioning out itself.
<Tooltip
on={this.state.tooltipText !== null}
text={this.state.tooltipText} />
With this setup, though, I lose the text prop right when the transition out should begin. There is very clearly never a state where on is false and I still have access to the text prop.
Is there a good way to handle this scenario?
Should the component keep an internal record of the previous state of tooltipText?
Or is it best to use two properties to track the state of the tooltip (tooltipOn and tooltipText) and never erase this.state.tooltipText?
Should the component keep an internal record of the previous state of tooltipText?
Yes. Otherwise the parent would have to decide when the transition starts / ends, which is not his authority.
Actually componentWillReceiveProps is best for this as you can access the current & next props:
componentWillReceiveProps(nextProps) {
if(this.props.text && !nextProps.text) {
this.fadeOut();
} else if(!this.props.text && nextProps.text) {
this.setState({ text: nextProps.text });
this.fadeIn();
}
}
Then the parent just has:
<Tooltip text={this.state.tooltipText} />
I have created a React application. In this application, I've created following components:
App Component (root) : where data is loaded into state
CardList Component: List of Cards, data is passed to it using props
Card Component: use forEach to pass data to Card and it has button
CustomButton Component: acts like a button with style
What I want is when the user clicks button on any of the Card, a number should get increased everytime. But I am not able to access Data here.
Can anyone help?
Here is the Card Component for your reference:
class Card extends Component {
render() {
return (
<div className="tc bg-light-green dib br3 pa3 ma2 grow bw2 shadow-5">
<h2>Votes: {this.props.votes}</h2>
<div>
<img src={this.props.pic} alt='Superstar'/>
<div>
<h2>{this.props.name}</h2>
<p>{this.props.email}</p>
</div>
</div>
<ActionButton btnText="Vote Now!" clickhandler={this.onVoteButtonClick}/>
</div>
);
}
onVoteButtonClick = (event) => {
console.log('It was clicked : '+this.props.id);
}
}
Two options which you should research more to see what suits your need best:
Redux (or something similar)
The new Context API available in React 16.
The gist of either solution is that you're managing application state independently of the dependent component tree(s). The Context API is arguably easier to implement whereas you'll currently find many more examples explaining the Redux approach as it's still the most common solution right now.
I'm using a component library and I cannot figure this out. I'm new to react and javascript and need help.
There is a component in the library that renders a header panel with tabs.
Component
|_Component.Tab
The Tab component has 2 states that change its appearance when it is clicked. But the click handler and state changes have to be defined by me outside of Tab component. How do I do this?
Seems to me by your question that you need to use props to pass the function to change state from the Component to the Tabs. Something like this:
Component
changeState(value) {
this.setState({ appearance: value });
}
render() {
return (
<div>
<Tab
appearance={this.state.appearance}
onChangeState={this.changeState}
/>
</div>
);
}
Tab
render() {
console.log('Appearance: ', this.props.appearance); // Use it for whatever you need it
return (
<div>
<Button
onClick={(value) => this.props.onChangeState(value)} />
</div>
);
}
Not sure why do you want to handle a function and it’s state outside of the component when it has to be within the Tab component. But here is the solution what you actually have to do in your Tab component to handle your state
Bind your handler function inside a constructor like below
Eg:
this.handlerFunction = this.handlerFunction.bind(this)
Call this.handlerFunction reference in your tab onClick
Eg:
onClick={this.handlerFunction}
Set state in handlerFunction
Eg:
handlerFunction(event){
this.setState({
tabClicked: event.target.value
})
}
Else I guess The outside component should be a child component that you are talking about. If so pass your tab click state as props to your outside component (i.e., child component) and receive that state as props in your child component and do setState there.
If you are still unclear then
Post your component code here. With Just theory it’s little difficult to understand the actual problem that you are talking about.
How can a React component control the scroll position of a sibling component?
Parent is the parent component, it has as children List (which contains a scrollable div) and Actions with a button that should control scrolling of the List scrollable div.
Some options:
Maintain a reference to the DOM element of the scrollable div and scroll position in the Redux Store. Trigger scrolling in the reducer on state changes.
Have Parent manage scroll. Somehow Parent needs to have a DOM reference to the scrollable div in List, not sure how List can pass up a ref.
Use something like react virtualized (VirtualScroll) to show virtual content in List. Don't actually scroll, just update the content to what would be seen at the new scroll position. This means we can't animate the scroll?
Option #2 seems most reasonable (animated scroll is important for this context), but I'm not sufficiently familiar with best practices in React/Redux to make good architecture decisions.
I would go for option 2. But the parent should not have to keep a reference to the List items other than having them as child components. You can save the the scroll position in the redux store, have the parent control it, and then pass it down as a prop to your List items.
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import List from './List';
class Parent extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
scrollPos: PropTypes.number.isRequired
};
updateScrollPos() {
const value = getValueFromSomewhere();
this.dispatch({ type: UPDATE_SCROLLPOS, value })
}
render() {
return <div>
<button onClick={::this.updateScrollPos}>Update scrollPos</button>
<List scrollPos={this.props.scrollPos} />
<List scrollPos={this.props.scrollPos} />
<List scrollPos={this.props.scrollPos} />
</div>
}
}
const select = (state) => ({
scrollPos: state.scrollPos
})
export default connect(select)(Parent);