I'm trying load an API response into a Next.js page but the props return undefined. I went through lot of answers from StackOverflow still couldn't find a solution to my problem.
Edited this post added a few more details for support.
Here is my code below.
_app.tsx
import 'tailwindcss/tailwind.css';
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import { UserProvider } from '#auth0/nextjs-auth0';
import App from 'next/app';
function MyApp({ Component, pageProps }: AppProps) {
return (
<UserProvider>
<Component {...pageProps} />
</UserProvider>
)
}
MyApp.getInitialProps = async (appContext) => {
const appProps = await App.getInitialProps(appContext);
return {...appProps};
}
export default MyApp
profile.tsx
import React from 'react';
import { useUser } from '#auth0/nextjs-auth0';
import withAuth from '../components/withAuth';
import { Navbar } from '../components/navbar'
import styles from '../styles/Home.module.css'
import type { GetStaticProps, NextPage } from 'next';
export const getStaticProps: GetStaticProps = async(context) =>{
// const res = await fetch(`http://localhost:3000/api/users`);
const data = "sed"
console.log(data);
return {
props: {
users:data
}
}
}
const Profile: NextPage<{users:any}> = ({users}) => {
const {user, isLoading} = useUser();
console.log(users);
return (
<>
<Navbar />
<div className={styles.container}>
<div>profile</div>
</div>
</>
)
}
export default withAuth(Profile);
Here is the code for HOC that is wrapped for each page after login.
withAuth.tsx
import Login from "../pages/login";
import { Spinner } from "./spinner";
const withAuth = Component => {
const Auth = (props) => {
const { isLoggedIn, loading } = props;
if(loading){
return (
<Spinner />
)
}
if (!isLoggedIn) {
return (
<Login />
);
}
// If user is logged in, return original component
return (
<Component {...props} />
);
};
if (Component.getInitialProps) {
Auth.getInitialProps = Component.getInitialProps;
}
return Auth;
};
export default withAuth;
Related
I'm currently attempting to hide the banner at a certain page. I have successfully hid the banner in all other pages except one with a page with a id. I have a dynamic folder named [content]
import { useRouter } from "next/router";
const HIDDEN_BOARDLIST = ["/board/board_list"];
//this is successful
const HIDDEN_BOARDDETAILS = [`board/${content}`].
//this does not work
//http://localhost:3000/board/620471f057aad9002de7f04f. I have to enter the id manually but since this is a dynamic, the id will change every time
export default function Layout(props: ILayoutProps) {
const router = useRouter();
console.log(router.asPath);
const isHiddenBoardList = HIDDEN_BOARDLIST.includes(router.asPath);
return (
<Wrapper>
<Header />
{!isHiddenBoardList && <Banner />}
<BodyWrapper>
<Body>{props.children}</Body>
</BodyWrapper>
</Wrapper>
);
}
useRouter is a hook.
CSR
import React, { useState, useEffect } from 'React';
import { useRouter } from "next/router";
interface ILayoutProps {
//...
}
export default function Layout(props: ILayoutProps) {
const router = useRouter();
const [hidden, setHidden] = useState(false);
useEffect(() => {
if(router.asPath.includes('board/')) {
setHidden(true);
}
}, [router.asPath]);
return (
<Wrapper>
<Header />
{!hidden && <Banner />}
<BodyWrapper>
<Body>{props.children}</Body>
</BodyWrapper>
</Wrapper>
);
}
Since this code is CSR, flickering may occur. <Banner /> will disappear after being rendered.
If you don't want that, there is a way to pass the current url as props of the <Layout /> component via getServerSideProps.
SSR
// pages/board/[id].tsx
import { GetServerSideProps, NextPage } from 'next';
import Head from 'next/head';
interface Props {
url: string;
}
const BoardPage: NextPage<Props> = (props: Props) => {
return (
<>
<Layout {...props} />
</>
);
};
export const getServerSideProps: GetServerSideProps = async (context) => {
const { resolvedUrl } = context; //ex) /board/12345?id=12345
return {
props: {
url: resolvedUrl ,
}, // will be passed to the page component as props
};
};
// components/Layout.tsx
import React, { useState, useEffect } from 'React';
import { useRouter } from "next/router";
interface ILayoutProps {
url: string;
// ...
}
export default function Layout(props: ILayoutProps) {
return (
<Wrapper>
<Header />
{props.url.includes('board/') && <Banner />}
<BodyWrapper>
<Body>{props.children}</Body>
</BodyWrapper>
</Wrapper>
);
}
I hope these two kinds of code are helpful.
I'm having some problems to set a state when repass the context provider for other elements, this is my code.
I'm creating a FancyboxContext for when i need it, i can call it anywhere.
import React, { createContext, useContext, useState } from 'react';
interface FancyboxContextInterface {
fancybox: boolean;
setFancybox(value: boolean): void;
}
interface FancyboxProviderProps {
children: React.ReactNode;
}
interface UseFancyboxInterface {
fancybox: boolean;
setFancybox: React.Dispatch<React.SetStateAction<boolean>>;
}
const FancyboxInitialState = {
fancybox: false,
setFancybox: () => {}
};
const FancyboxContext = createContext<FancyboxContextInterface>(
FancyboxInitialState
);
const FancyboxProvider: React.FC<FancyboxProviderProps> = ({
children
}: FancyboxProviderProps) => {
const [fancybox, setFancybox] = useState(FancyboxInitialState.fancybox);
return (
<FancyboxContext.Provider
value={{
fancybox,
setFancybox
}}
>
{children}
</FancyboxContext.Provider>
);
};
export const useFancybox = (): UseFancyboxInterface => {
const context = useContext(FancyboxContext);
const { fancybox, setFancybox } = context;
return {
fancybox,
setFancybox
};
};
export default FancyboxProvider;
involving _document.tsx in ContextProvider;
...
render(): JSX.Element {
return (
<Html lang="pt">
<Head>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Roboto:wght#300;400;700&display=swap"
/>
</Head>
<body>
<FancyboxProvider>
<Main />
</FancyboxProvider>
<NextScript />
</body>
</Html>
);
}
but when I am trying to start a simple call to setState(), nothing happens
import React, { useEffect } from 'react';
import { AppProps } from 'next/app';
import { useFancybox } from '../hooks/useFancybox';
import { Fancybox, GlobalStyles } from '../styles/global';
const myApp: React.FC<AppProps> = ({ Component, pageProps }) => {
const { fancybox, setFancybox } = useFancybox();
useEffect(() => setFancybox(true), []);
return (
<>
{fancybox ? <Fancybox /> : ''}
<Component {...pageProps} />
<GlobalStyles />
</>
);
};
export default myApp;
First, you should know that React Context can only be used on the client side.
You cannot place your ContextProvider in _document.js because it only runs on the server side. in your _app.js you are exporting a javascript function myApp instead of a React component MyApp.
What you can do is place your FancyProvider in _app.js and call your setState in any page
what your _app.js should looks like
import React from "react";
import { AppProps } from "next/app";
import { GlobalStyles } from "../styles/global";
const MyApp: React.FC<AppProps> = ({ Component, pageProps }) => {
return (
<FancyboxProvider>
<Component {...pageProps} />
<GlobalStyles />
</FancyboxProvider>
);
};
export default MyApp;
Now you can call your setState on /pages/index.js or any other page
import { useEffect } from "react";
import { useFancybox } from "../hooks/useFancybox";
import { Fancybox } from "../styles/global";
export default function Home() {
const { fancybox, setFancybox } = useFancybox();
useEffect(() => {
setFancybox(true);
}, []);
return (
<div>
{fancybox ? <Fancybox /> : ""}
</div>
);
}
I am working with react. When I try to play audio from a button I am receiving this error. Everything worked before when the website was only a one page website. Then I turned it into a multi-page website using react-router-dom router, switch, and route everything started to error out with the onClick. I am not sure what went wrong or how to fix it. I am still pretty new with react. Here is the code:
Player.js
import React, { useState } from "react";
import "./Button.css";
import {useTranslation} from "react-i18next";
import teaser from '../sounds/teaser-final.mp3';
const Player = ({ url }) => {
const useAudio = url => {
const [audio] = useState(new Audio(teaser));
const [playing, setPlaying] = useState(true); //nothing is playing on default
const toggle = () => {
setPlaying(!playing);
playing ? audio.play() : audio.pause()
console.log("audio is playing" + toggle);
};
return [playing, toggle];
};
const [toggle] = useAudio(url);
const {t} = useTranslation('common');
return (
<div>
<audio id="player" style={{'display': 'none'}} src={teaser}></audio>
<button
className="btns hero-button btn--outline btn--large"
onClick={toggle}
>
{t('heroSection.button')}
</button>
</div>
);
};
export default Player;
HeroSection.js (file where Player.js is used)
import React from 'react'
import './HeroSection.css'
import Player from '../Player';
function HeroSection() {
return (
<div className="hero-btns">
<Player />
</div>
)
}
export default HeroSection;
App.js
import React, { useState, useEffect } from 'react';
import './index.css';
import {BrowserRouter, Switch, Route} from 'react-router-dom';
import Axios from 'axios';
import AdminHome from './Components/auth/Admin';
import Login from './Components/auth/Login';
import Register from './Components/auth/Register';
import UserContext from './Context/UserContext';
import Navbar from './Components/Navbar/Navbar';
import HeroSection from './Components/HeroSection/HeroSection';
import ShareSection from './Components/ShareSection/ShareSection';
import Subscribe from './Components/Subscribe/Subscribe';
import Footer from './Components/Footer/Footer';
import About from './Components/About/About';
import TheApp from './Components/TheApp/TheApp';
import Contact from './Components/SocialSection/Contact';
import CookiesPopUp from './Components/Cookies/CookiesPopUp';
function Home() {
return (
<>
<CookiesPopUp />
<Navbar />
<HeroSection />
<ShareSection />
<Subscribe />
<TheApp />
<About />
<Contact />
<Footer />
</>
);
};
function App() {
const [userData, setUserData] = useState({
token: undefined,
user: undefined,
});
const [didMount, setDidMount] = useState(false);
useEffect(() => {
setDidMount(true);
const checkedLoggedIn = async () => {
let token = localStorage.getItem("auth-token");
if(token === null) {
localStorage.setItem("auth-token", "");
token = "";
}
const tokenRes = await Axios.post(
"http://localhost:5000/users/tokenIsValid", null,
{headers: { "x-auth-token": token }}
);
if (tokenRes.data) {
const userRes = await Axios.get("http://localhost:5000/users/", {
headers: { "x-auth-token": token },
});
setUserData({
token,
user: userRes.data,
});
}
return () => setDidMount(false);
};
checkedLoggedIn();
}, [])
if(!didMount) {
return null;
}
return (
<>
<BrowserRouter>
<UserContext.Provider value={{userData, setUserData}}>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/admin" component={AdminHome} exact />
<Route path="/admin/login" component={Login} exact />
<Route path="/admin/register" component={Register} exact />
</Switch>
</UserContext.Provider>
</BrowserRouter>
</>
);
}
export default App;
And this is the error I get in console when I try to play the audio.
You are overriding the definition of toggle with this code :
const [toggle] = useAudio(url);. The Player.js has multiple declarations and definitions of toggle. See:
const toggle = () => {
setPlaying(!playing);
playing ? audio.play() : audio.pause()
console.log("audio is playing" + toggle);
};
return [playing, toggle];
};
..
...
..
const [toggle] = useAudio(url);
Hence the Error Expected OnClick to be a function but provided a boolean
Thank you for your feedback! I managed to get everything to work by changing my code in Player.js to:
import React, { useState } from "react";
import "./Button.css";
import {useTranslation} from "react-i18next";
import teaser from '../sounds/teaser-final.mp3';
const Player = () => {
const [playing, setPlaying] = useState(true);
const [audio] = useState(new Audio(teaser));
const toggle = () => {
setPlaying(!playing);
playing ? audio.play() : audio.pause()
};
const {t} = useTranslation('common');
return (
<div>
<audio id="player" style={{'display': 'none'}} src={teaser}></audio>
<button
className="btns hero-button btn--outline btn--large"
onClick={toggle}
>
{t('heroSection.button')}
</button>
</div>
);
};
export default Player;
I'm building a practice app that uses Unsplash to render users photos. I'm using React and Redux. With react-router-dom, I'm trying to follow the docs but I find it very confusing to set up. Here's what I have so far. When I click on a result out of a returned list of results from a search, I want it to render a user page profile.
index.js (make sure I have react-router-do set up correctly):
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
// import store from './app/store';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import reducers from "./app/reducers/rootReducer";
import * as serviceWorker from './serviceWorker';
const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducers, storeEnhancers(applyMiddleware(thunk)));
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
Top component App
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import Images from "./app/components/Images";
import Search from "./app/components/Search";
import UserProfile from "./app/components/UserProfile";
import "./App.css";
function App() {
return (
<>
<Search />
<Images />
<Router>
<Route link="/userProfile">
<UserProfile />
</Route>
</Router>
</>
);
}
export default App;
search (parent component to searchResults where exists):
import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { queryAction } from "../actions/queryAction";
import SearchResults from "./SearchResults";
const Search = (props) => {
const [query, setQuery] = useState("");
console.log(props.searches);
const searchPhotos = async (e) => {
e.preventDefault();
console.log("submitting form");
props.queryAction(query);
};
const showUsers = (user, e) => {
e.preventDefault()
console.log(user)
};
return (
<>
<form className="form" onSubmit={searchPhotos}>
<label className="label" htmlFor="query">
{" "}
</label>
<input
type="text"
name="query"
className="input"
placeholder={`Try "dog" or "apple"`}
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<button type="submit" className="button">
Search
</button>
</form>
<SearchResults results={props.searches} showUsers={showUsers} />
</>
);
};
const mapStateToProps = (state) => {
return {
searches: state.searches,
};
};
const mapDispatchToProps = (dispatch) => {
return {
queryAction: (entry) => dispatch(queryAction(entry)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Search);
searchResults:
import React from "react";
import { BrowserRouter as Router, Link } from "react-router-dom";
import { getUserAction } from "../actions/getUserAction";
import { connect } from "react-redux";
const SearchResults = (props) => {
const { results } = props.results.searches;
const handleClick = (result, e) => {
e.preventDefault();
props.getUser(result.username);
};
return (
<>
{results &&
results.map((result, id) => {
return (
<div key={id}>
<Router>
<Link to="/userProfile" onClick={(e) => handleClick(result, e)}>
{result.username}
</Link>
</Router>
</div>
);
})}
</>
);
};
const mapDispatchToProps = (dispatch) => {
return {
getUser: (query) => dispatch(getUserAction(query)),
};
};
export default connect(null, mapDispatchToProps)(SearchResults);
and finally the UserProfile component:
import React from 'react';
import { connect } from 'react-redux';
const UserProfile = props => {
console.log(props)
return (
<div>
</div>
);
}
const mapStateToProps = state => {
return {
user: state.users
}
}
export default connect(mapStateToProps, null)(UserProfile);
app component
import React from "react";
import { Switch, Route } from "react-router-dom";
import Images from "./app/components/Images";
import Search from "./app/components/Search";
import UserProfile from "./app/components/UserProfile";
import "./App.css";
function App() {
return (
<>
<Search />
<Images />
<Switch>
<Route path="/userProfile/:username">
<UserProfile />
</Route>
</Switch>
</>
);
}
export default App;
SearchResults component
import React from "react";
import { Link } from "react-router-dom";
const SearchResults = (props) => {
const { results } = props.results.searches;
const handleClick = (result, e) => {
e.preventDefault();
props.getUser(result.username);
};
return (
<>
{results &&
results.map((result, id) => {
return (
<div key={id}>
<Link to={`/userProfile/${result.username}`}>
{result.username}
</Link>
</div>
);
})}
</>
);
};
export default SearchResults;
UserProfile component
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { getUserAction } from "../actions/getUserAction";
const UserProfile = props => {
useEffect(() => {
props.getUserAction(props.match.params.username)
},[])
console.log(props)
return (
<div>
{props.user
? <div>{user.username}</div>
: <div>Loading...</div>
}
</div>
);
}
const mapStateToProps = state => {
return {
user: state.users
}
}
const mapDispatchToProps = (dispatch) => {
return {
getUser: (query) => dispatch(getUserAction(query)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
Edit: Add a param to your link and remove the onclick. Update the Route to expect a :username param. You can access the param through props in UserProfile component.
Make sure to perform the action or access state when mounting the UserProfile component so you have some data when it renders.
Edit 2: Added UserProfile component to answer. You want to dispatch your action when the component is mounting. Also, set a ternary to show "Loading..." if state.user isn't done being fetched.
i'm trying to only render the component <IntercomClient /> after a user clicks "Accept" on a cookie consent banner. Clicking accept changes the GlobalLayout's intercomIsActive state to true and thereby renders the IntercomClient. This is working but the warning concerns me.
How can I workaround the child/parent state change? I've been looking around but don't really understand.
import React, { useState } from 'react'
import { CookieBanner } from '#palmabit/react-cookie-law'
import IntercomClient from '../components/intercomClient'
const GlobalLayout = ({ location, children }) => {
const [intercomIsActive, setIntercomIsActive] = useState(false)
return (
...
<CookieBanner
onAccept={() => setIntercomIsActive(true)}
/>
<IntercomClient active={intercomIsActive}/>
...
)}
IntercomClient
import React from 'react';
import Intercom from 'react-intercom'
const IntercomClient = ({ active }) => {
return active ? <div><Intercom appID="XXXXXX" /></div> : null
}
export default IntercomClient;
import React, {useState} from 'react';
const Example = () => {
const [intercomIsActive, setIntercomIsActive] = useState(false)
return (
<Layout>
...
<CookieBanner
onAccept={() => setIntercomIsActive(true)}
/>
<IntercomClient active={intercomIsActive}/>
...
</Layout>
);
};
export default Example;
import React, {useState} from 'react';
const Example = () => {
const [intercomIsActive, setIntercomIsActive] = useState(false)
return (
<Layout>
...
<CookieBanner
onAccept={() => setIntercomIsActive(true)}
/>
{
intercomIsActive &&
<IntercomClient active={intercomIsActive}/>
}
...
</Layout>
);
};
export default Example;