empty object with getStaticProps and typescript - reactjs

I'm trying to fetch some data and via the doc's it seems it has to be via getStaticProps but I keep getting an empty object when logging the props inside my component
if you have any idea why feel free to let me know
here is my _app.tsx
const Child:React.FC = ({ children }) => {
const isDark = useSelector<ThemeState, ThemeState["isDark"]>(state => state.isDark)
return <div className={`${isDark ? "dark blackThemeColor test" : "white whiteThemeColor"}` + " min-h-screen font-body pr-0"} style={{height:'150vh'}}>{children}</div>;
}
const MyApp = ({ Component, pageProps }: AppProps) => {
return (
<Provider store={ThemeStore}>
<Application theme={theme}>
<Child>
<Navigation {...pageProps} />
<Component {...pageProps} />
</Child>
</Application>
</Provider>
);
};
export default MyApp
and my navigation.tsx
interface NavProps {
title: string;
body: string;
id: number;
userId: number
}
const Navigation: React.FC<NavProps> = (props: NavProps) => {
useEffect(() => {
console.log(props)
}, [])
console.log(props)
return (
<>
<div className={`w-full h-full `}>
<p>{props.title}</p>
</div>
</>
)
}
export default Navigation
export const getStaticProps: GetStaticProps = async (context) => {
const res = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=6")
const data: NavProps = await res.json();
console.log(data)
return {
props: {
data
}
}
}

From your comment I take that the navigation.tsx file is not located in the pages folder. To quote the docs:
getStaticProps can only be exported from a page. You can’t export it from non-page files.
So you have to move the function to the page / route where you want to fetch the data or do it clientside.

Related

Error - JSX Element type 'X'does not have any constructor or call signatures

I am trying to render a data by context provider however i got this message reading JSX Element type Context does not have any constructor or call signatures.
My Code in App.tsx is
import { Context } from './interfaces/cardContext';
const App = () => {
return (
<div className="App">
<BrowserRouter>
<Navbar />
<Context>
<RestaurantDetails />
</Context>
<Footer />
</BrowserRouter>
</div>
);
};
export default App
and in my Context page
export interface ContextData {
restaurants: restaurantType[];
}
export interface Props {
children: React.ReactNode;
}
export const Context = createContext({} as ContextData);
export const Provider = ({ children }: Props) => {
const [restaurants, setRestaurants] = useState<restaurantType[]>([]);
useEffect(() => {
fetch('http://localhost:5001/restaurants')
.then((res) => res.json())
.then((data) => setRestaurants(data));
}, []);
return (
<Context.Provider value={{ restaurants }}>{children}</Context.Provider>
);
};
You should import your provider as a wrapper of other components:
import { Provider } from './interfaces/cardContext';
const App = () => {
return (
<div className="App">
<BrowserRouter>
<Navbar />
<Provider>
<RestaurantDetails />
</Provider>
<Footer />
</BrowserRouter>
</div>
);
};
export default App
And then use your context like this in other componens:
import {useContext} from 'react';
import { Context } from 'path-to-card-context';
const {restaurants} = useContext(Context);

how to keep state of sidebar same in every page in next.js?

I want to keep the same state of the sidebar when i navigate between pages , how do i achive that ?
code in _app.js
function MyApp({ Component, pageProps }) {
const open = useState(false);
const [jsonData, setJsonData] = useState({});
const headerVisibility = useState("");
return (
<>
<HeaderContext.Provider value={headerVisibility}>
<NavigationBar open={open} />
<Content open={open}>
<Component {...pageProps} />
</Content>
</HeaderContext.Provider>
</>
);
}
// console.clear();
export default MyApp;
this is how i am fetching data in navigation/sidebar
useEffect(async () => {
const rawData = await fetch("http://localhost:8000/data");
const jsonData = await abc.json();
setData((prevState) => jsonData);
setnavData((prevState) => jsonData);
}, []);

custom hook not triggering in component

I followed this tutorial for creating themes for night/day modes with styled-components.
I created a hook useDarkMode and for some reason, while it's detecting changes locally to the theme state within the hook, it's not sending these updates to my component (_app.tsx) where it needs to be read.
Am I missing something obvious here, why isn't theme changing on _app.tsx?
useDarkMode hook
import { useEffect, useState } from 'react';
export const useDarkMode = () => {
const [theme, setTheme] = useState('light');
const setMode = (mode) => {
window.localStorage.setItem('theme', mode);
setTheme(mode);
};
const themeToggler = () => {
theme === 'light' ? setMode('dark') : setMode('light');
};
useEffect(() => {
console.log('theme:', theme); <=== triggers and shows theme has been updated
}, [theme]);
useEffect(() => {
const localTheme = window.localStorage.getItem('theme');
console.log('localTheme', window.localStorage.getItem('theme'));
localTheme && setTheme(localTheme);
}, []);
return [theme, themeToggler];
};
_app.tsx
function App({ Component, pageProps }: AppProps) {
const store = useStore(pageProps.initialReduxState);
const [theme] = useDarkMode();
useEffect(() => {
console.log('t', theme); <=== only triggers on component mount, theme is not updating
}, [theme]);
const themeMode = theme === 'light' ? LIGHT_THEME : DARK_THEME;
return (
<Provider store={store}>
<ThemeProvider theme={themeMode}>
<RootPage Component={Component} pageProps={pageProps} />
</ThemeProvider>
</Provider>
);
}
where it's being invoked
const TopNav = () => {
const [theme, themeToggler] = useDarkMode();
return (
<Content className="full-bleed">
<NavInner>
<AuthLinks>
<>
<button onClick={themeToggler}>Switch Theme</button>
<Link href="/user/login" passHref>
<div>
<Typography format="body1">Login</Typography>
</div>
</Link>
<Link href="/user/register" passHref>
<div>
<Typography format="body1">Register</Typography>
</div>
</Link>
</>
...
</AuthLinks>
</NavInner>
</Content>
);
};
Issue
Each react hook is its own instance, they don't share state.
Suggested Solution
Use a single dark mode theme state in the provider and expose the themeToggler in a context so all components can update the same context value.
Theme toggle context
const ThemeToggleContext = React.createContext({
themeToggler: () => {},
});
App
import { ThemeToggleContext } from 'themeToggleContext';
function App({ Component, pageProps }: AppProps) {
const store = useStore(pageProps.initialReduxState);
const [theme, themeToggler] = useDarkMode();
useEffect(() => {
console.log('t', theme); <=== only triggers on component mount, theme is not updating
}, [theme]);
const themeMode = theme === 'light' ? LIGHT_THEME : DARK_THEME;
return (
<Provider store={store}>
<ThemeProvider theme={themeMode}>
<ThemeToggleContext.Provider value={themeToggler} > // <-- pass themeToggler to context provider
<RootPage Component={Component} pageProps={pageProps} />
</ThemeToggleContext>
</ThemeProvider>
</Provider>
);
}
Component
import { ThemeToggleContext } from 'themeToggleContext';
const TopNav = () => {
const themeToggler = useContext(ThemeToggleContext); // <-- get the context value
return (
<Content className="full-bleed">
<NavInner>
<AuthLinks>
<>
<button onClick={themeToggler}>Switch Theme</button>
<Link href="/user/login" passHref>
<div>
<Typography format="body1">Login</Typography>
</div>
</Link>
<Link href="/user/register" passHref>
<div>
<Typography format="body1">Register</Typography>
</div>
</Link>
</>
...
</AuthLinks>
</NavInner>
</Content>
);
};

NextJS - Passing state to children as props in custom app

I have this ./pages/_app.js from the official docs:
import App, { Container } from 'next/app'
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return { pageProps }
}
render () {
const { Component, pageProps } = this.props
return (
<Container>
<Component {...pageProps} />
</Container>
)
}
}
export default MyApp
I want to pass state from MyApp to every page it renders, but can't figure how. I tried this:
const childProps = {
...
}
<Container>
<Component {...childProps} />
</Container>
and got an error:
React.Children.only expected to receive a single React element child.
pass what you want just as you pass usual props
<Component whatyouwant={propsyouwant} />
For example: this is how I passed MobileDetect props to every component in my app:
class MyApp extends App {
static async getInitialProps({ Component, router, ctx }) {
let pageProps = {}
const { req } = ctx
var MobileDetect = require('mobile-detect')
const md = new MobileDetect(req ? req.headers['user-agent'] : "")
const phone = md.phone()
const tablet = md.tablet()
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return { pageProps,
phone: phone,
tablet: tablet }
}
render () {
const {Component, pageProps, phone, tablet} = this.props
return (
<Container>
<Provider store={reduxStore}>
<Nav />
<Component {...pageProps} phone={phone} tablet={tablet} />
<Footer />
</Provider>
</Container>
)
}
}
export default withReduxStore(MyApp)
The culprit was
<Link>
<Icon />
text
</Link>
in the rendered page (not _app.js).
The Link had two children (remnants of react-router Link) instead of one.

Passing prop to react component on test

Im tryng to test a class component that needs some props to render the html, not sure why is not working. Im starting tests with jest and react now, so im not really experienced. What im missing?
Test component
const mockStore = configureMockStore();
const store = mockStore({});
describe("Pokemon detail", () => {
const mockPokemon = {
sprites: {
back_default:
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/132.png"
},
name: "ditto",
abilities: [
{
ability: {
name: "imposter",
url: 'https://pokeapi.co/api/v2/ability/150/"'
}
},
{
ability: {
name: "imposter",
url: "https://pokeapi.co/api/v2/ability/150/"
}
}
],
types: [
{
type: {
name: "normal"
}
}
]
}
const wrapper = mount(
<Provider store={store}>
<PokemonDetail pokemon={mockPokemon} />
</Provider>
);
expect(wrapper).toMatchSnapshot();
});
error message is this
TypeError: Cannot read property 'sprites' of undefined
render() {
<h1>oi</h1>
if (this.props.pokemon.sprites) {
^
const habilidades = this.props.pokemon.abilities.map(element => {
return <li key={element.ability.url}>{element.ability.name}</li>;
});
tested component
class PokemonDetail extends React.Component {
render() {
if (this.props.pokemon.sprites) {
const habilidades = this.props.pokemon.abilities.map(element => {
return <li key={element.ability.url}>{element.ability.name}</li>;
});
const tipos = this.props.pokemon.types.map(element => {
return <li key={element.type.url}>{element.type.name}</li>;
});
return (
<div className="ui card">
<div className="image">
<img src={this.props.pokemon.sprites.back_default} />
</div>
<div className="content">
<a className="header">{this.props.pokemon.name}</a>
</div>
<button
onClick={() => this.props.favoritePokemon(this.props.pokemon.name)}
className="ui button primary"
>
Add
</button>
</div>
);
}
return <div />;
}
}
const mapStateToProps = state => {
return { pokemon: state.pokemon };
};
export default connect(mapStateToProps,{favoritePokemon})(PokemonDetail);
Not sure why the mockPokemon with fake data is not taking place on the props object....seems right to me
Since PokemonDetail is a Redux connected component, and pokemon prop is handled by Redux, the prop in <PokemonDetail pokemon={mockPokemon} /> will be overridden by Redux.
Instead, the object should be passed as a part of Redux store:
const store = mockStore({ pokemon: mockPokemon });
const wrapper = mount(
<Provider store={store}>
<PokemonDetail />
</Provider>
);

Resources