append material ui snackbar to body - javascript

I have a component that is only visible when a user hovers over it. In that component I have a button which allows the user to add something to the local storage. If the button is clicked the component is removed from the DOM. This works fine, but I want to show the user a toast when the action is completed. The issue is that the toast is also removed when the button is clicked because it's part of that component:
return (
<React.Fragment>
<Overlay backdrop_path={movie.backdrop_path}>
<div>
<AddMovie onClick={() => addMovie(movie)}>Add movie to your watchlist</AddMovie>
</div>
</Overlay>
<Snackbar
open={open}
onClose={handleClose}
TransitionComponent={Slide}
message="Movie has been added"
/>
</React.Fragment>
)
I rather not put the Snackbar toast in a different component because this component is responsible for adding a movie to the local storage, and I don't want to do a lot of props lifting etc to get the result.
So I thought, maybe it's possible to append the Snackbar element to the body instead of the components element. This way if the components element is removed the Snackbar should still be visible. Not sure if this logic will actually work though.
Is it possible to append a element/component to another part of the DOM structure, if so: how?

Seems appending rendered elements into other DOM elements isn't difficult:
return ReactDOM.createPortal(
<Snackbar
open={true}
onClose={handleClose}
TransitionComponent={Slide}
message="Movie has been added"
/>,
document.body
);
But this element also gets removed the instant the component that renders it is removed. Which makes sense. So it looks I need to render the Snackbar in a different element. Shame.
// edit. By using Redux it might be possible to create a global state for using the Snackbar elements > https://browntreelabs.com/snackbars-in-react-redux-and-material-ui/
// edit#2. https://github.com/iamhosseindhv/notistack this package makes it a lot easier and is supported by the Material UI team.

Related

How to force tabs to mount without ever clicking on the tab?

Using React & material-ui, I have a pretty big tab container and want to keep data fetches local to each Tab component. I want to be able to essentially greedy load some of the Tab components so as soon as the Tab container is mounted, the Tabs with a greedyLoad prop passed to them are mounted (although not the active tab/visible) and make the fetch for the data they need.
The reason is some of the tabs need a count from the data I fetch in the tab label.
I understand I can fetch the data from the parent component and pass the data as a prop downwards, but I really would like to keep the fetch’s local to each tab component. I’ve seen it done at a previous company I worked at and totally forgot how it worked. Something with CSS I think. Thanks in advance
If you hide the component with CSS, your component will mount on the DOM, but it will be invisible to the user. We just need to add some inline css and make use of the display: none property
function myComponent(show) {
// TODO: fetch the data
return (
<div style={{display: show ? "block" : "none"}}>
<h1 >This component may be invisible!</h1>
<p>{data}</p>
</div>
);
}

Navigate to # id in Layout component

I'm using Gatbsy and I'm trying to create an accessibility link for the users (using screen readers) to be able to navigate to content (skipping the navigation).
My layout component (which is used in every page of the website) looks something like this:
const Layout = ({ children }) => {
{/* hidden for brevity */}
return (
<>
Skip to content {/* <-reference line */}
{/* hidden for brevity */}
<main id="main-content">
{children}
</main>
{/* hidden for brevity */}
</>
);
}
Current behaviour:
As it's shown in the code example above the link will be generated once for the page I'm "landing on", and it will not update for subsequent pages I navigate to:
ex. landing on the 'blog' page my link will point to localhost:9000/blog#main-content and navigating to 'about' page will not make that section of the layout re-render (this is most likely normal behaviour).
I also tried using useRef, but got the referenced item being undefined during rendering.
What would be the way to complete this task (considering that the pages are server-side-rendered)? Is there a way I could do it without passing the full route path to use it inside the tag?
As I commented above, Gatsby's <Link> component extends from #reach/router (from React) doesn't allow navigating to any parameter. From the docs:
Neither <Link> nor navigate can be used for in-route navigation with a
hash or query parameter. If you need this behavior, you should either
use an anchor tag or import the #reach/router package—which Gatsby
already depends upon—to make use of its navigate function.
If you use navigate, for example, navigate("/blog#main-content") it will redirect to /blog, omitting the parameter since it's not allowed.
In your case, the useRef approach won't directly since at the moment you are creating it, it's not still rendered so, you can use the useRef hook approach alongside with useEffect hook, to ensure that the DOM tree is loaded or using some manual trigger:
const Layout = ({ children }) => {
const mainRef= useRef(null);
const navigateToContent= () => {
mainRef.current.scrollIntoView(); //manual trigger
}
useEffect(()=>{
mainRef.current.scrollIntoView(); //automatic trigger
}, [])
{/* hidden for brevity */}
return (
<>
<div onClick={navigateToContent}>Skip to content</div>
{/* hidden for brevity */}
<main id="main-content" ref={mainRef}>
{children}
</main>
{/* hidden for brevity */}
</>
);
}
In the snippet above I've added two different approaches, choose the one that fits your requeriements. The key part is to set correctly the reference of the main tag, initially set as null to avoid rehydration issues when the routing changes.
The manual trigger, just call a function (navigateToContent) in your Skip to content
element that uses the scrollIntoView() built-in function to scroll to the reference.
The automatic trigger, just uses the same idea but the function is triggered once the DOM tree is loaded (useEffect with empty deps, []).
The issue has been solved as following:
Actually changing this <Link to="/#main-content">Skip to content</Link> to this <Link to="#main-content">Skip to content</Link>
(removing the /) works perfectly fine

Render element onClick event, React JS

I'm wondering how to make a component like what Ant design did with their message, notification, etc component.
import { message, Button } from 'antd';
const info = () => {
// How to make component / api like this?
message.info('This is a normal message');
};
ReactDOM.render(
<Button type="primary" onClick={info}>
Display normal message
</Button>,
mountNode,
);
Compared to other common library or our own, usually we need to explicitly place the component where we want the component to show.
Other framework
ReactDOM.render(
<div>
<Button type="primary" onClick={showMessage}>
Display normal message
</Button>
{/* Others need to do this */}
<Message active={messageStatus} content="Lorem ipsum" />
</div>,
mountNode
);
I checked their github, but I can't understand the code since they are using typescript and a lot of abstraction.
Also tried using React.createElement() method, but still couldn't figured out how to put the created element to the dom tree.

How to solve "Invalid values for prop `propmethod` on <li> tag." when using SubMenu in another component

I am using SubMenu in another Component as I require State for checkboxes inside Menu. I get this warning when I am passing a method as props to that custom Component I made.
This is the whole warning:
Invalid values for props additem, selectallzones on li tag.
Either remove them from the element, or pass a string or number value
to keep them in the DOM. For details, see
(link)
My parent Component looks like this, keep in mind addItem() and selectAllZones() are in this component (I have simplified the code a bit so it only has things that are in question):
<SubMenu key="City" title="City">
{this.state.cities.map(city => <SubCity key={city.city} city={city} additem={this.addItem} selectallzones={this.selectAllZones}></SubCity>)}
</SubMenu>
And my SubCity Component looks like this:
<SubMenu {...this.props} key={this.props.city.city} title={this.props.city.city}>
{this.props.city.zone.map(zone =>
<Menu.Item key={zone.id}><Checkbox disabled={this.state.checkedAll} onChange={(e) => this.props.additem('zones', zone.zone, e)}>{`Zone ${zone.zone}`}</Checkbox></Menu.Item>)}
</SubMenu>
I added the {...this.props} in SubMenu component of SubCity looking at another StackOverflow question for other errors I was getting, and I assume this is what is causing the problem. But I can't remove that or my code doesn't run at all. Is there any way I can do this without any warnings in the console? (The code does work, only get a warning in the console.)

Why does React warn against an contentEditable component having children managed by React?

I get the following warning when rendering my component:
Warning: A component is contentEditable and contains children
managed by React. It is now your responsibility to guarantee that none
of those nodes are unexpectedly modified or duplicated. This is
probably not intentional.
This is my component:
import React, { Component } from "react";
export default class Editable extends Component {
render() {
return (
<div contentEditable={true} onBlur={this.props.handleBlur}>
{this.props.children}
</div>
);
}
}
What is the potential problem with my code that React wants to warn me about? I did not quite understand from reading the documentation at https://reactjs.org/docs/dom-elements.html.
I imagine that my component should work exactly like an managed input field, without any problem:
this.props.children is initial value
the onBlur callback updates the props from event.target.innerHTML
the component is rendered with the new props
Setting the contenteditable html attribute allows the contents of that element to be modified in the browser. React is warning you that you have children within that element that are managed by React. React only works from the top down. Meaning it manages a model at the top level and maintains a virtual DOM representing that data, then renders the DOM tree based on that virtual DOM. Any changes you make to the DOM outside of React (such as setting contenteditable and allowing the content to be edited by a user directly in the browser) will be potentially blown away or cause problems for React when it goes to update those managed elements.
In your situation you don't care that the {this.props.children} node gets blown away because you know you're catching the changes and doing what you need to with it. It's just warning you that you better not expect that node to remain intact and accurately updated by React when you're letting the content be edited by the browser directly.
If you know what you're doing (and for now it looks like you do) then you can suppress that warning by adding suppressContentEditableWarning={true}.
Thanks #Chev! It fixed the warnings..
<p
className={editing ? 'editing' : ''}
onClick={editOnClick ? this.toggleEdit : undefined}
contentEditable={editing}
ref={(domNode) => {
this.domElm = domNode;
}}
onBlur={this.save}
onKeyDown={this.handleKeyDown}
{...this.props}
suppressContentEditableWarning={true}
>
{this.props.value}
</p>

Categories