React pass multiple properties and children - reactjs

I'm new to React, typescript and nextjs and I'm trying to pass multiple properties to a component. But, I seem to override them.
For example I have this in my _app.tsx
function App({ Component, myProps }: MyAppProps): JSX.Element {
const { ...props } = myProps;
return (
<MyStateProvider>
<Component {...props} />{' '}
</MyStateProvider>
);
}
In MyStateProvider, I have this in my-state-context.tsx
export const MyStateProvider: React.FC<any> = ( {children}) => {
const contextValue = doSomething();
return (
<MyStateContext.Provider value={contextValue}>
{children}
</MyStateContext.Provider>
);
};
However, I wish to something like this in my _app.tsx
function App({ Component, myProps }: MyAppProps): JSX.Element {
const { ...props } = myProps;
return (
<MyStateProvider {...props}>
<Component {...props} />{' '}
</MyStateProvider>
);
}
And this MyStateProvider in my-state-context.tsx
export const MyStateProvider: React.FC<any> = ( props, {children }) => {
const contextValue = doSomething(props); // <-- trying to accomplish passing props
return (
<MyStateContext.Provider value={contextValue}>
{children}
</MyStateContext.Provider>
);
};
What is the correct way to go about this?

You can do the following:
function App({ Component, ...myProps }: MyAppProps): JSX.Element {
return (
<MyStateProvider {...myProps}>
<Component {...myProps} />
</MyStateProvider>
);
}
and in your provider, supposing that you are passing the component like this <MyStateProvider Children={SomeComponent}/>
// note that first letter of Children should be uppercase to behave as a component
export const MyStateProvider: React.FC<any> = ({Children, ...props}) => {
const contextValue = doSomething({...props});
return (
<MyStateContext.Provider value={contextValue}>
<Children />
</MyStateContext.Provider>
);
};
See the example

Related

Listening to events fired from Children component in Parent Component in React

How do i listen to children events in parent component?
Look at my code what i am trying to achieve.
Page Component
interface PageProps {}
const Page: FC<PageProps> = ({}) => {
//I don't want to bind handlers here and bind it to Parent.
return (
<div>
<Parent>
<>
<Child />
<Child />
<Child />
<Child />
</>
</Parent>
</div>
)}
export default Page;
Child Component
import { FC } from 'react';
import AnotherChild from '../components';
interface IProps {
handleChange?: (value: string) => void;
}
const Child: FC<IProps> = ({ handleChange }) => {
//Methods
const onChange = (value: string) => {
handleChange ? handleChange(value) : null;
}
return (
<div>
<AnotherChild onChange={onChange} />
</div>
)
}
export default Child;
Parent Component
import React, { FC, Children, cloneElement, isValidElement } from 'react';
interface IProps {
children: React.ReactNode
}
const Parent: FC<IProps> = ({ children }) => {
//Methods
const handleChange = (value: string) => {
console.log("VALUE: ", value)
}
const arrChildren = Children.toArray(children);
return (
<div>
{
arrChildren.map((child, index) => {
return (
React.cloneElement(child, { handleChange: handleChange })
)
})
}
</div>
)
}
export default Parent;
So, I have children components which is emitting handleChange event, I want to listen those events in my <Parent /> component which is wrapped around.
Look at my <Page /> Component that is how those components will be called.
I have tried something you can look at my code but i am not getting those events.
Please help me to figure it out what i was doing wrong here.
Thank you
well, you did have the correct idea for listening to child events.
the problem is that you wrapped the child components inside a React.Fragment.
I think you did that because of the type of child props. the witch says children: React.ReactNode. change that to children: React.ReactNode | React.ReactNode[] and then remove the fragment.
you should have something like this.
interface IProps {
handleChange?: (value: string) => void;
}
const Child: FC<IProps> = ({ handleChange }) => {
const onChange = (value: string) => {
handleChange ? handleChange(value) : null;
}
return (
<div>
<AnotherChild onChange={onChange} />
</div>
)
}
interface IProps {
children: React.ReactNode | React.ReactNode[]
}
const Parent: FC<IProps> = ({ children }) => {
const handleChange = (value: string) => {
console.log("VALUE: ", value)
}
const arrChildren = Array.isArray(children) ? children : [children];
return (
<div>
{
arrChildren.map((child, index) => {
return (
React.cloneElement(child, { handleChange: handleChange })
)
})
}
</div>
)
}
const Page: FC<PageProps> = ({}) => {
return (
<div>
<Parent>
<Child />
<Child />
<Child />
<Child />
</Parent>
</div>
)}
Assuming that the goal is to add a handleChange to props of the Child from the wrapper Parent, according to React document, perhaps try the following way of using cloneElement in this use case:
Simplified live demo on: stackblitz
Page Component:
interface PageProps {}
const Page: FC<PageProps> = ({}) => {
return (
<div>
<Parent>
{[1, 2, 3, 4].map((item) => (
<Child key={item} />
))}
</Parent>
</div>
);
};
Parent Component:
import React, { FC, Children, cloneElement } from 'react';
interface IProps {
children: React.ReactNode;
}
const Parent: FC<IProps> = ({ children }) => {
const handleChange = (value: string) => {
console.log('VALUE: ', value);
};
return (
<div>
{Children.map(children, (child, index) =>
cloneElement(child as React.ReactElement, {
handleChange: handleChange,
})
)}
</div>
);
};

How to type a component with children prop?

I want to pass a wrapper component as a prop
I tried this:
type HeaderProps = {
Wrapper: ReactNode;
}
const Header = ({ Wrapper }: HeaderProps) => {
return (
<Wrapper>
<button>click me</button>
</Wrapper>
)
}
But it results in an error:
JSX element type 'Wrapper' does not have any construct or call signatures.
Example wrapper component:
type SomeWrapperProps = {
children: ReactNode;
}
const SomeWrapper = ({ children }: SomeWrapperProps) => {
return (
<div>
{children}
</div>
)
}
Usage:
<Header Wrapper={SomeWrapper} />
or
<Header Wrapper={(props) => <SomeWrapper {...props} other={props} />} />
What should I replace ReactNode with to make it work?
Is there any other syntax to make things work like that?
I'd choose FC type. You could also pass your custom props to it if SomeWrapper accepts more props than children.
type HeaderProps<T = {}> = {
Wrapper: FC<T>;
};
const Header = ({ Wrapper }: HeaderProps<CustomProps>) => {
return (
<Wrapper>
<button>click me</button>
</Wrapper>
);
};

testing useContext with react testing library

How should I test if className was added to child component in Jest with react-testing-library when props comes from context? I'm using CSS modules
Here's an example i wrote for this issue:
const Context = createContext()
const ContextProvider = ({ children }) => {
const [state, setState] = useState(false);
return (
<ContextProvider value={state}>
{children}
</ContextProvider>
);
};
const Display = () => {
const state = useContext(Context)
return <div className={`${state && styles[my-class]}`}></div>
}
const App = () => {
return (
<ContextProvider>
<Display />
</ContextProvider>
)
}
I tried something like this
it('should add classname', () => {
const { baseElement } = render(
<Context.Provider value={value}>
<Display />
</Context.Provider>
);
expect(baseElement).toHaveAttribute(
'class',
'my-class'
);
});

How do I get type checking to work with React forwardRef

I am having trouble getting type checking to work while using forwardRef. My code is as follows:
Component:
export interface Props {
label?: string;
callback: () => void;
}
export const _Button = (props: Props) => {
return (
<div>
<p>test</p>
</div>
);
};
export const Button = React.forwardRef((props, ref) => {
return <_Button { ...props } forwarded={ref} />
});
Component Usage:
export const App = () => {
return (
<div>
<h1>Application</h1>
<Button label="foo" />
</div>
);
};
You can see I am not passing the required callback prop but typescript doesn't see an issue. Below is what I have tried and I still cannot get type checking to work.
export interface Props {
label?: string;
callback: () => void;
}
export const _Button = (props: Props) => {
return (
<div>
<p>test</p>
</div>
);
};
export const Button = React.forwardRef<HTMLButtonElement, Props>((props: Props, ref) => {
return <_Button { ...props } forwarded={ref} />
});
Any help would be appreciated. Thanks.
Added Image for response to answers 1 and 2. This is the typescript error I am expected to have.
I assume you want to do smth like that:
import React, { FC, forwardRef, ForwardedRef, createRef } from 'react'
export interface Props {
label?: string;
callback: () => void;
forwardedRef: ForwardedRef<HTMLDivElement>
}
export const _Button: FC<Props> = (props: Props) => {
return (
<div ref={props.forwardedRef}>
<p>test</p>
</div>
);
};
export const Button = forwardRef<
HTMLDivElement,
Omit<Props, 'forwardedRef'>
>((props, ref) => <_Button {...props} forwardedRef={ref} />);
export const App = () => {
const ref = createRef<HTMLDivElement>();
return (
<div>
<h1>Application</h1>
<Button ref={ref} label="foo" callback={() => null} />
</div>
);
};
Here you can find documentation regarding refs.
You are pass down the ref by props of _Button. You should declare forwarded prop for _Button component with type: React.Ref<HTMLDivElement>.
type Ref<T> = RefCallback<T> | RefObject<T> | null;
import React from 'react';
export interface Props {
label?: string;
callback: () => void;
forwarded: React.Ref<HTMLDivElement>;
}
export const _Button = (props: Props) => {
return (
<div>
<p>test</p>
</div>
);
};
export const Button = React.forwardRef<HTMLDivElement, Props>((props, ref) => {
return <_Button {...props} forwarded={ref} />;
});
package versions:
"typescript": "^4.0.3",
"react": "^17.0.1",

How to convert HOC to react custom hook

I have below code snippets, just wondering apart from passing a component to withGroupInput, do we have another way to re-use this GroupedInputWithLabel with different components? Thanks
export const GroupedInputWithLabel = (props) => {
const { required, children, fieldName } = props;
const inputComponent = (
<>
<ControlLabel htmlFor={fieldName} required={required} />
{children}
</>
);
return <GroupedInput {...props}>{inputComponent}</GroupedInput>;
};
export const withGroupInput = (props, Component) => (
<GroupedInputWithLabel {...props}>
<Component {...props} />
</GroupedInputWithLabel>
);

Resources