Material UI. Show different components depending on breakpoints (SSR) - reactjs

On small devices (phones) I want to show <Foo/> component and on large (desktops) <Bar/>, like this
function MyComp (props) {
if (isMobile)
return <Foo/>
else
return <Bar/>
}
}
I could find only 2 possible ways to implement it in Material UI :
Using Hidden component
Using withWidth HOC
Option with HOC seemed to me more correct in this case, but apparently it doesn't work correctly with SSR (but Hidden does).
So is it OK in terms of best practices to use two Hidden elems? Like this:
function MyComp (props) {
return (
<>
<Hidden mdUp implementation="css">
<Foo/>
</Hiddne>
<Hidden smDown implementation="css">
<Bar/>
</Hidden>
</>
)
}

There is no problem in using <Hidden /> like that, it just adds a <div> with the necessary CSS to show or not your component. A better approach though would be to add a CSS class directly to your <Foo /> and <Bar /> components, to avoid extra <div> in your page, like this:
function MyComp (props) {
return (
<>
<Foo className={props.classes.foo} />
<Bar className={props.classes.bar} />
</>
)
}
The implementation="css" is necessary in SSR setups because the JS implementation may "flash" the component on the page, then remove it when JS has loaded. The downside of the CSS implementation is that the element is kept in the DOM.
PS: withWidth will be deprecated.

Related

In React, what is a good pattern for having expected children element types while still allowing them to be wrapped?

This is similar to wrap children for react component which has children restricted to certain types, but I'm not using Flow or TypeScript. I maintain a component library and have seen the following pattern come up a few times.
There is a set of composable components where the parent component expects specific component types for children:
<Tabs>
<Tab />
<Tab />
<Tab />
</Tabs>
or
<Stepper>
<Step status="completed" />
<Step status="started" />
<Step status="notstarted" />
</Stepper>
The public APIs for the parent components say something like "children must be zero or more <X> elements" because we need to get in and either read props from these children or rely on their presence for other reasons.
However, we find authors often doing things like the following for valid reasons:
function MyTabChildren() {
// Do stuff
return (
<>
<Tab />
<Tab />
<Tab />
</>
);
}
<Tabs>
<MyTabChildren />
</Tabs>
Or, more commonly, we also use styled-components, so we could have something like the following:
import styled from "styled-components";
const StyledTab = styled(Tab)`
// make it nice
`;
<Tabs>
<StyledTab />
<StyledTab />
<StyledTab />
</Tabs>
As a side note, if we were doing something like the following, an HTML validity check would pass fine:
function MyListItems() {
// Do stuff
return (
<>
<li>One</li>
<li>Two</li>
<li>Three</li>
</>
);
}
<ul>
<MyListItems />
</ul>
So I'm not really asking about these specific implementations or how to get styled-components to be invisible.
We've found ourselves doing things like the following:
SCENARIO 1
function Tabs({ children }) {
const filteredChildren = React.Children.toArray(children).filter((child) => {
return child.type === Tab;
});
// Do stuff...
}
SCENARIO 2
function Stepper({ children }) {
const statuses = React.Children.map(
children,
(child) => child.props.status
);
// Do stuff...
}
But these become problematic if the children are wrapped in any way—even though the wrapper may ultimately only be rendering the expected UI. There is also the case where there may be a few levels of wrapping before the needed element is actually rendered.
Is there a good pattern for having expected children types while still allowing for "invisible" wrapping to occur? Or is doing this type of thing an anti-pattern?
(For Scenario 2, I thought about the parent providing a context that includes a state "setter" and the children could consume the context and call the setter, but this seems like too many re-renders.)

Material-ui Google Maps Place Component Doesn't Keep Text When Placed In My Custom Function

React/NextJS/Material-UI newbie issue, hoping someone can share some insight. I'm using Material-ui Google Maps Place component (https://material-ui.com/components/autocomplete/#google-maps-place) out-of-the-box, nothing custom. But for some reason it doesn't work when wrapped inside of my custom function HideOnScroll, as shown below, which just hides my header onScroll. It will render and fetch Google places but it will only accept one character and then immediately goes back to null/original state upon typing second character. But it works perfectly when outside of this HideOnScroll function. Any idea on what I'm doing wrong? My guess is it's a state issue but I'm not clear on how to resolve it.
My custom function:
const HideOnScroll = (props) => {
const { children } = props;
const trigger = useScrollTrigger();
return (
<Slide appear={false} direction="down" in={!trigger}>
{children}
</Slide>
);
}
This works:
return (
<header>
<AppBar className={header}>
<div className={topTrim}></div>
{mobileView ? displayMobile() : displayDesktop()}
<div>{GoogleMaps()}</div>
</AppBar>
</header>
);
This does not work:
return (
<header>
<HideOnScroll {...props}>
<AppBar className={header}>
<div className={topTrim}></div>
{mobileView ? displayMobile() : displayDesktop()}
<div>{GoogleMaps()}</div>
</AppBar>
</HideOnScroll>
</header>
);
FYI, the displayDesktop() function is below and I'm currently just trying to get this to work on desktop. I have also tried adding {GoogleMaps()} to this function as well but still experienced the same problem.
const displayDesktop = () => {
return (
<Toolbar className={toolbar}>
<div className={toolBarTop}>{logo()}</div>
</Toolbar>
);
};
GoogleMaps is a React functional component, and it must not be treated as a normal function. Since it is called like a normal function in JS, it no longer retains its properties as a component and loses its state and lifecycle methods. (This means your hooks will stop working too)
To fix this, consider calling the functional component using the angular bracket syntax, i.e. <GoogleMaps/>.
Also, by convention, the names of all user-defined components should start with a capital letter to distinguish them from regular/pre-defined components.
This article deals with this exact issue with details.

Ways of creating reusable header with react

I am new in react and I am searching the best way for creating a reusable header.
The goals is to reuse a header component in multiple apps by passing somes properties such as:
- Main Title
- Logo
- String List of menus and route paths for render multiple buttons menus for the navigation
- And a boolean for a last button menus for administration
Finally the signature exemple of the component would be something like:
<Header title="title" menus_json=[{'title':'tab1', 'route_path'='/tab1'},...] logo_path=[] admin=False />
How can we achieve that ? Any suggestion, documentation for that ?
You can create a Header component and pass dynamically to every component where needed by values.
Header.js
function Header({title,menus_json,logo_path,admin}){
return (
<header>
// use your title,menus_json,logo_path,admin
</header>
)
}
Use like this in needed components - example
Component1.js
function Component1(){
return (
<div>
<Header title="title" logo_path=[] admin=False
menus_json=[{'title':'tab1', 'route_path'='/tab1'}] />
<div>...</div>
</div>
)
}
Component2.js with different values title,menus_json...etc.
function Component2(){
return (
<div>
<Header title="title2" logo_path=[] admin=False
menus_json=[{'title':'tab2', 'route_path'='/tab2'}] />
<div>...</div>
</div>
)
}

Passing components into another in react

I have an app with a number of components. One of the components I need be to able to pass different variations of another two components into, based on a layout. I believe it can be passed in like a data attribute, but I'm unsure of the exact syntax to push the other components in.
Given two components <List /> and <Box /> which are currently in another component being imported into my main App.js file as such:
export const CallOut = () => {
return(
<div style={styles.sectionInner}>
<List />
<BoxRight/>
</div>
)
};
where <CallOut /> is being imported into App.js, I'd like to pass those two components into the <CallOut /> component.
What is the correct syntax to pass those two in and have them placed in the same spot they're currently in within the CallOut component?
I believe it should be something similar to
<CallOut param={List} param={BoxRight} />
but I know this isn't right.
You can use capital names for props, and use these to instantiate react components like this:
export const CallOut = ({List, Box}) => (
<div style={styles.sectionInner}>
<List/>
<Box/>
</div>
);
List and Box are the properties of this component. You can instantiate it like this:
<CallOut List={SomeComponent} Box={SomeOtherComponent}/>
I don't know if we're on the same page but maybe you're looking for children property ?
So your component will look like that:
export const CallOut = ({children}) => (
<div style={styles.sectionInner}>
{children}
</div>
);
And usage:
<CallOut >
<List/>
<Box/>
</CallOut>
You can pass any component as CallOut child or even do some filtering using children API
It's common usage when components don’t know their children ahead of time and it's used just for some kind of boxing/wrapping.

Set state property from URL using react-router

I have a container component with a modal in it that is opened and closed based on a state property.
I want to control this via the URL, i.e. I want to have
/projects - the modal is NOT open
/projects/add - the modal IS open
As well as being able to link directly to it, I want the URL to change when I click on links within the main container to open the modal.
Can someone explain how I could do this, or point me in the right direction of a good tutorial?
NOTE: This way is not perfect. Even more it's rather antipattern than pattern. The reason I publish it here is it works fine for me and I like the way I can add modals (I can add modal to any page and in general their components don't depends on the other app in any way. And I keep nice url's instead of ugly ?modal=login-form). But be ready to get problems before you find everything working. Think twice!
Let's consider you want following url's:
/users to show <Users /> component
/users/login to show <Users /> component and <Login /> modal over it
You want Login to not depend on Users in anyway, say adding login modal to other pages without pain.
Let's consider you have kinda root component which stands on top of other components. For example Users render may look something like this:
render() {
return (
<Layout>
<UsersList />
</Layout>
);
}
And Layout's render may look something like this:
render() {
return (
<div className="page-wrapper">
<Header />
<Content>
{this.props.children}
</Content>
<Footer />
</div>
);
}
The trick is to force modal's injection to <Layout /> every time we need it.
The most simple approach is to use flux for it. I'm using redux and have ui reducer for such page meta-information, you can create ui store if you use other flux implementation. Anyway, final goal is to render modal if <Layout />'s state (or even better props) contains modal equal to some string representing modal name. Something like:
render() {
return (
<div className="page-wrapper">
<Header />
<Content>
{this.props.children}
</Content>
{this.props.modal ?
<Modal key={this.props.modal} /> :
null
}
<Footer />
</div>
);
}
<Modal /> returns modal component depends on given key (In case of our login-form key we want to receive <Login /> component).
Okay, let's go to router. Consider following code snippet.
const modal = (key) => {
return class extends React.Component {
static displayName = "ModalWrapper";
componentWillMount() {
// this is redux code that adds modal to ui store. Replace it with your's flux
store.dispatch(uiActions.setModal(key));
}
componentWillUnmount() {
store.dispatch(uiActions.unsetModal());
}
render() {
return (
<div className="Next">{this.props.children}</div>
);
}
}
};
...
<Route path="users" component={Next}>
<IndexRoute component={Users}>
<Route path="login" component={modal('login-form')}>
<IndexRoute component={Users} />
</Route>
</Route>
(Don't care about Next - I add it here for simplicity. Imagine it just renders this.props.children)
modal() function returns react component that triggers change in ui store. So as soon as router gets /users/login it adds login-form to ui store, <Layout /> get it as prop (or state) and renders <Modal /> which renders corresponding for given key modal.
To programmatically assess to a new URL, pass the router to your component and use push. push for example will be call in the callback trigger by the user action.
When setting your router set a route to /projects/:status. then, in your component route, you can read the value of status using this.props.param.status. Read "whats-it-look-lik" from react-router for an example.

Resources