I don't know why I can log value and i saw it have value, but when I set this value to ReactNode, it don't show anything. I just follow the docs of Nextjs.
What is my problem and how can I fix it?
my file name is [productId].tsx
My code
const productDetail : NextPage<Product>= (product) => {
console.log(product)
return (
<div>
<Navbar />
<div style={{ marginTop: 200 }}>
<div className="grid wide">
<div className="row">
<div className="col l-12 c-12 m-12">
<h1 className={styles.productName}>Name:{product.productName}</h1>
</div>
</div>
...
</div>
</div>
<Footer />
</div>
);
};
export const getStaticPaths: GetStaticPaths = async () => {
const { data } = await client.query<GetProductsQuery>({
query: GetProductsDocument,
variables: {
paginationOptions:{
skip:0
}
},
});
return {
paths: data.getProducts.products!.map((product) => ({
params: { productId: `${product.id}` },
})),
fallback: "blocking",
};
};
export const getStaticProps: GetStaticProps<
{ [key: string]: any },
{ productId: string }
> = async ({ params }) => {
const data = await client.query<GetProductQuery>({
query: GetProductDocument,
variables: { productId: params?.productId },
});
return {
props:{
product:data.data.getProduct.product
}
};
};
export default productDetail;
Have a nice day!
I don't know why but i fix my error by create a Props interface
interface Props{
product:Product
}
const productDetail: NextPage<Props> = ({product}) => {
...
}
Related
import { FC } from 'react';
import { useDropzone } from 'react-dropzone';
import { FileIcon } from 'assets/icons';
import Typography from '../Typography';
import { TMultipleDropzoneProps } from './types';
import styles from './MultipleDropzone.module.scss';
const MultipleDropzone: FC<TMultipleDropzoneProps> = ({ title, onDrop }) => {
const { getRootProps, getInputProps, open, isDragAccept, isFocused, isDragReject } = useDropzone({
accept: { 'image/*': ['.jpeg', '.png'], 'video/mp4': ['.mp4', '.MP4'] },
onDrop,
noClick: true,
noKeyboard: true,
maxFiles: 3,
});
const accept = isDragAccept ? 1 : 0;
const focused = isFocused ? 1 : 0;
const rejected = isDragReject ? 1 : 0;
// This is used for warning in console for camel-case attributes to the DOM element and to make it boolean
return (
<div className={styles.wrapper}>
<div
onClick={open}
className={styles.container}
{...getRootProps({ accept, focused, rejected })}
>
<input {...getInputProps({})} />
{rejected === 1 && (
<div className={styles.error}>
Some files were rejected because they did not meet the requirements.
</div>
)}
<div className={styles.container__content}>
<Typography>{title}</Typography>
</div>
<button onClick={open} className={styles.icon}>
<FileIcon />
</button>
</div>
</div>
);
};
export default MultipleDropzone;
the type file:
export type TMultipleDropzoneProps = {
title: string;
onDrop: (e: any, a: any) => void;
isFileUploaded: boolean;
maxFiles?: number;
};
the modal I am using it in:
import { useContext, useState } from 'react';
import { ModalContext } from 'context/Modal';
import { FileExtended } from 'types/global/file';
import { useAppDispatch, useAppSelector } from 'hooks';
import { NewPostTextArea, MultipleDropzone, Typography, Button } from 'components';
import { createActivityPost } from 'store/slices/activitiesSlice/activitiesThunks';
import { CloseCircleIcon } from 'assets/icons';
import { TImages } from './types';
import styles from './NewPost.module.scss';
const NewPost = () => {
const { closeModal } = useContext(ModalContext);
const [images, setImages] = useState<TImages[]>([]);
const [description, setDescription] = useState<string>('');
const dispatch = useAppDispatch();
const { userData } = useAppSelector((state) => state.auth);
const createPost = () => {
const post = {
user_id: userData?.id as number,
description: description,
content_url: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__340.jpg',
content_type: 'string',
};
closeModal();
if (description.trim()) {
dispatch(createActivityPost(post));
}
};
const isFileUploaded = images.length > 0;
const onDrop = (acceptedFiles: FileExtended[], maxFiles: number) => {
acceptedFiles.forEach((file) => {
const reader = new FileReader();
reader.onload = (e) => {
setImages((prev) => [
...prev,
{
id: Date.now(),
type: file.type,
src: e.target && e.target.result,
file: file,
description: file.name,
},
]);
};
reader.readAsDataURL(file);
});
let modifiedAcceptedFiles = acceptedFiles;
if (acceptedFiles.length > maxFiles) {
// If the number of files exceeds the maxFiles limit,
// slice the extra files off the array and show an error message
modifiedAcceptedFiles = acceptedFiles.slice(0, maxFiles);
}
};
const removeImage = (id: number) => {
setImages(images.filter((image) => image.id !== id));
};
const imageFiles = images.map((image) => {
return (
<div key={image.id} className={styles.container__main__case__box}>
<CloseCircleIcon
onClick={() => removeImage(image.id)}
className={styles.container__main__case__box_close}
/>
{image.type.includes('video') ? (
<video src={image.src as string} autoPlay loop />
) : (
<img src={image.src as string} alt={image.description} />
)}
</div>
);
});
return (
<div className={styles.container}>
<Typography className={styles.container__head}>New Post</Typography>
<div className={styles.container__description}>
<NewPostTextArea value={description} setValue={setDescription} />
</div>
<div className={styles.container__main}>
<Typography className={styles.container__main__head}>{images.length} items</Typography>
<div className={styles.container__main__case}>{imageFiles}</div>
</div>
<MultipleDropzone
onDrop={onDrop}
title='Please attach your files here (Max 3)'
isFileUploaded={isFileUploaded}
/>
<div className={styles.container__footer}>
<Button className={styles.container__footer_close} onClick={closeModal}>
Close
</Button>
<Button type='submit' className={styles.container__footer_submit} onClick={createPost}>
Create
</Button>
</div>
</div>
);
};
export default NewPost;
I tried adding maxFiles to every single component, I also tried adding it to the onDrop component. New to React ( 1 week) and I am slowly losing my sanity. I will never forgive Zuckerberg for this apparition he has brought upon coderkin. Even chatGPT could not help my case.
I'm trying to setup a piece of code into its own component, however, by doing so the data doesn't show up after doing it:
Code StackBlitz Example
As you see from the picture, both of the inputs from the component setup are blank. I'm assuming that it might have something to do with closures, but I'm not sure, and even more confused as how to resolve the issue.
App.tsx
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { SimpleTable } from './components/SimpleTable/SimpleTable';
import ITableCol from './components/SimpleTable/ITableCol';
interface IItem {
userId: number;
id: number;
title: string;
body: string;
}
interface IIdItem {
[key: string]: number;
}
export const App = () => {
const getItems = async () => {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts');
const twoItems = [res.data[0], res.data[1]];
setItems(twoItems);
};
const getTableCols = (): ITableCol[] => {
return [
{
title: 'title',
renderFn: renderTitle,
},
{
title: 'input',
renderFn: renderInput,
},
];
};
const processData = () => {
let _idValues = { ...idValues };
for (const item of items) {
if (!_idValues[item.title]) {
_idValues[item.title] = item.id * 5;
}
}
return _idValues;
};
const renderTitle = (item: IItem) => {
return <div className="">{item.title}</div>;
};
const handleChange = (e: ChangeEvent<any>) => {};
const renderInput = (item: IItem) => {
const valueItem = idValues[item.title];
return (
<div className="">
<div>Id: {item.id}</div>
<div>
<input type="number" value={valueItem} onChange={handleChange} />
</div>
</div>
);
};
useEffect(() => {
getItems();
}, []);
const [items, setItems] = useState<IItem[]>([]);
const [idValues, setIdValues] = useState<IIdItem>({});
const [tableCols, setTableCols] = useState<ITableCol[]>([]);
useEffect(() => {
setTableCols(getTableCols());
setIdValues(processData());
}, [items]);
return (
<div className="p-2">
<h1>State Issue Example</h1>
<div className="mb-5">
<div>Simple Table</div>
<SimpleTable data={items} cols={tableCols} />
</div>
<div>
<div>Non Componentized Table</div>
<div className="d-flex">
<div className="w-50">
<b>title</b>
</div>
<div className="w-50">
<b>input</b>
</div>
</div>
</div>
<div>
{items.map((item) => {
return (
<div className="d-flex">
<div className="w-50">{renderTitle(item)}</div>
<div className="w-50">{renderInput(item)}</div>
</div>
);
})}
</div>
</div>
);
};
SimpleCode.tsx
import React, { useState } from 'react';
import ITableCol from '../SimpleTable/ITableCol';
type Props = {
cols: ITableCol[];
data: any[];
};
export const SimpleTable: React.FC<Props> = (props) => {
return (
<div>
<div className="d-flex">
{props.cols.map((col) => {
return (
<div className="w-50">
<b>{col.title}</b>
</div>
);
})}
</div>
{props.data.map((data, index) => {
return (
<div className="d-flex" key={'data-' + index}>
{props.cols.map((col, index) => {
return (
<div key={data.id} className="w-50">
{col.renderFn(data, index)}
</div>
);
})}
</div>
);
})}
<div>Data Size: {props.data.length}</div>
</div>
);
};
ITableCol.tsx
export default interface ITableCol {
renderFn: (item: any, index: number) => void;
title: string;
}
Problem is getTableCols gets render even before API response. Using two useEffect would have solved your issue.Screenshot of the Solution
useEffect(() => {
setIdValues(processData());
}, [items]);
useEffect(() => {
setTableCols(getTableCols());
}, [idValues]);
Given below is the full code of App.tsx
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { SimpleTable } from './components/SimpleTable/SimpleTable';
import ITableCol from './components/SimpleTable/ITableCol';
interface IItem {
userId: number;
id: number;
title: string;
body: string;
}
interface IIdItem {
[key: string]: number;
}
export const App = () => {
const getItems = async () => {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts');
const twoItems = [res.data[0], res.data[1]];
setItems(twoItems);
};
const getTableCols = (): ITableCol[] => {
return [
{
title: 'title',
renderFn: renderTitle,
},
{
title: 'input',
renderFn: renderInput,
},
];
};
const processData = () => {
let _idValues = { ...idValues };
for (const item of items) {
if (!_idValues[item.title]) {
_idValues[item.title] = item.id * 5;
}
}
return _idValues;
};
const renderTitle = (item: IItem) => {
return <div className=""> {item.title}</div>;
};
const handleChange = (e: ChangeEvent<any>) => {};
const renderInput = (item: IItem) => {
const valueItem = idValues[item.title];
return (
<div className="">
<div>Id: {item.id}</div>
<div>valueItem: {valueItem}</div>
<div>
<input type="number" value={valueItem} onChange={handleChange} />
</div>
</div>
);
};
useEffect(() => {
getItems();
}, []);
const [items, setItems] = useState<IItem[]>([]);
const [idValues, setIdValues] = useState<IIdItem>({});
const [tableCols, setTableCols] = useState<ITableCol[]>([]);
useEffect(() => {
setIdValues(processData());
}, [items]);
useEffect(() => {
setTableCols(getTableCols());
}, [idValues]);
return (
<div className="p-2">
<h1>State Issue Example</h1>
<div className="mb-5">
<div>Simple Table</div>
<SimpleTable data={items} cols={tableCols} />
</div>
<div>
<div>Non Componentized Table</div>
<div className="d-flex">
<div className="w-50">
<b>title</b>
</div>
<div className="w-50">
<b>input</b>
</div>
</div>
</div>
<div>
{items.map((item, i) => {
return (
<div key={i} className="d-flex">
<div className="w-50">{renderTitle(item)}</div>
<div className="w-50">{renderInput(item)}</div>
</div>
);
})}
</div>
</div>
);
};
This question already has answers here:
How to pass data from a page to another page using react router
(5 answers)
Closed 5 months ago.
I have 2 pages: (Datatable.jsx and Single.jsx).
I need to send id from Datatable.jsx to Single.jsx. After googling, i found that i can do that by using the <Link /> component, like this:
<Link
to={{
pathname: "/page",
state: data
}}
>
And then you can access the desired sent data to the second page:
render() {
const { state } = this.props.location
return (
// render logic here
)
}
I dont know how to apply this on my two pages:
Datatable.jsx:
//...
const Datatable = () => {
const [data, setData] = useState([]);
const handleDelete = (id) => {
setData(data.filter((item) => item.id !== id));
fetch(`https://api.factarni.tn/article/${id}`, {
method: "DELETE",
headers: {
Authorization:
"Bearer eyJhbGciOiJS...qw2QWltkyA",
},
}).then((response) => response.json());
};
useEffect(() => {
fetch("https://api.factarni.tn/article", {
method: "GET",
headers: {
Authorization:
"Bearer eyJhbGciOiJSUz...WltkyA",
},
})
.then((response) => response.json())
.then((json) => setData(json));
}, []);
const actionColumn = [
{
field: "action",
headerName: "Action",
width: 200,
renderCell: (params) => {
return (
<div className="cellAction">
<Link to="/users/test" style={{ textDecoration: "none" }}>
<div className="viewButton">Modifier</div>
</Link>
<div
className="deleteButton"
onClick={() => handleDelete(params.row.id)}
>
Delete
</div>
</div>
);
},
},
];
return (
<div className="datatable">
<div className="datatableTitle">
Add New Article
<Link to="/users/new" className="link">
<AddBusinessIcon className="icon" /> Add Article
</Link>
</div>
<DataGrid
className="dataGrid"
rows={data}
columns={userColumns.concat(actionColumn)}
pageSize={9}
rowsPerPageOptions={[9]}
checkboxSelection
/>
</div>
);
};
export default Datatable;
Single.jsx:
//...
const Single = ({ inputs, title }) => {
const [data, setData] = useState({
code: "",
article: "",
price: 0,
vat: 0,
status: 0,
company_id: 0,
});
const normalize = (v) => ({
code: v.code,
article: v.article,
price: Number(v.price),
vat: Number(v.vat),
status: Number(v.status),
company_id: Number(v.company_id),
});
function handle(e) {
const newdata = { ...data };
newdata[e.target.id] = e.target.value;
setData(newdata);
console.log(newdata);
}
const handleClick = async (e) => {
e.preventDefault();
const body = normalize(data);
await fetch("https://api.factarni.tn/article/create", {
method: "PUT",
body: JSON.stringify(body),
headers: {
"Content-Type": "application/json",
Authorization:
"Bearer eyJhbGciOiJ...w2QWltkyA",
},
});
};
return (
<div className="New">
<Sidebar />
<div className="newContainer">
<Navbar />
<div className="top">
<h1>{title}</h1>
</div>
<div className="bottom">
<div className="right">
<form>
<div className="formInput"></div>
{inputs.map((input) => (
<div className="formInput" key={input.id}>
<label>{input.label} </label>
<input
type={input.type}
placeholder={input.placeholder}
onChange={(e) => handle(e)}
id={input.label}
name={input.label}
value={input.label}
/>
</div>
))}
<button onClick={handleClick}>Update</button>
</form>
</div>
</div>
</div>
</div>
);
};
export default Single;
In the Database.jsx:
// ... code
<Link to={{ pathname: "/users/test", state: { id: params.row.id }}} style={{ textDecoration: "none" }}>
<div className="viewButton">Modifier</div>
</Link>
// ... code
In the Single.jsx:
import { useLocation } from 'react-router-dom';
// ... later in render function
const { state } = useLocation() // state.id should have your id
Although #deaponn's answer is good, you can also use the useNavigate hook and pass the id, name or any data in the state like below, using programmatic approach rather than Link component exported from react-router library
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
navigate('/(url on which you want to navigate)', { state: { id:1,name:encryptedId} });
On the navigated component, if you want to retrieve the passed id or name, you can use the useLocation hook as below:
import { useLocation } from "react-router-dom";
const location = useLocation();
var ttid = location.state.id;
NewsDetails
import React, { useState, useEffect } from 'react'
import { useParams } from 'react-router-dom'
const NewsDetail = ({ state }) => {
const { id } = useParams();
return (
<div>
{
state
.filter((a) => a.id === id)
.map((card, index) => (
<>
<div className="card" key={index}>
<h2>{card.title}</h2>
<h2>{card.content}</h2>
<img src={card.imageUrl} alt="" />
</div>
</>
))
}
</div>
)
}
export default NewsDetail
NewsItem
import React from 'react'
import clock from "../components/assets/img/Clock.svg"
import user from "../components/assets/img/User.svg"
import { Link } from 'react-router-dom'
const NewsItem = (props) => {
const { imageUrl, title, author, content, date, id } = props
return (
<Link className="col-lg-4 p-2" to={`/detail/${id}`}>
<div className="newsItem">
<img src={imageUrl} alt='newsPhoto' />
<h2>{id}</h2>
<div className="itemBody">
<p className='title'>{title}</p>
<div className="line"></div>
<p className='content'>{content}</p>
<div className="itemfooter">
<span><img src={clock} alt='clock' />{date}</span>
<span><img src={user} alt='user' />{author}</span>
</div>
</div>
</div>
</Link>
)
}
export default NewsItem
Home
import React, { useEffect, useState } from "react";
import NewsItem from "./NewsItem";
import SpinnerLoad from "./SpinnerLoad";
import { v4 as uuidv4 } from 'uuid';
const Home = (props) => {
const Category = [
"all",
"business",
"sports",
"world",
"technology",
"entertainment",
"science"
];
const { state, setState} = props;
const [loading, setLoading] = useState(false)
const fetchValue = (category) => {
fetch(`https://inshorts-api.herokuapp.com/news?category=${category}`)
.then(res => res.json())
.then(res => {
setState(res.data)
setLoading(true)
})
.catch((error) => console.log(error))
console.log(state);
setLoading(false);
};
// const fetchValue = async () => {
// try {
// const data = await axios
// .get(`https://inshorts-api.herokuapp.com/news?category=sports`)
// .then(res => {
// console.log(res);
// setState(res.data)
// })
// setLoading(true)
// console.log(loading);
// } catch (e) {
// console.log(e);
// }
// }
const CategoryButton = ({ category }) => (
<button onClick={() => fetchValue(category)} style={{ textTransform: 'capitalize' }}>{category}</button>
);
useEffect(() => {
fetchValue('all')
},[])
return (
<>
<div className="header-bg">
<h1 className="mb-3">News</h1>
<div className="btns ">
{Category.map((value, index) => {
return <CategoryButton category={value} key={index} />;
})}
</div>
</div>
<div className="news">
<div className="container">
<div className="row">
{
!loading
? <SpinnerLoad/>
:
state.map((data,index) => {
return (
<NewsItem
imageUrl={data.imageUrl}
author={data.author}
title={data.title}
content={data.content}
date={data.date}
id={uuidv4()}
key={index}
/>
);
})
}
</div>
</div>
</div>
</>
);
};
export default Home;
I have created a project with api. With categories it is possible to change the incoming data, but there is one thing where I want to get more detailed information when I click on the newsItem card. That api doesn't have id value, so I used uuid. Information corresponding to the id value should come with useParams. But it doesn't work. How can I fix this problem?
The first issue is that you are generating a GUID when rendering the state array which won't necessarily correlate to any data you are trying to match/filter by in the NewsDetail component.
state.map((data,index) => (
<NewsItem
imageUrl={data.imageUrl}
author={data.author}
title={data.title}
content={data.content}
date={data.date}
id={uuidv4()} // <-- new id each render cycle
key={index}
/>
))
You want to inject the id property when the data is fetch so that it's a stable reference that lives as long as the data does. In other words, it should be an intrinsic property of the data.
Example:
const fetchValue = async (category) => {
setLoading(true);
try {
const res = await fetch(`https://inshorts-api.herokuapp.com/news?category=${category}`);
const { data } = await res.json();
setState(data.map(el => ({
...el,
id: uuidv4(), // <-- map and inject id here
})));
} catch(error) {
console.log(error);
} finally {
setLoading(false);
}
};
...
state.map((data) => (
<NewsItem
key={data.id} // <-- use as React key
data={data} // <-- pass entire data object as prop
/>
))
NewsItem
const NewsItem = ({ data }) => {
const { imageUrl, title, author, content, date, id } = data;
return (
...
);
};
NewsDetail
const NewsDetail = ({ state }) => {
const { id } = useParams();
return (
<div>
{state
.filter((card) => card.id === id)
.map((card) => (
<div className="card" key={card.id}>
<h2>{card.title}</h2>
<h2>{card.content}</h2>
<img src={card.imageUrl} alt="" />
</div>
))
}
</div>
);
};
I have a child component (Gallery) that needs an array (posts) from its parent (Homepage). I tried through an interface (GalleryProps) but it's not working.
interface IPost {
id: number;
title: string;
author: string;
content: string;
}
interface GalleryProps {
posts: (IPost[]) => void;
}
const Gallery: React.FC<GalleryProps> = (props) => {
return (
{posts={props.posts}.map(post =>.map(post =>
<div className="postCard" key={post.id}>
<Link className="cardLink" to ={ `/detail/${post.id}`}>
<div>
{post.title}
</div>
<div>
{post.author}
</div>
</Link>
</div>
)}
);
};
const Homepage: React.FC = () => {
const [posts, setPosts] = useState<IPost[]>([]);
let navigateNew = useNavigate();
useEffect(() => {
getPosts()
.then(items => {
setPosts(items)
})
}, []);
return (
<div className="containerHomepage">
<Gallery
posts={posts}
/>
</div>
);
};
export default Homepage;
I think the problem is in GalleryProps. Do I have the array declaration wrong?
If this data you are having in props of Gallery is in the array , you can write it like below
interface IPost {
id: number;
title: string;
author: string;
content: string;
}
interface GalleryProps {
posts: IPost[];
}
const Gallery: React.FC<GalleryProps> = ({posts}) => {
return (
posts.map(post => {
return (
<div className="postCard" key={post.id}>
<Link className="cardLink" to ={ `/detail/${post.id}`}>
<div>
{post.title}
</div>
<div>
{post.author}
</div>
</Link>
</div>
)
})
);
};