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.
Related
I would like to force my parent to re-render the page when I click on a button from a child component.
(I don't use redux because I don't have the time to learn it in my project, so I use localStorage. Unfortunately React don't see when a change is operated on local Storage, so he don't re-render. It's why I would like to force it to re-render my page (to have the right content).)
I tried to use hook with the function useState to do it but it's not working and I don't know why...
(Nothing change in my page)
This is my parent page: (just the code important)
const[reload, setReload] = useState(false);
...
else if (user) { contents = [<Message_UserIdentified user={user} callBack={setReload}/>, contentform]; }
This is my child component:
const Message_UserIdentified = (props) => {
let user = props.user;
return (
<Alert color="primary" className="alert alert-dismissible alert-info">
<h4>Welcome {!user ? "" : user.firstname} {!user ? "" : user.lastname}</h4>
If you are not {!user ? "" : user.firstname} click <a onClick={() => {localStorage.removeItem('idUser'); props.callBack(true);}}>here.</a>
</Alert>
);
}
Why my parent page don't want re-render ?
Thanks in advance.
I have created a proof of concept of what you are trying to achieve and it works:
https://codesandbox.io/s/weathered-smoke-ojr5j
probably there's something else in your code that we can't see that's preventing the component to re render
Your child component can have a prop which directly pass setReload to it.
However one common usage is that, setReload can be associated with an event, ex. onReload. You can pass a prop onReload to the child instead.
<Child onReload={() => { setReload() }} />
Inside onReload implementation, you can call setReload.
The reload state variable in your parent component is strictly local to it; the child can't see it.
I've been using React Hooks for about 2 months now. The learning curve, at times, has been steep but I'm now getting really proficient at it.
A companion technology to Hooks called Context API is perfect for your needs. It's what you should be using rather than local storage because both components can access it. Your child component would set the equivalent of reload in the Context to true and your parent would have a useEffect function that would have reload as a dependency. Thus, when reload is changed from false to true, the useEffect function in the parent would be fired and run the code you desire.
Early on, I very much benefitted from this video series: https://www.youtube.com/watch?v=6RhOzQciVwI&t=46s Watch the first few videos and you should quickly understand how to implement the Context API in your functional React components.
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.
I'm trying to find a way to pass event to child component in react.
I have something like that:
class ParentComponent extends Component<IProps, IState> {
render() {
return (
<div className={"ParentComponent"} onClick={this.onPageClick}>
... some navbar ...
... some `tabs` component
...
<ChildComponent/>
<AnotherChildComponent/>
...
... some footer ...
</div>
)
}
}
the child components are actually sub pages (changed using the tabs) with lot of logic and data inside them. (so I prefer manage them in separate components rather then one giant page).
some of the inner component have Editable labels which changed into an input (or in other case to a textarea or to MD editor) when the label is clicked.
there is an inner state in the child components when the user enter
into "Edit Mode" of the label. every component can have several of
this editable-labels.
The product request is when the user is clicking anywhere in the page the labels should exit from edit mode, so I need to capture the onClick on the master div like in the example and pass somehow the event into a function into the active child component so it will update it's inner state to exit edit-mode (if any).
Now, the solution I thought is to create a state variable in the parent which will be changed by the onPageClick function and pass into the child components
so they could update the local state. and then reset it on the parent again.
something like:
onPageClick() {
this.setState({ pageClicked: true }, () => {
this.setState({ pageClicked: false }
});
}
...
<ChildComponent pageClicked={this.state.pageClicked}/>
But it will change the parent state twice per click (and thus also the child state) even if not neccesary. the ideal why if I'll find a way to pass some event delegate to the children so a function will be triggered only inside the child when the parent onClick is triggered without any state changes in the parent.
Doe's it possible? do anyone have an idea how to implement something like that?
You are making your problem way more complicated than it needs to be.
You shouldn't listen for clicks on the outside component.
Instead, you should use your text input's onBlur event.
onBlur event is fired whenever a text input loses focus.
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.
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