I am working on an app that has a range of routes that display essentially the same data except the route filters the list based on a status property that determines which entries we want shown in the list.
So in my App.js file I have all the routes set up and the listItems are passed into each of these route components as a property. These items are then rendered out as a table.
Now I have some logic that greys the list out and disabled the buttons on the list items when the list is refreshing using a toggle (isRefreshing) state variable.
The isRefreshing state variable is toggled off using a useEffect() that fires when listItems is updated.
This works great for the refreshing button, but not so well when the route changes.
I've figured out it's because when the route changes the existing listItems prop gets fed into the new rendered list component it see that as listItems changing, so the useEffect() fires, toggling isRefreshing off.
2 seconds later the real data for this new route turns up in the list.
So my question is how can I prevent this from happening? I feel like I might have backed myself into a corner and made a serious architectural error.
Can anyone point me in the right direction?
I've created an example here:
https://codesandbox.io/s/small-sound-rikxv?file=/src/App.js:0-2353
As you can see the refresh button works as expected, but the route change causes the listItems prop to immediately cause my refresh indicator to stop, so you can see a console.log fired immediately after hitting the "list2" link, then 5 seconds later when the real data loads it fires again. The problem is that the initial prop value causes my refresh to stop.
Regards, John.
So I found that if I lifted the "refreshing" state variable right up to the same component the data fetching was happening in then things got a lot easier for me.
Even though the property value changes twice still, because the refreshing state is being handled further up the tree, with refreshing now getting passed down, it's not an issue.
With refreshing getting managed up the tree, the refreshing toggle only gets toggled when refreshing is actually happening and not when the prop thinks it has changed.
Thanks for getting me to code out a simple example, that really helped me conceptualize what was happening free from the clutter of my app's code base.
Related
I am trying to use a parent component to control animations in a child Canvas element. Specifically I want an animation to happen when a user inputs a correct answer.
It works until the user changes the state in the parent component, and then it no longer works.
I've made a stripped-back, minimal version of my code here to show my issue: https://codesandbox.io/s/epic-leaf-08jqvy?file=/src/App.js
My desired behaviour is that the red box bounces when a user clicks submit. That happens if they don't type anything in the input box, but as soon as you enter anything into there - changing state and re-rendering the component - the button no longer triggers the animation in the Canvas child component.
As far as I can tell, the issue is to do with changing the state when inputing text. If I make a version where the input is just assigned to a variable, it works fine, but I need to be able to use state and re-render other parts of it.
I have put a console.log in the jump() function, so I can see that it is being called, but no animation is taking place in the canvas.
I assume that what's happening is that everything is being re-rendered when the state changes, and so the useRef is no longer tracking to the right thing.
Things I've tried:
putting the canvas in a memoized component to prevent it from re-rendering
using eventlisteners to see if I can trigger the animations in other ways - keydown ones work, but I need the user to be able to type, so I tried other ones (like hashchange or audio.play) but neither of those worked.
You can see the thing I'm actually trying to build here: https://papaya-platypus-86565f.netlify.app/play Basically users answer questions and an animation plays depending on whether they're right or wrong, to give it a game-y feel.
Thanks!
I like your red box as well as your reasoning. Yes, the input state changing on keystroke is causing the entire App component to re-render. Note that your App.js component has a lot going on (all good stuff), such as your Box class instantiation, your canvas instantiation, etc.
The key is to break your components into smaller parts, particularly separating stateful components from non-stateful components. We don't want your canvas re-mounting on every input change, so we make them sibling components!
Here's a working example of your code, just in smaller components:
https://codesandbox.io/s/strange-julien-d3n4zm
I hope this helps.
I've got an app that shows a list of items in a grid. Some of the items have an embedded video which flashes or stops playing (if it's already playing) when it's rerendered.
The list is maintained in Redux. When the user scrolls to the bottom of the page it loads more results which causes the Redux state to update which, in turn, causes the grid to rerender all of the items plus the new items.
I'm looking for a solution that will just add more items to the end of the grid instead of rerendering everything.
I've got a component that holds the entire grid. It renders a component for each item in the grid. In each item I have a component that holds the embedded video.
Any ideas?
If each item in the grid is a component, like you said, you should use React.memo (for functional compoents) or Reat.PureComponent (for class components). It will prevent the component from rerendering if the props have not changed. I can't guarantee your videos will keep playing, but if the only reason they stop playing or flash is because the component is being rerendered then it should fix the problem.
Maybe this can help: when passing information from redux to your component, try to update the list of the objects instead of sending a new one
It's redux UpdateObject method.
I'm making a chat interface in ReactJS, and each item in the chat log displays the same avatar icon over and over again. Currently the problem I'm facing is that when new items are added the chat log, this image blinks in every list item, which does not look very presentable.
I want to know if there is a way to reuse the Component (most probably not) or the bitmap data, so that once loaded in memory, it can be shown more quickly without a perceivable blink. I have tried using data URL, but not to much avail.
Per request in comments further details:
I have a separate Component to show each chat log item. It contains an Image component to show the avatar.
On the top log I'm using a FlatList and in the renderItem I'm rendering the said chat log component. Whenever any message is sent or received it is appended to the array that the data in the FlatList is pointing to.
Whenever an item is added the list gets re-rendered causing the Image to be created again (I have searched but haven't found any good away of appending items to a FlatList without affecting existing children). Therefore I believe the solution lies in making the image load faster so that the re-render is not so perceivable.
One reason I think of the flickering is if you reassign the key of list items and forcing it to re-render. Check if there is any such case. Thats one of the main reasons component to re-render on listviews.
I used react-infinite-scroll-component it's working just fine.
However, I want to avoid making the user lose his scroll position when he leaves the page and clicks back?
Also please put on consideration Firefox and Safari.
Not 100% sure because I haven't used it - but since no one else has chimed in... the docs say the component has a prop named key that is described as:
the key for the current data set being shown, used when the same
component can show different data sets at different times,
default=undefined
Also, it has a prop named onScroll that is described as:
a function that will listen to the scroll event on the scrolling
container. Note that the scroll event is throttled, so you may not
receive as many events as you would expect.
... which I suspect one of the arguments of which will tell you which keys it loaded / scrolled through.
So my approach would be to use componentWillUnmount to save the last key it loaded into a parent property (or Redux store if you're using Redux)... and when the component loads, if the key exists in the parent (or Redux store if you're using Redux) then pass it that key.
I am trying to change the route of my url to open a map overlay. The problem is when I switch states, the page template underneath switches as well. I don't want this to happen.
I am using $statechange to detect the map route and executing an event.preventDefault(); which should stop the route template from changing. But in my case, the map url gets put in the address bar and then quickly gets removed.
Any ideas?
It seems counterintuitive to override the state change. Why don't you create the overlay as a child state and just navigate to it normally.
I managed to solve the issue by using the $state.go and declaring options 'notify' to false.. This stops the $stateroute changes to fire.