Creating styled-component with React.createElement - reactjs

I would like to create a styled-component with a function, taking element as an argument, which I create with React.createElement call.
const StyledComponent = (element) => styled(element)`
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
`;
const myComponent = (props) => {
const element = React.createElement('div', null, `Hello World!`);
return (
StyledComponent(element)
);
}
This results in following error:
Uncaught Error: Cannot create styled-component for component: [object Object].
I am probably missing something, but am unable to figure it out.

You can't create a HOC of a rendered React node. The following line is a render call:
const element = React.createElement('div', null, `Hello World!`);
// this is the same as:
const element = <div >Hello World</div>
What you may want is to replace the createElement with an actual functional component:
const element = () => React.createElement('div', null, `Hello World!`);
// this is the same as:
const element = () => <div >Hello World</div>
Now you can create a styled component and render it. Full (relevant) code:
const element = () => React.createElement('div', null, `Hello World!`);
const SComponent = StyledComponent(element);
return React.createElement(SComponent);

Felix answer was almost right, but you still need to pass the className property in the inline component function in order to get the styles defined in your styled component applied.
I was able to find the right answer in this thread, I decided to use the JSX-syntax, If you need to pass refs, use React.cloneElement instead.
Complete example:
const StyleMyElement = (Element) => styled(({ className }) => <Element.type {...Element.props} className={className} />)`
position: absolute;
top: 0;
...
`;
...
const element = () => React.createElement('div', null, `Hello World!`);
const StyledComponent = StyleMyElement(element);
return (
<StyledComponent />
)

As of styled-components v4 you can just use the as prop to do this easily.
From the docs:
If you want to keep all the styling you've applied to a component but just switch out what's being ultimately rendered (be it a different HTML tag or a different custom component), you can use the "as" prop to do this at runtime.
import styled from "styled-components";
const Component = styled.div`
color: red;
`;
render(
<Component
as="button"
onClick={() => alert('It works!')}
>
Hello World!
</Component>
)

Here's how I implemented it:
The component:
export default function StyledCustomComponent({tag, className, children}) {
const customTag = React.createElement(tag);
const StyledCustomElement = styled(customTag.type)`
"some css rules"
`
return (
<StyledCustomElement {...className}>
{children}
</StyledCustomElement>
)
}
Then calling the component in another file:
<StyledCustomElement tag='div'> Some content <StyledCustomElement/>
Hope it helped!

Let make things simple
import styled from 'styled-component'//import library for making styled components
If for intance would like to create a styled component based on a <p></p> you would do lile this:
const MyP=styled.p
color:red;
font-size:10px;
font-weight:bold
;
You can ads as much css defintion as you want.
Now to use it:
const MyComponent=()=>{
<MyP>
Hello
</MyP>
}
Here instead of 'MyP' you could use 'p'(not styled).
It will also work for the const MyComponent=React.creatElement(MyP,null,'Hello');
Hope it helped

Related

How should I update individual items' className onClick in a list in a React functional component?

I'm new to React and I'm stuck trying to get this onClick function to work properly.
I have a component "Row" that contains a dynamic list of divs that it gets from a function and returns them:
export function Row({parentState, setParentState}) {
let divList = getDivList(parentState, setParentState);
return (
<div>
{divList}
</div>
)
}
Say parentState could just be:
[["Name", "info"],
["Name2", "info2"]]
The function returns a list of divs, each with their own className determined based on data in the parentState. Each one needs to be able to update its own info in parentState with an onClick function, which must in turn update the className so that the appearance of the div can change. My code so far seems to update the parentState properly (React Devtools shows the changes, at least when I navigate away from the component and then navigate back, for some reason), but won't update the className until a later event. Right now it looks like this:
export function getDivList(parentState, setParentState) {
//parentState is an array of two-element arrays
const divList = parentState.map((ele, i) => {
let divClass = "class" + ele[1];
return (
<div
key={ele, i}
className={divClass}
onClick={() => {
let newParentState =
JSON.parse(JSON.stringify(parentState);
newParentState[i][1] = "newInfo";
setParentState(newParentState);}}>
{ele[0]}
</div>
)
}
return divList;
}
I have tried to use useEffect, probably wrong, but no luck. How should I do this?
Since your Row component has parentState as a prop, I assume it is a direct child of this parent component that contains parentState. You are trying to access getDivList in Row component without passing it as a prop, it won't work if you write your code this way.
You could use the children prop provided by React that allow you to write a component with an opening and closing tag: <Component>...</Component>. Everything inside will be in the children. For your code it would looks like this :
import React from 'react';
import { render } from 'react-dom';
import './style.css';
const App = () => {
const [parentState, setParentState] = React.useState([
['I am a div', 'bg-red'],
['I am another div', 'bg-red'],
]);
React.useEffect(
() => console.log('render on ParentState changes'),
[parentState]
);
const getDivList = () => {
return parentState.map((ele, i) => {
return (
<div
key={(ele, i)}
className={ele[1]}
onClick={() => {
// Copy of your state with the spread operator (...)
let newParentState = [...parentState];
// We don't know the new value here, I just invented it for the example
newParentState[i][1] = [newParentState[i][1], 'bg-blue'];
setParentState(newParentState);
}}
>
{ele[0]}
</div>
);
});
};
return <Row>{getDivList()}</Row>;
};
const Row = ({ children }) => {
return <>{children}</>;
};
render(<App />, document.getElementById('root'));
And a bit of css for the example :
.bg-red {
background-color: darkred;
color: white;
}
.bg-blue {
background-color:aliceblue;
}
Here is a repro on StackBlitz so you can play with it.
I assumed the shape of the parentState, yu will have to adapt by your needs but it should be something like that.
Now, if your data needs to be shared across multiple components, I highly recommand using a context. Here is my answer to another post where you'll find a simple example on how to implement a context Api.

React does not recognize the X prop on a DOM element

I am beginner developer and I am working on react (gatsby, TS, styled components) project. I am getting this error:
"React does not recognize the isOpen prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase isopen instead. If you accidentally passed it from a parent component, remove it from the DOM element."
export const Navigation = () => {
const [isNavigationOpen, setIsNavigationOpen] = useState(false);
const { isTablet } = useQuery();
const showNavbar = () => {
setIsNavigationOpen((previousState) => !previousState);
};
const renderElement = isTablet ? (
<>
<SvgStyled
src='bars_icon'
isOpen={isNavigationOpen}
onClick={showNavbar}
/>
<MobileNavigation isOpen={isNavigationOpen}>
{NAVIGATION_DATA.map(({ id, url, text }) => (
<LinkMobile key={id} to={url}>
<ExtraSmallParagraph>{text}</ExtraSmallParagraph>
</LinkMobile>
))}
</MobileNavigation>
</>
) : (
<FlexWrapper>
{NAVIGATION_DATA.map(({ id, url, text }) => (
<LinkDekstop key={id} to={url}>
<ExtraSmallParagraph>{text}</ExtraSmallParagraph>
</LinkDekstop>
))}
</FlexWrapper>
);
return renderElement;
};
I am sure that I am missing some fundamental react stuff or something. Maybe someone could help me and explain the reason of this error.
When this happens it is because all props passed to the styled component are then also passed down to the DOM element that you are styling.
You've likely a component that looks like the following:
const SvgStyled = styled(SVG)<{ isOpen: boolean }>`
// your CSS and logic referencing the `isOpen` prop
`;
To resolve this issue you refactor the styled component definition and explicitly pass only the props you want to the element being styled. Use an anonymous function component and destructure the prop you don't want to pass on to the DOM element, and spread the rest of the props. This ensures the className prop that styled-components is creating a CSS class for is passed through.
Example:
interface SvgStyledProps {
className?: string,
isOpen: boolean,
}
const SvgStyled = styled(({ isOpen, ...props}) => (
<Svg {...props} />
))<SvgStyledProps>`
// your CSS and logic referencing the `isOpen` prop
`;
For any other Typescript specifics/caveats with styled-components see docs.
As of styled components v5.1, you can alternatively prevent undesired props from being passed down to your React node by prefixing it with a dollar sign ($) and designating it as a transient prop:
const SvgStyled = styled(SVG)<{ $isOpen: boolean }>`
// your CSS and logic referencing the `$isOpen` prop
`;
// SVG does NOT receive props.$isOpen
docs

How to properly use TypeScript types with a Styled Component canvas on React

I am creating a React component on Gatsby using TypeScript and have defined a canvas Styled Component this way:
const Background = styled.canvas`
position: fixed;
width: 100%;
height: 100%;
`;
And to use it I am assigning types to it this way:
const canvas: HTMLCanvasElement = Background,
context = Background.getContext('2d');
But I am getting this error with the canvas type:
Type 'String & StyledComponentBase<"canvas", any, {}, never> & NonReactStatics<never, {}>' is missing the following properties from type 'HTMLCanvasElement': height, width, getContext, toBlob, and 238 more.
I am also getting an error on the .getContext() method:
This expression is not callable.
Type 'never' has no call signatures.
I have been searching for a solution but can not find a proper one for this specific problem.
Could someone please explain to me the best way to use Styled Component canvas with TypeScript?
The answer from #101arrowz was almost right but had a problem with the context that gave me this error.
Object is possibly 'null'.
But it helped me to find a different approach with the styles being inline and solve the problem this way:
const Component: React.FC = () => {
const canvasRef = React.useRef<HTMLCanvasElement>(null);
const [context, setContext] = React.useState<CanvasRenderingContext2D | null>(
null
);
React.useEffect(() => {
if (canvasRef.current) {
const renderCtx = canvasRef.current.getContext('2d');
if (renderCtx) {
setContext(renderCtx);
}
}
}, [context]);
return (
<div>
<canvas
id="canvas"
ref={canvasRef}
style={{
position: 'fixed',
width: '100%',
height: '100%'
}}
></canvas>
</div>
);
The Background is a React.Component (i.e. a function that creates a virtual element) rather than an HTMLCanvasElement. Not only does the function need to be called for it to even return anything remotely like an HTMLCanvasElement, but you also need access to the underlying DOM element to make it work. I do have a suggestion that you might be able to try, though.
import { useRef, useEffect } from 'react';
const Background = styled.canvas`
position: fixed;
width: 100%;
height: 100%;
`;
const ComponentUsingTheCanvas = () => {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const context = canvasRef.current.getContext('2d');
// Do stuff with your canvas context here
});
return (
<Background ref={canvasRef} />
);
}
One note, I didn't type anything because TypeScript can do that automatically most of the time.
By the way, why use a styled component when you can just style inline? styled-components is only really useful if you do something with the props.

Images Rerendering inside Styled Component when Chrome Dev Tools is open

This is a bit of a strange one and not sure why it's happening exactly.
When the component mounts, I call a function that in my application makes an HTTP request to get an array of Objects. Then I update 3 states within a map method.
enquiries - Which is just the response from the HTTP request
activeProperty - Which defines which object id is current active
channelDetails - parses some of the response data to be used as a prop to pass down to a child component.
const [enquiries, setEnquiries] = useState({ loading: true });
const [activeProperty, setActiveProperty] = useState();
const [channelDetails, setChannelDetails] = useState([]);
const getChannels = async () => {
// In my actual project,this is an http request and I filter responses
const response = await Enquiries;
const channelDetailsCopy = [...channelDetails];
setEnquiries(
response.map((e, i) => {
const { property } = e;
if (property) {
const { id } = property;
let tempActiveProperty;
if (i === 0 && !activeProperty) {
tempActiveProperty = id;
setActiveProperty(tempActiveProperty);
}
}
channelDetailsCopy.push(getChannelDetails(e));
return e;
})
);
setChannelDetails(channelDetailsCopy);
};
useEffect(() => {
getChannels();
}, []);
Then I return a child component ChannelList that uses styled components to add styles to the element and renders child elements.
const ChannelList = ({ children, listHeight }) => {
const ChannelListDiv = styled.div`
height: ${listHeight};
overflow-y: scroll;
overflow-x: hidden;
`;
return <ChannelListDiv className={"ChannelList"}>{children}</ChannelListDiv>;
};
Inside ChannelList component I map over the enquiries state and render the ChannelListItem component which has an assigned key on the index of the object within the array, and accepts the channelDetails state and an onClick handler.
return (
<>
{enquiries &&
enquiries.length > 0 &&
!enquiries.loading &&
channelDetails.length > 0 ? (
<ChannelList listHeight={"380px"}>
{enquiries.map((enquiry, i) => {
return (
<ChannelListItem
key={i}
details={channelDetails[i]}
activeProperty={activeProperty}
setActiveProperty={id => setActiveProperty(id)}
/>
);
})}
</ChannelList>
) : (
"loading..."
)}
</>
);
In the ChannelListItem component I render two images from the details prop based on the channelDetails state
const ChannelListItem = ({ details, setActiveProperty, activeProperty }) => {
const handleClick = () => {
setActiveProperty(details.propId);
};
return (
<div onClick={() => handleClick()} className={`ChannelListItem`}>
<div className={"ChannelListItemAvatarHeads"}>
<div
className={
"ChannelListItemAvatarHeads-prop ChannelListItemAvatarHead"
}
style={{
backgroundSize: "cover",
backgroundImage: `url(${details.propertyImage})`
}}
/>
<div
className={
"ChannelListItemAvatarHeads-agent ChannelListItemAvatarHead"
}
style={{
backgroundSize: "cover",
backgroundImage: `url(${details.receiverLogo})`
}}
/>
</div>
{activeProperty === details.propId ? <div>active</div> : null}
</div>
);
};
Now, the issue comes whenever the chrome dev tools window is open and you click on the different ChannelListItems the images blink/rerender. I had thought that the diff algorithm would have kicked in here and not rerendered the images as they are the same images?
But it seems that styled-components adds a new class every time you click on a ChannelListItem, so it rerenders the image. But ONLY when the develop tools window is open?
Why is this? Is there a way around this?
I can use inline styles instead of styled-components and it works as expected, though I wanted to see if there was a way around this without removing styled-components
I have a CODESANDBOX to check for yourselves
If you re-activate cache in devtool on network tab the issue disappear.
So the question becomes why the browser refetch the image when cache is disabled ;)
It is simply because the dom change so browser re-render it as you mentioned it the class change.
So the class change because the componetn change.
You create a new component at every render.
A simple fix:
import React from "react";
import styled from "styled-components";
const ChannelListDiv = styled.div`
height: ${props => props.listHeight};
overflow-y: scroll;
overflow-x: hidden;
`;
const ChannelList = ({ children, listHeight }) => {
return <ChannelListDiv listHeight={listHeight} className={"ChannelList"}>{children}</ChannelListDiv>;
};
export default ChannelList;
I think it has to do with this setting to disable cache (see red marking in image)
Hope this helps.

Warning: Received `false` for a non-boolean attribute. How do I pass a boolean for a custom boolean attribute?

Warning: Received `false` for a non-boolean attribute `comingsoon`.
If you want to write it to the DOM, pass a string instead:
comingsoon="false" or comingsoon={value.toString()}.
How do I pass a boolean in a custom attribute for React?
I'm using styled-components and passing the attribute through the component. Here is a picture of how I'm passing the attr.
passing boolean custom attr as "comingsoon"
styled-components css props
Try this instead:
comingsoon={value ? 1 : 0}
As of 5.1 you can now use transient props ($) which prevents the props being passed to the DOM element.
const Comp = styled.div`
color: ${props =>
props.$draggable || 'black'};
`;
render(
<Comp $draggable="red" draggable="true">
Drag me!
</Comp>
);
You have to add $ prefix to attribute:
$comingsoon={value}
Styled Components had added transient props in 5.1 version:
https://styled-components.com/docs/api#transient-props
In my case, it was because I was accidentally passing {...#props} down into a div.
Usually passing attribute={false} is fine, but not to native elements.
Similar to Frank Lins answer above but I had to use undefined instead of 0 to get rid of the warning:
comingsoon={value ? 1 : undefined}
Just make it a number instead, this is the workaround from https://github.com/styled-components/styled-components/issues/1198:
This error with styled-components appears to be due to styled() attempting to apply a boolean to an element in the DOM, but DOM elements only accept strings as attributes.
This is well documented in the styled-components repository here: https://github.com/styled-components/styled-components/issues/1198
There are two solutions:
Lift the styled component w/ the passed attribute up, so that the attribute is not applied to the element directly. Or,
Filter the passed attribute out of the props when calling styled components.
Both of these options are demonstrated in the code below.
CodeSandbox: https://codesandbox.io/s/cool-thunder-9w132?file=/src/App.tsx
import React, { useState } from "react";
import styled from 'styled-components';
// demonstration of two different solutions for solving the styled-components error:
// `Warning: Received `false` for a non-boolean attribute`
// Solution 1: Lift the attribute up into a wrapper.
// Solution 2: Filter-out the `toggle` attribute passed to styled-component.
interface BtnProps {
toggle: boolean;
}
const Container = styled.div`
width: 100%;
height: 500px;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
`;
const StyledBtnOne = styled.div<BtnProps>`
& button {
background-color: ${({toggle}) => toggle ? ' #2ecc71' : '' };
};
`;
const StyledBtnTwo = styled(({primary, ...props}) =>
<button {...(({toggle, ...propz}) => propz)(props)} />)<BtnProps>`
background-color: ${({toggle}) => toggle ? ' #3498db' : '' };
`;
const App = () => {
const [ btnOne, setBtnOne ] = useState(false);
const [ btnTwo, setBtnTwo ] = useState(false);
const triggerOne = () => setBtnOne(!btnOne);
const triggerTwo = () => setBtnTwo(!btnTwo);
return (
<Container>
<StyledBtnOne toggle={btnOne}>
<button
onClick={triggerOne}>
Solution 1
</button>
</StyledBtnOne>
<StyledBtnTwo
toggle={btnTwo}
onClick={triggerTwo}>
Solution 2
</StyledBtnTwo>
</Container>
);
}
export default App;
This warning can be caused also if the property of styled component has the name existing in HTML. For example I had such issue when named property wrap. After renaming warning disappeared.
Add "+" before your booleans values.
comingsoon = {+creator.comingSoon}
example below from the Link to answer
import styled from "styled-components";
import { Link } from "react-router";
const StyledLink = styled(Link)`
color: ${({ inverted }) => (inverted ? "white" : "chartreuse")};
`;
function Navbar() {
return (
<nav>
{# Bad #}
<StyledLink inverted={true}>Home</StyledLink>
{# Good #}
<StyledLink inverted={+true}>About</StyledLink>
</nav>
);
}
Solved this by enclosing with brackets {} the boolean variable I was passing through props.
const childrenWithProps = React.Children.map(props.children, child => {
return React.cloneElement(child, { showcard: { showCard } }
)});
I got this issue and also shows React Hydration Error in my Next.js application. In my case it seems Styled component got a custom component and it can't process 'boolean'.
Here is my workaround:
before:
styled(Text)<{ center?: boolean}>
// Text is my custom component
after:
styled.div<{ center?: boolean}>

Resources