NextJS: some props lost in child component - reactjs

I created a project using NextJS template Blog starter kit, found here: https://vercel.com/templates/next.js/blog-starter-kit
I've successfully fetched my own posts from a graphql-endpoint and can render them, but when I add values to the post object on my own that weren't included in the original NextJS-project and pass them down to child components, they are available at first but then go undefined and throw an error if I try to use those values.
Please see below code and screenshot of how the values goes from defined to undefined.
I'm new to both Typescript and NextJS but have used React for some time, but I can't grasp why this happens. The code below renders, but I'd like to understand why just some of the props goes undefined, after clearly being included. Also in this specific example I'd like to be able to use coverImage.width in the Image component width-property instead of hardcoding a value.
index.tsx
type Props = {
allPosts: Post[]
}
export default function Index({ allPosts }: Props) {
const heroPost = allPosts[0]
const morePosts = allPosts.slice(1)
return (
<>
<Layout>
<Head>
<title>SWG - Blog</title>
</Head>
<Container>
<Intro />
{heroPost && (
<HeroPost heroPost={heroPost} />
)}
{morePosts.length > 0 && <MoreStories posts={morePosts} />}
</Container>
</Layout>
</>
)
}
export const getStaticProps = async () => {
const allPosts = (await getPosts()) || [];
return {
props: { allPosts },
}
}
hero-post.tsx. All heroPost-prop values are available here. Please see below image of console print as well (image link until I have 10 points on SO).
type Props = {
heroPost: {
title: string
coverImage: {
url: string
width: number
height: number
}
date: string
excerpt: string
author: Author
slug: string
}
}
const HeroPost = ({ heroPost }: Props) => {
return (
<section>
<div className="mb-8 md:mb-16">
<CoverImage title={heroPost.title} src={heroPost.coverImage.url} slug={heroPost.slug} coverImage={heroPost.coverImage} />
</div>
<div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28">
<div>
<h3 className="mb-4 text-4xl lg:text-5xl leading-tight">
<Link as={`/posts/${heroPost.slug}`} href="/posts/[slug]">
<a className="hover:underline">{heroPost.title}</a>
</Link>
</h3>
<div className="mb-4 md:mb-0 text-lg">
<DateFormatter dateString={heroPost.date} />
</div>
</div>
<div>
<p className="text-lg leading-relaxed mb-4">{heroPost.excerpt}</p>
<Avatar name={heroPost.author.name} picture={heroPost.author.picture} />
</div>
</div>
</section>
)
}
export default HeroPost
cover-image.tsx. Here's where the props of heroPost goes missing, but only the ones I've declared. Not "src" for example.
type Props = {
title: string
src: string
slug?: string
coverImage: {
url: string
width: number
height: number
}
}
const CoverImage = ({ title, src, slug, coverImage }: Props) => {
console.log(src);
//console.log(title);
console.log(coverImage);
const image = (
<>
<Image src={src} width={1495} height={841} />
</>
)
return (
<div className="sm:mx-0">
{slug ? (
<Link as={`/posts/${slug}`} href="/posts/[slug]">
<a aria-label={title}>{image}</a>
</Link>
) : (
image
)}
</div>
)
}
export default CoverImage
Apparently I can't post images since I'm a new member, but the above console.logs prints like this:
coverImage object exists
prop.src = correct src.value
props.coverImage = cover-image.tsx?0fdc:19 full coverImage object printed
Don't know where these react-devtools-backend lines come from but same thing here, both values exist.
react_devtools_backend.js:4082 correct src.value
react_devtools_backend.js:4082 full coverImage object printed
coverImage then goes undefined
cover-image.tsx?0fdc:19 undefined
but src in props still return its value
cover-image.tsx?0fdc:17 correct src.value
cover-image.tsx?0fdc:19 undefined
same thing with the lines from react_devtools_backend:
react_devtools_backend.js:4082 correct src.value
react_devtools_backend.js:4082 undefined
I've read numerous SO-threads and looked through the NextJS-docs but fail to realise what I'm doing wrong.
console.log of heroPost-object in hero-post component.

Related

trying to run "if" sentence and not working, react

I m calling the component like this
<div className="sidebar__chats">
<SidebarChat addNewChat />
i m expecting all avatars except the first one that should say "add new chat". this is the code of the component
function SidebarChat(addNewChat) {
const [seed, setSeed] = useState("");
useEffect(() => {
setSeed(Math.floor(Math.random() * 5000));
}, []);
const createChat = () => {
const roomName = prompt("please enter the name for chat");
if (roomName) {
//do some clever database stuff
}
};
return (
<>
{!addNewChat ? (
<div className="sidebarChat">
<Avatar
alt="João Espinheira"
src={`https://avatars.dicebear.com/api/pixel-art/${seed}.svg`}
sx={{ width: 38, height: 38 }}
/>
<div className="sidebarChat__info">
<h2>room name</h2>
<p>last message...</p>
</div>
</div>
) : (
<div onClick={createChat} className="sidebarChat">
<h2>add new chat</h2>
</div>
)}
</>
);
}
export default SidebarChat;
can anyone help, i think this should work but not entereing the else condition. does anyone knows why? when i dont use the " ! " in the addnewchat every single one turn into avatars, and doesnt do the else statement. i dont understand why cant i use like this, since the code is ok
currently i have this outcome
image1, and it should be something like this image2
Change the first line of the component to
function SidebarChat({addNewChat}) {
The argument to a component is an object containing all the props, so you need to destructure it to access a given prop.

Expression statement is not assignment or call when looping through a list and trying to create a grid layout of components

I am trying to create a grid layout of video components but my IDE gives me a warning saying
Expression statement is not assignment or call
import React, {Fragment} from 'react';
import VideoClip from "../Video/VideoClip";
function SubjectView(props) {
let ids = ["RfKHsvF69VdjdMu6bdugsyRcjYpQXrpKd6iZHeEknCkY00",
"RfKHsvF69VdjdMu6bdugsyRcjYpQXrpKd2ipHeEknCkY00",
"RfKHsvF69Vdjdiu6bdugsyRcjYpQXrpKd2iZHeEknCkY00"
];
return (
<Fragment>
<div className="columns-3" >
{ids.map((id)=>{
<VideoClip id={id}/>
console.log(id)
})}
</div>
</Fragment>
);
}
export default SubjectView;
I see the IDs printed in the console but nothing renders.
The video component looks like
function VideoClip() {
let { id } = useParams();
return (
<div className="container mx-auto px-4">
<MuxPlayer
streamType="on-demand"
playbackId={id}
metadata={{
video_id: "video-id-54321",
video_title: "Test video title",
viewer_user_id: "user-id-007",
}}
/>
</div>
);
}
export default VideoClip
I am wondering if I am trying to create the components incorrectly. Is there a best practice when trying to achieve this?
You're not returning any value from ids.map
<>
<div className="columns-3" >
{ids.map((id)=><VideoClip id={id}/>)}
</div>
</>
One issue is you are not returning <VideoClip id={id}/> in map function in jsx. Also, if map is used - key needs to be set
return (
<Fragment>
<div className="columns-3">
{ids.map((id) => {
console.log(id);
return <VideoClip key={id} id={id} />;
})}
</div>
</Fragment>
);
Next issue is about VideoClip component parameter. Id needs to be extracted in that way in case it is passed as an attribute. Also, memoize the objects that you are passing down to a components. Metadata here, for example.
function VideoClip({ id }) {
const metadata = useMemo(() => {
return {
video_id: "video-id-54321",
video_title: "Test video title",
viewer_user_id: "user-id-007"
};
}, []);
return (
<div className="container mx-auto px-4">
<MuxPlayer streamType="on-demand" playbackId={id} metadata={metadata} />
</div>
);
}
Last thing - wrap your array in useMemo so this array will not cause crazy rerenders.
const ids = useMemo(
() => [
"RfKHsvF69VdjdMu6bdugsyRcjYpQXrpKd6iZHeEknCkY00",
"RfKHsvF69VdjdMu6bdugsyRcjYpQXrpKd2ipHeEknCkY00",
"RfKHsvF69Vdjdiu6bdugsyRcjYpQXrpKd2iZHeEknCkY00"
],
[]
);
Note: you will see x2 logs in the console in the Codesandbox due to <StrictMode> is set.

Render a React component by mapping an array inside of an array of objects

I have trouble rendering a react component by mapping an array nested inside an array of objects.
I have this React component, which is simple images
function ProjectLangIcon(props) {
return (
<img className="project-icon" src={props.src} alt={props.alt}/>
)
}
This component is a part of this bigger one :
function Project(props) {
const projectLang = projectList.map(projects => {
return projects.lang.map(langs => {
return <ProjectLangIcon
key={langs.key}
src={langs.src}
alt={langs.alt}
/>
})
})
return (
<div className="project">
{props.title}
<div className="project-content">
{projectLang}
</div>
</div>
)
}
I'm mapping the data from this file :
const projectList = [
{
key: 1,
name: "Site de mariage from scratch",
link: "https://mariage-hugo-et-noemie.fr/view/index.php",
lang: [css, html, javascript, php, mySql]
},
{
key: 2,
name: "Site de paris sportifs",
link: "https://akkezxla.ae.lu/Akkezxla-Betting-Site/bet-list.php",
lang: [css, html, javascript, php, mySql]
},
{
key: 3,
name: "Site de location d'appartement",
link: "http://paris-island.com/",
lang: [wordpress, css, javascript]
}
]
with the lang array being constitued of objects like these :
const css = {
name: "CSS",
src: cssIcon,
alt: "Icône CSS"
}
const html = {
name: "HTML",
src: htmlIcon,
alt: "Icône HTML"
}
Then, I map the projects component inside the Realisation component like this :
function Realisation() {
const projects = projectList.map(item => {
return <Project
key={item.key}
title={item.name}
link={item.link}
/>
})
return (
<div className="realisations pro-block">
<BlockTitle
title="Réalisations"
close={closeReal}
/>
<div className="realisation-content">
{projects}
</div>
<BlockNav
left="Compétences"
switchl={switchComp}
center="Parcours Porfessionnel"
switchc={switchParcoursPro}
right="Parcours Académique"
switchr={switchParcoursAcad}
/>
</div>
)
}
the result I get is this : enter image description here
But I want each projects to have its corresponding language icons.
Does anyone have an idea of hiw I should proceed ?
Your issue is due to Project component. You are rendering this component for each Project of projectList but inside of this component you are not taking in the account the actual Project that is passed to this component.
Project (component)
const projectLang = projectList.map(projects => {
return projects.lang.map(langs => {
return <ProjectLangIcon
key={langs.key}
src={langs.src}
alt={langs.alt}
/>
})
})
Lines above are building the same projectLang for any Project you passed to this component. And is building it from All the projects you have, it returns the array of arrays. So [[all_icons_of_project_1], [all_icons_of_project_2], ...].
Here is what you should do:
First: You need to modify Product component to either pass a product item itself, either to pass additional property, langs in your case. And to modify .map function a little.
Notice: i destructured the props object passed as a parameter to Product component. Also, i wrapped the map function into useMemo for optimization purposes.
function Project({ title, link, langs }) {
const projectLang = useMemo(() => {
return langs.map((lang) => (
<ProjectLangIcon key={lang.key} src={lang.src} alt={lang.alt} />
));
}, [langs]);
return (
<div className="project">
<a href={link} className="project-txt" target="_blank">
{title}
</a>
<div className="project-content">{projectLang}</div>
</div>
);
}
Second - just pass the langs attribute (prop) to your Product component. (Again, i wrapped it into useMemo, for optimizations)
function Realisation() {
const projects = useMemo(() => {
return projectList.map((item) => (
<Project
key={item.key}
title={item.name}
link={item.link}
langs={item.lang}
/>
));
}, []);
return (
<div className="realisations pro-block">
{/* ... */}
<div className="realisation-content">{projects}</div>
{/* ... */}
</div>
);
}
Note - no icons in codesandbox, only placeholders and alt text.

Typescript React - use MouseEvents without passing it to child element

Can I use Mouse Events on whole React Element or I have to pass it to child element to get it work? After several functions components where I passed my handleEvent function I want to know if it's possible without getting a TypeScript error. My code is really simple
<Tile onHover={handleHover} name="Random name"/>
and Tile component
export const Tile: React.FC<{ name: string }> = ({ name }) => {
return (
<div className="tile-wrapper">
<h1 className="tile-header>
{name}
</h1>
</div>
)
}
What you want is to combine React.DOMAttributes<HTMLElement> with your custom props using an intersection. That will avail all the DOM events to your custom component, without having to manually pass it in.
export const Tile: React.FC<{ name: string } & React.DOMAttributes<HTMLElement>> = ({ name, ...events }) => {
return (
<div className="tile-wrapper" {...events}>
<h1 className="tile-header>
{name}
</h1>
</div>
)
}
As your Title component does not expect an onHover prop, it will just be ignored. The event you want to use is rather onMouseOver, which works on most HTML elements (according to documentation):
All HTML elements, EXCEPT: <base>, <bdo>, <br>, <head>, <html>, <iframe>, <meta>, <param>, <script>, <style>, and <title>
What you could do is the following, in the Title component:
export const Tile: React.FC<{ name: string, onHover: SomeType }> = ({ name, onHover }) => {
return (
<div className="tile-wrapper" onMouseOver={onHover}>
<h1 className="tile-header>
{name}
</h1>
</div>
)
}

Rendering different html in reusable components

So I'm trying to make a reuseable component, which takes in an array and a variable, then i want to map through that array and return html.
But when i reuse this component, the html wont necesarly be the same each time i use it. For example:
Home Component
<div className='home'>
<Mappedarray array={list} pattern={pattern}/>
</div>
Account Component
<div className='account'>
<Mappedarray array={users} pattern={pattern}/>
</div>
function Mappedarray(props) {
const {array, pattern} = props
const arrayrow = array?.map(el=>{
return VARIABLE_HTML
})
}
So this is the basic set up, now for the VARIABLE_HTML, I want to return different html elements, for example, in the Home Component I want to return
<Link to={el.link}>
<p>{el.title}</p>
<i className={el.src}'></i>
</Link>
But for the User Component, I want to return
<div className='usercont'>
<img src={el.src}/>
<p>{el.title}</p>
</div>
I've been using a solution like passing a boolean variable to the component to determine what html should be used, but the component will get very messy and doesn't seem like a good solution.
For example, in the Home Component I would pass down:
<Mappedarray array={list} pattern={pattern} home={true} />
Then in the Mappedarray Component I would do
function Mappedarray(props) {
const {array, pattern, home, user} = props
const arrayrow = array?.map(el=>{
return <>
{
home?
<Link to={el.link}>
<p>{el.title}</p>
<i className={el.src}'></i>
</Link>
:user?
<div className='usercont'>
<img src={el.src}/>
<p>{el.title}</p>
</div>
:ANOTHER_VAR?
...
}
</>
})
}
Therefore, by doing it like this it would get very messy and disorganized, looking to a more dynamic way of doing this
Well since you are mapping trough same array and want different result maybe reusable component is not for this the best case. But if you want to have this united into one component like this you can just add a flag isLink to your reusable component and you are done:
function Mappedarray(props) {
const { array, pattern, isLink } = props;
const arrayrow = array?.map((el) => {
return isLink ? (
<Link to={el.link}>
<p>{el.title}</p>
<i className={el.src}></i>
</Link>
) : (
<div className="usercont">
<img src={el.src} />
<p>{el.title}</p>
</div>
);
});
}
Than this would be usage of that component in two cases:
Home Component
<div className='home'>
<Mappedarray array={list} pattern={pattern} isLink={true}/>
</div>
Account Component
<div className='account'>
<Mappedarray array={users} pattern={pattern} isLink={false}/>
</div>
NOTE
If you just put isLink with no ={true} it will be implicitly true. But for this example i added it explicitly
You can accept a render function as a prop. But if you are doing that then your Mappedarray isn't doing much of anything.
function Mappedarray({ array = [], render }) {
return (
<div>{array.map(render)}</div>
);
}
You can define render components for various types. Make sure that you are setting a key property since we will use this as a callback for .map.
const MyLink = ({ link, title, src }) => (
<Link to={link} key={link}>
<p>{title}</p>
<i className={src}></i>
</Link>
)
You would call Mappedarray by passing the function component as the render prop. Your array prop would be an array of props for that component.
const Test = () => {
return (
<Mappedarray
array={[{ link: "/", title: "home", src: "/images/home.jpg" }]}
render={MyLink}
/>
)
}
With Typescript Types
You could also tweak this slightly to pass the array index as a prop to the render component instead of passing it as a second argument. This version allows for both class components and function components to be passed to render.
function Mappedarray<T>({ array = [], render: Render }: Props<T>) {
return (
<div>{array.map((props, index) => (
<Render {...props} index={index} />
))}</div>
);
}
With Typescript Types

Resources