how to pass prop into rowrender of react-virtualized - reactjs

I'm trying to render a card list with react-virtualized. The posts data on this particular component is passed as a prop from the parent. This is what I currently have in my component class.
state = {
listHeight: 1000,
listRowHeight: 800,
listRowWidth: 1000,
rowCount: 10
}
rowRenderer ({ index, key, style, posts }) {
if (!posts) {
return <div></div>
} else {
return (
<PostItem key={key} style={style} post={posts[index]}/>
);
}
}
render() {
return (
<div className="ui container">
<div id="postListContainer" className="ui relaxed list">
<List
width={this.state.listRowWidth}
height={this.state.listHeight}
rowHeight={this.state.listRowHeight}
rowRenderer={this.rowRenderer}
rowCount={this.state.rowCount}
posts={this.props.posts}
/>
</div>
</div>
);
}
}
I hardcoded the rowCount since I know I have 10 items in my posts array currently. Just for context, this was my original code that renders the entire list successfully.
renderPosts() {
return this.props.posts.map(post => {
return (
<PostItem key={post._id} post={post}/>
);
})
}
render() {
return (
<div className="ui container">
<div id="postListContainer" className="ui relaxed list">
{this.renderPosts()}
</div>
</div>
);
}
}
The issue i'm currently having is that I can't access the props passed down into this component from my rowRenderer function so it gives me an undefined error. So my question is, how do I access the posts props in the rowRenderer function? I'm just trying to return a PostItem component for each post in the posts prop array.

The signature for rowRenderer looks like this:
function rowRenderer ({
index, // Index of row
isScrolling, // The List is currently being scrolled
isVisible, // This row is visible within the List (eg it is not an overscanned row)
key, // Unique key within array of rendered rows
parent, // Reference to the parent List (instance)
style // Style object to be applied to row (to position it);
// This must be passed through to the rendered row element.
}) { .. }
So you don't get access to the props through the arguments. You can access the props via the instance variable, this.
You should bind your handler when you pass it into List like this:
<List
...
rowRenderer={this.rowRenderer.bind(this)}
/>
then inside of rowRenderer you can simpy access this.props.posts

You can access the properties sent from List tag in rowRenderer method using parent that is received in rowRenderer.Checkout signature here
rowRenderer ({ index, key, style, parent }) {
const posts = parent.props.posts;
if (!posts) {
return <div></div>
} else {
return (
<PostItem key={key} style={style} post={posts[index]}/>
);
}
}
That should solve your problem.Also you can access props either by binding this variable to rowrenderer method or by ES6 syntax
rowRenderer = ({ index, key, style, parent }) => {
const posts = parent.props.posts;
const { someThing } = this.props;
if (!posts) {
return <div></div>
} else {
return (
<PostItem key={key} style={style} post={posts[index]}/>
);
}
}

Related

How to fix this error in react `onClick` listener to be a function, instead got a value of `string` type

I have this codes in react:
const [categoryId, setCategoryId] = useState("");
{
catName.map((singleCategory, index) => {
const { catName, _id: categoryId } = singleCategory;
return (
<>
<div
className="category-single-div flex-3 center-flex-align-display"
key={index}
>
<p className="text-general-small2 category-custom-text">{catName}</p>
<div className="category-icons-div ">
<FaEdit
className="category-icon-edit"
onClick={() => {
setEditCategory(true);
setCategoryId(categoryId);
}}
/>
<AiFillDelete className="category-icon-edit category-icon-delete" />
</div>
</div>
</>
);
});
}
I used map to get an array of objects, and I needed their individual _id when a user clicks the edit button. I also want to call another function on the same edit button via onClick. It is working but displays an error.
Warning: Expected onClick listener to be a function, instead got a
value of string type.
I need that _id so as to pass it to a state and have access to it globally within the component at the top level.
Is this workable?
Your problem comes from the FaEdit component.
<FaEdit
id={categoryId}
className="category-icon-edit"
onClick={(editCategory, id) => { // you need to have onClick as a prop defined inside the FaEdit component
setEditCategory(editCategory);
setCategoryId(id);
}}
/>
Example...
export default function FaEdit({className, onClick, categoryId}){
const handleChange() => {
onClick(true, categoryId)
}
return(
<div className={className} onClick={() => handleChange()}>Click</div>
)
}

Get child component state in parent component

I'm making a contact list where you can add contacts to your favorites. Then filter my favorite contacts.
First all contacts have the state isFavorite: false, then I click on one contact, click on the star that sets isFavorite: true. I close that contact and click on the filter button, to see all my favorite contacts
so in here I add a contact to my favorites:
ContactName.js
state = {
isFavorite: false
}
handleFavorite = () => {
this.setState({
isFavorite: !this.state.isFavorite
})
}
render() {
return (
<React.Fragment>
<li onClick={this.handleClick}>
{this.props.contact.name}
</li>
{
this.state.isOpen ?
<Contact
contact={this.props.contact}
close={this.handleClick}
favorite={this.handleFavorite}
isFavorite={this.state.isFavorite}
/>
: null
}
</React.Fragment>
)
}
Contact.js
<Favorites
id={contact.id}
name={contact.name}
onClick={this.props.favorite}
state={this.props.isFavorite}
/>
Favorites.js
this is just where the favorite component is
<span onClick={this.props.onClick}>
{
!this.props.state
? <StarBorder className="star"/>
: <Star className="star"/>
}
</span>
and here is where I want to be able to get the isFavorite state. This is the parent component where the button for filtering the contacts is.
ContactList.js
<React.Fragment>
<span
className="filter-button"
>Filtera favoriter</span>
<ul className="contacts">
{
this.props.contacts
.filter(this.handleSearchFilter(this.props.search))
.map(contact => (
<ContactName
key={contact.id}
contact={contact}
name={contact.name}
/>
))
}
</ul>
</React.Fragment>
You are doing this in the wrong direction.
In the React, you can pass the data down with props (or by using Context which is no the case here). So if you need a data on the ancestor component, the data should be state/props of that ancestor.
In your case, the favorite data should be inside of the contacts (that is defined as props of the ContactName), and you should pass it to the ContactName just like other props.
<React.Fragment>
<span
className="filter-button"
>Filtera favoriter</span>
<ul className="contacts">
{
this.props.contacts
.filter(this.handleSearchFilter(this.props.search))
.map((contact, index) => (
<ContactName
key={contact.id}
contact={contact}
name={contact.name}
isFavorite={contact.isFavorite}
handleFavorite={() => this.props.handleFavorite(index))}
/>
))
}
</ul>
</React.Fragment>
and inside your ContactName.js
render() {
return (
<React.Fragment>
<li onClick={this.handleClick}>
{this.props.contact.name}
</li>
{
this.state.isOpen ?
<Contact
contact={this.props.contact}
close={this.handleClick}
favorite={this.props.handleFavorite}
isFavorite={this.props.isFavorite}
/>
: null
}
</React.Fragment>
)
}
and toggleFavorite function also should be the same place as the contacts state is.
In React, parent components should not have access to their children's state. Instead, you need to move your isFavorite state up a level to your ContactList component and turn it into a list or map instead of a boolean.
ContactList.js
class ContactList extends React.Component {
state = {
// In this example, `favorites` is a map of contact ids.
// You could also use an array to keep track of the favorites.
favorites: {},
};
render() {
return (
<React.Fragment>
<span className="filter-button">Filter a favorite</span>
<ul className="contacts">
{this.props.contacts
.filter(this.handleSearchFilter(this.props.search))
.map(contact => (
<ContactName
key={contact.id}
contact={contact}
isFavorite={!!this.state.favorites[contact.id]}
name={contact.name}
handleFavorite={() => this.handleFavorite(contact.id)}
/>
))}
</ul>
</React.Fragment>
);
}
handleFavorite = contactId => {
// Use a callback for state here, since you're depending on the previous state.
this.setState(state => {
return {
...state.favorites,
[contactId]: !state.favorites[contactId], // Toggle the value for given contact.
};
});
};
}
Now the handleFavorite and isFavorite props can simply be passed down as needed to your child components.
okay, I've managed to get the childs state in the parent. But now everytime I add a new contact to my favorites, it creates new objects - see codebox https://codesandbox.io/embed/charming-bohr-rwd0r
Is there a way to mash all of those new created objects into one object and set that one big objects equal to a new state called favoriteContacts = []?

Is there any obvious reason this won't render?

I'm pulling in an array of objects and mapping them to another component to be rendered.
renderRatings(){
if(this.props.ratings.length > 0){
return this.props.ratings.map(rating => {
<Rating
id={rating.id}
title={rating.title}
value={rating.value}
/>
});
}
}
This is where I render the rendering function.
render() {
return (
<div>
{this.renderRatings()}
</div>
);
}
}
This is the component I'm trying to populate and have rendered.
class Rating extends Component{
componentDidMount(){
console.log("props equal:", this.props)
}
render() {
return (
<div className="card darken-1" key={this.props._id}>
<div className="card-content">
<span className="card-title">{this.props.title}</span>
<p>{this.props.value}</p>
<button>Edit</button>
<button onClick={() => this.deleteRating(this.props._id)}>Delete</button>
</div>
</div>
);
}
}
export default connect({ deleteRating })(Rating);
No errors are being thrown, but when the page loads, the surrounding menu comes up, and the fetch request returns an array and supposedly maps it to the 'Rating' component, but no mapped Rating cards appear.
in your map, you're not returning the Rating etc... because you used { to define a code block, you have to type return. And since it's multi-line, use parens to mark the start and end of the Rating component.
return this.props.ratings.map(rating => {
<Rating
id={rating.id}
title={rating.title}
value={rating.value}
/>
needs to be
return this.props.ratings.map(rating => {
return (<Rating
id={rating.id}
title={rating.title}
value={rating.value}
/>)

Async loading of components in react-virtualized?

I have a react-virtualized masonry grid implemented in a component as follows:
const MasonrySubmissionRender = (media: InputProps) => {
function cellRenderer({ index, key, parent, style }: MasonryCellProps) {
//const size = (media.submissionsWithSizes && media.submissionsWithSizes.length > index) ? media.submissionsWithSizes[index].size : undefined
//const height = size ? (columnWidth * (size.height / size.width)) : defaultHeight;
function getCard(index: number, extraProps: any) {
var Comp = media.cardElement ? media.cardElement : SubmissionCard
return <Comp submission={media.media[index]} {...media.customCardProps} />
}
return (
<div>
<CellMeasurer
cache={cache}
index={index}
key={key}
parent={parent}>
<div style={style}>
{getCard(index, media.customCardProps)}
</div>
</CellMeasurer>
</div>
);
}
return (
<Masonry
overscanByPixels={1000}
autoHeight={false}
cellCount={media.media.length}
cellMeasurerCache={cache}
cellPositioner={cellPositioner}
cellRenderer={cellRenderer}
style={{ backgroundColor: 'red' }}
height={900}
width={900}
/>
);
};
It renders a list of fairly complicated components which contain an array of chips, css animations, etc.
Due to this rendering is very slow even with react-virtualized.
I'd like to implement a system like in imgur.com where the component itself won't necessary load immediately displaying only a silhouette while I can have the component preparing to render in the background.
I know there's a way to swap out the component during scrolling butt his hides all components including ones which have already rendered.
Like all react-virtualized cell/row renderers, masonry's cell render is passed an isScrolling property. When the masonry is scrolling you can render a place holder instead of the cell content:
if (isScrolling) return (
<div>placeholder</div>
);
In addition, you are recreating all functions whenever the stateless component is rerendred. This causes an extra overhead for the garbage collector, and might also cause components to rerender unnecessarily.
Convert the component to a class component. The cellRenderer should be an instance method (use class properties or bind in constructor). The getCard can be a class method, or you can extract it from the component, and pass the media when you call the function.
Your code should be something like this (not tested):
function getCard(media: InputProps, index: number) {
var Comp = media.cardElement ? media.cardElement : SubmissionCard
return <Comp submission = {
media.media[index]
} { ...media.customCardProps }
/>
}
class MasonrySubmissionRender extends React.Component {
cellRenderer = ({
index,
key,
parent,
style,
isScrolling
}: MasonryCellProps) => {
if (isScrolling) return (
<div>placeholder</div>
);
return (
<div>
<CellMeasurer
cache={cache}
index={index}
key={key}
parent={parent}>
<div style={style}>
{getCard(media, index)}
</div>
</CellMeasurer>
</div>
);
}
render() {
return (
<Masonry
overscanByPixels={1000}
autoHeight={false}
cellCount={media.media.length}
cellMeasurerCache={cache}
cellPositioner={cellPositioner}
cellRenderer={this.cellRenderer}
style={{ backgroundColor: 'red' }}
height={900}
width={900}
/>
);
}
}

Calling a function for ALL child components

I have 3 components. They parent layout, a select box, and a panel this is generated x times from some data.
<Layout>
<Dropdown>
<Panel>
<Panel>
<Panel>
I'm trying to make it so when the select value changes, the contents of each panel changes. The changes are made by doing some math between the new select value, and data that is stored in the panel component. Each panel has different data.
Layout.js
updateTrueCost(selected){
this.refs.panel.setTrueCost
}
render(){
return (
<div>
<div class="row">
Show me the true cost in
<CurrencyDrop currencyChange = {(e) => this.updateTrueCost(e)} data = {this.state.data} />
</div>
<div class="row">
{this.state.data.map((item, index) => (
<Panel ref="panel" key = {index} paneldata= {item} />
))}
</div>
</div>
);
}
Panel.js
setTrueCost(selected){
//Do some math
this.setState({truecost: mathresult})
}
render(){
return(
<div>
{this.state.truecost}
</div>
)
}
CurrencyDrop.js
onHandelChange(e){
this.props.currencyChange(e);
}
render(){
return(
<Select
onChange={this.onHandelChange.bind(this)}
options={options} />
)
}
The current result is only the last panel updates when the select changes. I'm guessing I'm doing something wrong with the ref handling, but I must not be searching the right terms because I can't find any related questions.
Instead of calling ref's method use React build-in lifecycle methods.
class Panel extends React.Component {
componentWillReceiveProps (newProps) {
// compare old and new data
// make some magic if data updates
if (this.props.panelData !== newProps.panelData) {
this.setState({trueCost: someMath()});
}
}
render () {
return <div>{this.state.trueCost}</div>
}
}
Then just change input props and all data will be updated automatically.

Resources