Related
I’ve got a Next.JS app. I want to render a todo feed on the homepage, but also the user page. I'm a bit stuck on how to break down my Prisma queries.
I fetch a big data object using getServerSideProps and pass this to the page component (and using react-query to hydrate and do re-fetching, but not relevant now)
- getRecentTodos (includes todos) for my homepage
- getUserDetailsByName (includes todos) for the user page
export type getRecentTodos = ReturnType<typeof getRecentTodos> extends Promise<
infer T
>
? T
: never
export const getRecentTodos = async (recentItemsAmount = 20) => {
return await prisma.todos.findMany({
where: { done: true },
select: {
id: true,
userId: true,
content: true,
done: true,
createdAt: true,
attachments: true,
todoReplies: {
select: {
id: true,
userId: true,
content: true,
todoReplyLikes: true,
todoId: true,
user: { select: { name: true, displayName: true, image: true } },
},
orderBy: { createdAt: 'asc' },
},
todoLikes: {
select: {
user: true,
},
},
user: {
select: {
name: true,
displayName: true,
image: true,
},
},
},
orderBy: { createdAt: 'desc' },
take: recentItemsAmount,
})
}
export const getUserDetailsByName = async (username: string) => {
return await prisma.user.findUnique({
where: {
name: username,
},
select: {
name: true,
displayName: true,
bio: true,
location: true,
twitter: true,
image: true,
createdAt: true,
todos: {
select: {
id: true,
content: true,
userId: true,
done: true,
updatedAt: true,
createdAt: true,
attachments: true,
user: true,
todoLikes: true,
todoReplies: {
take: 30,
orderBy: { createdAt: 'desc' },
select: {
id: true,
userId: true,
todoId: true,
createdAt: true,
content: true,
user: true,
},
},
},
take: 30,
orderBy: { createdAt: 'desc' },
},
projects: true,
},
})
}
Both queries return ‘todos,’ but they can return it in a slightly different way. The todo feed component expects certain properties to be available
- E.g. displayName on todoReplies
- But on getUserDetailsByName the displayName might not be part of the response or it’s nested one layer deeper or something
How to keep this from getting complex very fast?
You more or less want to select todos in your queries the same way (returning the same and omitting the same, apart of some things like order)
But manually keeping these things in sync over lot’s of queries qet’s complex quickly
Possible solutions?
Should I break the getServerSideProps into multiple fetches?
So instead of one ‘getUserDetailsByName’ which has todos as a relationfield included
fetch user details
fetch todos
This would mean I also have to write more react-query code for refetching etc… because you are dealing with multiple objects. But it does seperate concerns more.
Using Typescript to catch it in my codebase when a function tries to access a property which is not returned from that specific Prisma query? (I’m just now starting to see the possibilities of Typescript for stuff like this)
Should I just standardize the way the todos get created in a prisma query with a function and include that function inside of the Prisma queries? you can include like:
const todoSelect = {
id: true,
userId: true,
content: true,
{.......}
user: {
select: {
name: true,
displayName: true,
image: true,
},
},
}
export type getRecentTodos = ReturnType<typeof getRecentTodos> extends Promise<
infer T
>
? T
: never
export const getRecentTodos = async (recentItemsAmount = 20) => {
return await prisma.todos.findMany({
where: { done: true },
select: todoSelect,
orderBy: { createdAt: 'desc' },
take: recentItemsAmount,
})
}
const userSelect = {
name: true,
{......}
todos: {
select: todoSelect,
take: 30,
orderBy: { createdAt: 'desc' },
},
projects: true,
}
export const getUserDetailsByName = async (username: string) => {
return await prisma.user.findUnique({
where: {
name: username,
},
select: userSelect,
})
}
I'm using Highchart with React.
For the moment I created a starting options:
/**
* Initial chart options, with "loading" enabled
*/
const chartOptions = {
accessibility: {
enabled: false
},
chart: {
type: "column",
events: {
load() {
const chart = this;
chart.showLoading(`${LOADING}...`);
}
}
}
};
In that component I call an API that returns some data to use. So, at that moment, I want to remove the "loading" and adding the data.
I solved for the moment with:
const [options, setOptions] = useState(chartOptions);
useEffect(() => {
if (detail) {
setOptions({
...chartOptions,
chart: {
type: "column"
},
xAxis: {
categories: detail.map(
(item) =>
`${item.data_em.substring(4, 6)}/${item.data_em.substring(0, 4)}`
)
},
series: [
{
name: "Alfa",
color: "#69BC66",
data: detail.map((item) => item.ricevuti)
},
{
name: "Beta",
color: "#DC3545",
data: detail.map((item) => item.mancanti)
},
]
});
}
}, [detail]);
<ChartComponent options={options} />
So, setting a new options in local State when data are ready. But I imagine that in this manner I get a different chart.
It works, but is there a better method?
With using the highcharts-react-official wrapper, the chart is recreated on state update only if immutable option is enabled. Otherwise, only chart.update method is called to apply changes.
Therefore, it is better to apply only the new options. In your case it will be:
useEffect(() => {
if (detail) {
setOptions({
chart: {
type: "column"
},
xAxis: {
categories: detail.map(
(item) =>
`${item.data_em.substring(4, 6)}/${item.data_em.substring(0, 4)}`
)
},
series: [{
name: "Alfa",
color: "#69BC66",
data: detail.map((item) => item.ricevuti)
},
{
name: "Beta",
color: "#DC3545",
data: detail.map((item) => item.mancanti)
},
]
});
}
}, [detail]);
Live demo: https://codesandbox.io/s/highcharts-react-demo-3lvomg?file=/demo.jsx
Docs: https://www.npmjs.com/package/highcharts-react-official#optimal-way-to-update
I'm pretty new to the headless cms of sanity.io. As I'm trying to fetch my data im getting this error but couldn't figure out why or where the problem could be exactly.
Anybody some ideas?
Error: Error serializing .images returned from getServerSideProps in "/company/[slug]".
Reason: undefined cannot be serialized as JSON. Please use null or omit this value.
//the slug file
export const Company = ({ title, slug, logo, images, currency, categories, body, url, location }) => {
console.log(title, slug, logo, images, currency, categories, body, url, location )
return <>Content</>
};
export const getServerSideProps = async pageContext => {
const pageSlug = pageContext.query.slug;
if (!pageSlug) {
return {
notFound: true
}
}
const query = encodeURIComponent(`*[ _type == "company" && slug.current == "${pageSlug}" ]`);
const url = `https://op5ktqwm.api.sanity.io/v1/data/query/production?query=${query}`;
const result = await fetch(url).then(res => res.json());
const post = result.result[0];
if (!post) {
return {
notFound: true
}
} else {
return {
props: {
title: post.title,
slug: post.slug,
logo: post.logo,
images: post.images,
currency: post.currency,
categories: post.categories,
body: post.body,
url: post.url,
location: post.location
}
}
}
}
export default Company;
//backend code structure
export default {
name: 'company',
title: 'Company',
type: 'document',
fields: [
{
name: 'title',
title: 'Title',
type: 'string',
},
{
name: 'slug',
title: 'Slug',
type: 'slug',
options: {
source: 'title',
maxLength: 96,
},
},
{
name: 'logo',
title: 'Logo',
type: 'image',
},
{
name: 'images',
title: 'Images',
type: 'array',
of: [{type: 'companyImage'}]
},
{
name: 'currency',
title: 'Currency',
type: 'array',
of: [
{
type: 'reference',
to: {type: 'currency'},
},
],
},
{
name: 'categories',
title: 'Categories',
type: 'array',
of: [
{
type: 'reference',
to: {type: 'category'},
},
],
},
{
name: 'body',
title: 'Body',
type: 'localeBlockContent',
},
{
name: 'url',
title: 'URL',
type: 'string',
},
{
name: 'location',
title: 'Location',
type: 'geopoint',
},
],
preview: {
select: {
title: 'title',
manufactor: 'manufactor.title',
media: 'defaultProductVariant.images[0]',
},
},
}
I am implementing category and subcategory display in ReactJs using Apollo GraphQl Query.
I tried to using same table as category with fields.
id,
category_name,
category_img,
category_parent_id ( id from same table),
category_status,
typeDefs and resolver are belows
Category.js
const typeDefs = gql`
extend type Query {
getSingleCategory(id: ID): allCategory
}
`;
type allCategory {
id: ID!
category_name: String
category_img: String
category_parent_id: Int
category_status: Status
}
const resolvers = {
Query: {
getSingleCategory: async (parent, args, context, info) => {
var data = await db.category.findOne({
where: {
id: args.id,
},
include: [
{
model: db.category,
as: "children",
attributes: [["category_name", "children_name"]],
nested: true,
required: false,
},
],
required: false,
});
return data;
},
},
},
Model in GraphQl
module.exports = function (sequelize, DataTypes) {
var category = sequelize.define(
"category",
{
id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
category_name: {
type: DataTypes.STRING(256),
allowNull: false,
},
category_img: {
type: DataTypes.STRING(256),
allowNull: false,
},
category_parent_id: {
type: DataTypes.INTEGER(10).UNSIGNED,
allowNull: false,
references: {
// WorkingDays hasMany Users n:n
model: "category",
key: "children",
},
},
category_status: {
type: DataTypes.ENUM("Acitve", "Inactive"),
allowNull: false,
},
},
{
tableName: "category",
timestamps: false,
}
);
category.associate = function (models) {
models.category.belongsTo(models.category, {
onDelete: "CASCADE",
foreignKey: "category_parent_id",
as: "children",
targetKey: "id",
});
};
return category;
};
In ReactJs
category.ts
export const GET_CATEGORYBY_ID = gql`
query($catId: ID!) {
getSingleCategory(id: $catId) {
id
category_name
category_img
category_parent_id
category_status
}
}
`;
I am trying to accessing {data.getSingleCategory} , I got all parameters but not able to get children_name from same table as parent_name.
Anyone can tell me what is the issue I am not able to access that children_name as attribute from same table Or there any other way so that we can access category/subcategory from same table and display it to reactjs template.
Not defined [separately] in types, not used/defined [as 'children' property?] in parent type, not requested in query ... simply filtered out from response.
There is a nice looking schema-based Form component on fluentui
import React from 'react';
import { Form, Button } from '#fluentui/react-northstar';
const fields = [
{
label: 'First name',
name: 'firstName',
id: 'first-name-shorthand',
key: 'first-name',
required: true,
},
{
label: 'Last name',
name: 'lastName',
id: 'last-name-shorthand',
key: 'last-name',
required: true,
},
{
label: 'I agree to the Terms and Conditions',
control: {
as: 'input',
},
type: 'checkbox',
id: 'conditions-shorthand',
key: 'conditions',
},
{
control: {
as: Button,
content: 'Submit',
},
key: 'submit',
},
];
const FormExample = () => (
<Form
onSubmit={() => {
alert('Form submitted');
}}
fields={fields}
/>
);
export default FormExample;
But they don't offer any method/example to collect the data from what I can tell. (at least not in the documentation).
I can collect most values from the onSubmit event but it get's hacky because not all html components are necessarily input elements that have the value attribute. I also don't think this is the intended way to do it. Anyone can enlighten me please? I think you must be able to feed the onChange function to it somehow. Or am i supposed to add the onChange function in each field-object?
I ended up combing through the library components (Forms Input and Checkbox) to see what makes them tick.
This is what I ended up with. Please feel free to improve on it should anyone else stumble on this in the future.
Note the use of the attributes defaultValue and defaultChecked to set the initial value of the Input and Checkbox components respectively. As well as the onChange event passing the name and value parameters for the Input component and name and checked for the Checkbox component.
The Checkbox label must be inside the Control if you wish it to appear next to the checkbox, otherwise it will appear above the checkbox.
import React, { Component } from 'react';
import { Form, Button, Checkbox, Input } from '#fluentui/react-northstar';
class Login extends Component {
constructor(props) {
super(props);
this.handleSubmit.bind(this)
}
state = {
email: "",
password: "",
remember_me: true
}
fields = [
{
label: 'Email',
name: 'email',
id: 'email-inline-shorthand',
key: 'email',
required: true,
inline: true,
type: 'email',
control: {
as: Input,
defaultValue: this.state.email,
onChange: (e, { name, value }) => this.setState({ ...this.state, [name]: value })
}
},
{
label: 'Password',
name: 'password',
id: 'password-inline-shorthand',
key: 'password',
required: true,
inline: true,
type: 'password',
control: {
defaultValue: this.state.password,
onChange: (e, { name, value }) => this.setState({ ...this.state, [name]: value }),
as: Input,
}
},
{
name: "remember_me",
key: 'remember_me',
id: 'remember_me-inline-shorthand',
type: 'boolean',
control: {
label: 'Remember me',
as: Checkbox,
defaultChecked: !!this.state.remember_me,
onChange: (e, { name, checked }) => { this.setState({ ...this.state, [name]: checked }) }
},
},
{
control: {
as: Button,
content: 'Submit',
},
key: 'submit',
},
]
handleSubmit = (e) => {
console.log("submitting these values", this.state)
}
render() {
return (
<Form
onSubmit={this.handleSubmit}
fields={this.fields}
/>
)
}
};
export default Login;