Create reference to non loaded preact component - javascript

I am trying to change some text in a preact component based on an object the user clicks in this threejs scene. I am using Matt DesLauriers' template for this app as I wanted to see how professionals might do it. This is the first time I have used react(although its actually preact) aswell as threeJS.
In theory my method should work except for one problem. The way Matt DesLauriers set up the loading screen means that the main UI component, named Landing, is rendered after the loading has finished. This achieved by
getContent (section) {
// You are probably better off using a real "Router" for history push etc.
// NB: Ensure there is a 'key' attribute so transition group can create animations
switch (section) {
case 'Preloader': return <Preloader key='Preloader' />;
default:
case 'Landing': return <Landing key='Landing'/>;
}
}
render () {
const classes = classnames({
'App': true
});
const section = this.state.section;
const content = this.getContent(section);
// Render the WebGL if loaded
// And also render the current UI section on top, with transitions
return (
<div className={classes} ref={ c => { this.container = c; } }>
{ this.state.isLoaded && <WebGLCanvas />}
<PreactTransitionGroup className='content' >
{ content }
</PreactTransitionGroup>
</div>
);
}
I need to be able to reference Landing so I can set it's state, however giving it a ref case 'Landing': return <Landing key='Landing' ref={this.ref}/>; results in a reference to an empty object. If I put the Landing component in directly it works exactly as intended.
Any idea how to fix this?
Also here is the public GitHub repo if you want.

Related

Gatsby fetch data from local storage best practice

I would like to fetch data from local storge (in runtime) every time the app starts, then I store it in a store.
Gatsby docs explain the way to do this:
https://www.gatsbyjs.com/docs/conceptual/data-fetching/
Basically just use the useEffect hook in a page and get the data from local storage. However, I would like to get this data independently of the page being visited. For example, if I fetch the data on the index page, and the user refreshes another page, the data won't be fetched. I would like to do it in the equivalent of App.tsx file in a regular React app.
My current solution is to do it in wrap-pages file:
const MyLocalStorage = ({ children }) => {
const { getLocalStorage} = fromStore()
useEffect(() => {
getLocalStorage() // fetches data from local storage
}, [])
return null
}
export function wrapPagesDeep({ element }) {
return (
<>
<MyLocalStorage/>
{element}
</>
)
}
This however doesn't make much sense. This file is intended for wrapping components, not for data fetching. What would be the correct way to do that please?
There are multiple ways depending on your architecture, design system, and use cases (for example, from a provider to a wrapper, from an isolated service to a controller, etc.).
There is no such thing as "best practice" without knowing everything involved in the decision: making some super complicated and isolated logic (like adding an MVVM: controller, stores, etc.) may look good but can be an extremely bad practice for a simple scenario, and vice-versa: an easy and straightforward approach can be a bad solution for a complex app.
Following your approach, I think it could be easily isolated (and reused) by moving this logic into a Layout (or a wrapper that wraps your application) and adding a location prop to it. Something like:
const Layout = ({ children, location = {} }) => {
const { getLocalStorage} = fromStore()
useEffect(() => {
getLocalStorage() // fetches data from local storage
}, [location])
return (
<>
<main>{children}</main>
</>
)
}
export default Layout
Then, in every use of Layout:
const SomePage = ({ location }) => {
return (
<Layout location={location}>
<h1>Some content</h1>
</Layout>
);
};
Note: location prop is inherited by default in all top-level components (pages and templates) as you can see in the docs
So every time the location changes, you will fetch the local storage data. This can be easily moved to a provider that updates the value automatically. You will only need to wrap your application accordingly.

Plotly - Dash #app.callback does not re-render Dash elements inside custom component unless re-render is triggered somehow

Inside usage.py I try to update graph-tooltip:
#app.callback(
Output("grid-value", "children"),
Input("live-graph", "hoverData"),
)
def display_hover(hoverData):
if hoverData is None:
return no_update
# demo only shows the first point, but other points may also be available
pt = hoverData["points"][0]
x = pt["x"]
y = pt["y"]
children = f"{x}:{y}"
return children
Both the live-graph and grid-value elements are inside custom Dash component (dash-react-flow, but with a new node type that can contain anything passed to it from Dash).
Now if I over a the live-graph, it gets updated only once I click, so it seems not to be re-rendering properly.
I implemented the node:
function AnyContentNode({ data }) {
return (
<div className="any-content-node" id={data.id} >
<div>
{
data.elements?.map((node, index) => {
return window.elements[data.elementId][node];
})
}
</div>
{
data.handles?.map((node, index) => {
return <Handle key={index} type={node.direction} position={node.position} id={node.id} />
})
}
</div>
);
}
where window.elements contains the node that is not being updated properly. I suspect that storing elements inside window.elements might be the cause of this, but I did not find any other way to pass those elements into the custom node without having to change the source code for ReactFlow.
So far it works for non-dynamic elements, but currently I got stuck when I have to update dynamic element. It gets occasionally updated, but that is not very good user-experience.
Is there a way to force re-render/or somehow fix this? I am quite new to React, so I don't have a good knowledge of the default hooks etc. I tried to see whether componentDidUpdate gets triggered and it does (so many times, every second).

Reactjs: how to write a method to handle component creation and unmount

So let's say there is acomponent which displays 2 child components: a document list and the selected document. By default the selected document component is not rendered, only when a document is selected from the list. And i also want this whole thing work when a new document is selected from the list.
There is a state which holds the document content and responsible for the selected document rendering, so i thought i'm going to set it to null in the method which handles the list item selection in order to unmount the previously created child component. Like this (excerpts from the parent class):
handleResultListItemClick(docname) {
if (this.state.sectioncontainer != null) this.setState({sectioncontainer: null},()=>{console.log("muhoo");});
var selected_doc = this.state.resultlist.filter((doc) => {
return docname === doc.properties.title;
});
this.setState({sectioncontainer: selected_doc[0].content.sections},()=>{console.log("boohoo");});
}
...
render() {
return (
...
{this.state.sectioncontainer != null && <SectionContainer listOfSections={this.state.sectioncontainer}/>}
);
}
The only problem is that state handling is not fast enough (or something) in react, because putting the state nullification and its new value setting in the same method results in no change in ReactDOM.
With the above code, the component will be created when the parent component first rendered, but after selecting a new doc in the list results in no change.
How should i implement this in way which works and also elegant?
I found this: ReactDOM.unmountComponentAtNode(container) in the official react docs. Is this the only way? If yes, how could i get this container 'name'?
Edit:
Based on the answers and thinking the problem a bit more through, i have to explain more of the context.
As kingdaro explained, i understand why there is no need to unmount a child component on a basic level, but maybe my problem is bit more sophisticated. So why did i want to unmount the child?
The documents consist of several subsections, hence the document object which is passed to the child component is an array of objects. And the document is generated dynamically based on this array the following way (excerpt from the SectionContainer class which is responsible to display the document):
buildSectionContainer() {
return this.props.listOfSections.map((section, index) =>
{
if (section.type === 'editor') return (
<QuillEditor
key={index}
id={section.id}
modules={modules}
defaultValue={section.content}
placeholder={section.placeholder}
/>
);
else if (section.type === 'text') return (
<div key={index}>{section.value}</div>
);
}
);
}
render() {
return (
<div>
{this.buildSectionContainer()}
</div>
);
}
The SectionContainer gets the array of objects and generate the document from it according to the type of these sections. The problem is that these sections are not updated when a different doc is selected in the parent component. I see change only when a bigger length array is passed to the child component. Like the firstly selected doc had an array of 2 elements, and then the newly selected doc had 3 elements array of sections and this third section is added to the previously existing 2, but the first 2 sections remained as they were.
And that’s why i though it’s better to unmount the child component and create a new one.
Surely it can happen that i miss something fundamental here again. Maybe related to how react handles lists. I just dont know what.
Edit2:
Ok, figured out that there is a problem with how i use the QuillEditor component. I just dont know what. :) The document updates, only the content of QuillEditors doesnt.
The reason your current solution doesn't actually do anything is because React's state updates are batched, such that, when setState is called a bunch of times in one go, React "combines" the result of all of them. It's not as much of a problem with being "not fast enough" as it is React performing only the work that is necessary.
// this...
this.setState({ message: 'hello', secret: 123 })
this.setState({ message: 'world' })
// ...becomes this
this.setState({ message: 'world', secret: 123 })
This behavior doesn't really have much to do with the problem at hand, though. As long as your UI is a direct translation of state -> view, the UI should simply update in accordance to the state.
class Example extends React.Component {
state = {
documentList: [], // assuming this comes from the server
document: null,
}
// consider making this function accept a document object instead,
// then you could leave out the .find(...) call
handleDocumentSelection = documentName => {
const document = this.state.documentList.find(doc => doc.name === documentName)
this.setState({ document })
}
render() {
const { document } = this.state
return (
<div>
<DocumentList
documents={this.state.documentList}
onDocumentSelection={this.handleDocumentSelection}
/>
{/*
consider having this component accept the entire document
to make it a little cleaner
*/}
{document && <DocumentViewer document={document.content.sections} />}
</div>
)
}
}

Big list performance with React

I am in the process of implementing a filterable list with React. The structure of the list is as shown in the image below.
PREMISE
Here's a description of how it is supposed to work:
The state resides in the highest level component, the Search component.
The state is described as follows:
{
visible : boolean,
files : array,
filtered : array,
query : string,
currentlySelectedIndex : integer
}
files is a potentially very large, array containing file paths (10000 entries is a plausible number).
filtered is the filtered array after the user types at least 2 characters. I know it's derivative data and as such an argument could be made about storing it in the state but it is needed for
currentlySelectedIndex which is the index of the currently selected element from the filtered list.
User types more than 2 letters into the Input component, the array is filtered and for each entry in the filtered array a Result component is rendered
Each Result component is displaying the full path that partially matched the query, and the partial match part of the path is highlighted. For example the DOM of a Result component, if the user had typed 'le' would be something like this :
<li>this/is/a/fi<strong>le</strong>/path</li>
If the user presses the up or down keys while the Input component is focused the currentlySelectedIndex changes based on the filtered array. This causes the Result component that matches the index to be marked as selected causing a re-render
PROBLEM
Initially I tested this with a small enough array of files, using the development version of React, and all worked fine.
The problem appeared when I had to deal with a files array as big as 10000 entries. Typing 2 letters in the Input would generate a big list and when I pressed the up and down keys to navigate it it would be very laggy.
At first I did not have a defined component for the Result elements and I was merely making the list on the fly, on each render of the Search component, as such:
results = this.state.filtered.map(function(file, index) {
var start, end, matchIndex, match = this.state.query;
matchIndex = file.indexOf(match);
start = file.slice(0, matchIndex);
end = file.slice(matchIndex + match.length);
return (
<li onClick={this.handleListClick}
data-path={file}
className={(index === this.state.currentlySelected) ? "valid selected" : "valid"}
key={file} >
{start}
<span className="marked">{match}</span>
{end}
</li>
);
}.bind(this));
As you can tell, every time the currentlySelectedIndex changed, it would cause a re-render and the list would be re-created each time. I thought that since I had set a key value on each li element React would avoid re-rendering every other li element that did not have its className change, but apparently it wasn't so.
I ended up defining a class for the Result elements, where it explicitly checks whether each Result element should re-render based on whether it was previously selected and based on the current user input :
var ResultItem = React.createClass({
shouldComponentUpdate : function(nextProps) {
if (nextProps.match !== this.props.match) {
return true;
} else {
return (nextProps.selected !== this.props.selected);
}
},
render : function() {
return (
<li onClick={this.props.handleListClick}
data-path={this.props.file}
className={
(this.props.selected) ? "valid selected" : "valid"
}
key={this.props.file} >
{this.props.children}
</li>
);
}
});
And the list is now created as such:
results = this.state.filtered.map(function(file, index) {
var start, end, matchIndex, match = this.state.query, selected;
matchIndex = file.indexOf(match);
start = file.slice(0, matchIndex);
end = file.slice(matchIndex + match.length);
selected = (index === this.state.currentlySelected) ? true : false
return (
<ResultItem handleClick={this.handleListClick}
data-path={file}
selected={selected}
key={file}
match={match} >
{start}
<span className="marked">{match}</span>
{end}
</ResultItem>
);
}.bind(this));
}
This made performance slightly better, but it's still not good enough. Thing is when I tested on the production version of React things worked buttery smooth, no lag at all.
BOTTOMLINE
Is such a noticeable discrepancy between development and production versions of React normal?
Am I understanding/doing something wrong when I think about how React manages the list?
UPDATE 14-11-2016
I have found this presentation of Michael Jackson, where he tackles an issue very similar to this one: https://youtu.be/7S8v8jfLb1Q?t=26m2s
The solution is very similar to the one proposed by AskarovBeknar's answer, below
UPDATE 14-4-2018
Since this is apparently a popular question and things have progressed since the original question was asked, while I do encourage you to watch the video linked above, in order to get a grasp of a virtual layout, I also encourage you to use the React Virtualized library if you do not want to re-invent the wheel.
As with many of the other answers to this question the main problem lies in the fact that rendering so many elements in the DOM whilst doing filtering and handling key events is going to be slow.
You are not doing anything inherently wrong with regards to React that is causing the issue but like many of the issues that are performance related the UI can also take a big percentage of the blame.
If your UI is not designed with efficiency in mind even tools like React that are designed to be performant will suffer.
Filtering the result set is a great start as mentioned by #Koen
I've played around with the idea a bit and created an example app illustrating how I might start to tackle this kind of problem.
This is by no means production ready code but it does illustrate the concept adequately and can be modified to be more robust, feel free to take a look at the code - I hope at the very least it gives you some ideas...;)
react-large-list-example
My experience with a very similar problem is that react really suffers if there are more than 100-200 or so components in the DOM at once. Even if you are super careful (by setting up all your keys and/or implementing a shouldComponentUpdate method) to only to change one or two components on a re-render, you're still going to be in a world of hurt.
The slow part of react at the moment is when it compares the difference between the virtual DOM and the real DOM. If you have thousands of components but only update a couple, it doesn't matter, react still has a massive difference operation to do between the DOMs.
When I write pages now I try to design them to minimise the number of components, one way to do this when rendering large lists of components is to... well... not render large lists of components.
What I mean is: only render the components you can currently see, render more as you scroll down, you're user isn't likely to scroll down through thousands of components any way.... I hope.
A great library for doing this is:
https://www.npmjs.com/package/react-infinite-scroll
With a great how-to here:
http://www.reactexamples.com/react-infinite-scroll/
I'm afraid it doesn't remove components that are off the top of the page though, so if you scroll for long enough you're performance issues will start to reemerge.
I know it isn't good practice to provide a link as answer, but the examples they provide are going to explain how to use this library much better than I can here. Hopefully I have explained why big lists are bad, but also a work around.
First of all, the difference between the development and production version of React is huge because in production there are many bypassed sanity checks (such as prop types verification).
Then, I think you should reconsider using Redux because it would be extremely helpful here for what you need (or any kind of flux implementation). You should definitively take a look at this presentation : Big List High Performance React & Redux.
But before diving into redux, you need to made some ajustements to your React code by splitting your components into smaller components because shouldComponentUpdate will totally bypass the rendering of children, so it's a huge gain.
When you have more granular components, you can handle the state with redux and react-redux to better organize the data flow.
I was recently facing a similar issue when I needed to render one thousand rows and be able to modify each row by editing its content. This mini app displays a list of concerts with potential duplicates concerts and I need to chose for each potential duplicate if I want to mark the potential duplicate as an original concert (not a duplicate) by checking the checkbox, and, if necessary, edit the name of the concert. If I do nothing for a particular potential duplicate item, it will be considered duplicate and will be deleted.
Here is what it looks like :
There are basically 4 mains components (there is only one row here but it's for the sake of the example) :
Here is the full code (working CodePen : Huge List with React & Redux) using redux, react-redux, immutable, reselect and recompose:
const initialState = Immutable.fromJS({ /* See codepen, this is a HUGE list */ })
const types = {
CONCERTS_DEDUP_NAME_CHANGED: 'diggger/concertsDeduplication/CONCERTS_DEDUP_NAME_CHANGED',
CONCERTS_DEDUP_CONCERT_TOGGLED: 'diggger/concertsDeduplication/CONCERTS_DEDUP_CONCERT_TOGGLED',
};
const changeName = (pk, name) => ({
type: types.CONCERTS_DEDUP_NAME_CHANGED,
pk,
name
});
const toggleConcert = (pk, toggled) => ({
type: types.CONCERTS_DEDUP_CONCERT_TOGGLED,
pk,
toggled
});
const reducer = (state = initialState, action = {}) => {
switch (action.type) {
case types.CONCERTS_DEDUP_NAME_CHANGED:
return state
.updateIn(['names', String(action.pk)], () => action.name)
.set('_state', 'not_saved');
case types.CONCERTS_DEDUP_CONCERT_TOGGLED:
return state
.updateIn(['concerts', String(action.pk)], () => action.toggled)
.set('_state', 'not_saved');
default:
return state;
}
};
/* configureStore */
const store = Redux.createStore(
reducer,
initialState
);
/* SELECTORS */
const getDuplicatesGroups = (state) => state.get('duplicatesGroups');
const getDuplicateGroup = (state, name) => state.getIn(['duplicatesGroups', name]);
const getConcerts = (state) => state.get('concerts');
const getNames = (state) => state.get('names');
const getConcertName = (state, pk) => getNames(state).get(String(pk));
const isConcertOriginal = (state, pk) => getConcerts(state).get(String(pk));
const getGroupNames = reselect.createSelector(
getDuplicatesGroups,
(duplicates) => duplicates.flip().toList()
);
const makeGetConcertName = () => reselect.createSelector(
getConcertName,
(name) => name
);
const makeIsConcertOriginal = () => reselect.createSelector(
isConcertOriginal,
(original) => original
);
const makeGetDuplicateGroup = () => reselect.createSelector(
getDuplicateGroup,
(duplicates) => duplicates
);
/* COMPONENTS */
const DuplicatessTableRow = Recompose.onlyUpdateForKeys(['name'])(({ name }) => {
return (
<tr>
<td>{name}</td>
<DuplicatesRowColumn name={name}/>
</tr>
)
});
const PureToggle = Recompose.onlyUpdateForKeys(['toggled'])(({ toggled, ...otherProps }) => (
<input type="checkbox" defaultChecked={toggled} {...otherProps}/>
));
/* CONTAINERS */
let DuplicatesTable = ({ groups }) => {
return (
<div>
<table className="pure-table pure-table-bordered">
<thead>
<tr>
<th>{'Concert'}</th>
<th>{'Duplicates'}</th>
</tr>
</thead>
<tbody>
{groups.map(name => (
<DuplicatesTableRow key={name} name={name} />
))}
</tbody>
</table>
</div>
)
};
DuplicatesTable.propTypes = {
groups: React.PropTypes.instanceOf(Immutable.List),
};
DuplicatesTable = ReactRedux.connect(
(state) => ({
groups: getGroupNames(state),
})
)(DuplicatesTable);
let DuplicatesRowColumn = ({ duplicates }) => (
<td>
<ul>
{duplicates.map(d => (
<DuplicateItem
key={d}
pk={d}/>
))}
</ul>
</td>
);
DuplicatessRowColumn.propTypes = {
duplicates: React.PropTypes.arrayOf(
React.PropTypes.string
)
};
const makeMapStateToProps1 = (_, { name }) => {
const getDuplicateGroup = makeGetDuplicateGroup();
return (state) => ({
duplicates: getDuplicateGroup(state, name)
});
};
DuplicatesRowColumn = ReactRedux.connect(makeMapStateToProps1)(DuplicatesRowColumn);
let DuplicateItem = ({ pk, name, toggled, onToggle, onNameChange }) => {
return (
<li>
<table>
<tbody>
<tr>
<td>{ toggled ? <input type="text" value={name} onChange={(e) => onNameChange(pk, e.target.value)}/> : name }</td>
<td>
<PureToggle toggled={toggled} onChange={(e) => onToggle(pk, e.target.checked)}/>
</td>
</tr>
</tbody>
</table>
</li>
)
}
const makeMapStateToProps2 = (_, { pk }) => {
const getConcertName = makeGetConcertName();
const isConcertOriginal = makeIsConcertOriginal();
return (state) => ({
name: getConcertName(state, pk),
toggled: isConcertOriginal(state, pk)
});
};
DuplicateItem = ReactRedux.connect(
makeMapStateToProps2,
(dispatch) => ({
onNameChange(pk, name) {
dispatch(changeName(pk, name));
},
onToggle(pk, toggled) {
dispatch(toggleConcert(pk, toggled));
}
})
)(DuplicateItem);
const App = () => (
<div style={{ maxWidth: '1200px', margin: 'auto' }}>
<DuplicatesTable />
</div>
)
ReactDOM.render(
<ReactRedux.Provider store={store}>
<App/>
</ReactRedux.Provider>,
document.getElementById('app')
);
Lessons learned by doing this mini app when working with huge dataset
React components work best when they are kept small
Reselect become very useful to avoid recomputation and keep the same reference object (when using immutable.js) given the same arguments.
Create connected component for component that are the closest of the data they need to avoid having component only passing down props that they do not use
Usage of fabric function to create mapDispatchToProps when you need only the initial prop given in ownProps is necessary to avoid useless re-rendering
React & redux definitively rock together !
Like I mentioned in my comment, I doubt that users need all those 10000 results in the browser at once.
What if you page through the results, and always just show a list of 10 results.
I've created an example using this technique, without using any other library like Redux.
Currently only with keyboard navigation, but could easily be extended to work on scrolling as well.
The example exists of 3 components, the container application, a search component and a list component.
Almost all the logic has been moved to the container component.
The gist lies in keeping track of the start and the selected result, and shifting those on keyboard interaction.
nextResult: function() {
var selected = this.state.selected + 1
var start = this.state.start
if(selected >= start + this.props.limit) {
++start
}
if(selected + start < this.state.results.length) {
this.setState({selected: selected, start: start})
}
},
prevResult: function() {
var selected = this.state.selected - 1
var start = this.state.start
if(selected < start) {
--start
}
if(selected + start >= 0) {
this.setState({selected: selected, start: start})
}
},
While simply passing all the files through a filter:
updateResults: function() {
var results = this.props.files.filter(function(file){
return file.file.indexOf(this.state.query) > -1
}, this)
this.setState({
results: results
});
},
And slicing the results based on start and limit in the render method:
render: function() {
var files = this.state.results.slice(this.state.start, this.state.start + this.props.limit)
return (
<div>
<Search onSearch={this.onSearch} onKeyDown={this.onKeyDown} />
<List files={files} selected={this.state.selected - this.state.start} />
</div>
)
}
Fiddle containing a full working example: https://jsfiddle.net/koenpunt/hm1xnpqk/
Try filter before loading into the React component and only show a reasonable amount of items in the component and load more on demand. Nobody can view that many items at one time.
I don't think you are, but don't use indexes as keys.
To find out the real reason why the development and production versions are different you could try profiling your code.
Load your page, start recording, perform a change, stop recording and then check out the timings. See here for instructions for performance profiling in Chrome.
React in development version checks for proptypes of each component to ease development process, while in production it is omitted.
Filtering list of strings is very expensive operation for every keyup. it might cause performance issues because of single threaded nature of JavaScript.
Solution might be to use debounce method to delay execution of your filter function until the delay is expired.
Another problem might be the huge list itself. You can create virtual layout and reuse created items just replacing data. Basically you create scrollable container component with fixed height, inside of which you will place list container. The height of list container should be set manually (itemHeight * numberOfItems) depending on the length of visible list, to have a scrollbar working. Then create a few item components so that they will fill scrollable containers height and maybe add extra one or two mimic continuous list effect. make them absolute position and on scroll just move their position so that it will mimic continuous list(I think you will find out how to implement it:)
One more thing is writing to DOM is also expensive operation especially if you do it wrong. You can use canvas for displaying lists and create smooth experience on scroll. Checkout react-canvas components. I heard that they have already done some work on Lists.
Check out React Virtualized Select, it's designed to address this issue and performs impressively in my experience. From the description:
HOC that uses react-virtualized and react-select to display large lists of options in a drop-down
https://github.com/bvaughn/react-virtualized-select
For anyone struggling with this problem I have written a component react-big-list that handles lists to up to 1 million of records.
On top of that it comes with some fancy extra features like:
Sorting
Caching
Custom filtering
...
We are using it in production in quite some apps and it works great.
React has recommend react-window library: https://www.npmjs.com/package/react-window
It better than react-vitualized. You can try it
I recently developed multi-select input for React and tested it with 48.000 records. It's working without any problem.
https://www.npmjs.com/package/react-multi-select-advanced
Here's my attempt on this
react-async-lazy-list
OR,
https://github.com/sawrozpdl/react-async-lazy-list
you can give it a shot. it's pretty fast as it uses windowing/virtualization and comes with lazy loading and full customization.

How can I append react-native components to view without re-rendering?

Initially componentWillMount() will run a fetch() to an API endpoint & then save the javascript object to the redux store.
Now my problem is that when it comes to rendering the next set of components it re-renders all of them (meaning there's a little flash on the screen because of the rendering).
So essentially onScroll past a certain point it will run the same fetch() api call & grab a new list of javascript objects. It then grabs the data from the redux store & loops through it appending each new postComponent to the layout state.
handleScroll (event: Object) {
const offset = event.nativeEvent.contentOffset.y;
const screenHeight = Dimensions.get('window').height;
// Just for dev purposes until I find a proper way of determining half way down screen
if(offset >= screenHeight/2){
console.log("Halfway past...");
this.props.FeedActions.fetchFeed(this.props.feed.nextUrl, true);
}
}
render() {
var feed = this.props.feed;
if (!_.has(feed, 'posts')) {
return <ActivityIndicatorIOS />;
}
// Append more posts to state
for (var i = 0; i < _.size(feed.posts); i++) {
this.state.postComponents.push(
<PostComponent post={ feed.posts[i] } key={ "post_"+feed.posts[i].postId+Math.random() }/>
);
}
return (
<ScrollView key={Math.random()} onScroll={this.handleScroll.bind(this)}>
{ this.state.postComponents }
</ScrollView>
)
}
};
Is there a way around this? I thought react wouldn't re-render components that are already render, only the ones that are changed? But I guess in this case my components are all dynamic so that means they will be re-rendered.
The problem is in how you're creating your keys. What you want is a key that uniquely identifies that particular node, consistently, and doesn't change every render. Since you use Math.random() as part of your key, it changes the key every render, so react rebuilds that node. Try using postId without the random number trailing it.

Categories

Resources