Next js fetch API query - next.js

const Fetch = ({nationalize}) => {
return (
<div>
<h1>Nationalize</h1>
<h5>
<h4>You're {nationalize.name} and here's your results</h4>
{nationalize.country.map((i)=>{
return(
<div key={i.country_id}>
<h5>{(i.probability)*100}% {i.country_id} </h5>
</div>
)
})}
</h5>
</div>
);
}
export const getStaticProps = async (ctx) => {
const res = await fetch('https://api.nationalize.io?name=joe')
const nationalize = await res.json()
return {
props:{
nationalize,
}
}
}
export default Fetch;
So this is my NextJs page fetch API from nationalize.io, the API takes a name as a query. The code works just fine but I wanted to take the name query from an input field instead of being set manually. In this example it is Joe 'https://api.nationalize.io?name=joe', any suggestion? thank you

You use getStaticProps which pre-builds the page before sending it to your browser, so all data inside getStaticProps has to be static as the name says, and not being taken from any user input or query params.
If you'd like someone to change the name, you can either render it on the server using getServerSideProps and taking the name from the params, or fetch it on client-side via a user input as you mentioned:
import { useState } from "react";
const Fetch = () => {
const [name, setName] = useState("");
const [nationalize, setNationalize] = useState();
const submitName = async (name) => {
const res = await fetch("https://api.nationalize.io?name=" + name);
const nationalizeJson = await res.json();
setNationalize(nationalizeJson);
};
return (
<div>
<h1>Nationalize</h1>
{nationalize && (
<>
<h4>You're {nationalize.name} and here's your results</h4>
{nationalize.country.map((i) => {
return (
<div key={i.country_id}>
<h5>
{i.probability * 100}% {i.country_id}{" "}
</h5>
</div>
);
})}
</>
)}
<div>
<input
id="input"
type="text"
placeholder="Enter your name"
value={name}
onChange={(e) => setName(e.target.value)}
style={{ padding: "10px" }}
/>
<button
onClick={() => {
if (!name) return;
submitName(name);
}}
>
Submit
</button>
</div>
</div>
);
};
export default Fetch;
Here's how it works:
Let user enter his name with a controlled text input (https://reactjs.org/docs/forms.html#controlled-components) and store the value in the state name
When a user clicks the "Submit" button, call the api as you did in getStaticProps but take the name from the state name, and store the returned JSON from the api in the state nationalize.
In the JSX part, check if nationalize is defined, and only then display the information fetched from the api.
This is obviously just a demo and could use some optimization like styling or a loading indicator.

Related

next js empty render when i import data

my problem is when i want to fetch data from data.json nothing display it give me many empty array when i try to console.log
screenshot:
screenshot and path of files
i tried hard to change the import path to back with ../ but nothing display to me im so wonder about this error and this is my first time i work with next js
import Image from 'next/image'
const EventPerCityPage = ({data}) => {
console.log("helloa"+data)
return (
<div>
<h1> event in london</h1>
<div>
{data.map((ev)=>{
<a key={ev.id} href={`/events/${ev.city}/${ev.id}`}>
<Image width={300} height={300} alt={ev.title} src={ev.image} />
<h2>{ev.title}</h2>
<p>{ev.description}</p>
</a>
})}
</div>
<p>hello</p>
</div>
)
}
export default EventPerCityPage;
export async function getStaticPaths (){
const {events_categories} = await import ('/data/data.json')
console.log(events_categories+"this code")
const allPaths = events_categories.map(ev=>{
return{
params: {
cat: ev.id.toString()
}
}
})
console.log(allPaths)
return{
paths: allPaths,
fallback:false
}
}
export async function getStaticProps(context){
const id = context?.params.cat
console.log("all path aaaaaaaa" +id)
const {allEvents} = await import('/data/data.json')
console.log(allEvents+"rrrrrrtttttttt")//
const data = allEvents.filter((ev) => ev.city===id)
console.log(data+"eeeeeeeeeezzzzzzz")
return{
props: {data}
}
}

i have an issue with rendering content from graphCMS, the categories in particular

learning how to build a blog with nextjs and graphCMS, been able to get some content using gql queries but for no understandable reason, i can't seem to render the categories.name content onto the site and I am not getting an error. i would like to know what the problem is as I have encountered this prior to this moment with the sanity.io platform for an e-commerce project.
import React, { useState, useEffect } from 'react'
import Link from 'next/link'
import { getCategories } from '../services'
const Categories = () => {
const [categories, setCategories] = useState([]);
useEffect(() => {
getCategories()
.then((newCategories) => setCategories(newCategories))
}, []);
return (
<div className='bg-white shadow-lg rounded-lg p-8 mb-12'>
<h3 className='text-xl mb-8 font-semibold border-b pb-4'>
Categories
</h3>
{categories.map((category) => {
<Link key={category.slug} href={`/category/${category.slug}`}>
<span className='cursor-pointer block pb-3 mb-3'>
{category.name}
</span>
</Link>
})}
</div>
below is the query that I used to set up the process for rendering the content I need to display on the site
export const getCategories = async () => {
const query = gql`
query GetCategories {
categories {
name
slug
}
}
`
const result = await request(graphqlAPI, query);
return result.categories;
}
Assuming you are getting data and the state is correct, you're missing return in your map
{categories.map((category) => {
// notice this return keyword!!
return <Link key={category.slug} href={`/category/${category.slug}`}>
<span className='cursor-pointer block pb-3 mb-3'>
{category.name}
</span>
</Link>
})}
figured it out, had to change {} to () after the .map element and =>

Trying to create a dynamic link with API and React router, console is showing [object%20Object].json 404?

I'm currently working on a site that pulls Reddit data from different subreddits to show recipes, you can toggle between 3 different ones. That part works fine! I'm able to pull the list of posts from each subreddit, but now I'm trying to make it so when you click a button, it routes you to post details where it'll show the post and comments. I have to do another API call to get that information.
Somewhere along the way, it's getting messed up and it's showing "xhr.js:210 GET https://www.reddit.com/r/recipes/comments/[object%20Object].json 404:" It's a dynamic route, so there are two different parameters I'm trying to use. I did try and console log the parameter for ID(the one showing up as [object object] and it shows up fine by itself and is not an object from what I can tell.
Please see some of the code below where I think things could be going wrong. I'm guessing it's the API call because when I console log it, it only shows the subreddit as an arg but not sure..
redditAPI.js:
import axios from 'axios';
export default axios.create({
baseURL:"https://www.reddit.com/r/"
})
store.js:
import {configureStore} from '#reduxjs/toolkit';
import postReducer from './posts/postSlice';
export const store = configureStore({
reducer: {
posts: postReducer
}
});
postSlice.js:
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import redditApi from '../../common/api/redditApi';
import { redditDetails } from '../../common/api/redditApi';
export const fetchAsyncPosts = createAsyncThunk('posts/fetchAsyncPosts', async (subreddit) => {
const response = await redditApi.get(subreddit)
return response.data.data.children;
});
export const fetchAsyncPostsDetail = createAsyncThunk('posts/fetchAsyncPostsDetail', async (sub, postID) => {
const response = await redditApi.get(`${sub}/comments/${postID}.json`)
return response.data.data.children;
});
const initialState = {
posts: [],
selectedSubreddit: 'recipes.json',
selectedPost: []
}
const postSlice = createSlice({
name: "posts",
initialState,
reducers: {
addPosts: (state, { payload }) => {
state.posts = payload;
},
addSelectedPost: (state, {payload}) => {
state.selectedPost = payload;
},
setSelectedSubreddit(state, action) {
state.selectedSubreddit = action.payload;
}
},
extraReducers: {
[fetchAsyncPosts.pending] : () => {
console.log("Pending");
},
[fetchAsyncPosts.fulfilled] : (state, {payload}) => {
console.log("Fulfilled");
return {...state, posts: payload};
},
[fetchAsyncPosts.rejected]: () => {
console.log("Rejected");
},
[fetchAsyncPostsDetail.fulfilled] : (state, {payload}) => {
console.log("Fulfilled");
return {...state, selectedPost: payload};
},
},
});
export const {addPosts, setSelectedSubreddit} = postSlice.actions;
export const selectSelectedSubreddit = (state) => state.selectedSubreddit;
export const getAllPosts = (state) => state.posts.posts;
export const getSelectedPost = (state) => state.posts.selectedPost;
export default postSlice.reducer;
App.js
import "./App.scss";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import PageNotFound from "./components/PageNotFound/PageNotFound";
import Header from "./components/Header/Header";
import Home from "./components/Home/Home";
import PostDetails from "./components/PostDetails/PostDetails";
import Footer from "./components/Footer/Footer";
import 'bootstrap/dist/css/bootstrap.min.css';
function App() {
return (
<div className="App">
<Router>
<Header/>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/:sub/comments/:postID" element={<PostDetails />} />
<Route path="*" element={ <PageNotFound />} />
</Routes>
</Router>
</div>
);
}
export default App;
Home.js:
import React, {useEffect} from 'react'
import { useDispatch, useSelector } from 'react-redux';
import PostListing from '../PostListing/PostListing'
import { fetchAsyncPosts } from '../../features-redux/posts/postSlice';
function Home() {
const dispatch = useDispatch();
const subreddit = useSelector((state) => state.posts.selectedSubreddit);
useEffect(() => {
dispatch(fetchAsyncPosts(subreddit));
}, [dispatch, subreddit]);
return (
<div>
<div className="jumbotron jumbotron-fluid">
<div className="container text-center">
<h1 className="display-4">Welcome to Tasteful Reddit</h1>
<p className="lead">Toggle between subreddits above to view their recipes.</p>
</div>
</div>
<PostListing />
</div>
)
}
export default Home
postListing.js:
import React from 'react'
import { useSelector } from 'react-redux'
import { getAllPosts } from '../../features-redux/posts/postSlice'
import PostCard from '../PostCard/PostCard';
function PostListing() {
const posts = useSelector(getAllPosts);
const rendering = () => posts.map((post, key) => {
return <PostCard key={key} data={post.data} />;
});
console.log(posts);
return (
<div className="post-wrapper">
<div className="post-container">
{rendering()}
</div>
</div>
)
}
export default PostListing
postCard.js:
import React from 'react'
import parse from 'html-react-parser';
import {Link} from 'react-router-dom';
function PostCard(props) {
const {data} = props;
/* Function to change escaped HTML to string */
const htmlDecode = (input) => {
var doc = new DOMParser().parseFromString(input, "text/html");
return doc.documentElement.textContent;
}
/* Decode reddit JSON's youtube embed HTML */
const youtubeHtmlString = htmlDecode(data.media_embed.content);
/* This function runs through the reddit data to make sure that there is a
an image for the post. If so, shows image
and if its a reddit hosted video or youtube video it will render the video.
Gallery-style posts & all else shows empty div*/
const mediaRender = () => {
if (data.thumbnail !== 'self' && data.thumbnail !== 'default' && data.is_self !== true && data.is_gallery !== true && data.domain !== 'youtu.be' && data.domain !== 'v.redd.it') {
return <img src = {data.url} alt={data.title} className="card-img-top"/>;
} if ( data.is_video == true) {
return (
<div>
<video controls preload = "none">
<source src={data.media.reddit_video.fallback_url} type="video/mp4"/>
Your browser does not support the video tag.
</video>
</div>
)
} if (data.domain == 'youtu.be') {
return (
<div className="Container">
{parse(youtubeHtmlString)}
</div>
)
} else {
return <div></div>
}
}
/* If only text & no photos, render text info*/
const renderSelf = () => {
if(data.is_self == true) {
return (<p>{data.selftext}</p>)
} else {
return <p></p>
}
}
return (
<div className="card mb-3 mx-auto text-center" style={{width: "70%"}}>
<div className="row g-0">
<div className="col-md-5">
{mediaRender()}
</div>
<div className="col-md-7">
<div className="card-body">
<h5 className="card-title">{parse(data.title)}</h5>
<div className="card-text">{renderSelf()}</div>
<div className="card-text"><small className="text-muted">By {data.author}</small></div>
<Link to={`/${data.subreddit}/comments/${data.id}`}>
<button className="btn btn-primary">Go to post</button>
</Link>
</div>
</div>
</div>
</div>
)
}
export default PostCard
postDetails.js:
import React, { useEffect } from 'react'
import { useParams } from 'react-router-dom';
import {useDispatch, useSelector} from 'react-redux';
import { fetchAsyncPostsDetail, getSelectedPost } from '../../features-redux/posts/postSlice';
function PostDetails() {
let {sub, postID} = useParams();
const dispatch = useDispatch();
const data = useSelector(getSelectedPost);
console.log(postID)
useEffect(() => {
dispatch(fetchAsyncPostsDetail(sub, postID));
console.log(dispatch(fetchAsyncPostsDetail(sub, postID)))
}, [dispatch, postID, sub]);
console.log(data);
return (
<div>
PostDetails
</div>
)
}
export default PostDetails
Any help would be appreciated, because I'm lost!
Thanks!
I figured it out today, for those interested!
I finally got it! I figured I was doing something wrong with react router, and that's why I couldn't get it to work but I didn't want to stop using react router. I tried many different ways to fix it, including putting both arguments in an object, I tried changing it by passing what I needed with a pathname AND a state, but the state didn't work right. think that I was passing the state in an incorrect way somehow. I think I wrote it like state{ permalink: data.permalink} or something. I also tried changing it so it only took one parameter, but for some reason the react router Link only wanted to use the post IDs and would go to my page not found if I tried to use permalink. After researching other ways to pass data using React Router, I landed on using From to grab the data from the previous page the user was on and used useLocation to grab the state.
Here is a snippet of what I changed in Link for in PostCard.js. The data was passed in props from postListing.js, just for reference:
return (
<div className="card mb-3 mx-auto text-center" style={{width: "70%"}}>
<div className="row g-0">
<div className="col-md-5">
{mediaRender()}
</div>
<div className="col-md-7">
<div className="card-body">
<h5 className="card-title">{parse(data.title)}</h5>
<div className="card-text">{renderSelf()}</div>
<div className="card-text"><small className="text-muted">By {data.author}</small></div>
<Link to={`/post/${data.id}`}
state={{ from: data}}>
<button className="btn btn-primary">Go to post</button>
</Link>
</div>
</div>
</div>
</div>
)
}
I also stopped using the postId to try and make the API call, and instead only used it to make the dynamic link to the component. After that, I used From's state to grab the permalink(from previous reddit post data) to make the API call to get the comment information.
Here is a snippet of PostDetails.js where I made the changes:
function PostDetails() {
const location = useLocation();
const {from} = location.state;
const comments = useSelector(getSelectedPost);
const dispatch = useDispatch();
useEffect( () => {
dispatch(getPostComments(from.permalink));
}, [dispatch]);
I also found out I was trying to access the data incorrectly in my API call, as the data was structured differently than I originally thought.
Here is the changed thunk in PostSlice.js:
export const getPostComments = createAsyncThunk('posts/getPostComments', async (permalink) => {
const response = await fetch(`http://www.reddit.com${permalink}.json`);
const json = await response.json();
return json[1].data.children.map((comment) => comment.data)
});
There were multiple things wrong with my react app, but hopefully this helps someone else! :)
The second argument to createAsyncThunk is the payloadCreator.
It's called with two arguments, arg which is a single value, and the thunkAPI object.
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
}
)
// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))
You are passing the thunkAPI argument as the postID value in the GET request.
export const fetchAsyncPostsDetail = createAsyncThunk(
'posts/fetchAsyncPostsDetail',
async (sub, postID) => {
const response = await redditApi.get(`${sub}/comments/${postID}.json`)
return response.data.data.children;
}
);
If you need to pass multiple values to your thunk then pack them into a single object.
export const fetchAsyncPostsDetail = createAsyncThunk(
'posts/fetchAsyncPostsDetail',
async ({ sub, postID }, thunkAPI) => {
const response = await redditApi.get(`${sub}/comments/${postID}.json`)
return response.data.data.children;
}
);
...
useEffect(() => {
dispatch(fetchAsyncPostsDetail({ sub, postID }));
}, [dispatch, postID, sub]);

What do I need to do that appendChild and addEventListener is going to work?

I can see the issue here. Stuff is going to fetch before it's still there in the order of code execution, but I don't know how to fix that at all.
I want to grab some infos from my firebase firestore database and put it in the dom in a way I can show it of nicely.
The other thing is, css file isn't working cause of the same issue. When I setAttribute to it it's already initialized and of course, it wouldn't show up at all (for example: border: 50% to make the pic round).
What do I miss to learn?
import React from "react";
import firebase from "./firebase";
import "./artists-styles.css";
const db = firebase.firestore();
const form = document.querySelector("#add-artist-avatar");
function renderArtists(doc) {
const artistsAvatar = document.querySelector(".content");
let image = document.createElement("img");
image.setAttribute("src", doc.data().avatar);
artistsAvatar.appendChild(image);
}
// getting data
db.collection("artists")
.get()
.then(snapshot => {
console.log(snapshot.docs);
snapshot.docs.forEach(doc => {
console.log(doc.data());
renderArtists(doc);
});
});
// saving data
form.addEventListener("submit", e => {
e.preventDefault();
db.collection("artists").add({
avatar: form.avatar.value,
});
form.name.value = "";
});
const Artists = () => {
return (
<div>
<h1>Cloud Cafe</h1>
<form id="add-artist-avatar">
<input type="text" name="avatar" placeholder="Artists Avatar Link" />
<button>Add Avatar</button>
</form>
<div className="content"></div>
</div>
);
};
export default Artists;
Perhaps you need something like this
import React, { useEffect, useState } from "react";
import firebase from "./firebase";
import "./artists-styles.css";
const db = firebase.firestore();
const Artists = () => {
const [docs, setDocs] = useState([]);
const [inputValue, setInputValue] = useState('');
useEffect(() => {
db.collection("artists")
.get()
.then(snapshot => {
console.log(snapshot.docs);
setDocs(snapshot.docs);
});
}, []);
const handleSubmit = e => {
e.preventDefault();
db.collection("artists").add({
avatar: inputValue
});
};
return (
<div>
<h1>Cloud Cafe</h1>
<form id="add-artist-avatar" onSubmit={(e) => handleSubmit(e)}>
<input
type="text"
name="avatar"
placeholder="Artists Avatar Link"
value={inputValue}
onChange={e => setInputValue(e.target.value)}
/>
<button>Add Avatar</button>
</form>
<div className="content">
{docs.map(doc => (
<img src={doc.data().avatar}/>
))}
</div>
</div>
);
};
export default Artists;

When recaptcha gets rendered, a transparent container overlaps all the ui and spans all the viewport space

I'm working with next.js and firebase.
I'm doing authentication with google and with phone number.
The problem I have happens when authenticating with phone number, when the recaptcha is rendered, I can't click it and move forward to the next steps in the auth flow, because a transparent container also is rendered and overlaps completly ui.
I don't know why this happens, I followed every step of the google guide:
Phone authentication
My page component is the following...
In the page component I'm rendering a different form, one if it's time to write the verification code sent via SMS and another which is the first in being showed, to write and send the phone number.
import AppHead from '../../components/head'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from "react-i18next"
import Select from '../../components/select'
import Nav from '../../components/nav'
import PhoneNumberInput from '../../components/phone-input'
import Dialog from '../../components/dialog'
import Footer from '../../components/footer'
import { isPossiblePhoneNumber } from 'react-phone-number-input'
export default function Home({ M, authService, libService }) {
const verificationCodeInput = useRef(null)
const [phoneNumber, setPhoneNumber] = useState("")
const [shouldVerify, setShouldVerify] = useState(false)
const [openDialog, setOpenDialog] = useState(false)
const [valid, setValid] = useState(2)
const [confirmationCode, setConfirmationCode] = useState('')
const { t, i18n } = useTranslation('common')
let verificationValidClass = ""
const onChangePhoneNumber = (value) => {
setPhoneNumber(value)
};
const onClickLoginWithPhone = async (evt) => {
evt.preventDefault()
try {
if (isPossiblePhoneNumber(phoneNumber)) {
const confirmationResult = await authService.signInWithPhoneNumber(phoneNumber, window.recaptchaVerifier)
window.confirmationResult = confirmationResult
setShouldVerify(true)
} else {
setOpenDialog(true)
}
} catch (error) {
console.log(error)
}
};
const phoneNumberForm = (
<form className="home__formLogin">
<PhoneNumberInput onChangePhoneNumber={onChangePhoneNumber} phoneNumber={phoneNumber} labelText={t("forms.auth.phoneFieldLabel")} />
<button data-target="phoneLoginModal" className="btn-large home__login-phone modal-trigger" onClick={onClickLoginWithPhone}>
<span className="material-icons home__sendIcon" aria-hidden="true">
send
</span>
{t("forms.auth.sendPhone")}
</button>
<div id="recaptcha-container">
</div>
</form>
)
if (valid === 0) {
verificationValidClass = "invalid"
} else if (valid === 1) {
verificationValidClass = "valid"
}
useEffect(() => {
const elems = document.querySelectorAll('.modal');
const instances = M.Modal.init(elems);
window.recaptchaVerifier = authService.getVerifier('recaptcha-container', (response) => setShouldVerify(true), () => console.log("captcha-expired"))
}, []);
return (
<>
<main className="main">
<div className="main__layout home">
<Nav top={true} content={pickLanguage} contentLg={pickLanguageLG} />
<AppHead title={t("pages.login_phone.meta-title")} description={t("metaDescription")} />
<header className="home__header">
<h1>{shouldVerify ? t("pages.home.titleCode") : "LOGIN"}</h1>
<p>{t("pages.login_phone.subtitle_1")} <br /> {t("pages.login_phone.subtitle_2")}</p>
</header>
<section className="home__login-options">
<h1 className="home__formTitle">{shouldVerify ? t("pages.home.code") : t("pages.home.loginHeading")}</h1>
{shouldVerify ? verificationCodeForm : phoneNumberForm}
<Dialog open={openDialog} onConfirm={onClickConfirmDialog} heading={t("components.dialog.wrongPhoneNumber")} confirmText={t("components.dialog.wrongPhoneNumberConfirm")}>
{t("components.dialog.wrongPhoneNumberContent")}
</Dialog>
</section>
</div>
</main>
<Footer />
</>
)
}
In the useEffect callback I create the verifier object and then in onClickLoginWithPhone I submit the phone number.
The function call authService.signInWithPhoneNumber(phoneNumber, window.recaptchaVerifier) is implemented as shown below:
export default function AuthService(authProvider,firebase,firebaseApp) {
return Object.freeze({
signInWithPhoneNumber,
getVerifier,
getCredentials
})
function getVerifier(id,solvedCallback,expiredCallback){
return new firebase.auth.RecaptchaVerifier("recaptcha-container",{
'size': 'normal',
'callback': solvedCallback,
'expired-callback': expiredCallback
});
}
//used when user signs in with phone number and need to introduce a verification code sended via SMS
async function getCredentials(verificationId,verificationCode){
const credential = firebase.auth.PhoneAuthProvider.credential(verificationId,verificationCode);
const userCredential = await firebase.auth(firebaseApp).signInWithCredential(credential);
return userCredential
}
async function signInWithPhoneNumber(phoneNumber, phoneVerifier) {
const confirmationResult = await firebase.auth(firebaseApp).signInWithPhoneNumber(`${phoneNumber}`, phoneVerifier)
return confirmationResult
}
}
The container that is displayed is the following:
The part highlighted in blue is the full element which appears when I submit the phone number, and the part highlighted in red is the div which spans all the viewport and dont let me click the captcha widget
I really don't know why this thing is displayed, I'm not manipulating the DOM when submit the phone number, so I guess this problem is related to using next with firebase or with firebase itself.
Note: t("<json-object-field>"), t is just a function which reads an specific json file (a different one depending on the language the page is being showed) which contains all the strings translated to an specific language and this function returns a translated string individually.
The captcha widget is rendered but the container overlaps it, the container is transparent.

Resources