const RenderItem = (props) => {
return(
<ul id="todo">
{props.items.map((item,i) =>
<li className='list-group-item' data-id={item.id} key={i}>{item.name}
<button className="btn btn-sm btn-primary" onClick={() => props.remove(item.id)}>X</button>
</li>
)}
</ul>
)
};
const TodoItems = React.createClass({
getInitialState() {
return {
items: [
{id:1,name:"Gym"},
{id:2,name:"Jump"},
{id:3,name:"Racing"}
]
}
},
remove(id){
this.setState({
items: this.state.items.filter((el) => id !== el.id)
})
},
render(){
return(
<RenderItem items={this.state.items} remove={this.remove}/>
)
}
})
ReactDOM.render(
<TodoItems />,
document.getElementById('container')
);
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.24/browser.js"></script>
<script src="https://npmcdn.com/react#latest/dist/react-with-addons.js"></script>
<script src="https://npmcdn.com/react-dom#latest/dist/react-dom.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
I'm confused how stuff work here in react.js, I need presduo code how to passing work. I was from jquery background, it's so straight forward, just get the right dom and do $(this).remove(), while in react I'm confused.
<button className="btn btn-sm btn-primary" onClick={() => props.remove(item.id)}>X</button>
When you click what happened? arrow function for? and where does the remove come from?
React follows unidirectional data binding, which means, the data will flow in a single direction. So, the data is stored in a state. Whenever the state changes the component will re-render. A state can be changed at any point in time. But, mostly it will be changed on any action. In your case the action in onClick(). This will call the function will the id of the element which triggered the onClick(). Now, in that function, you will iterate through the state, identify, remove and set the new state using setState() which will re-render the component with the new data. This render will not have the clicked element as it was removed from the state.
The remove() function is passed on to the child component from the parent component which can be accessed using props. This will be a reference to the function declared in the parent component. During the click, using this reference, the function in the parent component will be called!
In react, components are rendered based on their props and state. props are passed from the parent component, and a component may or may not have internal state.
Using jQuery, you would try to remove it by removing the DOM node directly. However, in react, interacting with the DOM directly is an anti-pattern. Since the current state and props determines what is rendered, you want to change one of those that will cause a re-rendering and display something new.
In your case, it looks like you're trying to modify props directly. props are read only and should not be modified. Since props are passed down from the parent component, you would have to change what's being passed down from the parent. There are a few ways you can do that, such as passing a callback function defined in the parent component as a prop that can make such a change. But for this example, it might be easier/more applicable to use state.
Remember, you want to change the component's state to reflect what you want to see (what you want to render). So let's say you have an array called items in your component's state. And let's say you are
class ListDemo extends React.component {
constructor() {
super()
// initialize state
this.state = {
items: ['thing1', 'thing2', 'thing3']
}
}
onClick(index) {
// newItems is a new array with the element at the given index removed
// this logic doesn't handle edge cases, but that's besides the point
const newItems = [
...this.state.items.slice(0, index),
...this.state.items.slice(index + 1)
]
// here we set this component's state's items to
// the new state with what we want removed.
// it will then re-render the component to reflect the new state
this.setState({
items: newItems
})
}
render() {
return (
<div>
{
// here we render buttons for each item in the component's state's items
// we use `onClick` to make it so that clicking the button
// will remove it from the list
this.state.items.map((item, index) => {
return (
<button
onClick={() => this.onClick(index)}
key={`${item}${index}`}
>
{item}
</button>
)
})
}
</div>
)
}
}
Let's go over what is happening in this example. First, when the component is created, it's initial state is set with an items array that contains 3 elements. When it is first rendered, it will create a button for each element in the items array. Notice that what is rendered is purely determined by the state.
Each button also has a callback that is called when it's clicked (set by onClick). This callback is what will remove that specific element from the items array and then update the component's state to have a new items array without the element that was just removed. This in turn causes a re-rendering, which uses the new items array to display the buttons. This time around, the button we removed by click it is no longer there since it's no longer in this.state.items.
That is one of the key concepts of react - components are rendered based on their props and state, and changing either will update what is displayed.
As for why to use an arrow function, it's because it will automatically bind the this value to the ListDemo component. Similarly, you can use something like onClick={this.onClick.bind(this)}. Doing this is necessary since the value of this when clicking the button isn't guaranteed to what you expect. Reading this might make it more clear.
Related
I have an array with thousands of strings and is passed to a component:
Main component:
const array = ['name1', 'name2', 'name3'];
const [names, setNames] = useState(array);
const onClick = (index) => {
setNames(names.map((name, i) => {
if (i === index) {
return 'name changed';
}
};
};
return (
<ul>
{array.map((name, index) => (
<li key={index}>
<ShowName name={name} key={index} onClick={() => onClick(index)} />
</li>
)}
</ul>
);
ShowName component:
let a = 0;
export default function ShowName({ name, onClick }) {
a += 1;
console.log(a);
return (
<button type="button" onClick={onClick}>{name}</button>
);
}
There's also a button which changes a name randomly. But whenever the button is pressed, all the ShowName components are rerendering. I've been trying to use useCallback and useMemo, but the components are still rerendering x times (x is the length of the array).
const ShowNameHoc = ({ name }) => {
return <ShowName name={name} />
};
return (
<div>
{array.map((name, index) => <ShowNameHoc name={name} key={index} />)}
</div>
);
What should I do if I only want to rerender the component with a change?
You have a misunderstanding in the concepts here. The default is for React to call render on all children, regardless of whether the props changed or not.
After that happened, React will compare that new Virtual DOM to the current Virtual DOM and then only update those parts of the real DOM that changed.
That's why the code in a render method should be quick to execute.
This behavior can be changed by using features like useMemo, PureComponents or shouldComponentUpdate.
References:
https://reactjs.org/docs/rendering-elements.html (Bottom):
Even though we create an element describing the whole UI tree on every tick, only the text node whose contents have changed gets updated by React DOM.
https://reactjs.org/docs/optimizing-performance.html#avoid-reconciliation
Even though React only updates the changed DOM nodes, re-rendering still takes some time. In many cases it’s not a problem, but if the slowdown is noticeable, you can speed all of this up by overriding the lifecycle function shouldComponentUpdate, which is triggered before the re-rendering process starts.
...
In most cases, instead of writing shouldComponentUpdate() by hand, you can inherit from React.PureComponent. It is equivalent to implementing shouldComponentUpdate() with a shallow comparison of current and previous props and state.
Also, read this for some more background info: https://dev.to/teo_garcia/understanding-rendering-in-react-i5i
Some more detail:
Rendering in the broader sense in React means this (simplified):
Update existing component instances with the new props where feasible (this is where the key for lists is important) or create a new instance.
Calling render / the function representing the component if shouldComponentUpdate returns true
Syncing the changes to the real DOM
This gives you these optimization possibilities:
Ensure you are reusing instances instead of creating new ones, e.g. by using a proper key when rendering lists. Why? New instances always result in the old DOM node to be removed from the real DOM and a new one to be added. Even when unchanged. Reusing an instance will only update the real DOM if necessary. Please note: This has no effect on whether or not render is being called on your component.
Make sure your render method doesn't do heavy lifting or if it does, memoize those results
Use PureComponents or shouldComponentUpdate to prevent the call to render altogether in scenarios where props didn't change
Answering your specific question:
To actually prevent your ShowName component from being rendered - into the Virtual DOM - if their props changed, you need to perform the following changes:
Use React.memo on your ShowName component:
function ShowName({ name, onClick }) {
return (
<button type="button" onClick={onClick}>{name}</button>
);
}
export default memo(ShowName);
Make sure the props are actually unchanged by not passing a new callback to onClick on each render of the parent. onClick={() => onClick(index)} creates a new anonymous function every time the parent is being rendered.
It's a bit tricky to prevent that, because you want to pass the index to this onClick function. A simple solution is to create another component that is passed the onClick with the parameter and the index and then internally uses useCallback to construct a stable callback. This only makes sense though, when rendering your ShowName is an expensive operation.
That is happening because you are not using the key prop on <ShowName/> component.
https://reactjs.org/docs/lists-and-keys.html
it could look something like this
return (
<div>
{array.map(name => <ShowName key={name} name={name} />)}
</div>
);
In React, every time a component is rendered/re-rendered, it regenerates all of it's child nodes/components using createElement. How does React know when to persist the components state between re-renders?
As an example, consider the following code:
class Timer extends Component {
constructor(props) {
super(props);
this.state = { seconds: 0 };
}
tick() {
this.setState(state => ({ seconds: state.seconds + 1 }));
}
componentDidMount() {
this.interval = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return createElement('div', null,
'Seconds: ',
this.state.seconds
);
}
}
class Button extends Component {
constructor(props) {
super(props);
this.state = { clicks: 0 };
}
click() {
this.setState(state => ({ clicks: state.clicks + 1 }));
}
render() {
return createElement('button', { onClick: () => this.click() },
createElement(Timer, null),
'Clicks: ',
this.state.clicks
);
}
}
render(createElement(Button, null), document.getElementById('root'));
You can try this code with the Preact REPL here.
Notice that when the button is pressed and the clicks value is updated, the state of the Timer component persists and is not replaced. How does React know to re-use the component instance?
While this may seem like a simple question at first, it becomes more complex when you consider stuff like changing the props passed to a child component or lists of child components. How does React handle changing the props of a child component? Does the child component's state persist even though it's props have changed? (In Vue, the state of a component does persist when it's props change) How about lists? What happens when an entry in the middle of a list of child components is removed? A change to a list like that would obviously generate very different VDOM nodes, yet the state of the components still persists.
createElement vs render vs mount
When a React Component such as your Button is rendered, a number of children are created with createElement. createElement(Timer, props, children) does not create an instance of the Timer component, or even render it, it only creates a "React element" which represents the fact that the component should be rendered.
When your Button is rendered, react will reconcile the result with the previous result, to decide what needs to be done with each child element:
If the element is not matched to one in the previous result, then a component instance is created then mounted then rendered (recursively applying this same process). Note that when Button is rendered for the first time, all of the children will be new (because there is no previous result to match against).
If the element is matched to one in the previous result, then the component instance is reused: its props are updated, then the component is re-rendered (again, recursively applying this same process). If the props did not change, React might even choose not to re-render as an efficiency.
Any elements in the previous result that was not matched to an element in the new result will be unmounted and destroyed.
React's diffing algorithm
An element "matches" another one if React compares them and they have the same type.
The default way for React to compare children, is to simply iterate over both lists of children at the same time, comparing the first elements with each other, then the second, etc.
If the children have keys, then each child in the new list is compared to the child in the old list that has the same key.
See the React Reconciliation Docs for a more detailed explanation.
Examples
Your Button always returns exactly one element: a button. So, when your Button re-renders, the button matches, and its DOM element is re-used, then the children of the button are compared.
The first child is always a Timer, so the type matches and the component instance is reused. The Timer props did not change, so React might re-render it (calling render on the instance with the same state), or it might not re-render it, leaving that part of the tree untouched. Both of these cases would result in the same result in your case - because you have no side-effects in render - and React deliberately leaves the decision of when to re-render as an implementation detail.
The second child is always the string "Clicks: " so react leaves that DOM element alone too.
If this.state.click has changed since the last render, then the third child will be a different string, maybe changing from "0" to "1", so the text node will be replaced in the DOM.
If Buttons render were to return a root element of a different type like so:
render() {
return createElement(this.state.clicks % 2 ? 'button' : 'a', { onClick: () => this.click() },
createElement(Timer, null),
'Clicks: ',
this.state.clicks
);
}
then in the first step, the a would be compared to the button and because they are different types, the old element and all of its children would be removed from the DOM, unmounted, and destroyed. Then the new element would be created with no previous render result, and so a new Timer instance would be created with fresh state, and the timer would be back at 0.
Timer matches?
previous tree
new tree
no match
<div><Timer /></div>
<span><Timer /></span>
match
<div>a <Timer /> a</div>
<div>b <Timer /> b</div>
no match
<div><Timer /></div>
<div>first <Timer /></div>
match
<div>{false}<Timer /></div>
<div>first <Timer /></div>
match
<div><Timer key="t" /></div>
<div>first <Timer key="t" /></div>
Never used Vue, but this is my take.
Does the child component's state persist even though it's props have changed? (In Vue, the state of a component does persist when it's props change)
This depends on how you handle the props in your child.
This child will re-render every time you change (mutate) your props.
const Child = (props) => {
return <div>{ props.username }</div>;
};
This child will not re-render when props change since the return value is dependent on the local state, and not the props.
const Child = (props) => {
const [state, setState] = useState(props.username);
return <div>{ state }</div>;
};
This child will re-render when props change, as local state updates with the new props.
const Child = (props) => {
const [state, setState] = useState(props.username);
useEffect(() => {
// changing props changes the component's state, causing a re-render
setState(props.username);
}, [props]);
return <div>{ state }</div>;
};
As seen in the examples above, the programmer is the one in control of whether React triggers a re-render of a child.
How about lists? What happens when an entry in the middle of a list of child components is removed? A change to a list like that would obviously generate very different VDOM nodes, yet the state of the components still persists.
When a list of children is involved (eg. when .map is used) React will require the key parameter, so that React will be aware what was add/removed/changed between parent component re-renders.
React requires that the same key be used for the same components to prevent unnecessary re-renders (don't use Math.random() as your key).
I have a class component which renders a child component which cannot be modified (comes from a library).
This component has an internal state in which there is a data I want to retrieve and do something with it as soon at it is changed.
I tried using a callback on ref like this:
onRefChange = el => {
console.log('el state', el.state);
// prints the initial state of the child component, with the value still to be changed (it changes a little after initialization)
}
<Carousel
ref={(el) => {
this.onRefChange(el);
return this.Carousel = el;
}}
//other stuff
/>
Of course I can access the state by reading this.Carousel.state later but I'm trying to do an operation immediately after this data changes, with a callback.
Also apart from changing the behaviour of the child component, the parent component should remain a class componente, so no hooks like useEffect() can be used.
Is it a dead-end or is there a way to achieve this?
I am quite new in vue and working on a task which is based on vue.js. I am showing my data in a component using a prop. Now I want to add a method to increment the quantity of a product.
here is my code:
<div v-for="(products, index) in products">
<mdc-layout-cell span="2" align="middle">
{{ products.product_barcode }}
</mdc-layout-cell>
<mdc-layout-cell span="2" align="middle">
{{ products.product_quantity}}
</mdc-layout-cell>
<i class="mdc-icon-toggle material-icons float-left"
aria-pressed="false"
v-on:click="incrementItem(index)">
add
</div>
here is my JS:
export default {
props: [
'products',
],
methods: {
incrementItem(index) {
let item = this.products[index];
this.products[index].product_quantity =
this.products[index].product_quantity + 1;
console.log(this.products[index].product_quantity);
},
}
I can see the incremented value in the console, but the value is not increasing in the respective row. How could I increment the value of product_quantity? Any help would be highly appreciable
You can assign props value to a variable. Then use it child component.
export default {
props: {
products: Array,
},
data(){
return {
newProducts: this.products,
}
}
}
You should change props structure in child component. use newProducts in child component and if you update parent props data you should use emit for child to parent communication.
For further parent child communication you can follow this tutorial:
https://www.bdtunnel.com/2018/02/vue-js-component-communication-complete.html
First, in terms of vue flow, remember never mutating directly props. You should mutate the data in the parent component instead. To do this, the recommendation is to create a copy of props in children data so when click at the button, the children data change -> the parents data change, which makes the children props also change. There are many ways to do this. I dont have you parents component code so i make a generic code below, you can follow:
using sync
in parents components
<parent :products.sync=products />
in children components methods:
data() {
return {
productListInChildren: this.products; // pass props to children inner data
}
},
methods: {
incrementItem(index) {
//do modification in productListInChildren
let item = this.productListInChildren[index];
this.productListInChildren[index].product_quantity =
this.productListInChildren[index].product_quantity + 1;
// update it back to parents
this.$emit('update:products', this.productListInChildren)
}
}
<div v-for="(products, index) in productListInChildren"> // use productListInChildren instead
<mdc-layout-cell span="2" align="middle">
{{ products.product_barcode }}
</mdc-layout-cell>
<mdc-layout-cell span="2" align="middle">
{{ products.product_quantity}}
</mdc-layout-cell>
<i class="mdc-icon-toggle material-icons float-left"
aria-pressed="false"
v-on:click="incrementItem(index)">
add </i>
</div>
Second: in terms of code design, it is recommended in your case, the children should only handle the logic of display (dumb component). Logic of changing data should be moved to parents (like a controller). If that is the case. you can create a method in parents component and add the increment logic:
parents components
<parent :products=products #increment="incrementInParent"/>
methods: {
incrementInParent() {// move the logic here}
}
children components
methods: {
incrementItem(index) {
// call the methods in parents
this.$emit("increment");
}
}
First of all, you should never mutate props in the child component. You should use event communication pattern for child and parent communication.
While changing a particular value inside an array in particular index in an array or updating a property in an object the view does not react to these changes due to Object and Array Caveats in Vue.
You can read it here
https://v2.vuejs.org/v2/guide/reactivity.html
You can use $set to make the template react to changes in Object and Array.
Here is my sandbox URL which answers your question
Here is what I'm trying to achieve. I have two React components Product and ProductInfoPanel, shown inside a ProductList component. Product displays selected information about a product, such as product name, and price. When a product is clicked, more details will be shown in the ProductInfoPanel. So I need to pass wah twas clicked to the ProductInfoPanel.
Here is how I currently wire them up together. Each Product gets a click handler passed in, which passes back the product object when invoked, then that is passed into the ProductInfoPanel's props. The ProductList uses state to keep track of what was clicked, so when it changes, it triggers the re-rendering of the info panel.
class ProductList extends React.Component {
render() {
return (
<div>
<div className='content'>
<ul>
{ this.props.products.map((product, index) => {
return (
<li key={index}>
<Product product={product}
clickHandler={this.onProductClicked.bind(this)}/>
</li>
);
})}
</ul>
</div>
<div className='side-panel'>
<ProductInfoPanel product={this.state.selectedProduct} />
</div>
</div>
);
}
onProductClicked(clickedProduct) {
// Use the product object that was clicked, and updates the state.
// This updates the info panel content.
this.setState({ selectedProduct: clickedProduct });
}
}
Here is roughly how the two components are constructed.
class Product extends React.Component {
render() {
// Even though it needs only name and price, it gets the whole product
// object passed in so that it can pass it to the info panel in the
// click handler.
return (
<div onClick={this.onClicked.bind(this)}>
<span>{this.props.product.name}</span>
<span>{this.props.product.price}</span>
</div>
);
}
onClicked(e) {
this.props.clickHandler(this.props.product);
}
}
class ProductInfoPanel extends React.Component {
render() {
// Info panel displays more information about a product.
return (
<ul>
<li>{this.props.product.name}</li>
<li>{this.props.product.price}</li>
<li>{this.props.product.description}</li>
<li>{this.props.product.rating}</li>
<li>{this.props.product.review}</li>
</ul>
);
}
}
This is the best I could come up with, but using state to keep track of what product was clicked still sounds wrong to me. I mean, it's not really a state of a component, is it?
If I could update props of a referenced React component from outside of the render method, then I'd try to pass a reference to a ProductInfoPanel to each Product, so they could do update it in their click handler.
Is there a way to achieve what I want and avoid using state to keep track of what was clicked?
You could use a flux-like library like redux, or an alternative like mobx to remove state management from your component, but my personal feeling is to keep it as simple as possible until you really feel like there will be significant benefit in adding another layer of abstraction into your project.
I used to start off projects using redux by default but then one time I kicked myself as it turned out that the added complexity of introducing a redux implementation turned out to be overkill for what was actually a fairly small and simple project. I don't know if there is a hard line to know when you should shy away from using standard state and introduce another library to manage it for you, but I have learned that it's probably safest to do it the easiest and simplest way first until you genuinely feel there is actual benefit in bring in another dependency.
A few bits of advice on your current code...
You are binding your functions in the properties like so:
<Product product={product} clickHandler={this.onProductClicked.bind(this)}/>
When you call function bind it actually returns a new function instance, therefore React's reconciler will see it as a new prop coming into your component and will therefore always re-render the subcomponent tree. Something to be aware of. As an alternative approach you can do early binding in your constructor like so:
class ProductList extends React.Component {
constructor(props) {
super(props);
this.onProductClicked = this.onProductClicked.bind(this);
}
render() {
...
<li key={index}>
<Product product={product}
clickHandler={this.onProductClicked}/>
</li>
...
}
}
Additionally, where you are providing index as they unique key prop above - you should consider using a unique identifier from your product model (if it's available). That way if you add or remove items from the list React will have more information to know whether or not it should re-render all of the Product component instances.
For example:
render() {
...
{
this.props.products.map((product) =>
<li key={product.id}>
<Product product={product}
clickHandler={this.onProductClicked}/>
</li>
)
}
...
}
Read more about these concepts here:
https://facebook.github.io/react/docs/advanced-performance.html
I think it's fine. If there were more components that responded to changes in SelectedProduct, then the value of having the parent component control the state would be more apparent. In your case, it might not seem necessary, since only a single component changes.
However, if your Product also responded by highlighting the SelectedProduct, and a RecentlyViewedProducts list responded in some way to the SelectedProduct, then it would become evident that the SelectedProduct isn't the state of the ProductInfoPanel, but state of a higher level part of the application that it's an observer of.