React: Append component animations on local and redux state updates - reactjs

I am building a component that renders an animation on every local and redux state update. Local state is updated on a button click and it also makes an ajax request to a remote server to update that an user clicked the button. I update local state on every click like so:
const [selfAnimations, setAnimations] = useState<AnimationRenders>(initialAnimationState);
const onButtonClickCallback = () => {
makeRemoteAjaxCall();
selfAnimations.localEventsCount += 1;
selfAnimations.animationsToRender.push(Date.now);
setAnimations(selfAnimations);
};
I update the data coming in from remote server like this:
useEffect(() => {
if (animationDataCount && animationDataCount > 0) {
const {
updatedLocalEventsCount,
updatedAnimations,
} = filterDedupedLocalEvents(selfAnimations, animationDataCount);
setAnimations({
localEventsCount: updatedLocalEventsCount,
animationsToRender: updatedAnimations,
});
}
}, [animationDataCount]);
I render a div with a component I created:
<div classNames={...}>
<AnimationContainer numberOfAnimations={selfAnimations.animationsToRender.length} />
</div>
<div className={...}>
<MyButton ... />
</div>
On client side, data is received on regular intervals. My AnimationContainer component can render any number of received animations. However, everytime a user clicks the button or client recieves remote data it updates the state and drops previous animations. Looking online I could delay the rerender with css but the behavior I need is to append to existing animations on state updates. Is there a mechanism that I could use for that behavior?

I misunderstood how react renders. React does not necessarily create a new child component and unmount the old component on props/state updates. In my case, instead of storing counts of what needed to be rendered, I need to store distinct keys for what needed to be rendered, because even if the AnimationContainer component receives updates from props, if it only received counts, then it may not trigger rerenders because the count can be the same. I got around this by implementing the props of localEvents as an array of timestamps: number[]. By passing down timestamps to AnimationContainer and refactoring AnimationContainer to check props update and store into state of animations in middle of rendering and a queue of animations to render, I was able to create a component that can receive and continually renders animations based on events.

Related

FullCalendar: Render dayHeaderContent prop everytime an Event Changes

I use the dayHeaderContent prop from Full Calendar V5 to Inject certain extra content, Whenever I change or update an event and whenever the calendar re renders, the dayHeaderContent prop is being called and my new content is displayed on the top, It all works fine.
The issue comes when I update the event using the eventDrop and eventResize, The event card itself updates fine, but the dayHeaderContent on the top does not re render, until I manually re fetch the events or click the next page and come back to current page.
Is there a way I can make the dayHeaderContent render eveytime eventDrop and eventResize is used? I know I could just use the calendar refresh or refetch event source, But I cannot do that because I am supplying just one single event source static array of events, So I cannot use the refresh methods.
// Here's how I am supplying my events, here data is a static array
calendarRef.current.getApi().addEventSource(data)
// I want the below Inject function to run eveytime an event is update via eventDrop and eventResize props
const injectDayHeaderContent = (args) => {
if (args.view.type === 'timeGridWeek') {
return (
<span>
{convertDecimalToHHMM(showHours)}
</span>
)
}
}
return(
<FullCalendar
.....
.....
dayHeaderContent={injectDayHeaderContent}
/>
)

How can I make sure a React parent component loads data before its child component mounts?

I'm writing a simple calendar application that uses a common layout to wrap different views of events (month view shows a larger calendar with all the days of the month and events for each day, week view just shows a vertical list of events for that week, etc.). The common layout includes a calendar picker control for selecting the date, and then a list of event categories that can be checked or unchecked to show events relating to sports, entertainment, etc.
When the layout mounts, I'm calling an async Redux action creator to get the list of event categories from the database. When those are retrieved, they're saved in a Redux store with a property of Selected set to true, since they're all selected at initial load.
async componentWillMount() {
await this.props.getEventTypes();
}
When the month view, which is a child of the layout view, mounts, it's grabbing all the events for the given month. Part of the selection process of getting those events is sending the list of selected event categories to the backend so it only gets events from the selected categories.
async componentWillMount() {
await this.props.getWeeks();
}
The problem is, the selected categories list is always empty when the month view goes to grab the events for the month. So it's not going to select anything since no categories are selected.
It seems the only way this can be happening is if the child component is mounting first, or if the parent component is taking so long to get the event categories that the getWeeks process finishes first (this is unlikely as the process to grab the weeks and days and their events is much more involved than just selecting the event category list).
So, how can I make sure the parent component grabs the event categories from the database and puts them in the Redux store before the child component selects its events?
I know one way, probably the best way, to do this would be to have the list of event categories render into the page on the server side, so it's just already present at initial load. I'll probably end up doing it that way, but I'd also like to know how to do it all through client-side actions, in case I need to do it that way for some reason in the future.
You can try like this
Set isDataLoaded when data is available.
Use ternary operator for conditional rendering.
In you render
return(
<>
....
{ isDataLoaded ? <ChildComponent /> : null }
....other sutff
</>
);
Use can also use the && operator
return(
<>
....
{ isDataLoaded && <ChildComponent /> }
....other sutff
</>
);
You can integrate componentDidUpdate() and use it to render your child-components in a somewhat synchronous flow.
Let's say the structure of your Parent Component should look something like the following:
Parent
class Parent extends React.Component{
state = {
renderChildren: false
}
async componentDidMount() {
await this.props.getEventTypes();
}
componentDidUpdate(prevProps){
if(this.props.yourUpdatedReducer !== prevProps.yourUpdatedReducer){
this.setState({
renderChildren: true
})
}
}
render(){
const { renderChildren } = this.state
return(
{ renderChildren ? <Child/> : "Loading" }
)
}
}
You want a key in your state that determines whether you should
render the Child component.
In componentDidMount(), you call the action-creator function, when
it completes, you get updated props.
With updated props, you trigger componentDidUpdate(), where you
check the values in your reducer. If the values are
different that means you got the updated data from your database, so
everything has loaded.
Great, so now you want to mount your Child component, so you
update-state, setting renderChildren to true, thus re-rendering the
Parent component. Now Child gets rendered and should behave as expected.

How to make animation on each rerender? (when new props come)

I have a react component which has props coming from the redux store. I need to do animation for icon from this component each time when I got new props. I change state when my component gets props on componentWillUpdate(). In this way I can get animation but just the first time, then I already have this class in DOM element and new update doesn't call animation. How I see I have to delete class which provides animation from DOM, but I am not sure when to do it. I don't buttons, I have just props comes and each time when it happens I need the animation. I read that there is a way with refs, but I don't know how to use refs in such situation
Let us assume that the animation which is triggered on receipt of new props is a bounce animation, which is triggered once a bounce-class class is appended to the desired HTML element.
Instead of componentWillUpdate, I utilise the componentDidUpdate life cycle method, since I wish to call a setState when the required prop is updated. It takes the previous props and the previous state. Let us assume, that the prop which we are watching for changes is bounceProp.
componentDidUpdate(prevProps, prevState) {
if (prevProps.bounceProp !== this.props.bounceProp) {
this.setState({ shouldBounce: true });
}
}
React relies on Synthetic Events, which also includes animation-events. We use the onAnimationEnd event on the desired element, to make shouldBounce: false.
<div
className={
this.state.shouldBounce ? "bounce-class other-class" : "other-class"
}
onAnimationEnd={() => this.setState({ shouldBounce: false })}
/>;
Here the bounce-class class which is responsible for the animation, automatically removes and applies itself based on the shouldBounce variable.

Dynamically passing data to a child component in react

I have a react component that is responsible for listing out data and if the user clicks on a particular data item, it renders a popup with the data the user clicked passed to it. The popup is defined in a separate component but a single instance of the popup is defined in listing component as follows:
render(){
return(
...
{tasks.map((task, index) => {
return (
<p><a onClick={() => self.edit(task.id)}>{task.name}</a></p>
);
})}
<EditTaskPopup show={self.state.showEditPopup} onClose={self.onClosePopup} task={self.state.editData} />
...
)
}
The edit function, packages up the data and sets the component's state so that the data in included in the editData variable and the popup is shown as follows:
self.setState({showEditPopup: true, editData: tasks[x]});
This all works fine but my question is how I should correctly receive that data in the popup container, EditTaskPopup. The constructor of EditTaskPopup fire off when the parent component loads, so no user interaction has occurred, so no value is passed in. Same holds true for componentDidMount. I can see the correct value being passed in when the componentDidUpdate fires off, but that also fires off during the normal operation of the popup where I'm collecting information about what the user is typing within the popup and placing those values in state. Is there an event in a component that only fires off when a parent component changes the parameters passed into it but doesn't fire off when state changes within the component itself?
Try utilising lifecycle method: https://reactjs.org/docs/react-component.html#componentdidupdate

React conditional rendering not updating state in child component

I'm having a weird problem with conditional rendering in which state isn' going down into a child component. I have a viewer template, with a PDF viewer component and a Web viewer component (using an iframe). Depending on what comes back from the server as a media_type value, the appropriate component gets rendered. That's all working fine.
Externally, I have a sibling component responsible for searching inside the content, and in order to do so, it has to pass the search query up to the parent component, which then updates the parent state and then gets passed to the child as a prop. The reason for this is different content requires different search implementation, which is implemented inside the viewer component.
Apparently, my method of conditional rendering is breaking the search query prop update in the child component. None of the component update methods are being called when the prop changes, and therefore the search execution never gets called.
The sibling component calls this method in the common parent:
/**
* Search query execution handler. Passes the state as a prop to the catalog for search
* execution
* #param e Keyword or query string from SearchPanel
*/
searchQueryHandler(e) {
this.setState({
searchRequest: e
});
}
Parent component render method
render() {
let viewer = <div />;
if (this.state.link.media_type === 1)
viewer = <PDF file={this.state.link.id}
setOverlayVisibility={this.props.setOverlayVisibility}
searchQuery = {this.state.searchRequest}
searchMatchHandler={this.searchMatchHandler}
searchResultSelection={this.state.searchResultSelection}
/>;
else if (this.state.link.media_type !== '')
viewer = <WebViewer link={this.state.link}
setOverlayVisibility={this.props.setOverlayVisibility}
searchQuery={this.state.searchRequest}
/>;
return (
<Content>
<ContentLeft>
{viewer}
</ContentLeft>
<ContentRight>
<SidePanel institution={this.state.institution}
link={this.state.link}
searchQueryHandler={this.searchQueryHandler}
searchResults={this.state.searchResults}
searchResultClickHandler={this.searchResultClickHandler}
/>
</ContentRight>
</Content>
)
}
Now, the searchQueryHandler method is being hit by the event fired off in SidePanel, but none of componentWillReceiveProps, shouldComponentUpdate, willComponentUpdate are called inside PDF or WebViewer. I suspect this has to do with the if/else block inside render, but not sure how else to implement this.
The answer to this was the parent component was blocked from updating by a shouldComponentUpdate implementation that did not take into account the new state of the search query. As such, it was returning false all the time, and thus blocking propagation of state update to the appropriate child component.
shouldComponentUpdate(nextProps, nextState) {
return this.state.link !== nextProps.link || this.state.searchRequest !== nextState.searchRequest;
}
was the fix.
So simple, and yet so frustrating.

Resources