index.js file
export default function Home({ posts }) {
return (
<div>
{posts &&
posts.map((post) => (
<div key={post.id}>
<h2>{post.Title}</h2>
</div>
))}
</div>
);
}
export async function getStaticProps() {
const res = await fetch("http://localhost:1337/api/posts");
const posts = await res.json();
return {
props: { posts },
};
}
and this is the error that appears to me "TypeError: posts.map is not a function"
Any idea about it?
posts is an object — the array of posts you want to call map on is assigned to posts.data:
export default function Home({ posts }) {
const { data } = posts; // unpack `data` from `posts`
// call `map()` on `data`
return (
<div>
{data && data.length
? data.map((post) => (
<div key={post.id}>
<h2>{post.attributes.Title}</h2>
</div>
))
: "no posts"}
</div>
);
}
Related
I'm trying to add user authentication to every page in my NextJS project (pages, not app.) This tutorial was very helpful (and is exactly what I want to do) - https://alexsidorenko.com/blog/next-js-protected-routes/ - but I'm having trouble integrating Supabase's default auth UI and capabilities into that model (https://supabase.com/docs/guides/auth/auth-helpers/nextjs).
My basic goal is to move authentication branching into _app.tsx, rather than on each page:
// _app.tsx
import { useEffect, useState } from "react";
import { createBrowserSupabaseClient } from '#supabase/auth-helpers-nextjs'
import { SessionContextProvider, useUser, useSession, useSupabaseClient, Session } from '#supabase/auth-helpers-react'
import { Auth, ThemeSupa } from '#supabase/auth-ui-react'
import { AppProps } from 'next/app'
import { UserContext } from "#components/user"
function MyApp({Component, pageProps}: AppProps<{ initialSession: Session }>) {
const [supabase] = useState(() => createBrowserSupabaseClient())
const session = useSession()
const user = useUser()
console.log("session:" + session);
console.log("user:" + user);
useEffect(() => {
if (
pageProps.protected
) {
return <Auth supabaseClient={supabase} appearance={{ theme: ThemeSupa }} theme="dark" />
}
}, [])
return (
<SessionContextProvider supabaseClient={supabase} session={session} initialSession={pageProps.initialSession}>
<Component {...pageProps} />
</SessionContextProvider>
)
}
export default MyApp
A page I want to protect (for example, the index page) looks like this:
// index.tsx
import Account from "#components/account";
const Home = () => {
return (
<div>
<Account session={session} />
</div>
)
}
export async function getStaticProps(context) {
return {
props: {
protected: true,
},
}
}
export default Home
And then the Account component that's included on the index page is the Supabase out of the box profile panel, although it could be any content:
// #components/account.tsx
import { useState, useEffect } from 'react'
import { useUser, useSupabaseClient, Session } from '#supabase/auth-helpers-react'
import { Database } from '#utils/database.types'
type Profiles = Database['public']['Tables']['profiles']['Row']
export default function Account({ session }: { session: Session }) {
const supabase = useSupabaseClient<Database>()
const user = useUser()
const [loading, setLoading] = useState(true)
const [username, setUsername] = useState<Profiles['username']>(null)
useEffect(() => {
getProfile()
}, [session])
async function getProfile() {
try {
setLoading(true)
if (!user) throw new Error('No user')
let { data, error, status } = await supabase
.from('profiles')
.select(`username`)
.eq('id', user.id)
.single()
if (error && status !== 406) {
throw error
}
if (data) {
setUsername(data.username)
}
} catch (error) {
alert('Error loading user data!')
console.log(error)
} finally {
setLoading(false)
}
}
async function updateProfile({
username,
}: {
username: Profiles['username']
}) {
try {
setLoading(true)
if (!user) throw new Error('No user')
const updates = {
id: user.id,
username,
updated_at: new Date().toISOString(),
}
let { error } = await supabase.from('profiles').upsert(updates)
if (error) throw error
alert('Profile updated!')
} catch (error) {
alert('Error updating the data!')
console.log(error)
} finally {
setLoading(false)
}
}
return (
<div>
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" value={session.user.email} disabled />
</div>
<div>
<label htmlFor="username">Username</label>
<input id="username" type="text" value={username || ''} onChange={(e) => setUsername(e.target.value)} />
</div>
<div>
<button onClick={() => updateProfile({ username })} disabled={loading} >
{loading ? 'Loading ...' : 'Update'}
</button>
</div>
<div>
<button onClick={() => supabase.auth.signOut()}>
Sign Out
</button>
</div>
</div>
)
}
I think I have a fundamental misunderstanding of the relationship between protected routes and Supabase's use of session and user.
Any help would be very much appreciated.
I'd recommend using Next.js middleware for this: https://supabase.com/docs/guides/auth/auth-helpers/nextjs#auth-with-nextjs-middleware
import { createMiddlewareSupabaseClient } from '#supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(req: NextRequest) {
// We need to create a response and hand it to the supabase client to be able to modify the response headers.
const res = NextResponse.next()
// Create authenticated Supabase Client.
const supabase = createMiddlewareSupabaseClient({ req, res })
// Check if we have a session
const {
data: { session },
} = await supabase.auth.getSession()
// Check auth condition
if (session?.user.email?.endsWith('#gmail.com')) {
// Authentication successful, forward request to protected route.
return res
}
// Auth condition not met, redirect to home page.
const redirectUrl = req.nextUrl.clone()
redirectUrl.pathname = '/'
redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname)
return NextResponse.redirect(redirectUrl)
}
export const config = {
matcher: '/middleware-protected/:path*',
}
I am calling an async function in my html. But it's returning a promise. How can I display the value from the aysnc function?
<div>
{{ getUser(userId) }}
</div>
async getUser(userId) {
try {
const res = await this.getUser(userId);
const userName = res.data.name;
return userName
} catch (error) {}
},
In VueJs you can bind your data from a request to the data
So in the example below I'll comment how you can do it:
<template>
<div>
<span>{{ user.username }}</span>
<button #click="getUser(// The user Id)">Click me to get User</button>
</div>
</template>
<script>
export default {
data(){
user:{
username: "",
},
},
methods:{
async getUser(userId){
const res = await this.getUser(userId);
// Set response on the user registered in data
this.user.username = res.data.name;
// Now that you have set the username on the user object
// You can approach it with {{user.username}}
}
}
};
</script>
One of the easiest way to handle this is to create a data variable in Vue and let Vue re-render the variable on promise completion.
<div>
{{ username }}
</div>
data(){
return {
username: ''
}
}
methods: {
async getUser(userId) {
try {
const res = await this.getUser(userId);
this.username = res.data.name;
}
catch (error) {
this.username = 'Oh oh , something went wrong' // optional
}
},
}
I am really scratching my head at this.
I am making a CRUD application, and this problem started when I was working on the Edit component.
I am getting the error Cannot read property 'id' of null
BUT! The interesting thing is that the data actually DOES get updated, both in the application and on the server side.
The error however affects the layout. First of all, the delete button appears two places in the template instead of one, and instead of redirecting me to the main page when I update, the main page appears like a new div on the edit page. I have no idea what is going on.
Here are the different components/composables:
The Details component: Here the information about a specific document is stored based on it's ID.
<template>
<div v-if="playlist" class="playlist-details">
<div class="playlist-info">
<div class="cover">
<img :src="playlist.coverUrl">
</div>
<h2> {{ playlist.title }}</h2>
<p> {{ playlist.description }} </p>
</div>
</div>
<button #click="handleDelete">Delete</button>
<EditSong :playlist="playlist" />
</template>
<script>
import EditSong from '../components/EditSong'
import useDocument from '../composables/useDocument'
import getDocument from '../composables/getDocument'
import useStorage from '../composables/useStorage'
import { useRouter } from "vue-router";
export default {
props: ['id'],
components: { EditSong },
setup(props) {
const { document: playlist } = getDocument('playlists', props.id)
const { deleteDoc } = useDocument('playlists', props.id)
const router = useRouter();
const { deleteImage } = useStorage()
const handleDelete = async () => {
await deleteImage(playlist.value.filePath)
await deleteDoc()
confirm('Do you wish to delete this content?')
router.push({ name: "Home" });
}
return {
playlist,
handleDelete
}
}
}
</script>
Here is the Edit component: This is where I edit and update the data inside the Details component. This is where I am getting the TypeError.
It has something to do with the props.playlist.id field
<template>
<div class="edit-song">
<form #submit.prevent="handleSubmit">
<input type="text" required placeholder="title" v-model="title">
<input type="text" required placeholder="description" v-model="description">
<button v-if="!isPending">Update</button>
<button v-else disabled>Updating...</button>
</form>
</div>
</template>
<script>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import useDocument from '../composables/useDocument'
import useCollection from '../composables/useCollection'
export default {
props: ['playlist'],
setup(props) {
const title = ref('')
const description = ref('')
const { updateDoc } = useDocument('playlists', props.playlist.id)
const { error } = useCollection()
const isPending = ref(false)
const router = useRouter();
const handleSubmit = async () => {
await updateDoc({
title: title.value,
description: description.value,
})
isPending.value = false
if(!error.value) {
router.push({ name: "Home" })
}
}
return {
title,
description,
handleSubmit,
isPending,
error
}
}
}
</script>
And last, this is the Update composable: that stores the update function
import { ref } from 'vue'
import { projectFirestore } from '../firebase/config'
const useDocument = (collection, id) => {
const error = ref(null)
const isPending = ref(false)
let docRef = projectFirestore.collection(collection).doc(id)
const updateDoc = async (updates) => {
isPending.value = true
error.value = null
try {
const res = await docRef.update(updates)
isPending.value = false
return res
}catch(err) {
console.log(err.message)
isPending.value = false
error.value = 'Could not update document'
}
}
return {
error,
isPending,
updateDoc
}
}
export default useDocument
The likely scenario is getDocument() returns a ref to null for document, which gets updated asynchronously:
const getDocument = (collection, id) => {
const document = ref(null)
someAsyncFunc(() => {
document.value = {...}
})
return {
document
}
}
Since the document (renamed to playlist) is bound to the EditSong component, it receives both the initial value (null) and then the asynchronously populated value, which leads to the behavior you're seeing.
One solution is to conditionally render EditSong on playlist:
<EditSong v-if="playlist" :playlist="playlist" />
Another is to move the updateDoc initialization into handleSubmit, and add a null-check there:
const handleSubmit = async () => {
if (!props.playlist) return
const { updateDoc } = useDocument('playlists', props.playlist.id)
await updateDoc(...)
}
When I am using the below code with mounted function then it's perfectly pushing the data to "infox"
<script>
export default {
data() {
return {
infox: null,
dino: d_var
}
},
mounted() {
axios
.get(this.dino)
.then(response => (this.infox = response.data))
}
}
</script>
But when I am trying to convert the code to use method function as shown below then I am unable to get any data. Is it something I am doing wrong ?
<template>
<button v-on:click="loadmore" class="fluid ui button">Load More</button>
</template>
<script>
export default {
data() {
return {
infox: null,
dino: d_var
}
},
methods: {
loadmore: function(){
axios.get(this.dino)
.then(response => this.infox = response.data)
}
}
}
</script>
infox is set to null you should set it to array.
infox: []
I have a problem, I can't get data from wp API with axios react js
my code :
var React = require('react');
var ReactDOM = require('react-dom');
var axios = require('axios');
var Wordpress = React.createClass({
getInitialState: function() {
return {posts: []};
},
componentDidMount() {
axios.get(`http://localhost/scareid/wp-json/wp/v2/posts`).then(res => {
const posts = res.data.map(obj => obj.data);
this.setState({posts});
});
},
render() {
return (
<div>
<h1>{`/r/${this.props.title}`}</h1>
<ul>
{this.state.posts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
</div>
);
}
})
module.exports = Wordpress;
my api
http://pastebin.com/9x4rgJmE
console log data :
enter image description here
Ok found it.
You don't need the map step at all, the array is already returned in res.
Replace:
const posts = res.data.map(obj => obj.data);
this.setState({posts});
with:
if (res && res.data) {
this.setState({
posts: res.data
});
}