Mapping over an array in React typescript - reactjs

I have a static data structure that I'm serving from my Next project's API route which is an array of objects with the following interface:
export interface Case {
id: string;
title: string;
beteiligte: string[];
gerichtstermin?: string;
tasks?: [];
}
I'm using React Query to pull in the data in a component:
const Cases:React.FC = () => {
const { data, status } = useQuery("cases", fetchCases);
async function fetchCases() {
const res = await fetch("/api/dummy-data");
return res.json();
}
return (
<DefaultLayout>
<Loading loading={status === "loading"} />
{data.cases.map((case)=><p>{case.name}</p>)}
</DefaultLayout>
);
}
export default Cases;
But when trying to map over the data I keep getting these squiggly lines and an incredibly ambiguous error:
I've tried absolutely everything I can think of and have searched far and wide through React Query documentation, typescript guides and of course many, many stackoverflow posts. Please help! (typescript newbie)
UPDATE:
When I type {data.cases.map(()=>console.log("hello")} there is no error at all. But as soon as I add a parameter to the anonymous function, the errors pop up: {data.cases.map((case)=>console.log("hello"))}

In the case interface, you do not have "name" property.
you should be mapping only if success is true. you have to guard your code. see what you get console.log(data). also, use key prop when mapping
{status === "success" && (
// case is reserved word. use a different name
{data.cases.map((caseItem) => (
<p key={caseItem.id}>{caseItem.name}</p>
))}
)}
case is a reserved word. If you use any other word it works

I tried to reproduce your issue in sandbox and I managed to make it "transpile"
Sandbox link
You have to add prop name of type string to the Case interface in order to use it
export interface Case {
name: string;
id: string;
title: string;
beteiligte: string[];
gerichtstermin?: string;
tasks?: [];
}
// in my example interface is named CaseResonse
I assume your API response looks like this
{ "data": { "cases": [] } }
You have to define the API response to be usable:
interface GetCaseResponseWrapper {
cases: Array<Case>;
}
// in my example it is array of CaseResponse
interface GetCaseResponseWrapper {
cases: Array<CaseResponse>;
}
You need to decorate your fetchCases to return GetCaseResponseWrapper
async function fetchCases() {
const res = await fetch("/api/dummy-data");
const jsonResult = await res.json();
return jsonResult.data as GetCaseResponseWrapper;
}
the data from the useQuery may be undefined, so you should use ?.
{data?.cases.map((c) => (
<p>{c.name}</p>
))}
Sandbox link

Related

How to use useQuery hook without variables?

I'm using Typescript, and I know how to use useQuery hook with variables, but now I have a GraphQL query without variables like below:
const GetTopAlertsQuery = gql`
query getTopAlerts {
getTopAlerts {
ens
walletAddress
}
}
`;
Basically I just need it return all the data in the database without doing any filtering. I have already implemented the back-end and it works successfully, so the query should be good.
I also have set up these two interfaces to hold the data:
interface topAlertValue {
ens: string;
walletAddress: string;
}
interface jsonData {
topalerts: topAlertValue[];
}
And I have tried the below ways, but none of them work:
// attempt #1
const { data } = useQuery<jsonData>(
GetTopAlertsQuery
);
// attempt #2
const data = ({ topalerts }: jsonData ) => {
useQuery(GetTopAlertsQuery);
};
// attempt #3
const data = <Query<Data, Variables> query={GetTopAlertsQuery}>
{({ loading, error, data }) => { ... }}
</Query>
If you know how to use useQuery hook without variables, please help me out! Thanks!

Problem of pulling the image with the TypeScript of an external Api

Speak dev! I have a problem when importing images from an external api, I tag it: <img src = {photos [0] .src} /> However it doesn't recognize from .src, can someone explain to me how this works?
Note: It will pull everything at once, because I just wanted to do a test to see if I would be able to pull the api.
import React, { useState, useEffect } from 'react';
interface Post {
id: number;
nome: string;
preco: string;
fotos: string;
descricao: string;
src: string;
}
const Dasboard: React.FC = () => {
const [dados, setDados] = useState<Post [] | null>(null);
useEffect(() => {
(async () => {
const response = await fetch('https://ranekapi.origamid.dev/json/api/produto/');
const data = await response.json();
setDados(data);
console.log(data);
})();
}, []);
return (
<div>
<div>
{dados && dados.map(({id, nome, preco, fotos, descricao}) => (
<li key={id}>
{nome}
{preco}
<img src={fotos[0].src} alt={fotos[0].titulo}></img>
{descricao}
</li>
))}
</div>
</div>
);
}
export default Dasboard;
Elaborating on the discussion from the comments, #AlexChashin is correct in pointing out that you defined fotos as a string and you need to be defining it as an array of objects.
When you but brackets around the object like in your comment photos: [{src: string, title: string}] you are making a Tuple type which means that it as an array with exactly one entry which is that object. This is fine if that's what the API returns. However if the API might have 0 photos, or if you want to be able to access images other than just the first, you want to define it as an Array type which can have any length by adding the brackets at the end photos: {src: string, title: string}[].
If there is a possibility of an empty array, you may also want to check that fotos[0] exists before the img tag.
(also I think you need {!!dados && dados.map instead of {dados && dados.map so that it evaluates it as a boolean).

JSX Element does not have any construct or call signatures

I am trying to add Application Insights in my ReactJS Application. I changed the JS code that is provided on the GitHub Demo to TypeScript.. now I have
class TelemetryProvider extends Component<any, any> {
state = {
initialized: false
};
componentDidMount() {
const { history } = this.props;
const { initialized } = this.state;
const AppInsightsInstrumentationKey = this.props.instrumentationKey;
if (!Boolean(initialized) && Boolean(AppInsightsInstrumentationKey) && Boolean(history)) {
ai.initialize(AppInsightsInstrumentationKey, history);
this.setState({ initialized: true });
}
this.props.after();
}
render() {
const { children } = this.props;
return (
<Fragment>
{children}
</Fragment>
);
}
}
export default withRouter(withAITracking(ai.reactPlugin, TelemetryProvider));
But when I try to import the same component <TelemetryProvider instrumentationKey="INSTRUMENTATION_KEY" after={() => { appInsights = getAppInsights() }}></Telemetry> I get an error Severity Code Description Project File Line Suppression State
(TS) JSX element type 'TelemetryProvider' does not have any construct or call signatures.
I attempted to simply // #ts-ignore, that did not work. How do I go about solving this?
Given the example above, I hit the same issue. I added the following:
let appInsights:any = getAppInsights();
<TelemetryProvider instrumentationKey={yourkeyher} after={() => { appInsights = getAppInsights() }}>after={() => { appInsights = getAppInsights() }}>
Which seem to solve the issue for me, I am now seeing results in Application Insights as expected.
I guess if you want to have the triggers etc on a different Page/Component you may wish to wrap it in your own useHook or just add something like this to the component.
let appInsights:any;
useEffect(() => {
appInsights = getAppInsights();
}, [getAppInsights])
function trackEvent() {
appInsights.trackEvent({ name: 'React - Home Page some event' });
}
Not the best answer, but it's moved me forward. Would be nice to see a simple hooks version in typescript.
Really hope it helps someone or if they have a cleaner answer.

How to implement disjoint unions in Flow in logical component

SUMMARY:
I need help figuring out how to implement disjointed unions in Flow with the constraints that arise from using Contentful and React.
DETAILS:
We are making a single-page website for a client. We're using Flow for typechecking, React for development and Contentful for the content infrastructure. The site is pretty much completely built at this point. Oh, and this is our first time using Flow.
Our Main component works just fine, and it has a 100% flow coverage. However, we have 70 errors of the kind "Cannot create Releases element because property featured is missing in ContactsTypes [1] but exists in ReleasesTypes [2] in property data."
They're all stemming from a single component called Section, which is just a logical component that checks the section type (using the __typename property, which is generated by Contentful).
As far as I understand, using disjointed unions takes care of this problem, but I cannot for the life of me figure out how to fix my code. I have refactored in every possible way that I can think of and nothing has worked. I feel like I'm missing something either incredibly simple or incredibly complicated.
CODE:
Here is a simplified version of my code, including only two section types and skipping over the fields that are different between them.
Relevant part of components/Main/index.js:
type Props = {
data: MainTypes,
}
type SectionProps = {
data: SectionTypes,
}
type ReleasesProps = {
data: ReleasesTypes,
}
type ContactsProps = {
data: ContactsTypes,
}
const Main = ({ data }: Props) => {
const { sections } = data
const jsx = (
<main className="Main">
{sections.map(section => (
<Section data={section}></Section>
))}
</main>
)
return jsx
}
const Section = ({ data }: SectionProps) => {
const { __typename } = data
let jsx
if (__typename === 'ContentfulSectionReleases') {
jsx = <Releases data={data}></Releases> // the data inside the curly brackets has the red squiggly underneath
} else if (__typename === 'ContentfulSectionContacts') {
jsx = <Contacts data={data}></Contacts> // same here
} else {
jsx = null
}
const Releases = ({ data }: ReleasesProps) => {
const jsx = (
<section className="Section Releases">
// some stuff
</section>
)
return jsx
}
const Contacts = ({ data }: ContactsProps) => {
const jsx = (
<section className="Section Contacts">
// some other stuff
</section>
)
return jsx
}
Additional types (from types.js)
export type SectionTypes =
| ReleasesTypes
| EventsTypes
| PhotosTypes
| LyricsTypes
| ContactsTypes
export type MainTypes = {
title: string,
sections: Array<SectionTypes>,
}
export type ReleasesTypes = {
__typename: string,
id: string,
title: string,
slug: string,
// some stuff
}
export type ContactsTypes = {
__typename: string,
id: string,
title: string,
slug: string,
// some other stuff
}
GraphQL query (from pages/index.js)
export const query: string = graphql`
{
main {
title
sections {
__typename
... on ContentfulSectionReleases {
id
title
slug
// some stuff
}
... on ContentfulSectionContacts {
id
title
slug
// some other stuff
}
}
`
RESULTS:
Expected: no errors
Actual: 70 errors of the kind "Cannot create Releases element because property featured is missing in ContactsTypes [1] but exists in ReleasesTypes [2] in property data."
I think I found a good solution for the problem. To illustrate the changes I made, I recreated your example in Try Flow. This should represent your situation: there are two errors when trying to create the <Releases /> and <Contacts /> elements.
The two changes I made were:
to change the type definition of ReleasesTypes (and similarly ContactsTypes) to have __typename set to a literal,
type ReleasesTypes = {
- __typename: string,
+ __typename: 'ContentfulSectionReleases',
id: string,
title: string,
slug: string,
// some stuff
releasesProp: string,
}
to change the conditional in Section to refine on data.__typename instead of extracting the property,
const Section = ({ data }: SectionProps) => {
- const { __typename } = data
- if (__typename === 'ContentfulSectionReleases') {
+ if (data.__typename === 'ContentfulSectionReleases') {
return <Releases data={data}></Releases>;
- } else if (__typename === 'ContentfulSectionContacts') {
+ } else if (data.__typename === 'ContentfulSectionContacts') {
return <Contacts data={data}></Contacts>;
}
return null
}
Try Flow
Change (1) is needed because Flow needs to have some way of disambiguating between the two cases. If both ReleasesTypes and ContactsTypes had __typename: string, Flow would not be able to refine by checking the value of __typename. Change (2) is necessary because Flow assumes that objects will be modified and invalidates type refinements as aggressively as possible (for non-read-only types). Therefore, when refining on __typename directly, Flow will not refine the rest of data. However, if you check data.__typename, Flow will refine data to be the appropriate type. It's strange having to jump through this hoop because, functionally, the code is the same and it's clear that your code does not modify data at all. You could get around this by marking the types as read-only with the $ReadOnly utility type and then you wouldn't need to make change (2). This would require more work, which is why I suggested the above route.

How can I desctructure items out of an object that is returned from a React hook using Typescript?

I have a component that needs to tap into the React Router query params, and I am using the use-react-router hook package to access them.
Here is what I am wanting to do:
import React from "react;
import useReactRouter from "use-react-router";
const Foo = () => {
const { id } = useReactRouter().match.params;
return (
<Bar id={id}/>
)
}
The issue is that this throws the following error in VS Code, and at compile time:
Property 'id' does not exist on type '{}'.ts(2339)
I have found that if I refactor my code like so:
const id = match.params["id"], I do not get the error, but I feel like this is not the correct approach for some reason. If someone could point me in the right direction, I would appreciate it.
I figured it out. The solution was to include angle brackets between the hook's name and the parenthesis, like so:
const { match } = useRouter<{ id: string }>();
const { id } = useRouter<{ id: string }>();
Or if you prefer nested destructuring:
const { match: { params: id } } = useRouter<{ id: string }>();
You can try to give default value to params
const { id } = useReactRouter().match.params || {id: ""};
It may be possible that params to be null at initial level
The code is insufficient.
However, at first glance,
// right way
const { history, location, match } = useReactRouter()
// in your case
const { match: { params : { id } } } = useReactRouter()
// or
const { match } = useReactRouter()
const { id } = match.params
now, try to console the value first.
Also, please try to pass the props to a functional component from it's container, since it's more logical.
From your comment below, i can only assume you solved it. Also, it's recommended to handle possible undefined values when you use it.
{ id && id }
However, the first step should've been consoling whether it has value in it,
console.log('value xyz', useReactRouter())

Resources