use NextRouter outside of React component - javascript

I have a custom hook that will check whether you are logged in, and redirect you to the login page if you are not. Here is a pseudo implementation of my hook that assumes that you are not logged in:
import { useRouter } from 'next/router';
export default function useAuthentication() {
if (!AuthenticationStore.isLoggedIn()) {
const router = useRouter();
router.push('/login');
}
}
But when I use this hook, I get the following error:
Error: No router instance found. you should only use "next/router" inside the client side of your app. https://err.sh/vercel/next.js/no-router-instance
I checked the link in the error, but this is not really helpful because it just tells me to move the push statement to my render function.
I also tried this:
// My functional component
export default function SomeComponent() {
const router = useRouter();
useAuthentication(router);
return <>...</>
}
// My custom hook
export default function useAuthentication(router) {
if (!AuthenticationStore.isLoggedIn()) {
router.push('/login');
}
}
But this just results in the same error.
Is there any way to allow routing outside of React components in Next.js?

The error happens because router.push is getting called on the server during SSR on the page's first load. A possible workaround would be to extend your custom hook to call router.push inside a useEffect's callback, ensuring the action only happens on the client.
import { useEffect } from 'react';
import { useRouter } from 'next/router';
export default function useAuthentication() {
const router = useRouter();
useEffect(() => {
if (!AuthenticationStore.isLoggedIn()) {
router.push('/login');
}
}, []);
}
Then use it in your component:
import useAuthentication from '../hooks/use-authentication' // Replace with your path to the hook
export default function SomeComponent() {
useAuthentication();
return <>...</>;
}

import Router from 'next/router'

create a HOC which will wrap your page component
import React, { useEffect } from "react";
import {useRouter} from 'next/router';
export default function UseAuthentication() {
return () => {
const router = useRouter();
useEffect(() => {
if (!AuthenticationStore.isLoggedIn()) router.push("/login");
}, []);
// yous should also add isLoggedIn in array of dependancy if the value is not a function
return <Component {...arguments} />;
};
}
main component
function SomeComponent() {
return <>...</>
}
export default UseAuthentication(SomeComponent)

Related

React - Destructuring the useContext returns undefined

I'm working with authentication in React for the first time and I found it difficult to create it using contexts. When I destructuring an context in my App component, the variables get the value of undefined.
App file (renders the different project routes):
import React, { useContext } from 'react';
/* routes */
import { useRoutes } from 'hookrouter';
import { authRoutes } from './store/routes/auth.routes';
import { appRoutes } from './store/routes/app.routes';
/* contexts */
import { AuthContext } from './contexts/auth';
import { AuthProvider } from './contexts/auth';
function App() {
const { signed, user } = useContext(AuthContext);
const routes = signed ? appRoutes : authRoutes;
// the idea is that when the value of signed was changed (sign in / sign out), this code would be executed again and the routes corresponding to the current value would be rendered
console.log('context s:', signed); // undefined
console.log('context u:', user); // undefined
return (
<AuthProvider>
{ useRoutes(routes) }
</AuthProvider>
);
}
export default App;
AuthContext file:
import React, { createContext, useState } from 'react';
import * as auth from '../services/auth';
const AuthContext = createContext({});
export const AuthProvider = ({children}) => {
const [user, setUser] = useState(null);
async function signIn() {
const response = await auth.signIn();
// simulates an API call
setUser(response.user);
}
function signOut() {
setUser(null);
}
return (
<AuthContext.Provider value={{signed: !!user, user, signIn, signOut}}>
{children}
</AuthContext.Provider>
);
}
export { AuthContext }
Login file - authentication function:
const { signed, signIn } = useContext(AuthContext);
// I don't know why, but in that file the useContext works fine, returning the correct values on destructuring
console.log('signed:', signed); // starts as false, and becomes true when executing the authentication function below
const authenticateUser = (userData) => {
signIn();
}
Basically, I want the App to be re-rendered every time the AuthContext's signed variable changes (ie at sign in and sign out). The problem here is that the AuthContext variables inside the App function take on the value of undefined, but on the login screen they take on the correct value, and I don't see any difference in what I did between the two files. How to solve this?

How to fetch from getStaticProps and display on other pages? [duplicate]

I'm using Next.js with context API and styled components and I can't seem to get getStaticProps working.
I have read other posts and often they talk about the custom _app which I do have but I never ran into the issue before using context API. I have also tried the getInitialProps function and to no avail.
I should also note that even after not including the context wrapper I don't get a response from a function so I'm not at all sure of where to look.
Here is my code. Can you see what's going on?
import React from 'react';
import fetch from 'node-fetch';
export default function Header(props) {
console.log(props.hi);
return <div>Hey dis header</div>;
}
export async function getStaticProps(context) {
return {
props: {
hi: 'hello',
},
};
}
I have tried logging from the function but nothing is logging so I would imagine the problem is that the function isn't running for whatever reason.
Heres my custom _app file
import { GlobalContextWrapper } from 'context';
import Header from 'components/header/Header';
import App from 'next/app';
function MyApp({ Component, pageProps }) {
return (
<GlobalContextWrapper>
<Header />
<Component {...pageProps} />
<p>footer</p>
</GlobalContextWrapper>
);
}
MyApp.getInitialProps = async (appContext) => {
// calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(appContext);
return { ...appProps };
};
export default MyApp;
Here is my context file
import { useReducer } from 'react';
import initialState from './intialState';
import reducer from './reducer';
import GlobalStyle from '../GlobalStyle';
import theme from '../theme';
import { ThemeProvider } from 'styled-components';
export const GlobalContext = React.createContext();
export function GlobalContextWrapper({ children }) {
const [globalState, dispatch] = useReducer(reducer, initialState);
return (
<GlobalContext.Provider value={{ globalState, dispatch }}>
<GlobalStyle />
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</GlobalContext.Provider>
);
}
The issue was that i was not exporting this function from a page but instead a component and a custom app file.
Does anyone know a way i can get around this? The problem is that i have a header that gets data from a response and i want this header to be shown on every page without having to manually add it to each page along with repeating the getStaticProps function
A solution based on your code is just getting data in your _app.js - getInitialProps and pass to the Header
function MyApp({ Component, pageProps }) {
return (
<GlobalContextWrapper>
<Header data={pageProps.header}/>
<Component {...pageProps} />
<p>footer</p>
</GlobalContextWrapper>
);
}
MyApp.getInitialProps = async (appContext) => {
// calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(appContext);
const headerData = ....
return { ...appProps, header: headerData };
};

React, getting Error: Invalid hook call. Hooks can only be called inside of the body of a function component

Can anyone help me with React Hooks basics, I am relatively new and couldn't find proper help online
import React from 'react'
import { auth, provider } from "../../../firebaseSetup";
import { useNavigate } from "react-router-dom"
const GoogleAuth = async() => {
const navigate = useNavigate()
auth.signInWithPopup(provider).then(() => {
navigate('/home');
}).catch((error) => {
console.log(error.message)
})
}
export default GoogleAuth
I get error on const navigate = useNavigate() saying:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component
What they want for useNavigate (and all hooks) is to be called only at the top level of a React component or a custom hook.
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns.
See Rules of Hooks for more.
A solution to your problem could be calling const navigate = useNavigate() in the component where you will use GoogleAuth, and pass navigate as parameter.
As an example like so:
import React from 'react'
import { auth, provider } from "../../../firebaseSetup";
import { useNavigate } from "react-router-dom"
const GoogleAuth = async(navigate) => {
auth.signInWithPopup(provider).then(() => {
navigate('/home');
}).catch((error) => {
console.log(error.message)
})
}
export default GoogleAuth
import GoogleAuth from "GoogleAuth";
const App = ()=>{
/*
here at the top level, not inside an if block,
not inside a function defined here in the component...
*/
const navigate = useNavigate();
useEffect(()=>{
GoogleAuth(navigate)
},[])
return <div></div>
}
export default App;

React useEffect Hook not Triggering on First Render with [] Dependencies

I'm getting data via an Axios GET request from a local API and trying to save the data in a Context Object.
The GET request works properly when I run it outside the Context Provider function. But when I put it within a UseEffect function with no dependencies - ie. useEffect( () => /* do something*/, [] )the useEffect hook never fires.
Code here:
import React, { createContext, useReducer, useEffect } from 'react';
import rootReducer from "./reducers";
import axios from 'axios';
import { GET_ITEMS } from "./reducers/actions/types";
export const ItemsContext = createContext();
function ItemsContextProvider(props) {
const [items, dispatch] = useReducer(rootReducer, []);
console.log('this logs');
useEffect(() => {
console.log('this does not');
axios.get('http://localhost:27015/api/items')
.then(data => dispatch({type: GET_ITEMS, payload: data}))
}, [])
return (
<ItemsContext.Provider value={{items, dispatch}}>
{ props.children }
</ItemsContext.Provider>
);
}
export default ItemsContextProvider;
I never see 'this does not' in the console (double and triple checked). I'm trying to initialise the context to an empty value at first, make the GET request on first render, and then update the context value.
I'd really appreciate any help on what I'm doing wrong.
EDIT - Where Context Provider is being rendered
import React from 'react';
import AppNavbar from "./Components/AppNavbar";
import ShoppingList from "./Components/ShoppingList";
import ItemModal from "./Components/ItemModal";
//IMPORTED HERE (I've checked the import directory is correct)
import ItemsContextProvider from "./ItemsContext";
import { Container } from "reactstrap"
import "bootstrap/dist/css/bootstrap.min.css";
import './App.css';
function App() {
return (
<div className="App">
<ItemsContextProvider> //RENDERED HERE
<AppNavbar />
<Container>
<ItemModal />
<ShoppingList /> //CONSUMED HERE
</Container>
</ItemsContextProvider>
</div>
);
}
export default App;
I have it being consumed in another file that has the following snippet:
const {items, dispatch} = useContext(ItemsContext);
console.log(items, dispatch);
I see console logs showing the empty array I initialised outside the useEffect function in the Context Provider and also a reference to the dispatch function.
I had the same problem for quite a while and stumbled upon this thred which did not offer a solution. In my case the data coming from my context did not update after logging in.
I solved it by triggering a rerender after route change by passing in the url as a dependency of the effect. Note that this will always trigger your effect when moving to another page which might or might not be appropriate for your usecase.
In next.js we get access to the pathname by using useRouter. Depending on the framework you use you can adjust your solution. It would look something like this:
import React, { createContext, useReducer, useEffect } from 'react';
import rootReducer from "./reducers";
import axios from 'axios';
import { GET_ITEMS } from "./reducers/actions/types";
import { useRouter } from "next/router"; // Import the router
export const ItemsContext = createContext();
function ItemsContextProvider(props) {
const [items, dispatch] = useReducer(rootReducer, []);
const router = useRouter(); // using the router
console.log('this logs');
useEffect(() => {
console.log('this does not');
axios.get('http://localhost:27015/api/items')
.then(data => dispatch({type: GET_ITEMS, payload: data}))
}, [router.pathname]) // trigger useEffect on page change
return (
<ItemsContext.Provider value={{items, dispatch}}>
{ props.children }
</ItemsContext.Provider>
);
}
export default ItemsContextProvider;
I hope this helps anyone in the future!
<ItemsContextProvider /> is not being rendered.
Make sure is being consumed and rendered by another jsx parent element.

How would I dispatch an redux action from my API module?

I'm building an app in React Native, using the Redux methodology.
I want to be able to dispatch actions from my API "module".
Potentially, every API request could time out (or fail), and if that happens I want to dispatch an action to my global reducer (which handles the errorBar message and state). I'd rather not dispatch that message for every result (every API request) inside the scenes or components.
My structure is as follows (most content stripped):
index.android.js
import React from 'react';
import { AppRegistry } from 'react-native';
import configureStore from './app/store/configureStore'; // combines all reducers
const store = configureStore();
import RootContainer from './app/containers/rootContainer';
import { Provider } from 'react-redux';
const App = () => (
<Provider store={store}>
<RootContainer/>
</Provider>
);
// Register our app
AppRegistry.registerComponent('ReduxTest', () => App);
rootContainer:
import { connect } from 'react-redux';
import RootScene from '../components/scenes/RootScene';
import { hideSplash, showSplash, setSplashMessage } from '../actions/splashActions';
function mapStateToProps(state) {
return {
global: state.globalReducer,
splash: state.splashReducer
};
}
export default connect(
mapStateToProps,
{
hideSplash: () => hideSplash(),
showSplash: () => showSplash(),
setSplashMessage: (message) => setSplashMessage(message)
}
)(RootScene);
RootScene.js
import React, { Component } from 'react';
import Splash from '../views/Splash';
import ErrorBar from '../elements/ErrorBar';
import { View, Text, StyleSheet } from 'react-native';
import api from '../../helpers/api';
class RootScene extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentWillMount() {
api.checkConnectivity().then(response => {
// Hide splash, etc, optimally error states could be handled inside of "api"
});
}
render() {
return (
<View style={styles.rootWrapper}>
<ErrorBar props={this.props.global.errorBar}/>
<Splash/>
</View>
);
}
}
const styles = StyleSheet.create({
rootWrapper: {
flex: 1
}
});
export default RootScene;
api.js
const api = {
checkConnectivity() {
return _buildRequest({ endpoint: '/version' }).then(_makeRequest);
}
};
module.exports = api;
const _buildRequest = (request_data) => {
// ...
};
const _makeRequest = (request_object) => {
// ...
};
I'm aware that my stripped out code above is missing the actions to change the state of the errorBar.
If the way I'm structuring the app is completely nuts, I'm all ears.
Instead of API as "module", try to use it as a middleware. Therefore you will have access to dispatch() on your context.
The idea is dispatching the actions and based on the action your middleware will "decide" to call your api. In case of error the middleware can dispatch your default error action.
This post might help you: http://www.sohamkamani.com/blog/2016/06/05/redux-apis/
You can also use redux-api-middleware: https://github.com/agraboso/redux-api-middleware
You can do this with Thunk Middleware.
http://blog.nojaf.com/2015/12/06/redux-thunk/
https://github.com/gaearon/redux-thunk

Categories

Resources