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 />}
Related
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
/>
);
};
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
Before y'all say global state(redux), I'd like to say one thing. I'm mapping through an array I fetched from my API. I receive images and map over them and render my Slider component. Every 2 sliders must share the same state. So, then if i move to the next slide in the first slider, then the second slider must also go to the next slide(but not any other slides). If I move to the next slide in the 5th slider, the 6th must also move to the next slide... so on.
Component where I map over slides:
<div className='image-grid'>
{screenshots.map((imagesByResolution, resIdx, screenshotResArr) => {
return imagesByResolution.map((img, scriptIdx, screenshotScriptsArr) => {
return <Slider slides={formattedSlides} />;
});
})}
</div>
Slider:
import Button from '#material-ui/core/Button';
import MobileStepper from '#material-ui/core/MobileStepper';
import { useTheme } from '#material-ui/core/styles';
import KeyboardArrowLeft from '#material-ui/icons/KeyboardArrowLeft';
import KeyboardArrowRight from '#material-ui/icons/KeyboardArrowRight';
import React from 'react';
import SwipeableViews from 'react-swipeable-views';
import { autoPlay } from 'react-swipeable-views-utils';
import { encodeImage } from '../services/images';
import useStyles from '../styles/slider';
const AutoPlaySwipeableViews = autoPlay(SwipeableViews);
export interface ISlide {
title: string;
img: ArrayBuffer;
}
interface Props {
slides: ISlide[];
}
export default function Slider(props: Props) {
console.log(props);
const { slides } = props;
const classes = useStyles();
const theme = useTheme();
const [activeSlide, setActiveSlide] = React.useState(0);
const maxSlides = slides.length;
const handleNext = () => {
setActiveSlide((prevActiveStep) => prevActiveStep + 1);
};
const handleBack = () => {
setActiveSlide((prevActiveStep) => prevActiveStep - 1);
};
const handleSlideChange = (step: number) => {
setActiveSlide(step);
};
return (
<div className={classes.root}>
<div className={classes.header}>
<h4 className={classes.title}>{slides[activeSlide].title}</h4>
</div>
<AutoPlaySwipeableViews
axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'}
index={activeSlide}
onChangeIndex={handleSlideChange}
enableMouseEvents
>
{slides.map((slide, index) => (
<div key={index}>
{Math.abs(activeSlide - index) <= 2 ? (
<img className={classes.img} src={encodeImage(slide.img, 'image/png')} alt={slide.title} />
) : null}
</div>
))}
</AutoPlaySwipeableViews>
<MobileStepper
steps={maxSlides}
position='static'
variant='text'
activeStep={activeSlide}
nextButton={
<Button size='small' onClick={handleNext} disabled={activeSlide === maxSlides - 1}>
Next
{theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
</Button>
}
backButton={
<Button size='small' onClick={handleBack} disabled={activeSlide === 0}>
{theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
Back
</Button>
}
/>
</div>
);
}
If this is not possible using either some global state management library or plain ol' react state, what is the other alternative? Thanks in advance!
Pass a unique key prop to each instance of your component.
Credits: https://stackoverflow.com/a/65654818/9990676
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>
);
};
I have a list of items and for each item in the list has an edit button to show a modal with that items details. Originally I had a single modal component in the parent and when I click the edit button it would pass the visible values up to the parent state to show the modal.
The problem is when I did that, the entire list would re render which I dont want because the list can have hundreds of items in it. So right now the solution I have is that in each item of the list I have a modal associated with it. It works but it doesnt seem right because I am duplicated code unnecessarily.
The code is too large to put on here but these are the relevant parts:
import Modal from '../Modal';
const CustomCard = ({
...omitted
}) => {
const [editCustomerModal, setEditCustomerModal] = useState(false);
const onEditModal = () => {
setEditCustomerModal(true);
};
return (
<>
<CustomerModal
onSuccess={handleUpdateCustomer}
onCancel={handleCancelModal}
visible={editCustomerModal}
title="Edit Customer"
details={{
.. ommitted
}}
/>
<Card />
....data
</Card>
</>
);
};
export default CustomCard;
import CustomCard from '../CustomerCard/index';
const CustomList = ({ dataSource }) => {
return (
<div>
{dataSource?.map(i => (
<CustomCard
...props ommitted
/>
))}
</div>
);
};
export default CustomerList;
import CustomerList from './components/CustomerList/index';
// import Modal from './components/AddCustomerModal';
const CustomersPage = () => {
const [editCustomerModal, setEditCustomerModal] = useState(false);
const [editCustomer, setEditCustomer] = useState(null);
return (
<>
// This is where I would want it ideally
<Modal
onSuccess={handleSaveCustomerEdit}
onCancel={handleCancelCustomerEdit}
visible={editCustomerModal}
details={editCustomer}
/>
<CustomerList
dataSource={data}
/>
</div>
{/* </ModalContext.Provider> */}
</>
);
};
export default CustomersPage;
You can define one selected useState atribute, that stores only the register that you want to pass to your modal.
For example:
import CustomerList from './components/CustomerList/index';
import Modal from './components/AddCustomerModal';
const CustomersPage = () => {
const [editCustomerModal, setEditCustomerModal] = useState(false);
const [editCustomer, setEditCustomer] = useState(null);
const [selected, setSelected] = useState(null);
return (
<>
<Modal
onSuccess={handleSaveCustomerEdit}
onCancel={handleCancelCustomerEdit}
visible={editCustomerModal}
details={editCustomer}
selectedCard={selected}
/>
{dataSource?.map(i => (
<CustomCard ...props ommitted setSelected={setSelected}/>
//somewere in CustomCard, trigger the event to set selected props that you want to pass for Modal
//remember to be careful with nullable object. use selectedCard?.something
))}
</>
);
};
export default CustomersPage;