I added the ability to 'like' a post.
And suddenly react-native is crawling at a snails pace.
Im new to react so aren't really sure where and when to properly set state.
How should I properly attach the ability to like a row, in a listView table??
Heres the code...
The 'like' text to click on is this....
<Text onPress={this.likePost.bind(this, feeditem.post_id)}>{feeditem.likes} likes</Text>
Then the 'liking' function is this...
likePost(id){
if(this.state.feedData!=null){
var d = this.state.feedData;
// Find row by post_id
var ix = d.findIndex(x => x.post_id===id);
// Only set state if user hasn't already liked
if(d[ix].you_liked==0){
// Mark as liked, and increment
d[ix].you_liked=true;
d[ix].likes = parseInt(d[ix].likes)++;
// Set the state after liking
this.setState({
feedData: d,
feedList: this.state.feedList.cloneWithRows(d)
});
}
}
}
It works, and it properly updates the data, and shows in the dev tools just fine. However, the rendering of the new state visually is taking over a minute.
Am I updating the state wrong?
What is the cost for setState? I thought react was supposed to just re-render the changes it sees in the virtual DOM. Why then does it seem like its rerendering my entire listView. And why over 1 minute load time??
(obvious memory leak somewhere)??
Also, is it possible to just increment the integer on the 'DOM' without triggering re-renders?
Render your ListView passing dataSource and renderRow props
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderRow}/>
);
}
Create renderRow function, where your can access your data and index of clicked row
_renderRow = (rowData, sectionIndex, rowIndex, highlightRow) => {
return (
<TouchableOpacity onPress={() => {
//Here you shoud update state
}}/>
);
};
Furthermore, you always need to make copy of a state before manipulating it, because its immutable object. Try working with state mutating libraries, use https://facebook.github.io/react/docs/update.html or other options.
Animations can take a long time because of the chrome debugger.
A small way to mitigate this is using InteractionManager
likePost(id){
InteractionManager.runAfterInteractions(() => {
if(this.state.feedData!=null){
var d = this.state.feedData;
// Find row by post_id
var ix = d.findIndex(x => x.post_id===id);
// Only set state if user hasn't already liked
if(d[ix].you_liked==0){
// Mark as liked, and increment
d[ix].you_liked=true;
d[ix].likes = parseInt(d[ix].likes)++;
// Set the state after liking
this.setState({
feedData: d,
feedList: this.state.feedList.cloneWithRows(d)
});
}
}
});
}
Or disable chrome debugging and use android monitor to see debugging messages.
Related
I'm trying to set a state in an onTick event for a clock.
<Viewer>
<Clock
startTime={start.clone()}
stopTime={stop.clone()}
currentTime={start.clone()}
multiplier={50}
onTick={_.throttle(handleValue, 1000)} // this thing ticks every millisecond
/>
<Entity
ref={ref} // here is the ref to get the value I want to set state with
position={positionProperty}
tracked
selected
model={{ uri: model, minimumPixelSize: 100, maximumScale: 100.0 }}
availability={
new TimeIntervalCollection([
new TimeInterval({ start: start, stop: stop }),
])
}
/>
</Viewer>
Here is the handleValue function.
const handleValue = (clock) => {
//setting the state here ( I want to display the chaning the value over time)
setHeadingValue(ref.current.cesiumElement._properties._heading.getValue(clock.currentTime));
}
};
The problem is it looks like it tries to re-render over and over which freezes the app.
Due to the nature of setState, this behavior makes sense. But I feel like there is an answer that's escaping me.
May I have some insight as to what I could do? I'm out of ideas.
I'm using Resium ( a react library) and right now I'm setting the value using .getElementByID() and appending to the dom.. which defeats using react in the first place...
Here is a code sandbox: https://codesandbox.io/s/resium-cesium-context-forked-bpjuw?file=/src/ViewerComponent.js
Some elements are not showing because we need a token, but that does not affect the functionality I'm looking for. Just open the console of the code sandbox and go to the ViewerComponent.js
thank you for your help
As I see, the problem here not in the library, but in a way of managing calculation and visualization.
As few people already mentioned, for UI, user don't need more than 60fps, but for process sometimes we need more.
So the solution is to separate processing from visualization.
To be more generic, here is an example in pure JavaScript:
// Here it may be some component
const clockView = document.getElementById('speedyClock')
let state = null
// This function mimicking dispatch on state update
function setState(val) {
state = val
clockView.innerHTML = state
}
const FPS = 30
// Any state out of visualization scope
// Not the React state!!!
let history = []
let nextUiUpdate = Date.now()+(1000/FPS)
/// Super speedy process
setInterval(function() {
history.push(Math.random())
const now = Date.now()
// Update according to visual frame rate
if(now >= nextUiUpdate) {
// Prepare visual updates
setState(JSON.stringify({count: history.length, history}, null, 2))
history = []
nextUiUpdate = Date.now()+(1000/FPS)
}
}, 1)
<pre id="speedyClock"></pre>
I would suggest using requestAnimationFrame, instead of setInterval as their is no point rerendering the React tree more than once every screen refresh.
By using a combination of shouldComponentUpdate and setState you can effectively setState and prevent unnecessary renders.
This is my codesandbox url to show it.
You can access your wanted value as often as you want, but we need to prevent 1 render per millisecond with shouldComponentUpdate function.
This is a difficult one to explain so I will do my best!
My Goal
I have been learning React and decided to try build a Todo List App from scratch. I wanted to implement a "push notification" system, which when you say mark a todo as complete it will pop up in the bottom left corner saying for example "walk the dog has been updated". Then after a few seconds or so it will be removed from the UI.
Fairly simple Goal, and for the most part I have got it working... BUT... if you quickly mark a few todos as complete they will get removed from the UI and then get re-rendered back in!
I have tried as many different ways of removing items from state as I can think of and even changing where the component is pulled in etc.
This is probably a noobie question, but I am still learning!
Here is a link to a code sandbox, best way I could think of to show where I am at:
Alert Component State/Parent
https://codesandbox.io/s/runtime-night-h4czf?file=/src/components/layout/PageContainer.js
Alert Component
https://codesandbox.io/s/runtime-night-h4czf?file=/src/components/parts/Alert.js
Any help much appreciated!
When you call a set function to update state, it will update from the last rendered value. If you want it to update from the last set value, you need to pass the update function instead of just the new values.
For instance, you can change your setTodos in your markComplete function to something like this.
setTodos(todos => todos.map((todo) => {
if (id === todo.id) {
todo = {
...todo,
complete: !todo.complete,
};
}
return todo;
}));
https://codesandbox.io/s/jovial-yalow-yd0jz
If asynchronous events are happening, the value in the scope of the executed event handler might be out of date.
When updating lists of values, use the updating method which receives the previous state, for example
setAlerts(previousAlerts => {
const newAlerts = (build new alerts from prev alerts);
return newAlerts;
});
instead of directly using the alerts you got from useState.
In the PageContainer.js, modify this function
const removeAlert = (id) => {
setAlerts(alerts.filter((alert) => alert.id !== id));
};
to this
const removeAlert = (id) => {
setAlerts(prev => prev.filter((alert) => alert.id !== id));
};
This will also fix the issue when unchecking completed todos at high speed
I have a function that changes the screen and sets the state at the same time that works given below (initial state of weapon is null):
var { navigate } = this.props.navigation;
clickM16 = () => {
this.setState({
weapon: "M16"
});
navigate("Second", {weapon: this.state.weapon});
}
And I am calling this on my second screen via {this.props.navigation.state.weapon}, but the state doesn't seem to update to M16 until I go back and click the button again.
I have console logged both above and below the setState function and on the first click it always gives me null but M16 when I go back and click it again.
Can I not run setState at the same time as navigating between screens? If I can what am I doing wrong.
TLDR: I'm trying to set state and change page in same function so I can then display the new state on the new page as text. The state change doesn't happen until the second click of the button.
Try putting a small timeout for the navigate. The state change may not be complete when you hit the navigate instruction
var { navigate } = this.props.navigation;
clickM16 = () => {
this.setState({
weapon: "M16"
});
setTimeout(() => navigate("Second", {weapon: this.state.weapon}),20);
}
State is supposed to be used as a helper to handle a small amount of data inside your component. The state life cycle ends as the component it belongs completely unmount. Also, note that setState is an asynchronous function, so you must not rely on React to handle sync situations for you. Updating your state will also make your component rerender, so you should use it carefully to avoid loss memory unnecessarily.
If you just want to pass data from a component to another, in this case using navigation props is enough, like this navigate("Second", {weapon: 'M16'});. You don't need to update your state to then be able to pass this data further. In fact, it makes no sense to update your state before navigation, since the current state itself will be lost in the next screen.
If you need to share the exact same state prop between more components, which doesn't seem to be the case, maybe you should consider using another approach, like Redux (https://redux.js.org/).
I recommend you to read the official docs for more detailed info:
https://reactjs.org/docs/react-component.html#state
https://reactjs.org/docs/state-and-lifecycle.html
Hope it helps
Edit:
Based on the information you provided below, if weapon will be an array, for example, and you need to push a new value to it before navigation, you should not use setState, try this instead:
const { navigate } = this.props.navigation;
clickM16 = () => {
const { weapon } = this.state;
weapon.push('M16');
navigate("Second", { weapon });
}
Hope it helps
I will give another suggestion:
var { navigate } = this.props.navigation;
clickM16 = () => {
this.setState({
weapon: "M16"
});
let sendPara = this.state.weapon
navigate("Second", {weapon: sendPara});
}
Recive parameter in respective component.
catName={this.props.navigation.state.params.sendPara}
I hope this may help you.
I'm pretty new to react and have been working on this new page for work. Basically, there's a panel with filter options which lets you filter objects by color. Everything works but I'm noticing the entire filter panel flickers when you select a filter.
Here are the areas functions in the filter component I think bear directly on the filter and then the parent component they're inserted into. When I had originally written this, the filter component was also calling re render but I've since refactored so that the parent handles all of that - it was causing other problems with the page's pagination functionality. naturally. and I think that's kind of my problem. the whole thing is getting passed in then rerendered. but I have no idea how to fix it. or what's best.
checks whether previous props are different from props coming in from parent and if so, creates copy of new state, calls a render method with those options. at this point, still in child component.
componentDidUpdate(prevProps, prevState) {
if (prevState.selectedColorKeys.length !== this.state.selectedColorKeys.length ||
prevState.hasMatchingInvitation !== this.state.hasMatchingInvitation) {
const options = Object.assign({}, {
hasMatchingInvitation: this.state.hasMatchingInvitation,
selectedColorKeys: this.state.selectedColorKeys
});
this.props.onFilterChange(options);
}
}
handles active classes and blocks user from selecting same filter twice
isColorSelected(color) {
return this.state.selectedColorKeys.indexOf(color) > -1;
}
calls to remove filter with color name so users can deselect with same filter button or if its a new filter, sets state by adding the color to the array of selected color keys
filterByColor(color) {
if (this.isColorSelected(color.color_name)) {
this.removeFilter(color.color_name);
return;
}
this.setState({
selectedColorKeys:
this.state.selectedColorKeys.concat([color.color_name])
});
}
creating the color panel itself
// color panel
colorOptions.map(color => (
colorPicker.push(
(<li className={['color-filter', this.isColorSelected(color.color_name) ? 'active' : null].join(' ')} key={color.key} ><span className={color.key} onClick={() => { this.filterByColor(color); }} /></li>)
)
));
parent component
callback referencing the filter child with the onFilterChange function
<ThemesFilter onFilterChange={this.onFilterChange} />
onFilterChange(filters) {
const { filterThemes, loadThemes, unloadThemes } = this.props;
unloadThemes();
this.setState({
filterOptions: filters,
offset: 0
}, () => {
filterThemes(this.state.filterOptions.selectedColorKeys, this.state.filterOptions.hasMatchingInvitation);
loadThemes(this.state.offset);
});
}
when I place break points, the general flow seems to be :
filterByColor is triggered in event handler passing in that color
active classes are added to the color, a filter tag for that color is generated and appended
componentDidMount takes in the previous props/state and compares it to the new props/state. if they don't match, i.e state has changed, it creates a copy of that object, assigning the new states of what's changed. passes that as props to onFilterChange, a function in the parent, with those options.
onFilterChange takes those options, calls the action method for getting new themes (the filtering actually happens in the backend, all I really ever need to do is update the page) and passes those forward. its also setting offset to 0, that's for the page's pagination functionality.
It looks like the problem might be around the componentDidUpdate function which, after setting breakpoints and watching it go through the steps from filterByColor to componentDidMount, that componentDidMount loops through twice, checking again if the colorIsSelected, and throughout all that the color panel pauses to re-render and you get a flicker.
Is it possible creating the copy is causing it? since it's being treated, essentially, as a new object that isColorSelected feels necessary to double check? any help you guys have would be much appreciated, this shit is so far over my head I can't see the sun.
Can you change
componentDidUpdate(prevProps, prevState)
with
componentWillUpdate(nextProps, nextState)
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.