How to re-render a parent from a children component with hook? - reactjs

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.

Related

How can I tell if an React-Bootstrap component has finished rendering?

I have a React Bootstrap accordion with a lot of data in it that takes a couple seconds to load and doesn't work properly if you try to expand it before that. I would like to hide/overlay the accordion until the render is finished.
To be clear: this is not a question of waiting on the server for the data to load - it's all available on the client, there's just a lot to render. I can't find anything in the React docs about how to handle this case.
I tried using Accordion onLoad event to set a loading flag in component state. However, Bootstrap never seems to actually trigger it and I can't find any other event that I would expect to work.
Update: I am using functional components by the way.
Ok I figured it out, set the loading flag in an empty-array useEffect for the component and it'll trigger when the rendering is complete. Very simple solution, just not as immediately intuitive with functional components.
const [accordionRendering, setAccordionRendering] = React.useState(true);
...
React.useEffect(() => setAccordionRendering(false), [])
...
return <React.Fragment>
<Spinner animation="border" className={accordionRendering ? '' : 'd-none'}/>
<Accordion activeKey={activeKey} onSelect={onAccordionChanged} className={accordionRendering ? 'invisible' : ''}>
...

How to mutate the dom without breaking React

I'm using a library with a component that returns a bunch of HTML. Some of the HTML is ok, and some of it needs changed after rendering.
I'm aware this is unadvised however I am in a situation where this kind of hacky method seems necessary.
My problem is that following the dom mutations, React seems to break. Re-renders no longer occur, and there are no errors in the console to indicate why. This is obviously due to the mutations, but I think there must be some way to do this without breaking React. I've tried various things including using a ref and eg useLayoutEffect.
I have create a minimal-ish repro here:
https://codesandbox.io/s/busy-sea-mz04cr
couple of things here from your sandbox:
Re-renders are still occurring in both the App and MyComponent when you press the buttons, you can prove it by adding a console.log("XXX has re-rendered") in each component
Updating the DOM via a ref will not cause React to re-render
Your custom hook will always only ever execute once (when MyComponent mounts) because the dependencies never change
Your state changes (button presses) are still firing events and updating state but your DOM update via ref removes the rendering of the data variable
Going from
<div id="data">
<p>Data: {data}</p>
</div>
to
<div id="data">
<p>Data: mutation one</p>
</div>
So every button press, your <MyComponent /> is updating and rendering the same static JSX.
Here's a modified sandbox that passes the data state variable to the effect to cause it to trigger on button change + appendChild via ref instead replaceChild so we can see the re-renders
Some things that might be helpful:
Updating the DOM via refs will undoubtedly lead to problems, maybe you should parse the HTML that is returned from your mentioned library and render it with dangerouslySetInnerHTML (assuming it's safe to do so and is from a trusted source otherwise it opens potential exploits)
<div dangerouslySetInnerHTML={{__html: data}} />
Effects (useEffect and useLayoutEffect) are typically reserved for keeping your app in sync with external systems or other non-react effects, you might be able to create an event handler to achieve your goal
function MyComponent() {
const [data, setData] = useState("");
const handleButtonPressed = (html) => {
const fixedHtml = // ... make your changes to the HTML
setData(fixedHtml);
};
return (
<div>
<button onClick={handleButtonPressed}>some button</button>
<p>{data}</p>
</div>
);
}
}
Hope that helps, the new version of the React docs is a fantastic resource.

React Context always returns EMPTY

I have a Search parent component and a SideBar child component, I am trying to get context in SideBar, but everytime it returns empty.
I followed the tutorial exactly like: https://itnext.io/manage-react-state-without-redux-a1d03403d360
but it never worked, anyone know what I did wrong?
Here is the codesandbox link to the project: https://codesandbox.io/s/vigilant-elion-3li7v
I wrote that article.
To solve your specific problem:
When using the HOC withStore you're injecting the prop store into the wrapped component: <WrappedComponent store={context}.
The value of the prop store is an object that contains 3 functions: get, set, and remove.
So, instead of printing it, you should use it. For example this.props.store.get("currentAlbums") or this.props.store.set("currentAlbums", [album1, album2]).
This example is forked by your code: https://codesandbox.io/s/nameless-wood-ycps6
However
Don't rewrite the article code, but use the library: https://www.npmjs.com/package/#spyna/react-store which is already packed, tested, and has more features.
An event better solution is to use this library: https://www.npmjs.com/package/react-context-hook. That is the new version of the one in that article.
This is an example of a sidebar that updates another component content: https://codesandbox.io/s/react-context-hook-sidebar-xxwkm
Be careful when using react context API
Using the React Context API to manage the global state of an application has some performance issues, because each time the context changes, every child component is updated.
So, I don't recommend using it for large projects.
The library https://www.npmjs.com/package/#spyna/react-store has this issue.
The library https://www.npmjs.com/package/react-context-hook does not.
You pass the store as a prop, so to access it, you need this.props.store in your SideBar.
Not this.state.store
Create a wrapping App component around Search and Sidebar:
const App = props => (
<div>
<Search />
<SideBar />
</div>
);
export default createStore(App);
Now you can manipulate state with set and get that you have available in child components Search and Sidebar.
In Search component you can have something like:
componentDidMount() {
this.props.store.set("showModal", this.state.showModal);
}
also wrapped with withStore(Search) ofc.
and in SideBar you can now call:
render() {
return (
<div>
{"Sidebar: this.state.store: ---> " +
JSON.stringify(this.props.store.get("showModal"))}
}
</div>
);
}
and you will get the output.

How do I call an event handler or method in a child component from a parent?

I'm trying to implement something similar to the Floating Action Button (FAB) in the Material-UI docs:
https://material-ui.com/demos/buttons/#floating-action-buttons
They have something like:
<SwipeableViews>
<TabContainer dir={theme.direction}>Item One</TabContainer>
<TabContainer dir={theme.direction}>Item Two</TabContainer>
<TabContainer dir={theme.direction}>Item Three</TabContainer>
</SwipeableViews>
{
fabs.map((fab, index) => (
<Zoom>
<Fab>{fab.icon}</Fab>
</Zoom>
));
}
I have something like:
<SwipeableViews>
<TabContainer dir={theme.direction}>
<ListOfThingsComponent />
</TabContainer>
<TabContainer dir={theme.direction}>Item Two</TabContainer>
<TabContainer dir={theme.direction}>Item Three</TabContainer>
</SwipeableViews>
{
fabs.map((fab, index) => (
<Zoom>
<Fab onClick={ListOfThingsComponent.Add???}>
Add Item to List Component
</Fab>
</Zoom>
));
}
My ListOfThingsComponent originally had an Add button and it worked great. But I wanted to follow the FAB approach for it like they had in the docs. In order to do this, the Add button would then reside outside of the child component. So how do I get a button from the parent to call the Add method of the child component?
I'm not sure how to actually implement the Add Item to List click event handler given that my list component is inside the tab, while the FAB is outside the whole tab structure.
As far as I know I can either:
find a way to connect parent/child to pass the event handler through the levels (e.g. How to pass an event handler to a child component in React)
find a way to better compose components/hierarchy to put the responsibility at the right level (e.g. remove the component and put it in the same file with this in scope using function components?)
I've seen people use ref but that just feels hacky. I'd like to know how it should be done in React. It would be nice if the example went just a bit further and showed where the event handling should reside for the FABs.
thanks in advance, as always, I'll post what I end up doing
It depends on what you expect the clicks to do. Will they only change the state of the given item or will they perform changes outside of that hierarchy? Will a fab be present in every single Tab or you're not sure?
I would think in most cases you're better off doing what you were doing before. Write a CustomComponent for each Tab and have it handle the FAB by itself. The only case in which this could be a bad approach is if you know beforehand that the FAB's callback will make changes up and out of the CustomComponent hierarchy, because in that case you may end up with a callback mess in the long run (still, nothing that global state management couldn't fix).
Edit after your edit: Having a button call a function that is inside a child component is arguably impossible to do in React (without resorting to Refs or other mechanisms that avoid React entirely) because of its one-way data flow. That function has to be somewhere in common, in this case in the component that mounts the button and the ListOfThings component. The button would call that method which would change the state in the "Parent" component, and the new state gets passed to the ListOfThings component via props:
export default class Parent extends Component {
state = {
list: []
};
clickHandler = () => {
// Update state however you need
this.setState({
list: [...this.state.list, 'newItem']
});
}
render() {
return (
<div>
<SwipeableViews>
<TabContainer dir={theme.direction}>
<ListOfThingsComponent list={this.state.list /* Passing the state as prop */}/>
</TabContainer>
<TabContainer dir={theme.direction}>Item Two</TabContainer>
<TabContainer dir={theme.direction}>Item Three</TabContainer>
</SwipeableViews>
{
fabs.map((fab, index) => (
<Zoom>
<Fab onClick={this.clickHandler /* Passing the click callback */}>
Add Item to List Component
</Fab>
</Zoom>
))
}
</div>
)
}
}
If you truly need your hierarchy to stay like that, you have to use this method or some form of global state management that the ListOfThingsComponent can read from.

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