how to destructure property if possibly undefined? - reactjs

I'm getting stuck on this TS error created at build time. Does anyone has any suggestions?
TypeError: Cannot destructure property 'site' of '(intermediate value)' as it is undefined.
export default function Project({
data,
preview,
}: {
data: any
preview: any
}) {
const { site, page } = data?.post
return (
<Layout site={site} page={page}>
// Stuff
</Layout>
)
}
export async function getStaticProps({ params, preview = false }) {
const { post, morePosts } = await getClient(preview).fetch(projectQuery, {
slug: params.slug,
})
return {
props: {
preview,
data: {
post,
morePosts: overlayDrafts(morePosts),
},
},
}
}
export async function getStaticPaths() {
const paths = await sanityClient.fetch(projectSlugsQuery)
return {
paths: paths.map((slug) => ({ params: { slug } })),
fallback: true,
}
}

You can't destructure it
Better to have an early return (in my opinion), and then continue as normal
if (!data) {
return null
}
const { site, page } = data.post;
// Continue on
...

data?.post will return undefined if post does not exist, so you have to add a fallback object.
const { site, page } = data?.post || {};

You can't destructure without having a source to destructure from, but you can use a default via nullish coalescing:
const { site, page } = data?.post ?? {};
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^
Your current data is of type any, so that should work, but if it had a proper type you might want to provide default values for site and page. For instance if data had the type {post: {site: string; page: number; }; }:
const { site = "something", page = 42 } = data?.post ?? {};
// −−−−−−−−−^^^^^^^^^^^^^^−−−−−−^^^^^−−−−−−−−−−−−−−−^^^^^^

Related

Error: A required parameter (username) was not provided as a string in getStaticPaths

Nextjs, Directus and Algolia to build a drivers page where you can click on their profile and see their info. To see someones info the link would be http://localhost:3000/drivers/username - Nothing too fancy.
The issue I have is when I click a link I get Error: A required parameter (username) was not provided as a string in getStaticPaths
The username is a string, so i don't understand it. Any direction would be appreciated. Thanks.
let config = {
headers: {
"authorization": 'Bearer xxx'
}
}
export async function getStaticPaths() {
const username = await fetch(`https://xxx.directus.app/users`,config).then(res => res.json());
const paths = [username.data].map((driver) => ({
params: {
username: driver.username
},
}))
return { paths, fallback: false }
}
export async function getStaticProps({ params }) {
const results = await fetch(`https://xxx.directus.app/users/?fields=username&[username][_eq]=${params.username}`, config).then(res => res.json());
return {
props: {
results
},
}
}
export default function Driver({ results }) {
return (
`<div>
<h1>{results.data[0].username}</h1>
</div>`
)
}
Most likely here username.data is already an array so you don't need to convert it to array otherwise you'll get this as paths : [ { params: { username: undefined } } ] since [username.data] contains only one element which is username.data and this latter does not have a property username because it is an array so the returned username will be undefined.
const paths = username.data.map((driver) => ({
params: {
username: driver.username
},
}))

SWR hook not reflecting database change

This component is for counting views at page level in Next.js app deployed on AWS Lambda
function ViewsCounter({ slug }: { slug: string }) {
const { data } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
useEffect(() => {
const registerView = () =>
fetch(`/api/views/${slug}`, { method: "POST" })
.catch(console.log);
registerView();
}, [slug]);
return (
<>
{views}
</>
);
}
This one is for displaying views on homepage
function ViewsDisplay({ slug }: { slug: string }) {
const { data } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
return (
<>
{views}
</>
);
}
While it works as expected on localhost, looks like it displays only the first fetched value and doesn't revalidate it for some reason.
When visiting the page, Counter is triggered correctly and the value is changed in DB.
Probably it has something to do with mutating, any hints are appreciated.
useSWR won't automatically refetch data by default.
You can either enable automatic refetch using the refreshInterval option.
const { data } = useSWR(`/api/views/${slug}`, fetcher, { refreshInterval: 1000 });
Or explicitly update the data yourself using a mutation after the POST request to the API.
function ViewsCounter({ slug }: { slug: string }) {
const { data, mutate } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
useEffect(() => {
const registerView = () =>
fetch(`/api/views/${slug}`, { method: "POST" })
.then(() => {
mutate();
})
.catch(console.log);
registerView();
}, [slug]);
return (<>{views}</>);
}

Next.js] getServerSideProps always return 'undefined'

/Pages
/history
/[hid]
/ HistoryDetailContainer.tsx
/ HistoryDetailPresenter.tsx
/ index.tsx
I'm rendering /history/[hid] Page with Container for data, Presenter for view.
But, when I try to fetch data with getServerSideProps at HistoryDetailContainer, it always passes undefined data.
here's my code.
--- Container ---
const HistoryDetailContainer: NextPage<{
ssrData: string;
}> = ({ ssrData }) => {
console.log(`ssr - ${ssrData}`);
...
at the bottom, I'm calling getServerSideProps.
export const getServerSideProps: GetServerSideProps = async (context) => {
try {
const ssrData = 'SSR TEST';
return {
props: {
ssrData,
},
};
} catch (e) {
console.dir(e);
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
};
It always return undefined.
I want to receive data which is not 'undefined' from getServerSideProps.
Anyone know what the problem is?
I need you guys, thanks.

How to apply useIntl language translation in a TypeScript file? Or is there any alternative/preferred workaround?

I am current having a quick startup on an Ant Design Pro project, when I try to implement translation with useIntl function from umi, it always give me a Invalid hook call error. I tried several workarounds to fix it but failed.
Here are my codes:
src/pages/user/login/model.ts
import { Effect, history, Reducer, useIntl } from 'umi';
import { message } from 'antd';
import { parse } from 'qs';
import { fakeAccountLogin, getFakeCaptcha } from './service';
import { extend } from 'lodash';
export function getPageQuery() {
return parse(window.location.href.split('?')[1]);
}
export function setAuthority(authority: string | string[]) {
const proAuthority = typeof authority === 'string' ? [authority] : authority;
localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority));
// hard code
// reload Authorized component
try {
if ((window as any).reloadAuthorized) {
(window as any).reloadAuthorized();
}
} catch (error) {
// do not need do anything
}
return authority;
}
export interface StateType {
status?: 'ok' | 'error';
type?: string;
currentAuthority?: 'user' | 'guest' | 'admin';
}
export interface ModelType {
namespace: string;
state: StateType;
effects: {
login: Effect;
getCaptcha: Effect;
};
reducers: {
changeLoginStatus: Reducer<StateType>;
};
}
const Model: ModelType = {
namespace: 'userAndlogin',
state: {
status: undefined,
},
effects: {
*login({ payload }, { call, put }) {
const response = yield call(fakeAccountLogin, payload);
yield put({
type: 'changeLoginStatus',
payload: response,
});
// Login successfully
if (response.status === 'ok') {
const intl = useIntl();
// Error Here //
message.success(intl.formatMessage({ id: 'userandlogin.login.success' }));
const urlParams = new URL(window.location.href);
const params = getPageQuery();
let { redirect } = params as { redirect: string };
if (redirect) {
const redirectUrlParams = new URL(redirect);
if (redirectUrlParams.origin === urlParams.origin) {
redirect = redirect.substr(urlParams.origin.length);
if (redirect.match(/^\/.*#/)) {
redirect = redirect.substr(redirect.indexOf('#') + 1);
}
} else {
window.location.href = redirect;
return;
}
}
history.replace(redirect || '/');
}
},
*getCaptcha({ payload }, { call }) {
yield call(getFakeCaptcha, payload);
},
},
reducers: {
changeLoginStatus(state, { payload }) {
setAuthority(payload.currentAuthority);
return {
...state,
status: payload.status,
type: payload.type,
};
},
},
};
export default Model;
The error is from the line
message.success(intl.formatMessage({ id: 'userandlogin.login.success' }));
Initially I thought it might cause by I used the React function in the Typescript file, so I tried to call the message.success in another global service through event, but the same thing happened, so I guess, is that any mistake I made in declaring the const intl in a model response part (maybe not the actual phrase for it, if not understand I can explain further)?
Edited 1:
As references, here is the source of the original project.
Ant Design Pro
Found Solution
getIntl(getLocale()).formatMessage({id:''});
From: Github

How can I ensure that the Next.js router.query is not undefined?

I'm using next.js and the import { useRouter } from 'next/router'.
However on initial load, router.query.id is undefined. It quickly fills in, but that initial load is a killer.
I'm trying to figure out how to do it, and tried:
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
// const res = await fetch(`https://.../posts/${params.id}`)
// const post = await res.json()
console.log(params)
// Pass post data to the page via props
return { props: { params } }
}
but this returns an error:
Error: getStaticPaths is required for dynamic SSG pages and is missing for '/app/d/[id]'.
I can't use getStaticPaths, since [id] is variable and can be any number of things.
So what's the best way to handle this?
I would do smt like this(without staticProps):
function Page(props) {
const router = useRouter();
const { query = {} } = router || {};
const { id = 0 } = query || {};
useEffect(()=> {
if(id) {
(async ()=> {
const res = await fetch(`https://.../posts/${id}`)
const post = await res.json();
})();
}
}, [id]);
}
And this is what official doc. says:
// You also have to define a Post component in the same file (pages/posts/[id].js)
function Post({ post }) {
const router = useRouter()
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <div>Loading...</div>
}
return <h1>Posts are here</h1>;
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// Pass post data to the page via props
return { props: { post } }
}
UPDATE:
After a bit research, have figure out this solution with staticProps:
export default function Post({ post }) {
return <h1>Post is here</h1>;
}
export async function getStaticPaths() {
return {
paths: [
{ params: { id: '*' } }
],
fallback: true
};
}
export async function getStaticProps(context) {
const res = await fetch(`https://api.icndb.com/jokes/random/${context.params.id}`);
const post = await res.json()
return { props: { post } }
}

Resources