How to scroll to bottom when props changed in react-virtualized? - reactjs

I have component App with List from react-virtualized library.
And I need on initial render, that my List scroll to bottom.
And I did it, when added scrollToIndex option. But when I add new object in my list array, it does not scroll to my last added object. How can I fix it? And is it good solution to use "forceUpdate()" function?
import { List } from "react-virtualized";
import loremIpsum from 'lorem-ipsum';
const rowCount = 1000;
const listHeight = 600;
const rowHeight = 50;
const rowWidth = 800;
class App extends Component {
constructor() {
super();
this.renderRow = this.renderRow.bind(this);
this.list = Array(rowCount).fill().map((val, idx) => {
return {
id: idx,
name: 'John Doe',
image: 'http://via.placeholder.com/40',
text: loremIpsum({
count: 1,
units: 'sentences',
sentenceLowerBound: 4,
sentenceUpperBound: 8
})
}
});
}
handle = () => {
this.list = [...this.list, { id: 1001, name: "haha", image: '', text: 'hahahahahaha' }];
this.forceUpdate();
this.refs.List.scrollToRow(this.list.length);
};
renderRow({ index, key, style }) {
console.log('____________', this.list.length);
return (
<div key={key} style={style} className="row" >
<div className="image">
<img src={this.list[index].image} alt="" />
</div>
<div onClick={this.handle}>{this.state.a}</div>
<div className="content">
<div>{this.list[index].name}</div>
<div>{this.list[index].text}</div>
</div>
</div>
);
}
render() {
return (
<div className="App">
<div className="list">
<List
ref='List'
width={rowWidth}
height={listHeight}
rowHeight={rowHeight}
rowRenderer={this.renderRow}
rowCount={this.list.length}
overscanRowCount={3}
scrollToIndex={this.list.length}
/>
</div>
</div>
);
}
}
export default App;

You mentioning you need to scroll to the bottom when the list item is changed and to be honest i don't like to use forceUpdate. As mentioned on the React docs:
Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render().
Luckily, one of React lifecycle method is suitable for this case, it is call componentDidUpdate. But you need to do some refactor of your code. Instead using private field, i suggest to put it on state/props.
This method will invoked immediately after updating props/state occurs. However, This method is not called for the initial render.
What you need to do is, compare the props, is it change or not? Then call this.refs.List.scrollToRow(this.list.length);
Sample code
class App extends Component {
constructor() {
this.state = {
list: [] // put your list data here
}
}
// Check the change of the list, and trigger the scroll
componentDidUpdate(prevProps, prevState) {
const { list } = this.state;
const { list: prevList } = prevState;
if (list.length !== prevList.length) {
this.refs.List.scrollToRow(list.length);
}
}
render() {
// usual business
}
}
more reference for React lifecyle methods:
https://reactjs.org/docs/react-component.html#componentdidupdate

Related

Passing functions to child components in React - this.props.handleChange is not a function?

Trying to create a simple todo list and I figure out how to pass the function from the parent component down to the child component without it throwing an error
App Component
class App extends React.Component {
state = {
todo: todoData.map(todo => {
return <TodoItem handleChange={this.handleChange} todo={todo} key={todo.id} />
}),
count: 0,
}
handleChange = (id) => {
console.log(id)
}
render(){
return(
<div className="flex">
<Header />
<div className="todoList" >
{this.state.todo}
</div>
</div>
)
}
}
TodoItem component
class TodoItem extends React.Component {
render(){
console.log(this.props)
return(
<p className="todoItem" onClick={this.props.clickeds}>
<input type="checkbox" checked={this.props.todo.completed} onChange={() => this.props.handleChange(this.props.todo.id)} />
{this.props.todo.text}
</p>
)
}
}
I'm trying to mess with the onChange handler in the TodoItem component, but I keep getting the same error that this.props.handleChange is not a function
Todo just for reference
todoData = {
id: 2,
text: "Grocery Shopping",
completed: false
},
What am I doing wrong?
When I change the handleChange function to NOT an arrow function in the app component, it works. (handleChange(id)). If I change this function to an arrow function (handleChange = (id) => { } ) I run into this error.
I recommend you use ES6's class syntax for now (which I have added below) as this is how most React tutorials —including the official ones— are written.
The data structure you should keep in the state should be an Array of Todo Objects.
You don't need to keep the components inside the state, simply iterate them on render (and don't worry about its performance, React won't recreate the HTML dom by doing this, so the render will be very efficient).
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todos : todoData,
count : 0,
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(id) {
const todos = this.state.todos.map(todo => (
(todo.id === id)
? Object.assign(todo, { completed: !todo.completed }
: todo
));
this.setState({ todos });
}
render() {
// ...
this.state.todos.map(todo => (
<TodoItem handleChange={this.handleChange} todo={todo} key={todo.id} />
);
}
}
// todoData should probably be an array in case you want to include more by default:
todoData = [
{
id: 2,
text: "Grocery Shopping",
completed: false
},
{
id: 2,
text: "Grocery Shopping",
completed: false
}
];
Be careful about mutating the state
The reason for the ugly and confusing map() and Object.assign() inside handleChange is because you cannot do a shallow copy, nor edit the array directly:
this.state.todos[i].completed = true // ! mutates the state, no good
Preferably Lodash's library or even better, an immutable library or memoized selectors would do the trick of setting a todo as completed in a much nicer fashion.
Without deep cloning or immutability, you would need to copy the object, clone it, then create a new array with the new object, and assign that array to the state.

DOM Not Updating although function changes var

Even though the variable value changes to a value that would cause the element not to render, the page does not update and the element remains rendered.
Tried moving inside component, did not work.
function clickHandler(item)
{
object[item].active = 0;
}
let object = [{data:
<p onClick={() => clickHandler(0)}> Data </p>,
active:1},
{data:
<p onClick={() => clickHandler(1)}> Data2 </p>,
active:1}
];
class Objects extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div class="notifications">
{object[0].active == 1 ? object[0].data : " "}
{object[1].active == 1 ? object[1].data : " "}
</div>
);
}
}
ReactDOM.render(<Objects />, document.querySelector('#object_display'));"
Expects to disappear but it does not.
Changing external data isn't going to trigger an update of your component. You need to either change the props passed to the component or keep track of it in state inside the component itself.
Consider this:
// data declared outside the component; gets passed as a prop
// in the ReactDOM.render call below.
const data = [
{
title: "Object 1"
},
{
title: "Object 2"
},
{
title: "Object 3"
},
]
class Objects extends React.Component {
// initial state; start with the first item
state = {index: 0}
// onClick handler
switch = () => {
// get the current index out of this.state
const {index} = this.state;
// get the number of items in data so
// we can loop back to 0 when we get to
// the last item
const {data: {length}} = this.props;
// increment the index, don't go beyond length
const newIndex = (index + 1) % length;
// calling setState triggers a re-render
// with the new index value
this.setState({index: newIndex});
}
render () {
const {data} = this.props;
const {index} = this.state;
const item = data[index];
return (
<div onClick={this.switch}>{item.title} (Click for next item)</div>
);
}
}
// data passed as a prop
ReactDOM.render(<Objects data={data} />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app" />

react counter for each item of list

I'm trying to create a counter for each item in a list in React. I want each to be incremented or decremented individually depending on what the user clicks on. This issue is that all counters increment and decrement on click
of a single element, but I would like only the clicked element's counter to change.
this is my code:
class name extends Component {
constructor(){
super()
this.state = {
news: [],
voteing: 0
}
}
onVoting(type){
this.setState(prevState => {
return {voteing: type == 'add' ? prevState.voteing + 1: prevState.voteing- 1}
});
}
render() {
return (
<React.Fragment>
<Content>
{
this.state.news.map((item, i)=>{
return (
<Item key={i}>
<text>
{item.subject}
{item.details}
</text>
<Votering>
<img src="" onClick={this.onVoting.bind(this, 'add')} />
<div value={this.state.voteing}>{this.state.voteing}</div>
<img src="" onClick={this.onVoting.bind(this, 'min')} />
</Votering>
</Item>
)
})
}
</Content>
</React.Fragment>
)
}
}
I'm trying to do this:
<img src="" onClick={this.onVote(i).bind(this, 'add')} />
but it doesn't work also tried this.onVote(item.i) and same result
I cannot really see how you would like to see voting as part of the local component state, as it really has to do (in my opinion), with the entities on which you can vote.
So if I were you, I would rewrite the code slightly different. As I do not know what you intend to do afterwards with the votes (this rather assumes like a live process, or at least a kind of save button, as it is saved here in the local VotingApp state), I just save everything to the local state, how you would handle that is not really my intend to answer.
So personally, I would rather go for one functional component, just rendering the news item and it's voting capability, where the voteCount is part of the item entity. If this is not how you receive the data, nothing stops you from adding the data after your fetch and before really showing it on the screen. The app itself will receive the changes and the item that will be changed, and what it does there-after, would be all up to you ;)
const { Component } = React;
const NewsItem = ( item ) => {
const { subject, details, voteCount, handleVoteChange } = item;
return (
<div className="news-item">
<div className="news-vote">
<div className="vote-up" title="Vote up" onClick={ () => handleVoteChange( item, 1 ) }></div>
<div className="vote-count">{ voteCount }</div>
<div className="vote-down" title="Vote down" onClick={ () => handleVoteChange( item, -1 ) }></div>
</div>
<div className="news-content">
<h3>{ subject }</h3>
<div>{ details }</div>
</div>
</div>
);
};
class VotingApp extends Component {
constructor( props ) {
super();
this.handleVoteChange = this.handleVoteChange.bind( this );
// by lack of fetching I add the initial newsItems to the state
// and work by updating local state on voteChanges
// depending on your state management (I guess you want to do something with the votes)
// you could change this
this.state = {
newsItems: props.newsItems
};
}
handleVoteChange( item, increment ) {
this.setState( ( prevState ) => {
const { newsItems } = prevState;
// updates only the single item that has changed
return {
newsItems: newsItems
.map( oldItem => oldItem.id === item.id ?
{ ...oldItem, voteCount: oldItem.voteCount + increment } :
oldItem ) };
} );
}
render() {
const { newsItems = [] } = this.state;
return (
<div className="kiosk">
{ newsItems.map( item => <NewsItem
key={ item.id }
{...item}
handleVoteChange={this.handleVoteChange} /> ) }
</div>
);
}
}
// some bogus news items
const newsItems = [
{ id: 1, voteCount: 0, subject: 'Mars in 2020', details: 'Tesla will send manned BFR rockets to Mars in 2020' },
{ id: 2, voteCount: -3, subject: 'Stackoverflow rocks', details: 'Stackoverflow is booming thanks to the new friendly policy' },
{ id: 3, voteCount: 10, subject: 'DS9: Healthy living', details: 'Eat rice everyday and drink only water, and live 10 years longer, says Dax to Sisko, Sisko suprises her by saying that like that, he doesn\'t want to live 10 years longer...' }
];
// render towards the container
const target = document.querySelector('#container');
ReactDOM.render( <VotingApp newsItems={ newsItems } />, target );
.kiosk {
display: flex;
flex-wrap: no-wrap;
}
.news-item {
display: flex;
justify-content: flex-start;
width: 100%;
}
.news-vote {
display: flex;
flex-direction: column;
align-items: center;
padding-left: 10px;
padding-right: 10px;
}
.news-vote > * {
cursor: pointer;
}
.news-content {
display: flex;
flex-direction: column;
}
.vote-up::before {
content: '▲';
}
.vote-down::before {
content: '▼';
}
.vote-up:hover, .vote-down:hover {
color: #cfcfcf;
}
h3 { margin: 0; }
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script id="prop-types" src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
The reason all your items' counts change when any of them is clicked on is that they all share the same vote count value, voteing in the name component's state.
To fix this, you should break each item into its own stateful component. So that each can track its own click count.
For example:
class name extends Component {
constructor(){
super();
this.state = {
news: []
}
}
render() {
return (
<React.Fragment>
<Content>
{
this.state.news.map((item, i) => {
return <NewsItem key={ i }
subject={ item.subject }
details={ item.details }
/>
})
}
</Content>
</React.Fragment>
)
}
}
class NewsItem extends Component {
constructor() {
super();
this.state = {
voteCount = 0
}
}
handleVote(type) {
this.setState(prevState => ({
voteCount: type === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
}));
}
render() {
const { subject, details } = this.props;
const { voteCount } = this.state;
return (
<Item>
<text>
{ subject }
{ details }
</text>
<Votering>
<img src="" onClick={ this.handleVote.bind(this, 'add') } />
<div value={ voteCount }>{ voteCount }</div>
<img src="" onClick={ this.handleVote.bind(this, 'min') } />
</Votering>
</Item>
)
}
}
You could also maintain separate counts for each item within the parent component, but I find breaking into separate components to be much cleaner.
A few things I noticed unrelated to your question.
1) onVoting should be bound in your constructor or use onVoting = () => { ..... }
2) in your render function you have onVote instead of onVoting
On to your main question, in your state you are only maintaining one counter that is displayed and changed for all news elements. an easy way to get around this is to create a new react element for each news article that will handle the voting for each article.
class parent extends Component {
constructor(){
super()
this.state = {
news: null,
}
}
componentDidMount() {
// fetch data from api and minipulate as needed
this.setState({news: dataFromApi})
}
render() {
return (
<Content>
{
this.state.news.map((item, i)=>{
return (
<NewChildComponent data={item}/>
)
})
}
</Content>
)
}
}
class NewChildComponent extends Component {
constructor() {
super()
this.state = {
voting: 0,
}
}
onVoting = (e) => {
this.setState(prevState => ({
voteCount: e.target.name === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
}));
}
render () {
const {data} = this.props;
return (
<Item key={data.uniqueID}>
<text>
{data.subject}
{data.details}
</text>
<Votering>
<img src="" onClick={this.onVoting} name="add"/>
<div value={this.state.voteing}>{this.state.voteing}</div>
<img src="" onClick={this.onVoting} name="min"/>
</Votering>
</Item>
)
}
}
A little background on why you should not bind in your render function. https://medium.freecodecamp.org/why-arrow-functions-and-bind-in-reacts-render-are-problematic-f1c08b060e36
Here’s why: The parent component is passing down an arrow function on
props. Arrow functions are reallocated on every render (same story
with using bind). So although I’ve declared User.js as a
PureComponent, the arrow function in User’s parent causes the User
component to see a new function being sent in on props for all users.
So every user re-renders when any delete button is clicked. 👎
Also why you should not use an index as a key in React.
https://reactjs.org/docs/lists-and-keys.html
We don’t recommend using indexes for keys if the order of items may
change. This can negatively impact performance and may cause issues
with component state. Check out Robin Pokorny’s article for an
in-depth explanation on the negative impacts of using an index as a
key. If you choose not to assign an explicit key to list items then
React will default to using indexes as keys.
Here is an in-depth explanation about why keys are necessary if you’re
interested in learning more.

Rendering in react with array.map

I have an array of strings which I would like to render as a list, with a colored text. The user can change the color with a button.
For that I have built a component called which receives an array and renders a list with the array's values and a button to change the color:
import React, { Component } from "react";
const renderArray = arr => (arr.map(value => (
<li>
{value}
</li>
)))
class List extends Component {
constructor(props) {
super(props);
this.state = {
color: 'red'
}
}
toggleColor = () => {
if (this.state.color === "red") {
this.setState({color: "blue"});
} else {
this.setState({color: "red"});
}
}
render() {
const style = {
color: this.state.color
};
return (
<div style={style}>
<ul>
{renderArray(this.props.array)}
</ul>
<button onClick={this.toggleColor}>Change color</button>
</div>
);
}
}
export default List;
The List is called with:
<List array={arr} />
And arr:
const arr = ['one', 'two', 'three'];
Fiddle here: Fiddle
But this seems incorrect to me. I rerender the whole array by calling renderArray() each time the color changes. In this case it is not too bad but what if the renderArray() is much more complex?
To my understanding, I need to create a new list only if the array prop changes and this could do in getDerivedStateFromProps (or in componentWillReceiveProps which will be deprecated...):
componentWillReceiveProps(nextProps)
{
const renderedArray = renderArray(nextProps.array);
this.setState({ renderedArray });
}
And then, on render, use this.state.renderedArray to show the list.
But this seems strange, to store a rendered object in the state...
Any suggestions?
Thanks!
1) React uses the concept of virtual DOM to calculate the actual difference in memory and only if it exists, render the difference into DOM
2) You can "help" React by providing a "key", so react will better understand if it's needed to re-render list/item or not
3) Your code componentWillReceiveProps can be considered as a bad practice because you're trying to make a premature optimization. Is repaint slow? Did you measure it?
4) IMHO: renderArray method doesn't make sense and can be inlined into List component
React render the DOM elements efficiently by using a virtual DOM and checks if the update needs to happen or not and hence, it may not be an issue even if you render the list using props. To optimise on it, what you can do is to make use of PureComponent which does a shallow comparison of state and props and doesn't cause a re-render if nothing has changed
import Reactfrom "react";
const renderArray = arr => (arr.map(value => (
<li>
{value}
</li>
)))
class List extends React.PureComponent { // PureComponent
constructor(props) {
super(props);
this.state = {
color: 'red'
}
}
toggleColor = () => {
if (this.state.color === "red") {
this.setState({color: "blue"});
} else {
this.setState({color: "red"});
}
}
render() {
const style = {
color: this.state.color
};
return (
<div style={style}>
<ul>
{renderArray(this.props.array)}
</ul>
<button onClick={this.toggleColor}>Change color</button>
</div>
);
}
}
export default List;

Swiper renderExternal example in Virtual Slides with React

Need to render a Custom React component array as Virtual Slides using Swiper.
Document says renderExternal can be used for this but there's no example in the API doc http://idangero.us/swiper/api/#virtual
Need to know on how this can be done using renderExternal function.
I'm not sure that's the best way to do it, but that is roughly how I did :
const mySwiper = new Swiper('.swiper-container', {
virtual: {
slides: this.props.array (or this.state.array)
renderExternal: function(data){}
}
})
then you display your component in the template :
<div className="swiper-container">
<div className="swiper-wrapper">
<MyComponent
myProps="mySwiper.virtual.slides[mySwiper.activeIndex]"
/>
</div>
</div>
(I did it with Vue, not React)
The documentation got updated and now you can find an example of how to use it with React. For reference, here is the example that is given:
import React from 'react';
import Swiper from 'swiper/dist/js/swiper.esm.bundle';
export default class extends React.Component {
constructor() {
this.state = {
// dummy slides data
slides: (function () {
var slides = [];
for (var i = 0; i < 600; i += 1) {
slides.push('Slide ' + (i + 1));
}
return slides;
}()),
// virtual data
virtualData: {
slides: [],
},
}
}
componentDidMount() {
const self = this;
const swiper = new Swiper('.swiper-container', {
// ...
virtual: {
slides: self.state.slides,
renderExternal(data) {
// assign virtual slides data
self.setState({
virtualData: data,
});
}
},
});
}
render() {
{/* ... */}
<div className="swiper-container">
<div className="swiper-wrapper">
{/* It is important to set "left" style prop on every slide */}
{this.state.virtualData.slides.map((slide, index) => (
<div className="swiper-slide"
key={index}
style={{left: `${virtualData.offset}px`}}
>{slide}</div>
))}
</div>
</div>
{/* ... */}
}
}

Resources