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.
Related
The standard way to make an API call in functional React is with useEffect:
function Pizzeria() {
const [pizzas, setPizzas] = useState([])
useEffect(
() => fetchPizzas().then(setPizzas),
[]
)
return (
<div>
{pizzas.map((p, i) => <Pizza pizza={p} key={i} />)}
</div>
)
}
But, as this article points out, useEffect will not fire until after the component has rendered (the first time). Obviously in this trivial case it makes no difference, but in general, it would be better to kick off my async network call as soon as possible.
In a class component, I could theoretically use componentWillMount for this. In functional React, it seems like a useRef-based solution could work. (Allegedly, tanstack's useQuery hook, and probably other libraries, also do this.)
But componentWillMount is deprecated. Is there a reason why I should not do this? If not, what is the best way in functional React to achieve the effect of starting an async call early as possible (which subsequently sets state on the mounted component)? What are the pitfalls?
You're splitting milliseconds here, componentWillMount/render/useEffect all happen at essentially the same time, and the time spent fetching occurs after that. The difference in time from before to after rendering is tiny compared to the time waiting for the network when the request is sent. If you can do the fetch before the component renders, react-query's usePrefetch is nice for that.
Considering the scope of a single component, the earliest possible would be to just make the call in the component's function. The issue here is just that such statement would be executed during every render.
To avoid those new executions, you must keep some kind of "state" (or variable, if you will). You'll need that to mark that the call has been made and shouldn't be made again.
To keep such "state" you can use a useState or, yes, a useRef:
function Pizzeria() {
const pizzasFetchedRef = useRef(false)
const [pizzas, setPizzas] = useState([])
if (!pizzasFetchedRef.current) {
fetchPizzas().then(setPizzas);
pizzasFetchedRef.current = true;
}
Refs are preferred over state for this since you are not rendering the value of pizzasFetched.
The long story...
Yet, even if you use a ref (or state) as above, you'll probably want to use an effect anyway, just to avoid leaks during the unmounting of the component. Something like this:
function Pizzeria() {
const pizzasFetchStatusRef = useRef('pending'); // pending | requested | unmounted
const [pizzas, setPizzas] = useState([])
if (pizzasFetchStatusRef.current === 'pending') {
pizzasFetchStatusRef.current = 'requested';
fetchPizzas().then((data) => {
if (pizzasFetchStatusRef.current !== 'unmounted') {
setPizzas(data);
}
});
}
useEffect(() => {
return () => {
pizzasFetchStatusRef.current = 'unmounted';
};
}, []);
That's a lot of obvious boilerplate. If you do use such pattern, then creating a custom hook with it is the better way. But, yeah, this is natural in the current state of React hooks. See the new docs on fetching data for more info.
One final note: we don't see this issue you pose around much because that's nearly a micro-optimization. In reality, in scenarios where this kind of squeezing is needed, other techniques are used, such as SSR. And in SSR the initial list of pizzas will be sent as prop to the component anyway (and then an effect -- or other query library -- will be used to hydrate post-mount), so there will be no such hurry for that first call.
I have code example like this (codesandbox demo):
const [array, setArray] = useState([]);
const addElementToArray = () => {
array.push(1);
setArray([...array]);
}
return (
<div className="App">
<button onClick={addElementToArray}>addElementToArray</button>
{array.map(el => <div>{el}</div>)}
</div >
);
Here I dont use standard way of updating array like
setArray([...array, 1]);
What disadvantages of updating values like in my first example?
In this example you won't notice any problems. But then, this is a very small and contrived example, isn't it?
But in a larger application with more complex logic and functionality, this can easily lead to strange and difficult bugs. Why? Because you're directly mutating state:
array.push(1);
Does anything else hold a reference to that state or observe it in any way? Is this mutation happening in the middle of a component render, where some logic has already responded to the pre-mutated state and the remaining logic will respond to the post-mutated state? Are multiple state updates being queued and now we don't know which ones will see which version of state?
That's a lot to have to keep track of. Or you can avoid all of those potential problems by simply not mutating state and sticking with "the standard way" that you already know:
setArray([...array, 1]);
or, if there may be multiple state updates and you want to append the 1 to the most current:
setArray(a => [...a, 1]);
If you use this line alone,
array.push(1);
the state changes but React isn't aware of the change so the component won't rerender. In this case, the whole purpose of React is gone because the UI does not correspond to the state change, and that makes it a bad practice.
However, if you use this line alone:
setArray([...array, 1]);
the UI will correspond to the state change, which is the correct way of using React.
And regarding this:
array.push(1);
setArray([...array]);
you're basically mocking React because .push() changes the state without any rerender.
I've been struggling to get Ag-Grid's external filtering responding to state changes. When I update isExternalFiltered, the value of isExternalFiltered in the doesExternalFilterPass callback doesn't change. If I supply a key to the grid to force it to rerender each update, it seems to work, but it causes an expensive grid reinitialization.
I'll also note that I've tried calling the grid api's onFilterChanged method when the filter changes, but that doesn't seem to have any effect.
The docs don't seem to have any examples of external filtering with React (or any framework, for that matter), so I'm beginning to wonder if maybe it isn't supported?
const Grid = () => {
const [gridData, setGridData] = useState([]);
const [columnDefs, setColumnDefs] = useState([]);
const [isExternalFiltered, setExternalFiltered] = useState(false);
/*
Omitted for brevity
*/
return (
<div className="ag-theme-material">
<AgGridReact
// omitted for brevity
columnDefs={columnDefs}
rowData={gridData}
isExternalFilterPresent={() => true}
doesExternalFilterPass={node => {
return isExternalFiltered
? filterFunction(node.data)
: true;
}}
/>
</div>
);
};
A bit late but this morning I ran into a similar issue and I'm going to post what I found to help people in the future with this same problem. This happens because ag-grid React never updates the callback for isExternalFilterPresent. I confirmed this by storing the callback in a variable, causing the grid to rerender, and then inspecting the callback for isExternalFilterPresent inside of the gridOptions gotten from the gridApi. These two callbacks are always the same which means that any values that your callback, isExternalFilterPresent, close over are never updated. The way around this is to use a ref for any of the values that isExternalFilterPresent closes over so you always access the most up to date value.
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.
I've set up some tests of a React component that displays a table using Mocha. I can assert on its initial state but I have a click event which sorts the data that I'd like to test.
If I use React.addons.TestUtils.Simulate.click(theComponent) to try to test the sort.
I can see that the event is handled,
that the state change is fired
the data is sorted before calling setState
but when I assert against the component nothing has changed.
it('sorts the data when the year header is clicked', function() {
var React = require('react/addons');
var TestUtils = React.addons.TestUtils;
var payTable = TestUtils.renderIntoDocument(
<PayTable payYears={data} />
);
var headers = TestUtils.scryRenderedDOMComponentsWithTag(payTable, 'th');
var yearHeader = headers[0];
TestUtils.Simulate.click(yearHeader.getDOMNode());
var columnValues = getYearColumnValues(payTable, TestUtils);
columnValues.should.match([ 'Year', '1066', '1067', '1068' ]);
});
Do I need to force an update? Re-read the component?
The code is available on Github.
I can test other aspects of the Component but not the Component values after setState
I had the same issue. The thing is, TestUtils.Simulate.click(yearHeader.getDOMNode()) is making a click on the DOM, which brings about sorting data and DOM manipulation. Even if you use jsDom and not a real dom, this manipulation itself is an async event. So, just by checking the DOM state right after the click event you are making a syncronous call, within which the DOM state has not been changed yet.
The solution is, use a setTimeout with 0 milliseconds timeout, and check the DOM state there. As for you example :
setTimeout(function() {
var columnValues = getYearColumnValues(payTable, TestUtils);
columnValues.should.match([ 'Year', '1066', '1067', '1068' ]);
},0)
This would make your DOM update its state and you will be able to test your component.
I've been spending a lot of time trying to find a clean way to work with the asynchronousity... Ended up making this for testing:
https://github.com/yormi/test-them-all
Under the hood, it uses componentDidUpdate lifecycle to listen on props/state/route changes.
Hopefully, it'll help you guys. Anyhow your opinion would be greatly appreciated :)