Some components in Material-UI generate DOM elements out of the Web Component.
To overcome this issue, most of the time, a RootRef
inside a parent div
must be used first and then, the RootRef.rootRef
must be provided to a specific attribute of the component to make it generate DOM inside the Web Component.
Examples for each specific component are shown below. Examples are only partial and based on Functional Components.
To keep the generated DOM inside the Web Component:
- Use a
RootRef
- Set attribute
container
of the Popover
and provide the rootRef
Example
const [anchorEl, setAnchorEl] = React.useState(null);
const handlePopoverClick = event => {
setAnchorEl(event.currentTarget);
};
const handlePopoverClose = () => {
setAnchorEl(null);
};
const openPopover = Boolean(anchorEl);
const domRef = React.useRef();
return (
<div>
<RootRef rootRef={domRef}>
<Button variant="contained" color="primary" onClick={handlePopoverClick}>
Open Popover
</Button>
<Popover
open={openPopover}
anchorEl={anchorEl}
container={domRef.current}
onClose={handlePopoverClose}
>
<Typography>The content of the Popover.</Typography>
</Popover>
</RootRef>
</div>
);
To keep the generated DOM inside the Web Component:
- Use a
RootRef
- Set attribute
container
of the Modal
and provide the rootRef
Example
const domRef = React.useRef();
return (
<div>
<RootRef rootRef={domRef}>
<Modal
open={true}
container={domRef.current}
>
<div style={{textAlign: 'center', backgroundColor: 'white', marginTop: '100px', marginLeft: '50%', transform: 'translateX(-50%)', width: '50%'}}>
<h2 style={{color: 'black'}}>Text in a modal</h2>
</div>
</Modal>
</RootRef>
</div>
)
To keep the generated DOM inside the Web Component:
- Use a
RootRef
- Set attribute
container
inside MenuProps
of the Select
and provide the rootRef
Example
const domRef = React.createRef();
return (
<div>
<RootRef rootRef={domRef}>
<FormControl>
<InputLabel>Age</InputLabel>
<Select style={{ width: 100 }} MenuProps={{ container: domRef.current }}>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
</RootRef>
</div>
)
Autocomplete comes with @material-ui/lab
.
To keep the autocomplete list inside the Web Component, disable the portal used with disablePortal={true}
.
Install the library first:
npm install @material-ui/lab
Example:
return (
<Autocomplete
options={['movie1', 'movie2', 'film1', 'film2']}
style={{ width: 100 }}
disablePortal={true}
renderInput={params => <TextField {...params} label="Choice" variant="outlined" />}
/>
)
To keep the generated DOM inside the Web Component:
- Use a
RootRef
- Set attribute
container
of the Menu
and provide the rootRef
const domRef = React.createRef();
const [anchorMenuEl, setAnchorMenuEl] = React.useState(null);
const handleMenuClick = (event: any) => {
setAnchorMenuEl(event.currentTarget);
};
const handleMenuClose = () => {
setAnchorMenuEl(null);
};
return (
<div>
<RootRef rootRef={domRef}>
<Button onClick={handleMenuClick}>
Open Menu
</Button>
<Menu
anchorEl={anchorMenuEl}
open={Boolean(anchorMenuEl)}
onClose={handleMenuClose}
container={domRef.current}
>
<MenuItem onClick={handleMenuClose}>Profile</MenuItem>
<MenuItem onClick={handleMenuClose}>My account</MenuItem>
<MenuItem onClick={handleMenuClose}>Logout</MenuItem>
</Menu>
</RootRef>
</div>
)
It can be beneficial to use a shared Redux Store accross multiple Direflow Components in a setup.
In this example we will have one Direflow Component with an input field and an "Add item" button. We call that item-add-component
.
We will have another Direflow Component that displays a list of added items. We call that item-list-component
.
Now we want to use Redux to keep this list in sync between the two Direflow Components.
Our folder-structure will look similar to this:
.
├── public
│ └── index.html
├── src
│ ├── direflow-components
│ │ ├── item-add-component
│ │ │ ├── App.js
│ │ │ └── index.js
│ │ └── item-list-component
│ │ ├── App.js
│ │ └── index.js
│ └── index.js
├── direflow-webpack.js
└── package.json
The item-add-component
will look like this.
It simply contains an input field and a button to click.
Notice how we dispatch a CustomEvent
with the input as the detail
when the button is clicked.
This is the 'payload' of our event.
const App = () => {
const [input, setInput] = useState("");
const dispatch = useContext(EventContext);
const handleClick = () => {
if (!input) {
return;
}
const event = new CustomEvent("add-item", { detail: input });
dispatch(event);
};
return (
<div>
<input value={input} onChange={e => setInput(e.target.value)} />
<button onClick={handleClick}>Add Item</button>
</div>
);
};
The item-list-component
will look like this.
The item list is recieved as a prop sampleList
. We iterate through it and render the items.
Again we are dispatching a CustomEvent
with a detail
when we click an item.
This enables us to remove an item from the list.
const App = props => {
const dispatch = useContext(EventContext);
const handleRemoveItem = sample => {
const event = new CustomEvent("remove-item", { detail: sample });
dispatch(event);
};
const renderSampleList = props.sampleList.map(sample => (
<div
key={sample}
className="list-item"
onClick={() => handleRemoveItem(sample)}
>
→ {sample}
</div>
));
return (
<div>
<div>{props.componentTitle}</div>
<div>{renderSampleList}</div>
</div>
);
};
Now, head to the main index.js
file.
This is were the Redux magic will happen.
Since the React apps becomes Web Components when consumed in a host application, we need to put the Redux login "in between".
Remember how we can "hook into" the Direflow Components lifecycle and access the DOM element once loaded in the host application? (See here: get-started#access-the-dom-element
We will be using this ability in combination with Redux.
Let's start by importing the Promises that are return from DireflowComponent.create
for our two Direflow Components, thus importing createStore
from Redux.
import AddComponent from "./direflow-components/item-add-component";
import ListComponent from "./direflow-components/item-list-component";
import { createStore } from "redux";
Remember to export them in your Direflow Components' index files.
Then we create our reducer and the Redux Store:
const reducer = (state = [], action) => {
switch (action.type) {
case "add":
return [...state, action.item];
case "remove":
return state.filter(s => s !== action.item);
default:
return state;
}
};
const store = createStore(reducer);
Next, we want to wait for the add-item-component
to be loaded in the DOM, and then subscribe to the 'add-item' event.
Since we carry the item value as a detail
, we can now dispatch an add
action to update the Redux Store.
AddComponent.then(element => {
element.addEventListener("add-item", e => {
const currentList = store.getState();
if (currentList.includes(e.detail)) {
return;
}
store.dispatch({ type: "add", item: e.detail });
});
});
We do the very same for the item-list-component
.
As an addition, we also need to subscribe to the Redux Store and pass the updated list to the item-list-component
as a property.
ListComponent.then(element => {
element.addEventListener("remove-item", e => {
store.dispatch({ type: "remove", item: e.detail });
});
store.subscribe(() => {
const currentList = store.getState();
element.sampleList = currentList;
});
});
That's it! Everything should now be in sync.
Check out the working example here on Code Sandbox: codesandbox.io/s/redux-example-olkjn
→ Contributing