Get startedPropertiesEventsStylingPluginsConfigurationAdditionalGuides & tipsTips for Material-UIDireflow and ReduxContributingChangelog

Tips for Material-UI

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.

Popover

To keep the generated DOM inside the Web Component:

  • Use a RootRef
  • Set attribute container of the Popover and provide the rootRef

Example

const domRef = React.createRef();
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.createRef();
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>
)

Select

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

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>
)


Direflow and Redux

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