I'm building a portfolio site in Gatsby, and as I work more on the illustration and design side, would like to serve up multiple design options to showcase that work, based on a user selection via click. I've found some similar questions, but none that are specific to Gatsby.
I'm using styled components to create the layout, so It seems to me that just swapping between multiple Global stylesheets (with fonts, colors, etc) via a button would be the way to go, so the layout remains intact, but I'm honestly not sure how to go about this as I'm pretty beginner in Gatsby and Javascript in general.
Thanks in advance!
Given a simply <Layout> structure like:
import React from "react"
import { createGlobalStyle } from "styled-components"
const GlobalStyle = createGlobalStyle`
body {
color: ${props => (props.theme === "purple" ? "purple" : "white")};
}
`
export default function Layout({ children }) {
return (
<React.Fragment>
<GlobalStyle theme="purple" />
{children}
</React.Fragment>
)
}
You can easily attach an onClick event to the children by adding a boolean operator to switch your <GlobalStyle>, for example:
export default function somePage(){
const [defaultStyles, setDefaultStyles]=useState(false);
const handleChange=()=>setDefaultStyles(!defaultStyles)
return <Layout defaultStyles={defaultStyles}>
<h1 onClick={handleChange}> Click me to toggle the global styles</h1>
</Layout>
}
Basically, you are creating a boolean state (initially set as false) to change between default styles. The result of that state (toggled by clicking the <h1>) will be sent upwards to your <Layout> so, there:
import React from "react"
import { createGlobalStyle } from "styled-components"
const GlobalStyle = createGlobalStyle`
body {
color: ${props => (props.theme === "purple" ? "purple" : "white")};
}
`
export default function Layout({ children, defaultStyles }) {
return (
<React.Fragment>
{defaultStyles && <GlobalStyle theme="purple" />}
{children}
</React.Fragment>
)
}
The && operator will only render <GlobalStyle> if defaultStyles is true. Adapt it to your needs or add a ternary condition if needed.
Related
I have a route called "./checkout" that renders embedded elements from Xola. The issue is I am using client side routing and the page needs a refresh to load the checkout page correctly (if not, Xola elements do not show up on the DOM 1). When I try to reload the page on the initial load I get an infinite reload loop. I can't use a href for specific reasons so I need to continue to use Next.js routing. Anyway I can go about this? EDIT: I have reached out to Xola support team for further assistance.
After refresh
checkout.js
import Head from "next/head";
import { useRouter } from "next/router";
import { Container, Button } from "#mui/material";
import { makeStyles } from "#mui/styles";
import { CheckoutCard } from "../components/layout/directory";
import useIsSsr from "#/config/useSsr";
function Checkout() {
const isSsr = useIsSsr();
const router = useRouter();
const classes = useStyles();
return (
<>
{isSsr ? null : window.location.reload()}
<Head>
<title>checkout</title>
</Head>
<Container className={classes.root}>
<Button
className={classes.btn}
onClick={router.back}
color="secondary"
variant={"contained"}
>
back
</Button>
<CheckoutCard />
</Container>
</>
);
}
const useStyles = makeStyles((theme) => ({
root: { marginTop: theme.spacing(10) },
btn: { marginBottom: theme.spacing(5) },
}));
export default Checkout;
CheckoutCard.js
function CheckoutCard() {
return (
<div
className="xola-embedded-checkout"
data-seller="5f3d889683cfdc77b119e592"
data-experience="5f3d8d80d6ba9c6b14748160"
data-version="2"
id="xola-checkout"
></div>
);
}
export default CheckoutCard;
Please add one more prop to CheckoutCard component calling in checkout.js.
You need to update
<CheckoutCard
url={`https://checkout.xola.com/index.html#seller/5f3d889683cfdc77b119e592/experiences/${
url && url.slice(1)
}?openExternal=true`}
/>
to
<CheckoutCard
url={`https://checkout.xola.com/index.html#seller/5f3d889683cfdc77b119e592/experiences/${
url && url.slice(1)
}?openExternal=true`}
key={new Date().getTime()}
/>
"key" prop is to identify the component and you are going to use external service ( like iframe, not sure correctly )
So in order to render the embedded elements from Xola, you should add "key" prop for CheckoutCard component calling.
I want to understand the work of css-modules + dynamic modules. I have a global Button component that is used throughout the application. There is also some form (a modal window with a form) that uses a Button and is imported into the application via dynamic modules. The problem is that when the form opens, i.e. a dynamic module is loaded onto the page, the styles of this dynamic component get to the very bottom of the page, which leads to a repeated redefinition of css.
Code Examples:
// button.module
.button {
margin: 0;
}
// Button.js
import React from 'react';
import styles from './button.module.css';
// using styles
export function Button({ className = '', onClick = () => {} }) {
return (
<button
className={`${styles.button} ${className}`}
onClick={onClick}
>
Button text
</button>
)
}
Dynamic component:
// form.module.css
.form__button
{
margin: 10px 0 0;
}
// Form.js component
import React from 'react';
import styles from './form.module.css';
export function Form() {
return (
<>
...
<Button className={styles.form__button}/>
...
</>
)
}
Client code:
// page.module.css
.page__button {
margin: 20px 0;
}
// Client code
import React from 'react';
import styles from './page.module.css';
import dynamic from "next/dynamic";
// Dynamic form component
const Form = dynamic(() =>
import("#/compoents/Form").then((mod) => mod.Form)
);
export function Page() {
const [show, setShow] = useState(false);
return (
<>
...
{show ? <Form /> : null}
// before show form margin for button '20px 0'
<Button
className={styles.page__button}
onClick={() => setShow(true)}
/>
// after show form margin for button '10px 0 0'
</>
);
}
In this case, the problem will be that when the Form component is loaded, the styles for the Button will also be loaded down and will override our styles.form__button styles. The only solution I see is to use a local Button component, which will actually be used only for this component and contain minimal styles, layout. But this option seems to me not quite correct. I will be glad of any help
can somebody explain how pass in the defauklt theme in Material UI5
in Material UI6 i use to do it like this
const useStyles = makeStyles((theme) => ({
home: {
display: "flex",
paddingTop: "8rem",
width: "100vw",
height: "100vh",
backgroundColor: theme.palette.primary.dark,
color: "white",
},
}));
but as i got throught M-UI5 docs (as far as i found) there is no explanation on how it can be done , the only part they mention about makeStyle it contains this code in this page docs
+import { makeStyles } from '#mui/styles';
+import { createTheme, ThemeProvider } from '#mui/material/styles';
+const theme = createTheme();
const useStyles = makeStyles((theme) => ({
background: theme.palette.primary.main,
}));
function Component() {
const classes = useStyles();
return <div className={classes.root} />
}
// In the root of your app
function App(props) {
- return <Component />;
+ return <ThemeProvider theme={theme}><Component {...props} /></ThemeProvider>;
}
so am i suppose to run createTheme() on every component to get the theme? , apology if i missed out an obvious thing in the docs , probably coz my poor english
The part you are missing is from this part of the migration guide: https://mui.com/material-ui/guides/migration-v4/#style-library.
if you are using JSS style overrides for your components (for example overrides created by makeStyles), you will need to take care of the CSS injection order. To do so, you need to have the StyledEngineProvider with the injectFirst option at the top of your component tree.
Without this, the default styles for the MUI Card in your About component win over the styles specified via classes.about where the styles overlap (e.g. background).
Changing your AllProviders component to the following (just adding <StyledEngineProvider injectFirst>) fixes it:
import React from "react";
import CountriesProvider from "./countries-context";
import QuestionsProvider from "./questions-context";
import {
ThemeProvider,
createTheme,
StyledEngineProvider
} from "#mui/material/styles";
const theme = createTheme();
const AllProviders = (props) => {
return (
<StyledEngineProvider injectFirst>
<QuestionsProvider>
<ThemeProvider theme={theme}>
<CountriesProvider>{props.children}</CountriesProvider>
</ThemeProvider>
</QuestionsProvider>
</StyledEngineProvider>
);
};
export default AllProviders;
https://codesandbox.io/s/funny-flower-w9dzen?file=/src/store/AllProviders.js:303-342
The theme was being passed in fine without this change (otherwise you would have had errors when it tried to access parts of the theme), but the CSS injection order was not correct.
I am trying to optimize a component of mine with dynamic rendering, but I'm facing some issues, so this is my current situation:
I have an Alert component that renders an alert message, along with an icon.
I have a Icons module, which is a library of icons
I am currently rendering the icon as follows (this is not actual code, it's just to give the idea):
import * as icons from '../icons';
import DefaultIcon from '../icons';
function Alert(iconName='defaultIcon', message) {
function getIcon(iconName) {
if (iconName === 'defaultIcon') {
return DefaultIcon()
}
return icons[iconName]
}
return (
<div>
<span>{getIcon(iconName)}</span>
<span>{message}</span>
</div>
)
}
So, suppose Alert gets called without iconName most of the times, so the components doesn't need to import all of the icons at all.
I would like to avoid including all of the icons in the Alert component by default, but only doing so if iconName is specified
Is it even possible to do so?
I don't think it's possible this way.
Maybe create a component for the Icon that imports the icon libary. In the Alert component you could implement the Icon component as a child:
<Alert message="Alert!">
<Icon iconName="defaultIcon" />
</Alert>
You should import icons dynamically with type or name etc. something like below.
import React from 'react';
export default function Icon({ type, ...props }) {
const Icon = require(`./icons/${type}`).default;
return <Icon {...props} />
}
import Icon from './Icon';
<Icon type="addIcon" />
Ok, looks like I managed, and that's how I did it:
import DefaultIcon from '../icons';
function Alert(message, iconName="") {
const [icon, useIcon] = useState();
//componentDidMount
const useEffect(() => {
//if an icon name is specified, import the icons
if (iconName) {
import("./icons").then(icons => setIcon(icons[iconName]))
} else {
setIcon(DefaultIcon)
}
}
,[])
return (
<span>{icon}</span>
<span>{message}</span>
)
}
I have the following (using Material UI)....
import React from "react";
import { NavLink } from "react-router-dom";
import Tabs from "#material-ui/core/Tabs";
import Tab from "#material-ui/core/Tab";
function LinkTab(link){
return <Tab component={NavLink}
to={link.link}
label={link.label}
value={link.link}
key={link.link}
/>;
}
In the new versions this causes the following warning...
Warning: Function components cannot be given refs. Attempts to access
this ref will fail. Did you mean to use React.forwardRef()?
Check the render method of ForwardRef.
in NavLink (created by ForwardRef)
I tried changing to...
function LinkTab(link){
// See https://material-ui.com/guides/composition/#caveat-with-refs
const MyLink = React.forwardRef((props, ref) => <NavLink {...props} ref={ref} />);
return <Tab component={MyLink}
to={link.link}
label={link.label}
value={link.link}
key={link.link}
/>;
}
But I still get the warning. How do I resolve this issue?
Just give it as innerRef,
// Client.js
<Input innerRef={inputRef} />
Use it as ref.
// Input.js
const Input = ({ innerRef }) => {
return (
<div>
<input ref={innerRef} />
</div>
)
}
NavLink from react-router is a function component that is a specialized version of Link which exposes a innerRef prop for that purpose.
// required for react-router-dom < 6.0.0
// see https://github.com/ReactTraining/react-router/issues/6056#issuecomment-435524678
const MyLink = React.forwardRef((props, ref) => <NavLink innerRef={ref} {...props} />);
You could've also searched our docs for react-router which leads you to https://mui.com/getting-started/faq/#how-do-i-use-react-router which links to https://mui.com/components/buttons/#third-party-routing-library. The last link provides a working example and also explains how this will likely change in react-router v6
You can use refs instead of ref. This only works as it avoids the special prop name ref.
<InputText
label="Phone Number"
name="phoneNumber"
refs={register({ required: true })}
error={errors.phoneNumber ? true : false}
icon={MailIcon}
/>
In our case, we were was passing an SVG component (Site's Logo) directly to NextJS's Link Component which was a bit customized and we were getting such error.
Header component where SVG was used and was "causing" the issue.
import Logo from '_public/logos/logo.svg'
import Link from '_components/link/Link'
const Header = () => (
<div className={s.headerLogo}>
<Link href={'/'}>
<Logo />
</Link>
</div>
)
Error Message on Console
Function components cannot be given refs. Attempts to access this ref will fail.
Did you mean to use React.forwardRef()?
Customized Link Component
import NextLink from 'next/link'
import { forwardRef } from 'react'
const Link = ({ href, shallow, replace, children, passHref, className }, ref) => {
return href ? (
<NextLink
href={href}
passHref={passHref}
scroll={false}
shallow={shallow}
replace={replace}
prefetch={false}
className={className}
>
{children}
</NextLink>
) : (
<div className={className}>{children}</div>
)
}
export default forwardRef(Link)
Now we made sure we were using forwardRef in the our customized Link Component but we still got that error.
In order to solve it, I changed the wrapper positioning of SVG element to this and :poof:
const Header = () => (
<Link href={'/'}>
<div className={s.headerLogo}>
<Logo />
</div>
</Link>
)
If you find that you cannot add a custom ref prop or forwardRef to a component, I have a trick to still get a ref object for your functional component.
Suppose you want to add ref to a custom functional component like:
const ref = useRef();
//throws error as Button is a functional component without ref prop
return <Button ref={ref}>Hi</Button>;
You can wrap it in a generic html element and set ref on that.
const ref = useRef();
// This ref works. To get button html element inside div, you can do
const buttonRef = ref.current && ref.current.children[0];
return (
<div ref={ref}>
<Button>Hi</Button>
</div>
);
Of course manage state accordingly and where you want to use the buttonRef object.
to fix this warning you should wrap your custom component with the forwardRef function as mentioned in this blog very nicely
const AppTextField =(props) {return(/*your component*/)}
change the above code to
const AppTextField = forwardRef((props,ref) {return(/*your component*/)}
const renderItem = ({ item, index }) => {
return (
<>
<Item
key={item.Id}
item={item}
index={index}
/>
</>
);
};
Use Fragment to solve React.forwardRef()? warning
If you're using functional components, then React.forwardRef is a really nice feature to know how to use for scenarios like this. If whoever ends up reading this is the more hands on type, I threw together a codesandbox for you to play around with. Sometimes it doesn't load the Styled-Components initially, so you may need to refresh the inline browser when the sandbox loads.
https://codesandbox.io/s/react-forwardref-example-15ql9t?file=/src/App.tsx
// MyAwesomeInput.tsx
import React from "react";
import { TextInput, TextInputProps } from "react-native";
import styled from "styled-components/native";
const Wrapper = styled.View`
width: 100%;
padding-bottom: 10px;
`;
const InputStyled = styled.TextInput`
width: 100%;
height: 50px;
border: 1px solid grey;
text-indent: 5px;
`;
// Created an interface to extend the TextInputProps, allowing access to all of its properties
// from the object that is created from Styled-Components.
//
// I also define the type that the forwarded ref will be.
interface AwesomeInputProps extends TextInputProps {
someProp?: boolean;
ref?: React.Ref<TextInput>;
}
// Created the functional component with the prop type created above.
//
// Notice the end of the line, where you wrap everything in the React.forwardRef().
// This makes it take one more parameter, called ref. I showed what it looks like
// if you are a fan of destructuring.
const MyAwesomeInput: React.FC<AwesomeInputProps> = React.forwardRef( // <-- This wraps the entire component, starting here.
({ someProp, ...props }, ref) => {
return (
<Wrapper>
<InputStyled {...props} ref={ref} />
</Wrapper>
);
}); // <-- And ending down here.
export default MyAwesomeInput;
Then on the calling screen, you'll create your ref variable and pass it into the ref field on the component.
// App.tsx
import React from "react";
import { StyleSheet, Text, TextInput, View } from "react-native";
import MyAwesomeInput from "./Components/MyAwesomeInput";
const App: React.FC = () => {
// Set some state fields for the inputs.
const [field1, setField1] = React.useState("");
const [field2, setField2] = React.useState("");
// Created the ref variable that we'll use down below.
const field2Ref = React.useRef<TextInput>(null);
return (
<View style={styles.app}>
<Text>React.forwardRef Example</Text>
<View>
<MyAwesomeInput
value={field1}
onChangeText={setField1}
placeholder="field 1"
// When you're done typing in this field, and you hit enter or click next on a phone,
// this makes it focus the Ref field.
onSubmitEditing={() => {
field2Ref.current.focus();
}}
/>
<MyAwesomeInput
// Pass the ref variable that's created above to the MyAwesomeInput field of choice.
// Everything should work if you have it setup right.
ref={field2Ref}
value={field2}
onChangeText={setField2}
placeholder="field 2"
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
app: {
flex: 1,
justifyContent: "center",
alignItems: "center"
}
});
export default App;
It's that simple! No matter where you place the MyAwesomeInput component, you'll be able to use a ref.
I just paste here skychavda solution, as it provide a ref to a child : so you can call child method or child ref from parent directly, without any warn.
source: https://github.com/reactjs/reactjs.org/issues/2120
/* Child.jsx */
import React from 'react'
class Child extends React.Component {
componentDidMount() {
const { childRef } = this.props;
childRef(this);
}
componentWillUnmount() {
const { childRef } = this.props;
childRef(undefined);
}
alertMessage() {
window.alert('called from parent component');
}
render() {
return <h1>Hello World!</h1>
}
}
export default Child;
/* Parent.jsx */
import React from 'react';
import Child from './Child';
class Parent extends React.Component {
onClick = () => {
this.child.alertMessage(); // do stuff
}
render() {
return (
<div>
<Child childRef={ref => (this.child = ref)} />
<button onClick={this.onClick}>Child.alertMessage()</button>
</div>
);
}
}