Handling Arrays & Objects in componentDidMount (React) - reactjs

Imagine that you have a component that bridges an imperative API to react component, for example, Mapbox API.
So you made yourself a Map component and you want the every time that user changes the center prop, the map updates to the new coordinates. It looks something like this:
class Map extends Component {
el = React.createRef();
componentDidMount(props) {
this.map = new mapboxgl.Map({
container: this.el.current,
style: 'mapbox://styles/mapbox/streets-v9',
center: this.props.center,
zoom: this.props.zoom
});
}
componentDidUpdate(prevProps) {
// notice this condition
if (prevProps.center !== this.props.center) {
this.map.setCenter(this.props.center);
}
}
componentWillUnmount() {
this.map && this.map.remove();
}
render() {
return (
<div ref={this.el} className="map" />
)
}
}
And we use it like so:
<Map center={[35.173906, 32.706769]} zoom={16} />
Now here is the catch: since we pass a new array every time, the condition is always true, even for changes unrelated to the map at all.
I made a simple example to demonstrate it:
https://codesandbox.io/s/pmz40wkm8j
Notice that when we increment the counter, the map center updates too.
There are 2 ways I can think of to solve it:
using _.isEqual which will work. but I want if it will affect performance for large arrays and Objects, for example, you can pass a pretty lengthy object of layers to mapbox.
Enforce the user to use Immutable.js, but it doesn't feel right because not everyone feels comfortable with it
What will be the best approach to handle that in your opinion?

I think _.isEqual is superfluous here, just use this function in your componentDidUpdate
centerPropsHasBeenChanged(prevProps, props) {
return (
props.center[0] !== prevProps.center[0] || props.center[1] !== prevProps.center[1]
)
}
Option 2
If you're using big arrays (so not coordinates, but something else), it looks like the fastest option is to JSON.stringify() and compare then

Related

ReactJS: How to render a collection of objects

So I'm quite new on web development last couple of days. I come from c++ background and I can't wrap my head through all the principles of reactjs. I have 2 classes. The child class called JobAd should render some information that it got from props.
export default class JobAd extends Component {
constructor(props) {
super(props);
this.state ={
index: props.index,
id: props.jobId,
name: props.name,
description: props.description,
location: props.location,
adress: props.adress,
alreadyApplied: props.alreadyApplied,
open: false,
// toggleJob: props.toggleJob,
};
this.toggleJob = props.toggleJob;
}
render() {
return (
<div className={`${styles.jobAd} d-flex` + "job " + (this.state.open ? 'open': '')} key={this.state.index} onClick={() => this.toggleJob(this.state.index)}>
<div className={`${styles.jobTitle}`}>
{this.state.location} - {this.state.name}
</div>
<div className={`${styles.jobDetails}`}>
<div className={`${styles.jobDescription}`}> {this.state.description}</div>
<div className={`${styles.jobAdress}`}>{this.state.adress}</div>
<ApplyButton jobId= {this.props.id} alreadyApplied = {this.props.alreadyApplied}/>
</div>
</div>
)
}
}
The second class, queries a mongoDB db and creates jobAd objects populating them from the info gotten from db.
class JobExplorer extends React.Component
{
...
result.data.jobs.forEach(job => {
var find = job.employees.find(obj => obj === userId);
if (!(find === undefined)) {
alreadyApplied = true;
}
var toPush = new JobAd ({
index: i,
id:job._id,
description:job.description,
name:job.name,
location:job.locationName,
adress:job.locationAdress,
alreadyApplied:alreadyApplied,
open:false,
toggleJob: this.toggleJob.bind(this)
});
jobList2.push(toPush);
console.log("look");
console.log(jobList2)
});
this.setState({
jobList: jobList2
})
this.setState({
error: null,
jobs: result.data.jobs
});
...
render()
{
console.log("look2");
console.log(this.state.jobList);
return (
<div><Navigation />
{this.state.jobList}
</div>
);
}
But I am faced with the following error which I cannot find a fix for.
Error: Objects are not valid as a React child (found: object with keys {props, context, refs, updater, state, toggleJob}). If you meant to render a collection of children, use an array instead.
How should I instantiate those objects so I could render them using the "architecture" I wrote. Is there a fundamental flaw that I have in my classes?
The below snippet doesn't work because new will return an object (this) not the react component.
So, instead of
var toPush = new JobAd({
index: i,
id: job._id,
...
});
jobList2.push(toPush);
you can do this
var toPush = <JobAd
index={i}
id={job._id}
...
/>;
The above snippet works because <JobAd ... /> is converted to React.createElement(JobAd, ... ). However, you still shouldn't do it like this. since there are a lot of better ways to do this. one of them is:
save just the data in joblist and then render the data list on JobAd component
like below:-
render(){
return this.state.joblist.map((job, i) => (
<JobAd
key={job._id}
index={i}
...
/>
));
}
The key is a really important thing. Read about it: https://reactjs.org/docs/lists-and-keys.html
Things that could be improved:-
Don't copy props in the state as you are doing in JobAd class instead directly render the props.
Don't call setState twice as in JobExplorer. you could set all the keys in
setState at the same time. since that would render the component twice.
Suggestions:-
You should avoid using var as that might cause some issues here.
since, you are just a starter, try using functional component first. they are
quite easier to grasp
You seem to have a misconception about state/props in React and web development. It's very normal; I learned python and Java first and many tutorials seem to assume that people just know this already.
"State" in generally refers to variables containing/referring to values that can change without a page refresh in your application. If you know a value is not going to change, it does not need to be held in state. Storing it in a normal variable is exactly what you should do.
"Props" is just another word for arguments that are passed to React components. There's more to it in reality, but as a beginner, that's all you need to really know for now.
So in your job add, things like name, address, jobs, description shouldn't go in state because they aren't going to change as a result of user interaction or for any other reason, unless the underlying data they are loaded from changes, but then that wouldn't be handled by React but instead by the API that your app gets data from. They should just be rendered, so refer to them like this.props.address in your render method. The value for open, however, need to be in state, because that definitely can change.
As for the error, it looks like you are not calling JobAd correctly. You need to use the syntax <Job Ad/> rather than new JobAd...that won't work in React.
I would recommend doing a tutorial to get the basics down.

Migrating away from componentWillReceiveProps

The componentWillReceiveProps is becoming deprecated, however, I am unclear as to how to migrate away from it. For example, a simplified version of my current looks something like this:
import Reorder, {reorder, reorderImmutale, reorderFromTo, reorderFromToImmutable} from 'react-reorder'
class ObjectsArea extends React.Component {
constructor(props) {
super(props);
this.state = {
items: this.props.objects ? this.props.objects.items : []
};
}
componentWillReceiveProps(nextProps){
//May have to do a deep compare between nextProps.items and current items?
if (nextProps.objects){
this.setState({items: this.nextProps.objects.items})
}
}
onReorder (event, previousIndex, nextIndex, fromId, toId) {
let new_items = reorder(this.state.items, previousIndex, nextIndex)
this.setState({
items: new_items
});
//call to parent function
}
render(){
orderable_items = <Reorder reorderId="objects" onReorder={this.onReorder.bind(this)}>
{
this.state.items.map(item => (
<div key={item.id}>
{item.text}
</div>
))
}
</Reorder>
return (
<div>{orderable_items}</div>
)
}
My requirements:
Sometimes there will be no objects property (there isn't one on initial load)
When there is an objects property a sortable/draggable list is created using the react-reorder component
When items in the list are dragged to be rearranged the onReorder function is called.
The onReorder function should do two things: update the list on the screen, call a parent function passed in from props.
Currently all of this will work with componentWillReceiveProps, however, what is the proper way to migrate away from componentWillReceiveProps based on the above requirements?
While Tolsee's answer is perfectly correct it is also worth mentioning that the react docs suggest removing derived state (state that is calculated based on props) altogether. There is a great article here that is a great read in my opinion.
Your example fits the Anti-pattern: Unconditionally copying props to state example perfectly.
Without knowing your environment I cannot recommend a solution certainly, but to me it looks like you will be able to use the Fully controlled component example.
In that case, you'd need to lift your state up, simply use objects.items to render your Reorder child, and during the onReorder event simply call a function that you received as a prop.
In your problem you can do.
static getDerivedStateFromProps(nextProps, prevState){
if (nextProps.objects){){
return {items: this.nextProps.objects.items};
}
else return null;
}
Please follow this post for better understanding

Backbone => React - Higher Order Components, inheritance and specialisation

I have a legacy Backbone app which I have begun to rewrite in React. The app has a main view containing two subviews, arranged vetically. The top panel displays some data, and the bottom one displays the result of some algorithm taking this data as input. Since I have many different data sources, each with a different algorithm applied to it, I have an abstract base View class, which I then subclass for each data source, adding, decorating and overriding methods as necessary. Somewhat like this:
// Base View.
const BaseView = Backbone.View.extend({
events: {},
initialize() {
this.subViewA = // instantiate subview...
this.subViewB = // instantiate subview...
},
generateResultData() {
// 'Abstract' method which should be specialised to generate data rendered by subViewB...
},
render() {
// render subviews...
},
});
// Derived View.
const Derived = BaseView.extend({
events: {
// event handlers...
},
add(a, b) {
return a+b;
},
// additional methods...
generateResultData() {
return {
result: this.add(2,2);
}
},
})
This results in a shallow hierarchy of many similar View classes. It's all terribly imperative, but it's a simple, intuitive and easy-to-reason-about pattern, and just works. I'm struggling to see how to achieve the same thing in React, however. Given that subclassing of subclasses of React.Component is considered an anti-pattern, my focus has naturally been on composition, and in particular Higher Order Components. HOCs (which I find beautiful, but unintuitive and often just downright confusing) seem to involve adding general features, rather than specialising/refining something more general. I have also considered passing in more specialised versions of Componenet methods through props. but that just means I have to use the same boilerplate Component definition over and over again:
// General functional component, renders the result of prop function 'foo'.
function GeneralComponent(props) {
const foo = this.props.foo || ()=>"foo";
return (
<div>
<span> { this.props.foo() } </span>
</div>
)
}
// Specialised component 1, overrides 'foo'.
class MySpecialisedComponent extends React.Component {
foo() {
return this.bar()
}
bar() {
return "bar"
}
render() {
return (
<GeneralComponent foo={this.foo} />
)
}
}
// Specialised component 2, overrides 'foo' and adds another method.
class MyOtherSpecialisedComponent extends React.Component {
foo() {
return this.bar() + this.bar()
}
bar() {
return "bar"
}
baz() {
return "baz"
}
render() {
return (
<GeneralComponent foo={this.foo} />
)
}
}
The above is a very simplistic case, obviously, but essentially captures what I need to do (though I would of course be manipulating state, which the example does not do, for simplicity). I mean, I could just do things like that. But I want to avoid having to repeat that boilerplate all over the place. So is there a simpler and more elegant way of doing this?
Generally, if a component is stateless and doesn't use lifecycle hooks, there are no reasons for it to be Component class. A class that acts as a namespace and doesn't hold state can be considered an antipattern in JavaScript.
In constrast to some other frameworks, React doesn't have templates that would need to map variables in order for them to be available in view, so the only place where bar function needs to be mentioned is the place where it's called. JSX is an extension over JavaScript, JSX expressions can use any names that are available in current scope. This allows to compose functions without any classes:
const getBar => "bar";
const getBaz => "baz";
const getBarBaz => getBar() + getBaz();
const MySpecialisedComponent = props => <GeneralComponent foo={getBar} />;
const MyOtherSpecialisedComponent = props => <GeneralComponent foo={getBarBaz} />;
An anonymous function could be passed as foo prop instead of creating getBarBaz but this is generally discouraged because of unnecessary overhead.
Also, default prop values could be assigned with defaultProps without creating new ()=>"foo" function on each component call:
function GeneralComponent({ foo }) {
return (
<div>
<span> {foo()} </span>
</div>
)
}
GeneralComponent.defaultProps = { foo: () => 'foo' };
IMO what is throwing you off isn't inheritance vs composition, it's your data flow:
For example, many of my derived views need to do custom rendering after the main render. I'm using a third-party SVG library, and the data rendered into the 'result' subview is derived from analysis of rendered SVG elements in the main data view above it
So what you're trying to do here is have a child update props of a distantly related component after render, correct? Like this?
// after the svg renders, parse it to get data
<div id="svg-container">
<svg data="foo" />
<svg data="bar />
</div>
// show parsed data from svg after you put it through your algos
<div id="result-container">
// data...
</div>
There's a lot of state management libraries out there that will help you with this problem, that is, generating data in one component and broadcasting it to a distantly related component. If you want to use a tool built-in to react to address this you may want to use context, which gives you a global store that you can provide to any component that wants to consume it.
In your example your child classes have data-specific methods (add, etc.). IMO it's more typical in react to have a generic class for displaying data and simply passing it down map functions as props in order to rearrange/transform the rendered data.
class AbstractDataMap extends PureComponent {
static defaultProps = {
data: [],
map: (obj, i) => (<div key={i}>{obj}</div>)
};
render() {
const { data, map, children } = this.props;
const mapped = data.map(map);
return (
<Fragment>
{mapped.map((obj, i) => (
children(obj, i)
))}
</Fragment>
);
}
}
// in some other container
class View extends Component {
render() {
return (
<div>
<AbstractDataMap data={[1, 2, 3]} map={(n) => ({ a: n, b: n + 1 })}>
{({ a, b }, i) => (<div key={i}>a: {a}, b: {b}</div>)}
</AbstractDataMap>
<AbstractDataMap data={[2, 4, 6]} map={(n) => (Math.pow(n, 2))}>
{(squared, i) => (<div key={i}>squared: {squared}</div>)}
</AbstractDataMap>
</div>
);
}
}
IMO this pattern of using an HOC to abstract away the labor of explicitly using .map in your render calls (among other uses) is the pattern you are looking for. However, as I stated above, the HOC pattern has nothing to do your main issue of shared data store across sibling components.
Answering my own question, which I've never donw before...
So my question really arose from a concern that I would need to refactor a large, imperative and stateful codebase so as to integrate with React’s composition-based model (also with Redux). But it occurred to me after reading the (very insightful and helpful) responses to my question that my app has two parallel parts: the UI, and an engine which runs the algorithms (actually it's a music analysis engine). And I can strip out the Backbone View layer to which the engine is connected quite easily. So, using React’s context API I've built an ‘AnalysisEngineProvider', which makes the engine available to subcomponents. The engine is all very imperative and classically object-oriented, and still uses Backbone models, but that makes no difference to the UI as the latter has no knowledge of its internals - which is how it should be (the models will likely be refactored out at some point too)...
The engine also has responsibility for rendering the SVG (not with BB views). But React doesn’t know anything about that. It just sees an empty div. I take a ref from the div and pass it to the engine so the latter knows where to render. Beyond that the engine and the UI have little contact - the divs are never updated from React state changes at all (other components of the UI are though, obviously). The models in the engine only ever trigger updates to the SVG, which React knows nothing about.
I am satisfied with this approach, at least for now - even if it's only part of an incremental refactor towards a fully React solution. It feels like the right design for the app whatever framework I happened to be using.

React native performance issue

I am using coincap api's to first fetch Data of about 1500+ crypto currency and then Web-socket to update the updated value of crypto Currency.
I a using redux to manage my state here
Inside My componentDidMount(), I am calling a redux action fetchCoin which fetches the value of the coin
componentDidMount() {
this.props.fetchCoin()
}
And then In return I am doing something like this
<FlatList
data={this.state.searchCoin ? displaySearchCrypto : this.props.cryptoLoaded}
renderItem={({ item }) => (
<CoinCard
key={item["short"]}
coinShortName = {item["short"]}
coinName = {item["long"]}
coinPrice = {item["price"].toFixed(2)}
percentChange = {item["perc"].toFixed(2)}
/>
Then I have a web-socket which updates the value of cryptocurrency like this
componentDidUpdate() {
if (this.state.updateCoinData || this.updateCoinData.length < 1 ) {
this.updateCoinData = [...this.props.cryptoLoaded];
this.setState({updateCoinData: true})
}
this.socket.on('trades', (tradeMsg) => {
for (let i=0; i< this.updateCoinData.length; i++) {
if (this.updateCoinData[i]["short"] == tradeMsg.coin ) {
//Search for changed Crypto Value
this.updateCoinData[i]["perc"] = tradeMsg["message"]["msg"]["perc"]
this.updateCoinData[i]["price"] = tradeMsg['message']['msg']['price']
//Update the crypto Value state in Redux
this.props.updateCrypto(this.updateCoinData);
}
}
})
}
Now, While this work, the problem is that this is slowing my app like hell since whenever the socket sends new data, it has to render every component and hence events like touch and search takes lot of time to execute. [Update] It turns out my app is rendering something even If i remove socket connection, check out update 2
[Question:] What should I do so that I can improve the performance of App? (Something like not using state or using DOM to update my app and so on).
[Update 1:] I am using https://github.com/irohitb/Crypto
And these two are js files where all the logic is happening
https://github.com/irohitb/Crypto/blob/master/src/container/cryptoContainer.js
https://github.com/irohitb/Crypto/blob/master/src/components/CoinCard.js
I have also move from map to Flatlist.
[Update: 2] I found that there are endless render happening inside my App which is probably keeping my thread busy (I mean it is endless & unnecessarily passing props). I asked the same question on separate Stackoverflow thread but didn't received a proper response and since it is related to performance, I thought about putting a bounty on it here.
Please check this thread: infinite Render in React
[Answer Update:] While there are many great answers here, Just in case someone wants to understand how it worked, You could probably clone my repository and go back to before this commit. I have linked the commit to the point where my problems was solved (so you might need to go back and see what I was doing wrong). Also, All the answers were very useful and not hard to comprehend so you should definitely go through them.
Each time your component updates it starts a new socket which results in a memory leak and will cause this.props.updateCrypto(updateCoinData); to be called multiple times for the same data. This can be fixed by opening the socket in componentDidMount() and closing it in componentWillUnmount().
You can also buffer multiple record updates and change the FlatList data in one go every couple of seconds.
Edit, working example (App.js):
import React, { Component } from 'react';
import { Text, View, FlatList } from 'react-native';
import SocketIOClient from 'socket.io-client';
type Props = {};
export default class App extends Component<Props> {
constructor(props) {
super(props);
this.currencies = {};
this.state = {
currenciesList: [],
}
}
componentDidMount() {
this.socket = SocketIOClient('https://coincap.io');
this.socket.on('trades', (tradeMsg) => {
const time = new Date();
// Store updates to currencies in an object
this.currencies[tradeMsg.message.msg.short] = {
...tradeMsg.message.msg,
time: time.getHours() + ':' + time.getMinutes() + ':' + time.getSeconds(),
};
// Create a new array from all currencies
this.setState({currenciesList: Object.values(this.currencies)})
});
}
componentWillUnmount() {
this.socket.disconnect();
}
render() {
return (
<FlatList
data={this.state.currenciesList}
extraData={this.state.currenciesList}
keyExtractor={(item) => item.short}
renderItem={({item}) => <View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
<Text style={{flex: 1}}>{item.time}</Text>
<Text style={{flex: 1}}>{item.short}</Text>
<Text style={{flex: 1}}>{item.perc}</Text>
<Text style={{flex: 1}}>{item.price}</Text>
</View>}
/>
);
}
}
There're many standard ways to improve react app performance, the most common:
use usual react optimizations (shouldComponentUpdate, PureComponent - read docs)
use virtual lists (limit visible parts of data)
In this case I would add:
Don't process data before optimizations - f.e. formatting data that didn't changed is at least unnecessary. You can insert intermediate component (optimization layer) that will pass/update formatted data into <CoinCard /> only on 'raw data' change.
You might not need Redux at all (store data in state) when data is used in one place/simple structure. Of course you can use redux for other globally shared app state (f.e. filtering options).
Use <FlatList /> (react-native), search for sth more suitable?
UPDATE
Some code was changed in mean time (repo), at this time (08.09) one issue still exist and probably causing memory leaks.
You're calling this.socket.on on each componentDidUpdate call (wrongly coded conditions) - continuously adding a new handler!
componentDidUpdate() {
// call all ONLY ONCE afer initial data loading
if (!this.state.updateCoinData && !this.props.cryptoLoaded.length) {
this.setState({updateCoinData: true}) // block condition
this.socket.on('trades', (tradeMsg) => {
// slice() is faster, new array instance
// let updateCoinData = [...this.props.cryptoLoaded];
let updateCoinData = this.props.cryptoLoaded.slice();
for (let i=0; i<updateCoinData.length; i++) {
//Search for changed Crypto Value
if (updateCoinData[i]["short"] == tradeMsg.coin ) {
// found, updating from message
updateCoinData[i]["long"] = tradeMsg["message"]["msg"]["long"]
updateCoinData[i]["short"] = tradeMsg["message"]["msg"]["short"]
updateCoinData[i]["perc"] = tradeMsg["message"]["msg"]["perc"]
updateCoinData[i]["mktcap"] = tradeMsg['message']['msg']["mktcap"]
updateCoinData[i]["price"] = tradeMsg['message']['msg']['price']
//Update the crypto Value state in Redux
this.props.updateCrypto(updateCoinData);
// record found and updated, no more looping needed
break;
}
}
})
}
}
Minor errors: initial fetching states set to true in reducers.
Searching for performance issues I would look at <CoinCard />:
make it PureComponent;
increased and decreased aren't required to be saved at state which forces unnecessasry render calls;
I would use update time (not saved in state, just passed as prop in parent and only for updated rows, within updateCoinData in code above) and derive direction (check for 0 and sign only) of difference (already calculated in perc) only for visible items (from render) and only during time limit (difference between render time and data update prop). setTimeout can be used, too.
finally removing componentWillReceiveProps, componentDidUpdate and shouldComponentUpdate should (highly?) improve performance;
Like Bhojendra Rauniyar said, you should use shouldComponentUpdate in CoinCard. You probably also want to change your FlatList, your downsized sample has the FlatList in a ScrollView, this causes the FlatList to fully expand, thus rendering all it's items at once.
class cryptoTicker extends PureComponent {
componentDidMount() {
this.socket = openSocket('https://coincap.io');
this.props.fetchCoin()
this.props.CurrencyRate()
this.socket.on('trades', (tradeMsg) => {
for (let i=0; i< this.updateCoinData.length; i++) {
if (this.updateCoinData[i]["short"] == tradeMsg.coin ) {
//Search for changed Crypto Value
this.updateCoinData["short"] = tradeMsg["message"]["msg"]["short"]
this.updateCoinData[i]["perc"] = tradeMsg["message"]["msg"]["perc"]
this.updateCoinData[i]["price"] = tradeMsg["message"]['msg']['price']
//Update the crypto Value state in Redux
this.props.updateCrypto(this.updateCoinData);
}
}
})
}
componentWillReceiveProps(newProps){
// Fill with redux data once
if (this.updateCoinData.length < 1 && newProps.cryptoLoaded) {
this.updateCoinData = [...newProps.cryptoLoaded];
}
}
render() {
return (
<View style={{height: '100%'}}>
<Header/>
<FlatList
style={{flex:1}}
data={this.props.cryptoLoaded}
keyExtractor={item => item.short}
initialNumToRender={50}
windowSize={21}
removeClippedSubviews={true}
renderItem={({item, index}) => (
<CoinCard
index={index}
{...item}
/>
)}
/>
</View>
)
}
}
class CoinCard extends Component {
shouldComponentUpdate(nextProps) {
return this.props.price !== nextProps.price || this.props.perc !== nextProps.perc
}
render() {
console.log("here: " + this.props.index);
return (
<View>
<Text> {this.props.index} = {this.props.long} </Text>
</View>
)
}
}
When rendering Flatlist you should consider using PureComponent or utilizing shouldComponentUpdate hook to update only if required.
From the doc:
If your application renders long lists of data (hundreds or thousands of rows), we recommended using a technique known as “windowing”. This technique only renders a small subset of your rows at any given time, and can dramatically reduce the time it takes to re-render the components as well as the number of DOM nodes created.
Take a deep drive to this performance guide.
If you still want some advanced look through, then I will recommend you to look into the following threads:
FlatList and VirtualizedList Scroll performance is laggy after 30+ rows
Performance problems with react when using a big list
You should never do API calls in React's componentWillMount() lifecycle method, instead it should be done in componentDidMount().
Check out this very neat article on lifecycle methods and what should be done in which method: https://medium.com/#baphemot/understanding-reactjs-component-life-cycle-823a640b3e8d.
Many will be tempted to use this function in order to send a request to fetch data and expect the data to be available before the initial render is ready. This is not the case — while the request will be initialized before the render, it will not be able to finish before the render is called.
Instead of creating a socket to update the coinData, you might want to make use of redux subscibe/unsubscribe methods.

React JS Component "wait for props"

This is not a question as much "how to make this work" as much as it is a "was this the best way." Here's my code:
/**
* React Static Boilerplate
* https://github.com/koistya/react-static-boilerplate
* Copyright (c) Konstantin Tarkus (#koistya) | MIT license
*/
import React, { Component } from 'react';
// import './InputWidgetText.scss';
import ContentBlock from '../ContentBlock';
var i = 0;
var contentBlocks = [];
var ContentContainer = React.createClass({
addNewBlock: function(){
i++;
contentBlocks.push(<ContentBlock key={i} index={i}/>)
this.forceUpdate();
},
render: function(){
if (this.props.inputs) {
contentBlocks = this.props.inputs.map(function(item, index){
i++;
return(<ContentBlock key={index} index={index} content={item} />)
});
}
return (
<div>
{contentBlocks}
<button onClick={this.addNewBlock}>+</button>
</div>
)
}
});
export {ContentContainer as default};
The problem is that every so often on a refresh the props.inputs are not getting passed down to this component and throwing an error when I try to map undefined. So the simple solution is to put the map process in an if check for whether or not the props are there yet - is that actually the right way to handle this? My data is passed in via a reflux mixin on the parent. I just feel like there might be a more proper way to handle this. Thanks for the feedback!
May I strongly suggest you refactor your code to do away with the file variables i and contentBlocks.
The contentBlocks variable seems completely unnecessary, whilst your i variable should be part of the state. Whilst you're at it, give i a more meaningful name, e.g. blockCount.
getInitialState: function () {
return {
blockCount: 0
};
},
Then define your click event handler to modify the state:
addNewBlock: function () {
this.setState({
blockCount: this.state.blockCount + 1
});
},
Every time you call setState(), React will trigger a re-render. You should never need to call forceUpdate().
Finally, your render() function should return its content based SOLELY on this.props and this.state. That is, for any given props and state, the output will be predictable. Think of this.props and this.state as input parameters to the render() function. That is all render() can, or needs to, know about.
I won't try to write the render() function as I'm not sure exactly what you're trying to achieve with this component. But for a given this.props.input and this.state.blockCount (or whatever you choose to use as props and state) you should know exactly what you're outputting.
I know I haven't directly answered the question you put, but I hope this clarifies some React concepts.

Resources