useswr crashes react component - reactjs

After adding an SWR data fetch to my react component it crashes, if I comment it out it works fine.
I get the following error after uncommenting line const { data } = useSWR(`/api/views/${slug}`, fetcher)
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
The component
import { useEffect } from 'react'
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((r) => r.json())
export default function ViewCounter({
slug,
addView = false,
}: {
slug: string
addView?: boolean
}) {
const { data } = useSWR(`/api/views/${slug}`, fetcher)
// const viewCount = new Number(data?.total)
useEffect(() => {
console.log('ViewCounter useEffect', slug, addView)
const registerView = () =>
fetch(`/api/views/${slug}`, {
method: 'POST',
})
if (addView) {
registerView()
}
}, [slug])
// return `${viewCount > 0 ? viewCount.toLocaleString() : '–––'} views`
return (
<span className="flex items-center gap-1">
{data && data?.total}
</span>
)
}
Maybe it also has something to do with the fact that this component is included within another component that uses getStaticPaths and getStaticProps?
The full codebase can be found here: https://github.com/SennR-1952135/sennr

The problem seemed to be with the version of swr, i had to use 1.1.2 or lower, still dont know the exact reason but it works.

Related

GetStaticPaths Error:Objects are not valid as a React child (found: object with keys {}).If you meant to render a collection of children, use an array

I am getting this error when as I map the response from my get staticpath. I have tried to destructure the response object but to no avail. As a result when i click on a single item, i get this error message. I have added the link to the codesandbox.
import axios from 'axios';
axios.defaults.baseURL = 'https://www.themealdb.com/api/json/v1/1/';
export async function fetchMeal(id) {
const res = await axios.get(`/lookup.php?i=${id}`);
const meal = await res.data.meals[0];
return meal;
}
export async function fetchMeals() {
const res = await axios.get('/search.php?s=');
const meal = await res.data.meals;
return meal;
}
export async function getStaticPaths() {
const res = await fetchMeals()
const paths = res.map((meal) => ({
params: { id: meal.idMeal },
}))
return {
paths,
fallback: false,
}
}
export async function getStaticProps({ params }) {
const recipe = await fetchMeal(params.id)
return {
props: {
recipe,
},
}
}
CodeSandBox
How can I get the right array instead of the object?
Thanks you in advance.
I believe the problem is in your <Navbar/> component.
You defined is as
function Navbar(title = "Recipe Finder") {
return (
<div className={styles.head}>
...
<title>{title}</title>
....
</div>
)
Note the title variable that you're passing as a parameter to the function.
The problem is that this function is actually a React component. When React is calling your component it passes props to the function. Although you have it as Navbar(title = "Recipe Finder"), during the runtime React will call it as Navbar(props). It will override your title variable with prop object, which I believe in this case is just empty object {}. Hence you're getting the error, because react cannot render the title (i.e. it effectively becomes <title>{{}}</title>)
To fix the problem you can change it to
function Navbar() {
const title = "Recipe Finder";

process is not defined on Next Page that use a HOC only on first render

I am trying to use a Higher-Order Component to read some values from the server (As environment configuration) in NextJS and React. The versions are 12.0.3 and 17.0.2 respectively. Also I am using Typescript.
I am tryng to use a HOC in order to reuse this functionality. When I was using a getServerSideProps method everything was fine, but now the first render when I start the app is failing. It is important to mention that everytime I just refresh the page, all seems to work correctly
This is my HOC:
// src/components/app/withEnvironment.tsx
import {
EnvironmentValues,
readPublicEnvironmentValues,
} from 'config/environment';
import {NextPage} from 'next';
import {EnvironmentProvider} from 'providers';
export function withEnvironment<TProps>(
Page: NextPage,
): (props: TProps & {environmentValues: EnvironmentValues}) => JSX.Element {
const PageWithEnvironment = ({
environmentValues,
...props
}: TProps & {environmentValues: EnvironmentValues}) => {
return (
<EnvironmentProvider value={environmentValues}>
<Page {...props} />
</EnvironmentProvider>
);
};
(PageWithEnvironment as NextPage).getInitialProps = () => {
return {
environmentValues: readPublicEnvironmentValues(),
};
};
return PageWithEnvironment;
}
And this is how I am trying to implement it:
// src/pages/change-sets/index.tsx
import {withEnvironment} from 'components/app';
import {useEnvironment} from 'providers'
function ChangeSetPage(): JSX.Element {
const environmentValues = useEnvironment();
return (
<div>
<h1>ChangeSetPage</h1>
</div>
);
}
export default withEnvironment(ChangeSetPage);
EnvironmentProvider and useEnvironment are part of a context that I would like to use to use to share values. This is how I defined them:
import {EnvironmentValues} from 'config/environment';
import React, {useContext} from 'react';
const EnvironmentContext = React.createContext<EnvironmentValues>(
{} as EnvironmentValues,
);
export const EnvironmentProvider = EnvironmentContext.Provider;
export const EnvironmentConsumer = EnvironmentContext.Consumer;
export function useEnvironment(): EnvironmentValues {
return useContext(EnvironmentContext);
}
Finally, the method readPublicEnvironmentValues only try to extract ENV values from the server, but as I mentioned on the first load process variable is always undefined;
export function readPublicEnvironmentValues(): EnvironmentValues {
// Here is where the exception throws becaouse of there is no process variable only at first attempt
const typedOptions = process.env as EnvironmentOptions;
const currentEnv = ENVIRONMENTS.find(
e => e === typedOptions.NEXT_PUBLIC_ENV || e === typedOptions.APP_ENV,
);
if (currentEnv) {
const values: EnvironmentValues = {
currentEnv,
skipAuth:
typedOptions.DEBUG === 'true' &&
typedOptions.SKIP_AUTH_ON_DEBUG === 'true',
};
if (typedOptions.BACKEND_PIVOT)
values.backendPivotApi = typedOptions.BACKEND_PIVOT;
if (typedOptions.BACKOFFICE_PIVOT)
values.backofficePivotApi = typedOptions.BACKOFFICE_PIVOT;
return values;
} else {
throw 'It is necessary to define an environment for the application. Please set NEXT_PUBLIC_ENV or APP_ENV';
}
}
It seems the getInitialProps method is being executed on the client side. This is the error message:

How to create React elements in an array (with props) and then render them by simply mapping over that array?

I am wondering if there is a way to create elements in an array and then simply render them, if that is possible it will simplify my code a bit.
Right now I am defining sections for a page in one file, then that gets sent to another and so on, so at the moment it looks like this
const heroSectionProps = { ... }
const pageLayout = {
sections: [{name: "Hero", props: heroSectionProps}]
}
and later when rendering the page I am doing the following:
import { Hero } from "~/sections"
const section = {
Hero: Hero
}
export const Page = ({ sections, ...props }) => {
return sections.map(({ name, props }) => {
const Section = section[name];
return <Section {...props} />
})
}
And that works just fine, now the question is, is it possible to somehow move to a syntax that would look more like this:
import { Hero } from "~/sections"
const heroSectionProps = { ... }
const pageLayout = {
sections: [Hero(heroSectionProps)]
}
and then in page:
export const Page = ({ sections, ...props }) => {
return sections.map(Section => (<Section />))
}
So the idea is to already have the components created within the layout object and then to simply pass them to the page to directly render them. Of course, doing it the above way, I got an error:
Warning: React.jsx: type is invalid -- expected a
string (for built-in components) or a class/function (for composite components) but got: <HeroContainer />. Did you accidentally export a JSX literal
instead of a component?
And other similar about invalid type.
So is that even possible, worth it and how?
You can render an array, so long as it's filled with renderable values (strings, numbers, elements and components). So, say we have an array of components:
import Hero from '../Hero';
const componentsArray = [<p>Hello world!</p>, <div><img src="images/flower.png" /></div>, <Hero {...heroSectionProps} />];
You can instantly render it:
const Component = (props) => {
//...
return componentsArray;
};

How to link to a show view from an index using react hooks with firestore data

I am trying to figure out how to define a link to reference that can use a firebase document id to link to a show view for that document. I can render an index. I cannot find a way to define a link to the document.
I've followed this tutorial - which is good to get the CRUD steps other than the show view. I can find other tutorials that do this with class components and the closest I've been able to find using hooks is this incomplete project repo.
I want to try and add a link in the index to show the document in a new view.
I have an index with:
const useBlogs = () => {
const [blogs, setBlogs] = useState([]); //useState() hook, sets initial state to an empty array
useEffect(() => {
const unsubscribe = Firebase
.firestore //access firestore
.collection("blog") //access "blogs" collection
.where("status", "==", true)
.orderBy("createdAt")
.get()
.then(function(querySnapshot) {
// .onSnapshot(snapshot => {
//You can "listen" to a document with the onSnapshot() method.
const listBlogs = querySnapshot.docs.map(doc => ({
//map each document into snapshot
id: doc.id, //id and data pushed into blogs array
...doc.data() //spread operator merges data to id.
}));
setBlogs(listBlogs); //blogs is equal to listBlogs
});
return
// () => unsubscribe();
}, []);
return blogs;
};
const BlogList = ({ editBlog }) => {
const listBlog = useBlogs();
return (
<div>
{listBlog.map(blog => (
<Card key={blog.id} hoverable={true} style={{marginTop: "20px", marginBottom: "20px"}}>
<Title level={4} >{blog.title} </Title>
<Tag color="geekblue" style={{ float: "right"}}>{blog.category} </Tag>
<Paragraph><Text>{blog.caption}
</Text></Paragraph>
<Link to={`/readblog/${blog.id}`}>Read</Link>
<Link to={`/blog/${blog.id}`}>Read</Link>
</Card>
))}
</div>
);
};
export default BlogList;
Then I have a route defined with:
export const BLOGINDEX = '/blog';
export const BLOGPOST = '/blog/:id';
export const NEWBLOG = '/newblog';
export const EDITBLOG = '/editblog';
export const VIEWBLOG = '/viewblog';
export const READBLOG = '/readblog/:id';
I can't find a tutorial that does this with hooks. Can anyone see how to link from an index to a document that I can show in a different page?
I did find this code sandbox. It looks like it is rendering a clean page in the updateCustomer page and using data from the index to do it - but the example is too clever for me to unpick without an explanation of what's happening (in particular, the updateCustomer file defines a setCustomer variable, by reference to useForm - but there is nothing in useForm with that definition. That variable is used in the key part of the file that tries to identify the data) - so I can't mimic the steps.
NEXT ATTEMPT
I found this blog post which suggests some changes for locating the relevant document.
I implemented these changes and while I can print the correct document.id on the read page, I cannot find a way to access the document properties (eg: blog.title).
import React, { useHook } from 'react';
import {
useParams
} from 'react-router-dom';
import Firebase from "../../../firebase";
import BlogList from './View';
function ReadBlogPost() {
let { slug } = useParams()
// ...
return (
<div>{slug}
</div>
)
};
export default ReadBlogPost;
NEXT ATTEMPT:
I tried to use the slug as the doc.id to get the post document as follows:
import React, { useHook, useEffect } from 'react';
import {
useParams
} from 'react-router-dom';
import Firebase from "../../../firebase";
import BlogList from './View';
function ReadBlogPost() {
let { slug } = useParams()
// ...
useEffect(() => {
const blog =
Firebase.firestore.collection("blog").doc(slug);
blog.get().then(function(doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
doc.data();
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
});
return (
<div>{blog.title}
</div>
)
};
export default ReadBlogPost;
It returns an error saying blog is not defined. I also tried to return {doc.title} but I get the same error. I can see all the data in the console.
I really can't make sense of coding documentation - I can't figure out the starting point to decipher the instructions so most things I learn are by trial and error but I've run out of places to look for inspiration to try something new.
NEXT ATTEMPT
My next attempt is to try and follow the lead in this tutorial.
function ReadBlogPost(blog) {
let { slug } = useParams()
// ...
useEffect(() => {
const blog =
Firebase.firestore.collection("blog").doc(slug);
blog.get().then(function(doc) {
if (doc.exists) {
doc.data()
console.log("Document data:", doc.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
},
[blog]
);
return (
<div><Title level={4} > {blog.title}
</Title>
<p>{console.log(blog)}</p>
</div>
)
};
export default ReadBlogPost;
When I try this, the only odd thing is that the console.log inside the useEffect method gives all the data accurately, but when I log it form inside the return method, I get a load of gibberish (shown in the picture below).
NEXT ATTEMPT
I found this tutorial, which uses realtime database instead of firestore, but I tried to copy the logic.
My read post page now has:
import React, { useHook, useEffect, useState } from 'react';
import {
useParams
} from 'react-router-dom';
import Firebase from "../../../firebase";
import BlogList from './View';
import { Card, Divider, Form, Icon, Input, Switch, Layout, Tabs, Typography, Tag, Button } from 'antd';
const { Paragraph, Text, Title } = Typography;
const ReadBlogPost = () => {
const [loading, setLoading] = useState(true);
const [currentPost, setCurrentPost] = useState();
let { slug } = useParams()
if (loading && !currentPost) {
Firebase
.firestore
.collection("blog")
.doc(slug)
.get()
.then(function(doc) {
if (doc.exists) {
setCurrentPost(...doc.data());
console.log("Document data:", doc.data());
}
}),
setLoading(false)
}
if (loading) {
return <h1>Loading...</h1>;
}
return (
<div><Title level={4} >
{currentPost.caption}
{console.log({currentPost})}
</Title>
</div>
)
};
export default ReadBlogPost;
Maybe this blog post is old, or maybe it's to do with it using .js where I have .jsx - which I think means I can't use if statements, but I can't get this to work either. The error says:
Line 21:9: Expected an assignment or function call and instead saw
an expression no-unused-expressions
It points to the line starting with Firebase.
I got rid of all the loading bits to try and make the data render. That gets rid of the above error message for now. However, I still can't return the values from currentPost.
It's really odd to me that inside the return statement, I cannot output {currentPost.title} - I get an error saying title is undefined, but when I try to output {currentPost} the error message says:
Error: Objects are not valid as a React child (found: object with keys
{caption, category, createdAt, post, status, title}). If you meant to
render a collection of children, use an array instead.
That makes no sense! I'd love to understand why I can log these values before the return statement, and inside the return statement, I can log them on the object but I cannot find how to log them as attributes.
First of all: is your useBlog() hook returning the expected data? If so, all you need to do is define your <Link/> components correctly.
<Link
// This will look like /readblog/3. Curly braces mean
// that this prop contains javascript that needs to be
// evaluated, thus allowing you to create dynamic urls.
to={`/readblog/${blog.id}`}
// Make sure to open in a new window
target="_blank"
>
Read
</Link>
Edit: If you want to pass the data to the new component you need to set up a store in order to avoid fetching the same resource twice (once when mounting the list and once when mounting the BlogPost itself)
// Define a context
const BlogListContext = React.createContext()
// In a top level component (eg. App.js) define a provider
const App = () => {
const [blogList, setBlogList] = useState([])
return (
<BlogListContext.Provider value={{blogList, setBlogList}}>
<SomeOtherComponent/>
</BlogListContext.Provider>
)
}
// In your BlogList component
const BlogList = ({ editBlog }) => {
const { setBlogList } = useContext(BlogListContext)
const listBlog = useBlogs()
// Update the blog list from the context each time the
// listBlog changes
useEffect(() => {
setBlogList(listBlog)
}, [listBlog])
return (
// your components and links here
)
}
// In your ReadBlog component
const ReadBlogComponent = ({ match }) => {
const { blogList } = useContext(BlogListContext)
// Find the blog by the id from params.
const blog = blogList.find(blog => blog.id === match.params.id) || {}
return (
// Your JSX
)
}
There are other options for passing data as well:
Through url params (not recommended).
Just pass the ID and let the component fetch its own data on mount.
I found an answer that works for each attribute other than the timestamp.
const [currentPost, setCurrentPost] = useState([]);
There is an empty array in the useState() initialised state.
In relation to the timestamps - I've been through this hell so many times with firestore timestamps - most recently here. The solution that worked in December 2019 no longer works. Back to tearing my hair out over that one...

Upload file with the Admin Component of API Platform not working

In the API Platform Docs there is a part Handling File Uploads. The code with VichUploaderBundle works like expected. But further in the Docs regarding the Admin Component Managing Files and Images doesn't work. At least the create and edit forms doesn't show up. I know that i had to adjust the code a little bit to make it work with the code in Handling File Uploads but it still doesn't give me the expected results.
I have modified the App.js file accordingly
import React from 'react';
import { FunctionField, ImageField, ImageInput } from 'react-admin';
import { HydraAdmin } from '#api-platform/admin';
import parseHydraDocumentation from '#api-platform/api-doc-parser/lib/hydra/parseHydraDocumentation';
const entrypoint = "http://localhost:8080";
const myApiDocumentationParser = entrypoint => parseHydraDocumentation(entrypoint)
.then( ({ api }) => {
api.resources.map(resource => {
if ('http://schema.org/MediaObject' === resource.id) {
resource.fields.map(field => {
if ('http://schema.org/contentUrl' === field.id) {
field.denormalizeData = value => ({
src: value
});
field.field = props => (
<ImageField {...props} source={`${field.name}.src`} />
);
console.log(field);
field.input = (
<ImageInput accept="image/*" key={field.name} multiple={false} source={field.name}>
<ImageField source="src"/>
</ImageInput>
);
field.normalizeData = value => {
if (value && value.rawFile instanceof File) {
console.log("inst of file");
const body = new FormData();
body.append('file', value.rawFile);
return fetch(`${entrypoint}/media_objects`, { body, method: 'POST' })
.then(response => response.json());
}
return value.src;
};
}
return field;
});
}
return resource;
});
return { api };
});
export default (props) => <HydraAdmin apiDocumentationParser={myApiDocumentationParser} entrypoint={entrypoint}/>;
I am getting the following error on the client:
Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
Hi get the same problem I resolve the issue by changing the field.input like that:
field.input = props =>(
<ImageInput {...props} accept="image/*" multiple={false} source={field.name}>
<ImageField source="src"/>
</ImageInput>
);
And also change that line
if ('imageFile' === field.name) {
Now i can choose the file to upload from my directories but the Post method return 404 error, don't know how to resolve that path issue with Vich.
Hope that little anwser can help.

Resources