So far every question I can find about async rendering involves an AJAX call.
I've got a text input in my React app, which you can type into to filter a big list of items rendered underneath. Of course, as this list gets bigger, it gets more expensive, so typing into the search box is slow and laggy.
Is there a way to render the list asynchronously from the text input? Or somehow else have it on a separate thread? I don't really want to turn this into a remote AJAX request just because it's too pricy to render - I have all the data I need already.
Basically, my text input's onChange method points at handleChange, then in each item's render() function, it checks the hiddenBySearch method to see if it should be displayed:
handleChange = value => {
this.setState({
searchValue: value
})
}
hiddenBySearch = item => {
if(this.props.data.hiddenBySearch){
return this.props.data.hiddenBySearch(item, this.state.searchValue)
}else{
return false
}
}
There's a little more to it but I'd say it's not relevant.
EDIT: It's not a possible duplicate of this post - I'm specifically asking about offsetting React's rendering. I'm fairly sure it's impossible to put this in a Web Worker.
Related
I'm setting the data that my flatlist component displays using a state called selectedStream. selectedStream changes every time the user presses a different group option. I've noticed that the flatlist takes 1-3 seconds to refresh all the posts that it's currently displaying already. I want there to be a loading indicator so that by the time the indicator goes away, the list is already properly displayed with the newly updated data.
<FlatList
maxToRenderPerBatch={5}
bounces={false}
windowSize={5}
ref={feedRef}
data={selectedStream}/>
Whenever we are working with anything related to the UI, sometimes we may face delays in UI re-rendering. However, we need to first figure out what is actually causing the delay.
The right question to ask about your code would be:
Is the rendering of items taking longer than expected? Or, is the data being passed with a delay because it is dependant on an API call or any other async task?
Once you answer that question, you may end up with two scenarios:
1. FlatList taking longer to render views
This doesn't usually happen as the RN FlatList will only render views that are visible to the user at any given time and will keep rendering new views as the user scrolls through the list. However, there may be some flickering issues for which you can refer to the below article:
8 Ways to optimise your RN FlatList
2. Passing the data causes the delay
This is the most common scenario, where we may call an API endpoint and get some data and then do setState to update any view/list accordingly. A general approach is to show some sort of a progress-bar that would indicate that the application is busy and thus maintaining a proper user-experience. The easiest way to do that is by conditional rendering.
A general example would be:
const [myList, setMyList] = useState();
function callAPIforMyList(){
// logic goes here
}
return {
{myList ? <ActivityIndicator .../> : <Flatlist .... />
}
The above code will check if myList is undefined or has a value. If undefined, it will render the ActivityIndicator or else the FlatList.
Another scenario could be when myList may have existing data but you need to update/replace it with new data. This way the above check may fail, so we can put another check:
const [myList, setMyList] = useState();
const [isAPIbusy, setAPIBusy] = useState(false)
function callAPIformyList() {
setAPIBusy(true)
/// other logics or async calls or redux-dispatch
setAPIBusy(false)
}
return {
{!isAPIBusy && myList ? (<Flatlist .... />) : (<ActivityIndicator .../>)
}
You can add multiple conditions using more turneries such as isAPIBusy ? <View1> : otherBoolean ? <View2> : <Default_View_When_No_Conditions_Match)/>
Hope this helps clarify your needs.
Im trying to create a listener for voice using react-speech-recognition. I have it listening and getting the phrase isolated. It comes with some command startListening, stopListening, finalTranscript, interimTranscript, and resetTranscript. I have it working where I can click a button and it listens, click another button and it stops, click reset and it resets. interimTranscript is basically its first guess at the word, and then after a split second once its sure it turns into finalTranscript. Herein lies my problem. The basic flow is that theres a range slider with two values, when moved it sets off the onChange handler which calls this.startListening().
handleChange(event) {
console.log(`eventname: ${event.target.name}`)
console.log(`eventvalue: ${event.target.value}`)
this.setState({
[event.target.name]: event.target.value,
});
this.props.startListening();
this.setState({ listening: "Yes"})
}
then in my render I have this if statement which detects the moment the phrase goes from interimTranscript to finalTranscript
render(){
if(this.props.finalTranscript){
this.sendCommand();
}
which triggers sendCommand(), Here is one of the issues, finalTranscript will keep adding all the different final transcripts into one long string. So I can say "hello" send command prints "hello" then i say "bye" and send command prints "hello bye", BUT sendcommand() is fired once for each word. so I will hit sendCommand() twice. I got around this by clearing out finalTranscript with resetTranscript
sendCommand(){
console.log("send command", this.props.finalTranscript);
this.props.resetTranscript;
}
but then I get this error telling me I shouldn't use this.props.resetTranscript through the render function. I tried creating a while loop in my handle change but kept getting stuck in infinite loops. What would be a good way to go about creating a while loop to start the voice listener, trigger a post upon final transcript, clear out finalTranscript with resetTranscript, and go back to listening?
Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
As the error stated, render methods should be just pure functions. It's meant to return a view that was derived from your state and props, not induce state changes. Not to mention that might cause an infinite render if you're not careful. If you want to do those effects, such as if this.props.finalTranscript is truthy (or not empty) send a command. Do that in the lifecycle methods like so.
componentDidUpdate(){
if(this.props.finalTranscript){
this.sendCommand()
}
}
I assume you're using class components. if you want to use hooks, look into using useEffect
Update: Here is an example Pen
https://codepen.io/anon/pen/vwzGYY?editors=0011
Preface
Based on my research, it seems like I need a completely different approach. Maybe you can suggest one?
Context
I'm using a Redux-Form (technically an older version, but the API's
in question seem really stable. We can burn that bridge when we get there.) to set some "filters" for a sort of search results list.
In particular, since I want the pages to be link-able, I'm also setting the form content in the URL query params, via React-Router, or initially setting it on page load via similar mechanism.
The only field so far is "organization_name", a text field, used to set the query param value, and trigger an API request for /endpoint?name={some_name}.
E.g.,
<Field
name="organization_name"
component="input"
type="text"
placeholder="Organization Name"
value={value}
/>
I've tried several things, but here's a recent shot:
I'm grabbing reset, change, and other things from default props. I'm passing in a handleSubmit as required.
handleSubmit works correctly, to do some state updating, set/push the URL query params with React Router, and then make a new API call/update display of new results! Woot!
What I want / expect
In the long run, I would like a "reset filters" button that sets all filter values back to defaults (e.g., set the "name" value to empty string), and re-submits the form (thus triggering handleSubmit).
What I first tried to implement was a button, as such:
<button
name="reset_filters_button"
type="button"
onClick={resetAndSubmit}
disabled={pristine || submitting}
>
Clear Search
</button>
Where resetAndSubmit is defined on the form container as such:
const resetAndSubmit = event => {
reset();
handleSubmit();
};
What actually happens... (submit takes precedence over dispatched events?)
Using the Chrome dev tools debugger, I can clearly see that the reset method is called, and returns it's dispatch(...)'d event. However, the form and state values are not updated before handleSubmit() runs and submits the form.
I think this might have to do with the submit event taking priority?
I have also tried something janky, like importing change (default prop for the container) and defining the reset button thus:
<button
name="reset_filters_button"
type="button"
onClick={() => {
change('organization_name', '');
methodThatDispatchesSubmitAction();
}}
disabled={pristine || submitting}
>
Clear Search
</button>
Which (if I remove methodThatDispatchesSubmitAction()) works correctly to set the field value back to blank, making the form technically "pristine" again as well.
methodThatDispatchesSubmitAction() (if it's not obvious) is bound on the parent via dispatchToProps, and passed in to the form container, where it uses the "remote submit" suggestion, e.g,
// organization_list_filter == name of the Redux-Form to submit.
dispatch(submit('organization_list_filter'));
TL;DR and final question:
How does one properly reset a form and submit its' default/empty values?
Every time I dispatch or directly call Redux Form 'submit', it ends up submitting the form before clearing values from state, or the UI. I have walked through this with a debugger and it's not skipping my call to reset or change. It's like an async/race issue, but I admit I am out of my league in this particular case for sure.
Am I just Straight Up Doing It Wrong?
It is most definitely a race condition issue (or since we aren't actually dealing with threads, an order of events issue).
The reason using a methodThatDispatchesSubmitAction works when your current example does not, is because a dispatched action has the benefit of reading data directly from the redux store. Your example is not reading from the redux store, it's reading from a property that is passed in. Yes, this property comes from the redux store, but the problem you are seeing is that it hasn't been updated in your component yet.
Bear with me as this next piece is not going to be entirely accurate but it should suffice to explain what you are seeing.
Submit is clicked
-> Reset action is dispatched
-> Reducer receives action and returns updated state
-> Handle submit is fired using values prop (old state data still)
Component is updated with new props from redux state
As you can see, the order of events don't allow for an updated state to be given to the property until our click code has finished running. If you've ever watched a video on the JS Event Loop (I highly recommend it), you'll know that our onClick handle will run in full before any other async operations (or sync operations that come after our click) have a chance to run.
There are good reasons why Components aren't given updated props right away but the primary one is performance. You can see that this order is in fact the problem by wrapping the handleSubmit in an async event that fires immediately (it doesn't actually fire immediately, all other sync/async operations queued before it will finish).
const resetAndSubmit = (event) => {
reset();
setImmediate(() => handleSubmit());
}
This changes the order of events as follows:
Submit is clicked
-> Reset action is dispatched
-> Reducer receives action and returns updated state
-> Handle submit is queued on the event loop (not run yet)
Component is updated with new props from redux state
Event loop reaches queued code and runs is
-> Handle submit is fired using values prop (new state data)
Hopefully, this helps you understand why the problem is occurring. As for solutions to fix it. Obviously, you can queue the handle submit as I've shown above. Another option would the one you've described as using a dispatch to perform the submit. A third option would be to use something a bit heavier like redux-thunk or redux-sagas that tie the resetAndSubmit action into a single dispatch. Although honestly, this is the same as option two, just reduced into a single dispatch. Option four, don't use redux for all your data. Obviously, this fourth option comes with trade-offs but my point being, just because you are using redux in a project doesn't mean every single piece of data needs to be in redux. Though it completely defeats the purpose of redux-forms.
I should also add, you are not alone in being confused by this. When you introduce redux, it messes with how you traditionally think about working with code. Normally you think, I do A then B. But with redux, you do A, wait for A's changes to make it through the system, and then you do B. That's where Sagas or Thunks can be nice. You move more logic to the store to act on the dispatch rather than wait for it to all make its way back down to a component via props.
I have a class where I have to check for existence of item to display in one html form(say hyperlink) or normal text.
I get the object as a prop. I am finding it hard to use fetch to correctly recognise if the item exists based on return code
this.props.item.map(function(item) {
fetch(url+item)(). then (
if(statusCode === 200)
//display hyperlink
return <a href=url+item>{item.a:item.b}</a>
else
//normal text
return <disp>{item.a:item.b}</disp>
)
// But its expecting a return here!
//return <disp>{item.a:item.b}</disp>
})
The problem is this is asynchronous code.
You have a couple of problems:
Firstly, (if I'm right in assuming that's an Array.prototype.map function) the .map function needs to return something. The map function converts one array, into another array of the same size.
Secondly, and more importantly, fetch returns a Promise.
So even if you changed your code to:
this.props.item.map(function(item) {
return fetch(url+item)(). then (
if(statusCode === 200)
//display hyperlink
return <a href=url+item>{item.a:item.b}</a>
else
//normal text
return <disp>{item.a:item.b}</disp>
)
})
All you would get is an array of promises.
What I recommend you do, is start using react-redux, and personally I recommend using redux-saga.
The basic concept with redux and other state management solutions, is that your React components are responsible for displaying data and responding to user interaction (usually by dispatching a redux action) only. They shouldn't be responsible for actually fetching or manipulating the data. That's what your state management middleware (such as redux-saga) is for.
I won't go into it here, but that should put you in the right direction.
I have been working with react for about 6 months now and something that always used to bother me is the way re-renders work.
Below is a traditional component that has one input box and sends data to the server to whose value is used by some other forms along with multiple almost static HTML elements that are never used or change very rarely. I am saying very rarely because static elements can be built and stored in a variable in the componentWillMount() method. But for this question to be a little more than that, render should contain a call to buildComplexHTMLFromData method.
buildComplexHTMLFromData = (data) => {
// Lot of javascript to build the boxes based on the initial or data
// that changes so rarely
// array.map.filter.find etc.
return (
<div>
//Complex HTML element 1
//Complex HTML element 2
//....
//....
//Complex HTML element n
</div>
)
}
sendDataToBackend = (event) => {
this.setState(
{ value: event.target.value },
() => this.functionThatSendsDataToBackend()
)
}
render() {
<div>
// Active input that sends data to the backend
<input
value={this.state.value}
onChange={this.sendDataToBackend}
/>
{this.buildComplexHTMLFromData()}
</div>
}
Now setting state upon input box change will trigger even the buildComplexHTMLFromData method that does complex javascript all over again. I heard React does something smart by diffing across DOM to efficiently re-render but this javascript is executed anyway.
On the other hand the same functionality can be achieved using two varieties of sendDataToBackend method as shown in the snippet below. This however ensures that only the target input element is changed without touching the already rendered elements or executing any javascript on buildComplexHTMLFromData method.
buildComplexHTMLFromData = (data) => {
// Lot of javascript to build the boxes based on the initial or data
// that changes so rarely
// array.map.filter.find etc.
return (
<div>
//Complex input box 1
//Complex input box 2
//....
//....
//Complex input box n
</div>
)
}
sendDataToBackend = (event) => {
//First strategy
var element = document.getElementById("persistable-input");
element && element.value = event.target.value
//Second strategy
this.persistableInput.value = event.target.value
}
render() {
<div>
// Active input that sends data to the backend or for other forms
<input
id="persistable-input"
ref={(elem) => { this.persistableInput = elem }}
value={this.state.value}
onChange={this.props.persistedValue}
/>
{this.buildComplexHTMLFromData()}
</div>
}
I don't know if I am missing something or if this is very minimal on performance but I feel it could be quite taxing for complex components. I looked multiple articles on React's reconciliation paradigm but it does not seem to address this.
I would really appreciate if anyone could shed some light into this area of React because I am looking for some cool tips and inputs on performant reconciliation in React in most cases.
Thanks in advance.
This is exactly what the shouldComponentUpdate lifecycle hook was created for. If you know that your component shouldn't always re-render, then you can add this lifecycle hook to detect which piece of state is changing. If it something that you don't care about, you can return false and the buildComplexHTMLFromData function won't ever run.
EDIT:
They also expose a base class called PureComponent that handles shouldComponentUpdate under the hood for you.