Pass property to component as variable - javascript

I have parent component as below:
import React from "react"
import PropTypes from "prop-types"
import Header from "./header"
import "./layout.css"
import TopBar from "./topbar"
import Bottom from "./bottom"
const Layout = ({ children, isMobile = false }) => {
const mainStyle = !isMobile ? ".main-desktop" : ".main-mobile"
//children.isMobile = isMobile
return (
<>
<Header siteTitle={'MAIN TITLE'}
moto={'SLOGAN.'}
isMobile={isMobile} />
<TopBar isMobile={isMobile} />
<div
style={{
margin: `0 auto 0 auto`,
minHeight: `50%`,
padding: `0 1.0875rem 1.45rem`,
paddingLeft: "90px"
}}
>
<main className={mainStyle}>{children}</main>
<br />
<br />
</div>
<Bottom isMobile={isMobile} />
</>
)
}
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout
I have a child component as below:
import React from "react"
import SEO from "../components/seo"
const ContactUsPage = ({ isMobile = false }) => (
<>
<SEO title="Contact Us"
</>
)
export default ContactUsPage
ContactUsPage is being called inside Layout by the Gatsby framework into children variable. Now, I want to pass isMobile property from Layout. I tried setting it directly but is giving error that object is not extensible.
What is the way and if possible, what is the correct way to set the property for variable components?

I think you can do this in two way, the Gatsby way and the React Context way:
The Gatsby way:
Gatsby allows you to pass a state prop to its <Link/> component.
The value of the state can then be retrieved from the page component (that automatically receives a location prop. See the Gatsby documentation
The Context way:
Create a React context that holds the isMobile value and a Component Provider:
export const MobileContext = React.createContext({ isMobile: false });
export MobileProvider = ({ children }) => {
const [isMobile, setIsMobile] = useState(false);
// Define your logic to check if it is mobile or not
const value = useMemo(() => ({ isMobile }), [isMobile]);
return (
<MobileContext.Provider value={value}>
{children}
</MobileContext.Provider>
)
}
And wrap your layout with that.
Then you can access the context value on all Layout children (and their children too) with const { isMobile } = useContext(MobileContext);.
Check the React context documentation

In return in the parent component:
<ContactUsPage isMobile={isMobile} />
And inside ContactUsPage component:
const ContactUsPage = ({ isMobile }) => {
return (
<div>
</div>
);
};

Related

SonarQube "Do not define components during render" with MUI/TS but can't send component as prop

I am getting the following error during sonarqube scan:
Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state. Instead, move this component definition out of the parent component “SectionTab” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true.
I understand that it says that I should send the component as a prop from the parent, but I don't want to send the icon everytime that I want to use this component, is there another way to get this fixed?
import Select from "#mui/material/Select";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faAngleDown } from "#fortawesome/pro-solid-svg-icons/faAngleDown";
const AngleIcon = ({ props }: { props: any }) => {
return (
<FontAwesomeIcon
{...props}
sx={{ marginRight: "10px" }}
icon={faAngleDown}
size="xs"
/>
);
};
const SectionTab = () => {
return (
<Select
id="course_type"
readOnly={true}
IconComponent={(props) => <AngleIcon props={props} />}
variant="standard"
defaultValue="cr"
disableUnderline
/>
);
};
export default SectionTab;
What can you do:
Send the component as the prop:
IconComponent={AngleIcon}
If you need to pass anything to the component on the fly, you can wrap it with useCallback:
const SectionTab = () => {
const IconComponent = useCallback(props => <AngleIcon props={props} />, []);
return (
<Select
id="course_type"
readOnly={true}
IconComponent={IconComponent}
variant="standard"
defaultValue="cr"
disableUnderline
/>
);
};
This would generate a stable component, but it's pretty redundant unless you need to pass anything else, and not via the props. In that case, a new component would be generated every time that external value changes, which would make it unstable again. You can use refs to pass values without generating a new component, but the component's tree won't be re-rendered to reflect the change in the ref.
const SectionTab = () => {
const [value, setValue] = useState(0);
const IconComponent = useCallback(
props => <AngleIcon props={props} value={value} />
, []);
return (
<Select
id="course_type"
readOnly={true}
IconComponent={IconComponent}
variant="standard"
defaultValue="cr"
disableUnderline
/>
);
};

React - toggle text and class in an HTML element?

I am trying to create a system where I can easily click a given sentence on the page and have it toggle to a different sentence with a different color upon click. I am new to react native and trying to figure out the best way to handle it. So far I have been able to get a toggle working but having trouble figuring out how to change the class as everything is getting handled within a single div.
const ButtonExample = () => {
const [status, setStatus] = useState(false);
return (
<div className="textline" onClick={() => setStatus(!status)}>
{`${status ? 'state 1' : 'state 2'}`}
</div>
);
};
How can I make state 1 and state 2 into separate return statements that return separate texts + classes but toggle back and forth?
you can just create a component for it, create a state to track of toggle state and receive style of text as prop
in React code sandbox : https://codesandbox.io/s/confident-rain-e4zyd?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function ToggleText({ text1, text2, className1, className2 }) {
const [state, toggle] = useState(true);
const className = `initial-style ${state ? className1 : className2}`;
return (
<div className={className} onClick={() => toggle(!state)}>
{state ? text1 : text2}
</div>
);
}
in React-Native codesandbox : https://codesandbox.io/s/eloquent-cerf-k3eb0?file=/src/ToggleText.js:0-465
import React, { useState } from "react";
import { Text, View } from "react-native";
import styles from "./style";
export default function ToggleText({ text1, text2, style1, style2 }) {
const [state, toggle] = useState(true);
return (
<View style={styles.container}>
<Text
style={[styles.initialTextStyle, state ? style1 : style2]}
onPress={() => toggle(!state)}
>
{state ? text1 : text2}
</Text>
</View>
);
}
This should be something you're looking for:
import React from "react"
const Sentence = ({ className, displayValue, setStatus }) => {
return (
<div
className={className}
onClick={() => setStatus((prevState) => !prevState)}
>
{displayValue}
</div>
);
};
const ButtonExample = () => {
const [status, setStatus] = React.useState(false);
return status ? (
<Sentence
className="textLine"
displayValue="state 1"
setStatus={setStatus}
/>
) : (
<Sentence
className="textLineTwo"
displayValue="state 2"
setStatus={setStatus}
/>
);
};
You have a Sentence component that takes in three props. One for a different className, one for a different value to be displayed and each will need access to the function that will be changing the status state. Each setter from a hook also has access to a function call, where you can get the previous (current) state value, so you don't need to pass in the current state value.
Sandbox

Render Variable Styled Component From Context

Hi I am making a website building app that uses styled components and forms to render data into sections of a page. So I decided all of my styled components will be default exports then imported to a context and then loaded into the form when its opened. Then from a select menu the user gets a map of all the possible components to render and when an option is selected a string reference filters the component array down to the called function and then adds all the other data and then displays it on screen. When I launch I get this error: "React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: < BurgerMenu / >. Did you accidentally export a JSX literal instead of a component?"
Here are all of the parts:
BurgerMenu.js
import React, { useState, useRef } from "react";
import { ThemeProvider } from "styled-components";
import { useOnClickOutside } from "../../state/useOnClickOutside";
import { GlobalStyles } from "../../state/globals";
import { useTheme } from "../../state/useTheme";
import Hamburger from "./Hamburger";
import Menu from "./Menu";
const BurgerMenu = () => {
const [open, setOpen] = useState(false);
const node = useRef();
useOnClickOutside(node, () => setOpen(false));
const { theme } = useTheme();
return (
<ThemeProvider theme={theme}>
<>
<GlobalStyles />
<div ref={node}>
<Hamburger open={open} setOpen={setOpen} />
<Menu open={open} setOpen={setOpen} />
</div>
</>
</ThemeProvider>
);
};
export default BurgerMenu;
Component Context:
import BurgerMenu from "../header/Hamburger/BurgerMenu";
const components = [{
name: "BurgerMenu",
func: BurgerMenu,
els: ["a", "a", "a", "a", "a", "a"],
}]
Site Form (where the component context is called)
const { components } = useComponentContext();
const [mount, setMount] = useState('')
<select name='component' onChange={(e) => setMount(e.target.value)}>
{components.map((component) => (
<option key={component.name} value={component.name}>
{component.name}
</option>
))}
</select>
<button
className='btn primary btn-block'
onClick={() => {
convertStringToComponent(mount);
setTimeout(setLoaded((prevState) => !prevState),2000)}}>
Add Component
</button>
Function convertStringToComponent
const convertStringToComponent = (mount, compStyle) => {
const ComponentName = components
.filter((comp) => comp.name === mount)
.map(({ func }) => func)[0];
return (
<ComponentName
content={content}
font={font}
pallet={pallet}
h={h}
icon={icon}
p={p}
vid={vid}
img={img}
a={a}
li={li}
button={button}></ComponentName>
);
};
const VariableComponent = convertStringToComponent(mount);
this is then called in a different component with
{loaded === true && <VariableComponent />}
Any help would be great!
The issue is with how you are using convertStringToComponent's function reeturn valaue.
When you call, convertStringToComponent you are returned an instance of component which is
<ComponentName
content={content}
font={font}
pallet={pallet}
h={h}
icon={icon}
p={p}
vid={vid}
img={img}
a={a}
li={li}
button={button}></ComponentName>
Now when rendering you are trying to again create an instance eout of this by using it as
{loaded === true && <VariableComponent />}
instead of
{loaded === true && VariableComponent}
However, there is another issue, when you call convertStringToComponent you shouldn't try to store the result in a variable and export it, instead you should be storing it in instance state and render it.
A better way to structure your code would be
export const convertStringToComponent = (mount, compStyle) => {
const ComponentName = components
.filter((comp) => comp.name === mount)
.map(({ func }) => func)[0];
return (otherProps) => (
<ComponentName
{...otherProps}
content={content}
font={font}
pallet={pallet}
h={h}
icon={icon}
p={p}
vid={vid}
img={img}
a={a}
li={li}
button={button}></ComponentName>
);
};
Now you can use it like
const [VariableComponent, setVariableComponent] = useState(null);
<button
className='btn primary btn-block'
onClick={() => {
const comp = convertStringToComponent(mount);
setVariableComponent(comp);
setTimeout(setLoaded((prevState) => !prevState),2000)}}>
Add Component
</button>
{isLoaded && <VariableComponent />}

React - sending props to component

I'm using React or Gatsby for a static website. A subpages need to send an prop or a variable(bool) to the main layout component, to determinme if we show a Hero image or not.
I got the following code (simplified) for the page:
import React from 'react'
import { graphql } from 'gatsby'
import Layout from '../components/layout'
import dividerIcon from '../images/hair-cut-tool.svg'
const IndexPage = ({ data }) => (
<Layout showHero={true}>
<div className="divider-wrapper">
<div className="divider">
<img alt="divider" src={dividerIcon} />
</div>
</div>
</Layout>
)
export default IndexPage
How can I "get" the prop in may Layout.js?
I'm sending it with "" but I have no idea, how to get this variable and use it.
As for right now the Layout.js looks like this:
const Layout = ({ children }) => (
<StaticQuery
query={graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
}
}
heroImgLogo: file(relativePath: { eq: "logo.png" }) {
childImageSharp {
fixed(width: 300) {
...GatsbyImageSharpFixed_withWebp_noBase64
}
}
}
}
`}
render={data => (
<>
<div className="site">
{(children.showHero) ?
<Hero logoImg={data.heroImgLogo.childImageSharp.fixed} />
:
null }
<div className="site-content container">{children}</div>
</div>
</>
)}
/>
);
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout
yet again, simplified.
I tried to children.showHero but it wasn't the right approach, I guess.
Any hints?
You can destructure it alongside children:
const Layout = ({ children, showHero }) => (
Be sure to replace children.showHero with just showHero.

How do I conditionally wrap a React component?

I have a component that will sometimes need to be rendered as an <anchor> and other times as a <div>. The prop I read to determine this, is this.props.url.
If it exists, I need to render the component wrapped in an <a href={this.props.url}>. Otherwise it just gets rendered as a <div/>.
Possible?
This is what I'm doing right now, but feel it could be simplified:
if (this.props.link) {
return (
<a href={this.props.link}>
<i>
{this.props.count}
</i>
</a>
);
}
return (
<i className={styles.Icon}>
{this.props.count}
</i>
);
UPDATE:
Here is the final lockup. Thanks for the tip, #Sulthan!
import React, { Component, PropTypes } from 'react';
import classNames from 'classnames';
export default class CommentCount extends Component {
static propTypes = {
count: PropTypes.number.isRequired,
link: PropTypes.string,
className: PropTypes.string
}
render() {
const styles = require('./CommentCount.css');
const {link, className, count} = this.props;
const iconClasses = classNames({
[styles.Icon]: true,
[className]: !link && className
});
const Icon = (
<i className={iconClasses}>
{count}
</i>
);
if (link) {
const baseClasses = classNames({
[styles.Base]: true,
[className]: className
});
return (
<a href={link} className={baseClasses}>
{Icon}
</a>
);
}
return Icon;
}
}
Just use a variable.
var component = (
<i className={styles.Icon}>
{this.props.count}
</i>
);
if (this.props.link) {
return (
<a href={this.props.link} className={baseClasses}>
{component}
</a>
);
}
return component;
or, you can use a helper function to render the contents. JSX is code like any other. If you want to reduce duplications, use functions and variables.
Create a HOC (higher-order component) for wrapping your element:
const WithLink = ({ link, className, children }) => (link ?
<a href={link} className={className}>
{children}
</a>
: children
);
return (
<WithLink link={this.props.link} className={baseClasses}>
<i className={styles.Icon}>
{this.props.count}
</i>
</WithLink>
);
Here's an example of a helpful component I've seen used before (not sure who to accredit it to). It's arguably more declarative:
const ConditionalWrap = ({ condition, wrap, children }) => (
condition ? wrap(children) : children
);
Use case:
// MaybeModal will render its children within a modal (or not)
// depending on whether "isModal" is truthy
const MaybeModal = ({ children, isModal }) => {
return (
<ConditionalWrap
condition={isModal}
wrap={(wrappedChildren) => <Modal>{wrappedChildren}</Modal>}
>
{children}
</ConditionalWrap>
);
}
There's another way
you could use a reference variable
let Wrapper = React.Fragment //fallback in case you dont want to wrap your components
if(someCondition) {
Wrapper = ParentComponent
}
return (
<Wrapper parentProps={parentProps}>
<Child></Child>
</Wrapper>
)
const ConditionalWrapper = ({ condition, wrapper, children }) =>
condition ? wrapper(children) : children;
The component you wanna wrap as
<ConditionalWrapper
condition={link}
wrapper={children => <a href={link}>{children}</a>}>
<h2>{brand}</h2>
</ConditionalWrapper>
Maybe this article can help you more
https://blog.hackages.io/conditionally-wrap-an-element-in-react-a8b9a47fab2
You could also use a util function like this:
const wrapIf = (conditions, content, wrapper) => conditions
? React.cloneElement(wrapper, {}, content)
: content;
You should use a JSX if-else as described here. Something like this should work.
App = React.creatClass({
render() {
var myComponent;
if(typeof(this.props.url) != 'undefined') {
myComponent = <myLink url=this.props.url>;
}
else {
myComponent = <myDiv>;
}
return (
<div>
{myComponent}
</div>
)
}
});
Using react and Typescript
let Wrapper = ({ children }: { children: ReactNode }) => <>{children} </>
if (this.props.link) {
Wrapper = ({ children }: { children: ReactNode }) => <Link to={this.props.link}>{children} </Link>
}
return (
<Wrapper>
<i>
{this.props.count}
</i>
</Wrapper>
)
A functional component which renders 2 components, one is wrapped and the other isn't.
Method 1:
// The interesting part:
const WrapIf = ({ condition, With, children, ...rest }) =>
condition
? <With {...rest}>{children}</With>
: children
const Wrapper = ({children, ...rest}) => <h1 {...rest}>{children}</h1>
// demo app: with & without a wrapper
const App = () => [
<WrapIf condition={true} With={Wrapper} style={{color:"red"}}>
foo
</WrapIf>
,
<WrapIf condition={false} With={Wrapper}>
bar
</WrapIf>
]
ReactDOM.render(<App/>, document.body)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This can also be used like this:
<WrapIf condition={true} With={"h1"}>
Method 2:
// The interesting part:
const Wrapper = ({ condition, children, ...props }) => condition
? <h1 {...props}>{children}</h1>
: <React.Fragment>{children}</React.Fragment>;
// stackoverflow prevents using <></>
// demo app: with & without a wrapper
const App = () => [
<Wrapper condition={true} style={{color:"red"}}>
foo
</Wrapper>
,
<Wrapper condition={false}>
bar
</Wrapper>
]
ReactDOM.render(<App/>, document.body)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
With provided solutions there is a problem with performance:
https://medium.com/#cowi4030/optimizing-conditional-rendering-in-react-3fee6b197a20
React will unmount <Icon> component on the next render.
Icon exist twice in different order in JSX and React will unmount it if you change props.link on next render. In this case <Icon> its not a heavy component and its acceptable but if you are looking for an other solutions:
https://codesandbox.io/s/82jo98o708?file=/src/index.js
https://thoughtspile.github.io/2018/12/02/react-keep-mounted/

Categories