I am in the process of learning React and Redux. Currently I am working on a project where I need to append a component on button click.
New Component should be added down the previous component
Previously added component contains the data added and it should not be refreshed while adding a new component.
I tried to search but all the solutions are recommending to use a List and incrementing the count on every click.
This is my requirement diagram:
Update:
I have added my code which I tried in the below JS Fiddle.
While appending the new component, the data modified in the existing component should be retained.
https://jsfiddle.net/np7u6L1w/
constructor(props) {
super(props)
this.state = { addComp: [] }
}
addComp() { // Onclick function for 'Add Component' Button
//this.setState({ addComp: !this.state.addComp })
this.setState({
addComp: [...this.state.addComp, <Stencil />]
});
}
render() {
return (
<div>
<div class="contentLeft"><h2>Workflows:</h2>
<Stencil />
{this.state.addComp.map((data, index) => {
{ data }
})}
</div>
<div class="contentRight" >
<button name="button" onClick={this.addComp.bind(this)} title="Append new component on to the end of the list">Add Component</button>
</div>
<div class="clear"></div>
</div>
)
}
Code is Updated:
You can do something like that
// New state
this.state = {
appendedCompsCount: 0
}
// Outside render()
handleClick = () => {
this.setState({
appendedCompsCount: this.state.appendedCompsCount + 1
})
}
getAppendedComponents = () => {
let appendedComponents = [];
for (let i = 0; i < this.state.appendedCompsCount; i++) {
appendedComponents.push(
<AppendedComponents key={i} />
)
}
return appendedComponents;
}
// In render()
<button onClick={this.handleClick}>Click here</button>
{
this.getAppendedComponents()
}
maybe when added new child, you want animation to work.
this is the best method react-transition-group
example: https://reactcommunity.org/react-transition-group/transition-group
Related
This should be pretty simple, but I can't figure out how to do it.
I have a component with multiple buttons, each with a "count" value, set with state. When a user clicks, the count goes up.
Right now, when I click one of the buttons, both counters change. How can I make it so only the div that was clicked updates, using the same state?
Edit: I don't want to have different counts, as I'd like for this component to render buttons dynamically. What if I don't know how many buttons I'll have at first?
class Block extends React.Component {
state = {
count: 0
};
handleClick = e => {
const count = this.state.count;
this.setState({ count: count + 1 });
};
render() {
return (
<div>
<button className="block" onClick={this.handleClick}>
<div className="counter">{this.state.count}</div>
</button>
<button className="block" onClick={this.handleClick}>
<div className="counter">{this.state.count}</div>
</button>
</div>
);
}
}
This is more of an issue of learning how to think in react.
If you need to be able to reuse a piece of functionality like a counter, you can make it its own component and have it manage its own state. Then you can reuse it wherever you need.
Here's an example:
class Counter extends React.Component {
state = {
count: 0
};
handleClick = () => {
// Use updater function when new state is derived from old
this.setState(prev => ({ count: prev.count + 1 }));
};
render() {
return (
<button className="block" onClick={this.handleClick}>
<div className="counter">{this.state.count}</div>
</button>
);
}
}
// Now you can use it dynamically like this:
class Block extends React.Component {
render() {
return (
<div>
<div>There are 4 counter component instances that each manage their own state.</div>
{[1,2,3,4].map(v => <Counter />)}
</div>
);
}
}
ReactDOM.render(<Block />, document.getElementById('root'));
<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="root"></div>
you should define two state and when press each button update the current state and you can render the current state in the dome like this
state = {
firstCount: 0,
secondCount: 0
}
and write your action (function) to handle update state like this
handleUpdateCount = stateName => {
this.setState({
[stateName]= this.state[stateName] + 1
})
}
then you should called this function like this =>
this.handleUpdateCount('firstCount')
If your buttons are dynamic you can set your state to be an array and update the relevant index
class Block extends React.Component {
state = [];
handleClick = index => {
this.setState(state => {
const newState = [...state]; //keep state immutable
!newState[index] && (newState[index] = 0)
newState[index]++
return newState
});
};
render() {
return (
<div>
{[1,2,3].map((value, index) => <button className="block" onClick={() => this.handleClick(index)}>
<div className="counter">{this.state[index]}</div>
</button>)}
</div>
);
}
}
You have to use another value to update function when new state is derived from old state (like increment)
import React, { Component } from 'react'
export class Ref3 extends Component {
constructor(props) {
super(props)
this.state = {
count:0
}
}
//use prevState to help you update the old value to a new one
clickHandler=()=>{
this.setState((prevState=>({
count:prevState.count+1
})))
}
render() {
return (
<div>
<button onClick={this.clickHandler}>Click To Count</button>
{this.state.count}
</div>
)
}
}
export default Ref3
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}/>
I'm trying to render dynamically a collection of component using componentDidUpdate.
This is my scenario:
var index = 0;
class myComponent extends Component {
constructor(props) {
super(props);
this.state = {
componentList: [<ComponentToRender key={index} id={index} />]
};
this.addPeriodHandler = this.addPeriodHandler.bind(this);
}
componentDidUpdate = () => {
var container = document.getElementById("container");
this.state.componentList.length !== 0
? ReactDOM.render(this.state.componentList, container)
: ReactDOM.unmountComponentAtNode(container);
};
addHandler = () => {
var array = this.state.componentList;
index++;
array.push(<ComponentToRender key={index} id={index} />);
this.setState = {
componentList: array
};
};
render() {
return (
<div id="Wrapper">
<button id="addPeriod" onClick={this.addHandler}>
Add Component
</button>
<div id="container" />
</div>
);
}
}
The problem is that componentDidUpdate work only one time, but it should work every time that component's state change.
Thank you in advance.
This is not how to use react. With ReactDOM.render() you are creating an entirely new component tree. Usually you only do that once to initially render your app. Everything else will be rendered by the render() functions of your components. If you do it with ReactDOM.render() you are basically throwing away everything react has already rendered every time you update your data and recreate it from scratch when in reality you may only need to add a single node somewhere.
Also what you actually store in the component state should be plain data and not components. Then use this data to render your components in the render() function.
Example for a valid use case:
class MyComponent extends Component{
state = {
periods: []
};
handleAddPeriod = () => {
this.setState(oldState => ({
periods: [
...oldState.periods,
{/*new period data here*/}
],
});
};
render() {
return (
<div id="Wrapper">
<button id="addPeriod" onClick={this.handleAddPeriod}>
Add Component
</button>
<div id="container">
{periods.map((period, index) => (
<ComponentToRender id={index} key={index}>
{/* render period data here */}
</ComponentToRender>
))}
</div>
</div>
);
}
}
}
Also you should not work with global variables like you did with index. If you have data that changes during using your application this is an indicator that is should be component state.
try
addHandler = () =>{
var array = this.state.componentList.slice();
index++;
array.push(<ComponentToRender key={index} id={index}/>);
this.setState=({
componentList: array
});
}
if that works, this is an issue with the state holding an Array reference that isn't changing. When you're calling setState even though you've added to the Array, it still sees the same reference because push doesn't create a new Array. You might be able to get by using the same array if you also implement shouldComponentUpdate and check the array length of the new state in there to see if it's changed.
So I am getting my hands dirty on React and I can't seem to figure out this simple problem (probably because of lack of sleep)
I want to add elements (or divs) inside the render on the fly when I click "Add Row".
How would I go on about it? Do I need to keep it in an array and within the render function, I will have to map it?
class SimpleExample extends React.Component {
constructor(props) {
super(props)
this.handleAddingDivs = this.handleAddingDivs.bind(this)
}
handleAddingDivs() {
const uniqueID = Date.now()
return (
<div>
This is added div! uniqueID: {uniqueID}
</div>
)
}
render() {
return (
<div>
<h1>These are added divs </h1>
<button className="btn-anchor-style add-row-link" type="button" onClick={this.handleAddingDivs}>{'Add Row'}</button>
</div>
)
}
}
Let's say you want to add multiple divs, so maintain a state variable for that, count or any other data (you can use any array also and store the unique value of all the divs), then use map or any other loop to create the divs for that.
Check this working snippet:
class SimpleExample extends React.Component {
constructor(props) {
super(props);
this.state = {count : 0}
this.handleAddingDivs = this.handleAddingDivs.bind(this)
}
handleAddingDivs() {
this.setState({count: this.state.count + 1})
}
renderDivs(){
let count = this.state.count, uiItems = [];
while(count--)
uiItems.push(
<div>
This is added div! uniqueID: {count}
</div>
)
return uiItems;
}
render() {
return (
<div>
<h1>These are added divs </h1>
<button className="btn-anchor-style add-row-link" type="button" onClick={this.handleAddingDivs}>{'Add Row'}</button>
{this.renderDivs()}
</div>
)
}
}
ReactDOM.render(<SimpleExample/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id ='app'/>
Try the code below. Whenever you click the button, a unique id is generated and stored in state.uids. In render(), added divs are rendered according to state.uids.
class SimpleExample extends React.Component {
constructor(props) {
super(props)
this.handleAddingDivs = this.handleAddingDivs.bind(this)
this.state = {uids:[]}
}
handleAddingDivs() {
let curr = this.state.uids;
const uniqueID = Date.now()
this.setState({uids:[...curr, uniqueID]});
}
render() {
return (
<div>
<h1>These are added divs </h1>
<button className="btn-anchor-style add-row-link" type="button" onClick={this.handleAddingDivs}>{'Add Row'}</button>
{ this.state.uids.map((uid, idx)=>
<div key={uid}>This is added div! uniqueID: {uid}</div>
)}
</div>
)
}
}
ReactDOM.render(<SimpleExample/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id ='app'/>
yes you will need to store data about new divs somewhere...
this is what flux/redux is sometimes used for: you store all data you need to render in Store and then you know what to render.
But if you whant this using only React, then use state !
Your state should be like this in your case:
{
addedDivs: [
{uniqueId: 123},
{uniqueId: 754},
]
}
then in render you will map it (don't forget to add key)
this.state.addedDivs.map((item) = > {return (<div key={item.uniqueId}></div>) })
and onClick you should just add some, using setState:
this.setState((prevState, props) => {
var addedDivs = prevState.addedDivs;
var uniqueId = Date.now();
addedDivs.push({uniqueId: uniqueId});
return {addedDivs: addedDivs}
});
You are thinking of terms of DOM manipulation/jQuery. When you use React, you need think in terms of data, and how it relates to your DOM.
In your case, you need to:
Update your component state with a new row, everytime there is a click to the 'Add Row" button, in the handleAddingDivs() method
Render rows based on the state, in the render() method
class SimpleExample extends React.Component {
constructor(props) {
super(props)
this.handleAddingDivs = this.handleAddingDivs.bind(this)
}
//Modify state of component when 'Add Row' button is clicked
handleAddingDivs() {
const uniqueID = Date.now();
this.setState({rows: this.state.rows.concat(uniqueId)});
}
//The `render()` function will be executed everytime state changes
render() {
return (
<div>
<h1>These are added divs </h1>
//The rows are rendered based on the state
{this.state.rows.map(function(uniqueId) {
return (
<div key={item.uniqueId}>This is added div! uniqueID: {uniqueID}</div>
)
}}
<button className="btn-anchor-style add-row-link" type="button" onClick={this.handleAddingDivs}>{'Add Row'}</button>
</div>
)
}
}