I have a React class that takes a JSON object array and outputs a set of divs representing keys and values. The thing is, each object in the json has around 60 key value pairs inside; in this example i am rendering the divs for the 19th index for each of the objects:
import React, { Component } from "react";
import "./Maps.css";
import df3 from "./data/df3.json"
import sample from "./data/sample.json"
class Maps extends Component {
constructor() {
super();
const data = df3;
this.state = data
}
renderDiv = () => {
var df4 = df3["Devotions"];
return df4.map(v => {
return Object.keys(v).map((host) => {
return (
<div class={host}>
{host} {v[host][19]}
<div class='space' style={{ borderRadius:'19px',
transform:`scale(${v[host][19]},${v[host][19]})`,
opacity:'9%'}} >
</div>
</div>
);
});
});
};
render() {
return <div id="Maps">{this.renderDiv()}</div>;
}
}
export default Maps
what I would like to do is control the rendering, so that the divs for each index appears sequentially on the screen.
return Object.keys(v).map((host) => {
return (
<div class={host}>
{host} {v[host][19]}
<div class='space' style={{ borderRadius:'19px',
transform:`scale(${v[host][19]},${v[host][19]})`,
opacity:'9%'}} >
</div>
</div>
Im not sure if if should just wrap all of sets of divs id like to return in a single div, and just have them connected to a single keyframe, but im not sure if theres a more elegant way to do it.
As always, help is appreciated!
I think this is what you need :
Considering, you have multiple objects, inside each object, there are some data in array, and you want to display all of them in sequence.
renderDiv = () => {
var df4 = df3["Devotions"];
let updatedArray = [];
df4.forEach(v => { //<--- no need of map
Object.keys(v).forEach((hosts) => { //<--- no need of map
updatedArray = [...updatedArray ,
...v[hosts].map((host) => {
return (
<div className={host}>
{host} {host}
<div className='space' style={{ borderRadius:'19px',
transform:`scale(${host},${host})`,
opacity:'9%'}} >
</div>
</div> )
})
]
})
})
return updatedArray;
}
Related
I have an array of 30 unique options that I use to render 30 SVGs.
There is a button that adds 30 more unique options to the array each time it's clicked. When the 30 new options are added, React will re-render all 60 SVGs instead of just rendering the 30 new ones.
This is a problem because it's causing the frame rate to drop and makes the browser stutter. The problem gets worse and worse as there are more SVGs in the array.
I want to know how I can get React to only render the newly added 30 SVGs and not re-render everything in the array.
I even added a unique key to each SVG.
Here's my code:
import generateRandomSVG from "../randomGenerator";
import SVGElement from "./SVGElement";
import Button from "./button";
const getSVGs = (num) => {
let avatarArray = [];
for (let i = 0; i < num; i++) {
avatarArray.push(generateRandomSVG());
}
return avatarArray;
};
class Home extends React.Component {
state = {
svgs: []
};
componentDidMount() {
//render 30 SVGs when the component first loads
this.setState({
svgs: getSVGs(30)
});
console.log(this.state.avatars);
}
handleClick = () => {
//when user clicks on button, add 30 more SVGs
const moreSVGs = getSVGs(30);
this.setState({
svgs: this.state.svgs.concat(moreSVGs)
});
};
render() {
return (
<div>
<Button onClick={this.handleClick}>Add 30 more SVGs </Button>
{this.state.svgs.map((SVGObject) => {
const key = SVGObject.uniqueKey;
return (
<div key={key}>
<SVGElement {...SVGObject.options} />
</div>
);
})}
</div>
);
}
}
Instead of holding all your svgs in one array, make svgs array hold arrays where each array holds 30 svgs.
componentDidMount() {
this.setState({
svgs: svgs.push(getSVGs(30))
});
}
handleClick = () => {
const moreSVGs = getSVGs(30);
const updatedSVGs = this.state.svgs.push(moreSVGs)
this.setState({
svgs: updatedSVGs
});
};
render() {
return (
<div>
<Button onClick={this.handleClick}>Add 30 more SVGs </Button>
{this.state.svgs.forEach((svgBatch) => {
svgBatch.map((SVGObject) => {
const key = SVGObject.uniqueKey;
return (
<div key={key}>
<SVGElement {...SVGObject.options} />
</div>
);
})}
})
</div>
);
}
This way you iterate over a batch of 30 svgs and create a compoment for each batch.
After this you can create a check in the shouldComponentUpdate method in your SVGElement to check if the new props differ from the currentones. That's how you prevent the rerendering.
shouldComponentUpdate(nextProps, nextState) {
//I don't know what your SVGObject.options contain so you will have to change
//the check a liitle but I think you get the idea
if (JSON.stringify(this.props.options.svgArray) === JSON.stringify(nextProps.options.svgArray)) {
return false;
}
}
I'm trying to generate several divs based off an array - but I'm unable to. I click a button, which is supposed to return the divs via mapping but it's returning anything.
class History extends Component {
constructor(props) {
super(props);
this.state = {
info: ""
};
this.generateDivs = this.generateDivs.bind(this);
}
async getCurrentHistory(address) {
const info = await axios.get(`https://api3.tzscan.io/v2/bakings_history/${address}?number=10000`);
return info.data[2];
}
async getHistory() {
const info = await getCurrentHistory(
"tz1hAYfexyzPGG6RhZZMpDvAHifubsbb6kgn"
);
this.setState({ info });
}
generateDivs() {
const arr = this.state.info;
const listItems = arr.map((cycles) =>
<div class="box-1">
Cycle: {cycles.cycle}
Count: {cycles.count.count_all}
Rewards: {cycles.reward}
</div>
);
return (
<div class="flex-container">
{ listItems }
</div>
)
}
componentWillMount() {
this.getHistory();
}
render() {
return (
<div>
<button onClick={this.generateDivs}>make divs</button>
</div>
);
}
You are not actually rendering the the divs just by invoking the generateDivs function, the JSX it is returning is not being used anywhere.
To get it to work you could do something like -
render() {
return (
<div>
<button onClick={this.showDivs}>make divs</button>
{this.state.isDisplayed && this.generateDivs()}
</div>
);
}
where showDivs would be a function to toggle the state property isDisplayed to true
The main point is that the JSX being returned in the generateDivs function will now be rendered out in the render function. There is many ways to toggle the display, that is just one straight forward way
I'm trying to figure out how to render out a set of divs, without re-rendering the entire list as a new set is added.
So I've got a stateful component. Inside said stateful component, I've got a function that A, gets a list of post id's, and B, makes a request to each of those post id's and pushes the results to an array. Like so:
getArticles = () => {
axios.get(`${api}/topstories.json`)
.then(items => {
let articles = items.data;
let init = articles.slice(0,50);
init.forEach(item => {
axios.get(`${post}/${item}.json`)
.then(article => {
this.setState({ articles: [...this.state.articles, article.data]});
});
})
});
}
Then, I've got a second function that takes this information and outputs it to a list of posts. Like so:
mapArticles = () => {
let articles = this.state.articles.map((item, i) => {
let time = moment.unix(item.time).fromNow();
return(
<section className="article" key={i}>
<Link className="article--link" to={`/posts/${item.id}`}/>
<div className="article--score">
<FontAwesomeIcon icon="angle-up"/>
<p>{item.score}</p>
<FontAwesomeIcon icon="angle-down"/>
</div>
<div className="article--content">
<div className="article--title">
<h1>{item.title}</h1>
</div>
<div className="article--meta">
{item.by} posted {time}. {item.descendants ? `${item.descendants} comments.` : null}
</div>
</div>
<div className="article--external">
<a href={item.link} target="_blank">
<FontAwesomeIcon icon="external-link-alt"/>
</a>
</div>
</section>
)
});
return articles;
}
I then use {this.mapArticles()} inside the render function to return the appropriate information.
However, whenever the app loads in a new piece of data, it re-renders the entire list, causing a ton of jank. I.e., when the first request finishes, it renders the first div. When the second request finishes, it re-renders the first div and renders the second. When the third request finishes, it re-renders the first and second, and renders the third.
Is there a way to have React recognize that the div with that key already exists, and should be ignored when the state changes and the function runs again?
A technique that I use to only render the part that are new is to keep a cache map of already drawn obj, so in the render method I only render the new incoming elements.
Here is an example:
Take a look at https://codesandbox.io/s/wq2vq09pr7
In this code you can see that the List has an cache array and the render method
only draw new arrays
class RealTimeList extends React.Component {
constructor(props) {
super(props);
this.cache = [];
}
renderRow(message, key) {
return <div key={key}>Mesage:{key}</div>;
}
renderMessages = () => {
//let newMessages=this,props.newMessage
let newElement = this.renderRow(this.props.message, this.cache.length);
this.cache.push(newElement);
return [...this.cache];
};
render() {
return (
<div>
<div> Smart List</div>
<div className="listcontainer">{this.renderMessages()}</div>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { message: "hi" };
}
start = () => {
if (this.interval) return;
this.interval = setInterval(this.generateMessage, 200);
};
stop = () => {
clearTimeout(this.interval);
this.interval = null;
};
generateMessage = () => {
var d = new Date();
var n = d.getMilliseconds();
this.setState({ title: n });
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={this.start}> Start</button>
<button onClick={this.stop}> Stop</button>
<RealTimeList message={this.state.message} />
</div>
);
}
}
If items arrive at the same time, wait till all items are fetched, then render:
getArticles = () => {
axios.get(`${api}/topstories.json`)
.then(items => {
let articles = items.data;
let init = articles.slice(0, 50);
Promise.all(init.map(item => axios.get(`${post}/${item}.json`)).then(articles => {
this.setState({
articles
});
})
});
}
If you really want to render immediately after an item is fetched, you can introduce a utility component that renders when promise resolves.
class RenderOnResolve extends React.Component {
state = null
componentDidMount() {
this.props.promise.then(data => this.setState(data))
}
render() {
return this.state && this.props.render(this.state);
}
}
// usage:
<RenderOnResolve promise={promise} render={this.articleRenderer}/>
This may be a quick fix but I have been racking my brain for the past little while, and could really use another set of eyes to take a look.
Basically I am trying to render an array full of generated JSX elements. I fell like I have done this a million times, but it does not seem to work here.
Heres the code:
import React, { Fragment } from 'react'
import css from './Search.scss';
import Header from '../SectionHeader/Header';
import SearchItem from '../SearchItem/SearchItem';
const Search = (props) => {
const { coinObject, coinKeys } = props;
let searchResults = []; // Array in question
const findResults = (searchText) => {
searchResults = []; // Reset the array to blank for each new character typed in input
for(let i = 0; i < coinKeys.length; i++) {
const { FullName } = coinObject[coinKeys[i]]; // App specific logic, not important, or the problem here
if(FullName.toLowerCase().includes(searchText) && (searchResults.length < 5)) {
console.log(FullName, searchText); // Prints the correct Full name based off of the searched text
searchResults.push(<SearchItem key={i} searchText={FullName} />);
}
}
console.log(searchResults); // Prints the updated array with all react elements
}
return (
<Fragment>
<Header title='Find Coins!' />
<div className={css.searchContainer}>
<div className={css.inputContainer}>
<input onChange={input => findResults(input.target.value)} className={css.searchInput} type='text' placeholder='Start Typing a Coin'/>
</div>
{ searchResults }
</div>
</Fragment>
);
}
export default Search;
And the SearchItem Component, which is super simple:
import React from 'react'
import css from './SearchItem.scss';
const SearchItem = (props) => {
return (
<div className={css.searchItem}>
{props.searchText}
</div>
)
}
export default SearchItem;
For a little bit of context, this component just gets a giant object of data, and will display the first 5 instances of what matches the input text. I am trying to make one of those search filter things, where as you type it suggests things that match from the data.
The array gets updated, and I can see the JSX objects in the array, they just do not render. I have a feeling it is due to the array not re-rendering?
Any help is much appreciated. Thanks!
You could make the Search component into a stateful component and store the searchResults in your state instead, so that when it is updated your component will be re-rendered.
Example
class Search extends React.Component {
state = { searchResults: [] };
findResults = searchText => {
const { coinObject, coinKeys } = this.props;
const searchResults = [];
for (let i = 0; i < coinKeys.length; i++) {
const { FullName } = coinObject[coinKeys[i]];
if (
FullName.toLowerCase().includes(searchText) &&
searchResults.length < 5
) {
searchResults.push(FullName);
}
}
this.setState({ searchResults });
};
render() {
return (
<Fragment>
<Header title="Find Coins!" />
<div className={css.searchContainer}>
<div className={css.inputContainer}>
<input
onChange={event => findResults(event.target.value)}
className={css.searchInput}
type="text"
placeholder="Start Typing a Coin"
/>
</div>
{this.state.searchResults.map((fullName, i) => (
<SearchItem key={i} searchText={fullName} />
))}
</div>
</Fragment>
);
}
}
I am trying to append a "Item" component which consists of some array items, in the main "App" Component. But the component is getting replaced with the new array items instead of getting appended. Following is the code snippet:
//the App render function
render: function() {
return (
<div>
{
this.state.productDisplayed.map(function(product, i) {
return (
<Item source = {product.url} prodId = {product.id} key = {product.id} />
)
})
}
</div>
)
}
//The Item render function
render: function(){
return(
<div className = "col-sm-4" >
<img src = {this.props.source} width = "70%" className = "img-responsive"></img>
<div>{this.props.prodId}
</div>
</div>
)
}
"ProductDisplayed" is an array which gets replaced by new items which are then displayed using a "for" loop.
How can i append the items as if I am adding some extra items to the main App component. I am trying to implement infinite scrolling.
In order to append items to your app component, you need to append data to your productDisplayed state array
For this you can do something like
addItem=(item)=>{
var productDisplayed=[...this.state.productDisplayed];
productDisplayed.push(item);
this.setState({productDisplayed});
}
And you can call this function addItem on some event.
You need to append them to the array in your state. I have used the function argument to setState because your nextState is dependent on your previous state.
component ProductList extends React.Component {
constructor() {
super()
this.setState({
productDisplayed: []
});
}
getMoreItems = ( startingId ) => {
Api.getMoreItems(startingId).then(this.addItems);
}
addItems = ( items ) => {
this.setState(( prevState ) => ({
updatedItems: [...prevState.productDisplayed, ...items]
}));
}
render() {
// no changes
// something triggers this.getMoreItems(id)
}
}