How to pass state to child component with function based React? - javascript

I try to pass uploaded file to the child component that both parent and child components are the function based.
I am using React, TypeScript and Material-UI
Parents
import React from 'react';
import Child from './Child';
const Parent:React.FC =() => {
const [file, setFile] = React.useState(null);
const onChangeFile = (e:any) => {
setFile(e!.target.files[0]);
}
return(
<div>
<input
accept="*.*"
onChange={onChangeFile}
id="uploadFile"
type="file" />
<Button
onClick={() => document.getElementById('uploadFile')!.click()}
component='span'>
Upload file
</Button>
<Child files={file: File}>
</div>
Child
import React from 'react';
const Child:React.FC = (props) => {
return(
<div>
{{ props }}
</div>
)
}
export default Child
At this point, I just want to print out the file's information that I have {{ props }}. I am a newbie to React and please tell me anything I did wrong. Thanks in advance!
EDIT
I changed Child,
const Child:React.FC = ({files}) => {
return(
<div>
{ files }
</div>
)
}
then it throws error,
property 'files' does not exist on type '{children?:ReactNode; }'
EDIT2
Following to #EarlePoole, I changed my code.
Parents
const Parent:React.FC = () => {
//...
<Child file={file} />
Child
const Child:React.FC =(props) => {
return(
<div>
{props.file.file}
</div>
)
}
In parent component, I got this error
Type '{ file: any; } is not assignable to type 'IntrinsicAttributes & {children ?:ReactNode; }'. Property 'file' does not exist on type 'IntrinsicAttributes & { children?: ReactNode}'
In Child component,
Property 'file' does not exist on type '{children?:ReactNode; }'

While you did provide the typings for your functional component, you did not specify the generic type for React.FC.
You should define an interface for the props of your functional component, and supply it as the generic type.
interface ChildProps {
files: any; // try not to use any.
}
const Child:React.FC<ChildProps> = ({files}) => {
return(
<div>
{ files }
</div>
)
}
On my example, I used any, as I am unsure the exact type of files. You should avoid using any, and replace it with the respective type.

In your child, if you put {{props}} in your div to be printed out, it's going to likely give you [object Object] or something, because that is what props is.
In your parent you have this code <Child files={file: File}>. This assigns your {file: File} object to props.files in your Child component, which can then be accessed, from within your Child component, by doing props.files.file which will return the File item.
To avoid confusion and redundancy I would recommend you change your Child props assignment in your Parent component to <Child file={file}>.

Related

Getting Property 'ref' does not exist on type 'IntrinsicAttributes' error

I'm implementing a link in React and TypesSript that, after clicking, scrolls to the right component, I'm using useRef Hook.
But when I do that, the error occurs:
Type '{ ref: MutableRefObject<HTMLDivElement | null>; }' is not assignable to type 'IntrinsicAttributes'.
Property 'ref' does not exist on type 'IntrinsicAttributes'.
Below is the code, what am I doing wrong? I've tried several ways.
import NewComponent from '../NewComponent';
const titleRef = useRef<null | HTMLDivElement>(null);
const handleBackClick = (): any => {
titleRef!.current!.scrollIntoView({ behavior: 'smooth' });
};
return (
<Container>
<Link onClick={handleBackClick}>
Button Text
</Link>
...
...
...
<SomeComponents />
...
...
...
...
<NewComponent ref={titleRef} />
</Container>
)
The NewComponent is a simple component, like:
const NewComponent = () => {
return (
<Container>
// Its a simple component
</Container>
);
};
export default NewComponent;
Thanks to anyone who can help me with this!
You would wanna use forwardRef to tell TypeScript that NewComponent can accept a ref that will hold a div element. Here is how you would do it as an example:
import { forwardRef } from "react";
const NewComponent = forwardRef<HTMLDivElement>((props, ref) => {
return <div ref={ref}>{props.children}</div>;
});
import { useRef } from "react";
export default function App() {
const titleRef = useRef<HTMLDivElement>(null);
return (
<div>
<NewComponent ref={titleRef} />
</div>
);
}
I created this CodeSandbox if you wanna play with it. Also notice I change useRef<null | HTMLDivElement>(null) to useRef<HTMLDivElement>(null).
In React, ref can only attach to DOM, if you want to pass it to your react component, you should use forwardRef
you can find more detail here
https://reactjs.org/docs/react-api.html#reactforwardref

How to type link prop in component (React + TypeScript)?

I'm fairly new to TypeScript and I've been struggling with this for a bit now.
I would like to pass on routes to a component as a prop, but I don't know how to type it, so I keep getting TypeErrors from TypeScript.
ParentComponent (where I pass on the routes):
import { ChildComponent } from '../components';
export const ParentComponent = () => (
<div>
...
<ChildComponent to={"/page_one"}>text</ChildComponent>
<ChildComponent to={"/page_two"}>text</ChildComponent>
<ChildComponent to={"/page_three"}>text</ChildComponent>
...
</div>
)
And then my child component looks like this:
import { Link } from 'react-router-dom';
interface ChildProps {
children: React.ReactNode
to?: React.LocationDescriptor<any>
}
export const ChildComponent = ({children, to}): ChildProps => {
...
<Link to={to}/>
{children}
...
}
I keep getting the Error: "... is not assignable to type 'LocationDescriptor | ((location: Location) => LocationDescriptor)'."
I have tried multiple ways to type it and have searched around, but couldn't find any solution that worked for me.
So, what's the correct way to type the 'to' prop?
As far as I know keeping the type as string would solve the matter
type Props = { to: string; };
Also remove the question mark to make sure that the value is always passed. For some reason if the value you might get can be undefined, add a check or default value to it.
Hope this answers your query.

Render prop as string or component with Typescript

I have a function that conditionally renders/returns a string or a dynamic component depending on what type the prop is:
const renderValue = (value: string | React.ReactNode) => {
if (React.isValidElement(value)) {
const Component = value
return <Component />
}
return value
}
However, with the above code I get the following message from Typescript:
JSX element type 'Component' does not have any construct or call signatures.ts(2604)
I have read other answers on this topic on SO, but still haven't concluded an answer.
<Component /> is JSX notation and it's basically telling React to render this component. That's only possible in React Component which has to return JSX code. To solve the problem you could just check if argument is valid element and then render it conditionally in desired React Component
import React from 'react'
interface Props {
value: string | React.ReactNode
}
const SomeComponent:React.FC<Props> = ({value}) => {
return (
<div>
<span>Hello World</span>
{React.isValidElement(value) ? <Component/> : value}
</div>
)
}

Props from contextual provider: Property 'xxx' does not exist on type 'IntrinsicAttributes & InterfaceProps'

I am getting a Typescript error of Property 'toggleAuthError' does not exist on type 'IntrinsicAttributes & InterfaceProps'.ts(2322) when trying to pass a function from a context provider to a component.
The context provider is
interface InterfaceProps {
children?: any;
}
interface InterfaceState {
error: any;
toggleAuthError: any;
}
const GlobalContext = createContext({
error: null,
toggleAuthError: () => {}
});
export class GlobalProvider extends React.Component<InterfaceProps, InterfaceState> {
public toggleAuthError = ({ authError }: any) => {
this.setState({ error: authError });
};
public state = {
error: null,
toggleAuthError: this.toggleAuthError
};
public render() {
console.log(this.state);
const { children } = this.props;
return <GlobalContext.Provider value={this.state as any}>{children}</GlobalContext.Provider>;
}
}
export const GlobalConsumer = GlobalContext.Consumer;
And the component in which I am accessing the value is
const SignInPageBase = () => (
<GlobalProvider>
{({ toggleAuthError }: any) => (
<Form toggleAuthError={toggleAuthError} />
)}
</GlobalProvider>
);
The error is shown on the import component Form.
What is causing this error and how can I fix it? I have many components that are similar without this issue.
You seem to haved missed out your GlobalConsumer to consume the context (see here).
const SignInPageBase = () => (
<GlobalProvider>
<GlobalConsumer>
{({ toggleAuthError }: any) => (
<Form toggleAuthError={toggleAuthError} />
)}
</GlobalConsumer>
</GlobalProvider>
);
Form is a standard HTML tag (as opposite to react component).
You're getting this error because toggleAuthError is not a standard attribute for Form tag.
One good solution would be to stick to the standard and use custom props only for your react component, Which in this case may be to wrapper the Form with your own component and use the toggleAuthError attribute on it.
Sometimes it is not possible or simply not maintainable (Like when you use external libraries which demand using such attribute on the element directly), So another option would be to expand the type-definition to include the additions you like.
For doing so, create a type-definition file (like my-app.d.ts), then you can add the definition you like as you do normally with typescript:
declare module 'react' {
interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
'toggleAuthError'?: string;
}
}

React + TypeScript: Passing React Component as Props, rendering with props [Error: TS2339]

I am using React + TypeScript.
I have a scenario where I have to pass a React.SFC component to another component. Which I am doing like this:
Container.tsx
import ChildComp from './ChildComp';
<ParentComponent CustomComp={ ChildComp } someArray={ [1, 2] } />
Now the issue is, I want to iterate this ChildComp component with someArray value, inside ParentComponent.
This child Component has it's own prop someValue, and I have added types to it, like what this child component can accept, and I am passing this prop while iterating it inside Parent.
ParentComponent.tsx
const content = someArray.map((value, index) => (
<CustomComp someValue={ value } key={ index } />
));
{ content }
But I am getting error, that TS2339: Property 'someValue' does not exist on type 'IntrinsicAttributes & { children?: ReactNode; }'.
Although, if I directly use above iteration in Container.tsx where I am importing it, it works fine. But not working if I am passing it to another component.
Am I missing something here?
Note: Since the same iteration is working on Container.tsx, I am passing the iterated content to ParentComponent and rendering it like a variable:
{ CustomComp }
This works, but I want to know why the other solution did not work.
More details
ChildComp.tsx
type Props = {
someValue: number,
};
const ChildComp: React.SFC<Props> = ({ someValue }) => {
return (
// Content
{ someValue }
)
}
Type of CustomComp, in ParentComponent:
type Props = {
CustomComp?: React.ReactNode | null
};
Before it was React.ComponentType, but I was getting error, so I changed it ReactNode, since I am directly passing content now.
CustomComp prop isn't typed properly. It accepts a component (CustomComp={ ChildComp }), not an element (CustomComp={ <ChildComp /> }). In this case it would be not React.ReactNode but React.ComponentType.
It's also not random component but specific component that accepts someValue prop. ParentComponent props likely should be typed as:
type ParentCompProps = {
CustomComp: React.ComponentType<ChildCompProps>,
someArray: number[]
};

Categories

Resources