React does not recognize the `xxx` prop on a DOM element - javascript

I've got a component that wrap any children that I give him. This component has a useState to give an editMode status and setter. So inside this component, to pass this state, I've done it like this :
import ShowMoreWrapper from 'components/ui/showmore/ShowMoreWrapper'
type PropsTypes = {
children: JSX.Element
}
const TargetCard = ({
children,
}: PropsTypes): JSX.Element => {
const [editMode, setEditMode] = useState(false)
return (
<ShowMoreWrapper initialHeight={initialHeight} maxHeight={maxHeight}>
{cloneElement(children, {
editMode,
setEditMode,
})}
</ShowMoreWrapper>
)
}
Then
<TargetCard>
<ProfileDetails />
</TargetCard>
And inside my children component, I received it like this :
type ProfileDetailsTypes = {
editMode?: boolean
setEditMode?: React.Dispatch<React.SetStateAction<boolean>>
}
const ProfileDetails = ({
editMode,
setEditMode,
}: ProfileDetailsTypes): JSX.Element => {
Inside this component, I use editMode as a boolean to display/hide things and setEditMode as a setter, like this example :
{editMode && <div>Hello world</div>}
<div onClick={() => setEditMode(false)}>Cancel edit mode</div>
But on render I've this double error :
React does not recognize the `editMode` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `editmode` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
React does not recognize the `setEditMode` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `seteditmode` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
In another child component I don't have any problem to deal with them, but on this one, I'm stuck... Can you see something obvious in my code ? Thanks.

Related

Create react component to conditionally wrapping children

I would like to create a ConditionalWrapper component to being more declarative, in my app.
My idea is to use it as following
<ConditionalWrapper condition={whatever} element={<a href="my-link" />}>
...other children
</ConditionalWrapper>
I got this so far, but obviously it is not working, and I really cannot see where I am wrong.
interface ConditionalWrapperProps {
condition: boolean
children?: React.ReactNode
element: React.ReactElement
defaultElement?: React.ReactElement
}
const ConditionalWrapper = ({
condition,
children,
element,
defaultElement
}: ConditionalWrapperProps): JSX.Element => {
const Element = (Wrapper): JSX.Element => <Wrapper>{children}</Wrapper>
return condition ? (
<Element Wrapper={element}>{children}</Element>
) : (
<Element Wrapper={defaultElement || Fragment}>{children}</Element>
)
}
The error I get at the moment is Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
It's clear that my types and logic are wrong, but I also tried different variations without success. Any suggestions?
You need to do several things. First of all your Element function isn't actually a valid React Function Component.
Then you need to accept parameters which are function components, and not quite elements yet.
I have separated the Element into its own scope called ElementWrapper, just for sake of understanding how the parameters were incorrect. You can of course move this back into the ConditionalWrapper.
You'll also have to move the fragment logic elsewhere, since Fragment is not a FunctionComponent
interface ConditionalWrapperProps {
condition: boolean;
children?: React.ReactNode;
element: React.FunctionComponent; //These need to be FunctionComponents
defaultElement?: React.FunctionComponent;
}
//Here you can see you forgot to have a children property
const ElementWrapper = (props: {
Wrapper: React.FunctionComponent;
children: React.ReactNode;
}): JSX.Element => <props.Wrapper>{props.children}</props.Wrapper>;
const ConditionalWrapper = ({
condition,
children,
element,
defaultElement,
}: ConditionalWrapperProps): JSX.Element => {
return condition ? (
<ElementWrapper wrapper={element>{children}</ElementWrapper>
) : DefaultElement ? (
<ElementWrapper Wrapper={defaultElement}>{children}</ElementWrapper>
) : (
<>{children}</>
);
);
};
Personally I don't think you even need the ElementWrapper class function, just call the functionComponents directly in ConditionalWrapper, like so. The properties are renamed to follow the guidelines that React Elements should have capitalized names.
const ConditionalWrapper = ({
condition,
children,
WrapperElement,
DefaultElement,
}: ConditionalWrapperProps): JSX.Element => {
return condition ? (
<WrapperElement>{children}</WrapperElement>
) : DefaultElement ? (
<DefaultElement>{children}</DefaultElement>
) : (
<>{children}</>
);
};
If you define your function as const Element = (Wrapper):...-->Wrapper refers to the Element properties, not the Wrapper property, changing to const Element = ({Wrapper}):... will work.
I think you can make you code shorter and save a few lines by doing this:
const ConditionalWrapper = ({
condition,
children,
element,
defaultElement
}: ConditionalWrapperProps): JSX.Element => {
const Wrapper = condition ? element : defaultElement || Fragment;
return (<Wrapper>{children}</Wrapper>)
}

Use ref inside React.memo

React.memo can be used to control whether a React function component should update or not. It compares by props by default. Now I want to compare a prop with one of the ref inside the function component. To be more specific, here is the code(Written in Typescript):
interface RichEditorProps {
value: string;
onChange: (value: string) => void;
}
export const RichEditor = React.memo((props: RichEditorProps) => {
const contentEditableDiv = useRef<HTMLDivElement>(null);
useEffect(() => {
contentEditableDiv.current!.addEventListener("input", () => {
props.onChange(contentEditableDiv.current!.innerHTML);
});
});
return (
<div contentEditable suppressContentEditableWarning ref={contentEditableDiv}>
{props.value}
</div>
)
}, ((prevProps, nextProps) => {
// I want to do this, but it doesn't work because contentEditableDiv is not available here.
return nextProps.value !== contentEditableDiv.current!.innerHTML;
}));
As you can see, I want to update the component when contentEditableDiv.current!.innerHTML doesn't equal to nextProps.value, but I cannot do it because contentEditableDiv is not available there. Is there any other way to achieve this without turning it into a class Component?
P.S. One way to achieve this may be to give the div an id and use document.getElementById to grab the div component inside the compare function. But I want to avoid this approach since in this way, I have to think of a method to assign a unique id to it, I haven't found an easy way to do it. And I'm curious if there's any other way to do it.

React forwardRef is never defined, even after re-render

EDIT: This problem was caused because I was attempting to use forwardRef in conjunction with Redux connect. Take a look at this post for the solution.
I am attempting to access a DOM element in a parent component by using React.forwardRef. The problem is that ref.current never gets defined, even after the first render.
In this parent component, I want to access a DOM element from one of the child components:
const MyFunctionComponent = () => {
const bannerRef = useRef<HTMLDivElement>(null);
let bannerIndent = useRef<string>();
useEffect(() => {
// bannerRef.current is ALWAYS null. Why?
if (bannerRef.current) {
// this conditional block never runs, so bannerIndent is never defined
const bannerWidth = bannerRef.current.getBoundingClientRect().width;
bannerIndent.current = `${bannerWidth}px`;
}
}, []);
return (
<>
<Banner ref={bannerRef} />
<ComponentTwo bannerIndent={bannerIndent.current} />
</>
)
}
The component which receives the forwarded ref:
const Banner = React.forwardRef<HTMLDivElement, Props> ((props, ref) => (
<div ref={ref} />
));
The component which needs some data derived from the forwarded ref:
const ComponentTwo = (props: {bannerIndent?: string}) => (
<StyledDiv bannerIndent={props.bannerIndent} />
)
// styled.div is a styled-component
const StyledDiv = styled.div<{bannerIndent?: string}>`
... // styles, some of which depend on bannerIndent
`
Answers to existing SO questions which discuss forwardRef always being null or undefined point out that the ref will only be defined after the first render. I don't think that's the issue here, since the useEffect should run after the first render, yet bannerRef.current is still null.
The logic for setting the value of bannerIndent with a useEffect hook works correctly if used inside of the Banner component with a normal ref. However, I need to put the ref in the parent component so that bannerWidth (generated using the forwarded ref) can be passed to a sibling of the Banner component.
Help would be greatly appreciated :D

React children throw PropTypes error

I'd like to know, how to handle the PropTypes Error when passing a component as a child:
Failed prop type: The prop `value` is marked as required in `ChildComponent`, but its value is `undefined`.
The render works as expected and it's passing the value prop correctly.
I suppose this happens because I am putting the component in the App component's render function without any props.
I am only passing those props to the ChildComponent when the ParentComponent maps over its children (which is the ChildComponent).
See the code: https://codesandbox.io/embed/r70r5z3j9q
Is there a way to prevent this from happening?
How should I be structuring my components?
Am I not supposed to passed components as children?
EDITED: Changed prop "name" to "value". To give it a more generic feel.
I tried to simplify the problem in the code.
I know I could pass the prop directly in App.
The use case would be when the parent is doing calculations and those calculations are supposed to be passed to the child. Without explicitly knowing what these children are.
That's why I'm using it as child in the first place.
You're using cloneElement and you're passing prop to it, not to original element. To fix it, pass props directly:
const App = () => (
<div>
<ParentComponent>
<ChildComponent name="bob" />
</ParentComponent>
</div>
);
You could easily pass component as a prop (not children) to you ParentComponent and render it only after it takes some heavy calculations:
const App = () => (
<div>
<ParentComponent component={ChildrenComponent} />
</div>
);
const ParentComponent extends React.Component {
state = { heavyComputationFinished: false } // initial state
componentDidMount() {
runYourHeavyComputations
.then(() => { this.setState({ heavyComputationsFinished: true }) })
}
render() {
const { component } = this.props
const { heavyComputationsFinished, name } = this.state
// return nothing if heavy computations hasn't been finished
if (!heavyComputationsFinished) { return null }
// we're getting this component (not its rendering call) as a prop
return React.render(component, { name })
}
}

Passing jsx content to redux state and rendering it from there

I am having trouble figuring out how to pass JSX to my redux state i.e. for modal component that is used globally and has its redux state where one parameter is content such content can be updated to include JSX code.
At the moment I am getting it to render correct content however it doesn't seem that functions are called correctly and I am also getting following error:
invariant.js:38 Uncaught Error: Objects are not valid as a React child
(found: object with keys {dispatchConfig, _targetInst,
isDefaultPrevented, isPropagationStopped, _dispatchListeners,
_dispatchInstances, nativeEvent, type, target, currentTarget, eventPhase, bubbles, cancelable, timeStamp, defaultPrevented,
isTrusted, view, detail, screenX, screenY, clientX, clientY, ctrlKey,
shiftKey, altKey, metaKey, getModifierState, button, buttons,
relatedTarget, pageX, pageY}). If you meant to render a collection of
children, use an array instead or wrap the object using
createFragment(object) from the React add-ons. Check the render method
of styled.div.
With a lot of following errors:
warning.js:36 Warning: This synthetic event is reused for performance
reasons. If you're seeing this, you're accessing the property
nativeEvent on a released/nullified synthetic event. This is set to
null. If you must keep the original synthetic event around, use
event.persist(). See https://fbme(replaced this as so doesn't allow links to fb)/react-event-pooling for more
information.
Example implementation:
Function called from a page to show modal and add contents to it
onToggleModal = () => {
this.props.modalToggle(
(<TopUp account={getSession().accounts[0] || {}} />)
);
}
Where this.props.modalToggle is a redux action like this:
export const modalToggle = (content = '') => ({
type: MODAL_TOGGLE,
payload: content
});
I then try to render such content inside my Modal container:
return (
<div>{this.props.content}</div>
)
I imported React into my reducer, in hopes to resolve jsx issues, but had no luck. It also seems like components are passed to reducer as some sort of weird objects.
Redux state can only contain plain objects and data types, so a JSX object would probably cause issues. Also it's most likely not a good idea to have a JSX object (which is basically a view) as part of your state.
Instead, you should pass around whatever data is required to render the final view. I think this would work:
onToggleModal = () => {
this.props.modalToggle(getSession().accounts[0] || {});
}
export const modalToggle = (account = {}) => ({
type: MODAL_TOGGLE,
account: account
});
return (
<div><TopUp account={account} /></div>
)
Hey it's been a while now but for the record I stumbled upon the same use case you are explaining in the reply to the accepted answer.
You can achieve this by adding a property of JsxElement | ReactElement | HTMLElement type in your modalSlice state:
export interface modalState {
active: boolean;
component?: ReactElement;
}
reducers: {
updateModalComponent: (state, value: PayloadAction<ReactElement>) => {
state.component = value.payload;
},
}
Then your modal component file could look something like this:
import { ReactElement } from 'react';
import './modal.scss';
interface Props {
active: boolean,
children: ReactElement | JsxElement | HTMLElement,
}
export const Modal = (props: Props) => {
return (
<div id="modal-container">
{
props.active &&
<div id="overlay">
<div id="modal-content-container">
{props.children}
</div>
</div>
}
</div>
)
}
Finally use it anywhere!
const element = <OtherComponent />;
dispatch(updateModalComponent(element));
Cheers!

Categories