NextJS - Add provider for only a few specific pages - reactjs

Is it possible for me to add a provider only for a specific route instead of going to my entire app in the next js?
my "pages" folder
My context file [server.jsx]
import { createContext, useState } from 'react';
const ServerContext = createContext({});
export const ServerProvider = ({ children }) => {
const [ data, setData] = useState(null);
return (
<ServerContext.Provider value={{ data, setData }}>
{children}
</ServerContext.Provider>
);
};
export default ServerContext;
And instead of passing it on to the entire app as below, I wanted to just pass it to my dashboard routes
[_app.jsx]
import { Provider } from 'next-auth/client';
import { ThemeProvider } from 'styled-components';
import GlobalStyles from '#/styles/global';
import theme from '#/styles/theme';
import { ServerProvider } from '#/contexts/server';
function MyApp({ Component, pageProps })
{
return (
<ThemeProvider theme={theme}>
<Provider session={pageProps.session} >
<ServerProvider>
<Component {...pageProps} />
<GlobalStyles />
</ServerProvider>
</Provider>
</ThemeProvider>
);
}
export default MyApp;

You can use the files in pages folder to wrap your route.
This file would be something like pages/server/index.js, if is a static one.
Example:
import { ServerProvider } from "..."
import { ServerPageOrSomething } from "..."
export default function MyRoute({ ...props }) {
return (
<ServerProvider>
// my components, like:
<ServerPageOrSomething {...props} />
</ServerProvider>
)
}
//your Next stuff here
export async function getServerSideProps(props) {
// ...
return {
props: {
//...
},
};
}
The Next stuff and the ...props depends in what you're using Next for. You can just ignore then if you don't need to process anything (get data, etc)

Related

sql.js react context provider

I want to run a local-first app, so node.js is not available.
I try to provide a sqljs database via context provider.
I mostly times get the error
TypeError: Cannot read properties of null (reading 'exec')
The code looks as following
SQLiteContext.js
import React, { createContext, useEffect, useState } from "react";
export const SQLiteContext = createContext();
const SQLiteProvider = ({ children }) => {
const [SQL, setSQL] = useState(null)
const [sqliteDB, setSqliteDB] = useState(null)
useEffect(() => {
initSqlJs({ locateFile: file => `/js/${file}` }).
then((aSQL)=>{
setSQL(aSQL)
setSqliteDB(new aSQL.Database())
});
}, []);
return (
<SQLiteContext.Provider value={{ SQL,setSQL ,sqliteDB,setSqliteDB }}>
{children}
</SQLiteContext.Provider>
)
}
export { SQLiteProvider };
_document.js - static html
import { Html, Head, Main, NextScript } from 'next/document'
import Script from 'next/script'
export default function Document() {
return (
<Html>
<Head />
<body>
<Script src='/js/sql-wasm.js' strategy="beforeInteractive" />
<Main />
<NextScript />
</body>
</Html>
)
}
App.js
import React, { useContext, useEffect } from "react";
import { SQLiteContext } from "../contexts/SQLiteContext";
export default function App() {
const {SQL,sqliteDB} = useContext(SQLiteContext)
const res = sqliteDB.exec("SELECT * FROM hello")
return (
<>
test
{res}
</>
)
}
index.js
import { SQLiteProvider } from "../contexts/SQLiteContext"
import App from "../components/App"
export default function Home() {
return (
<div>
<SQLiteProvider>
<App />
</SQLiteProvider>
</div>
)
}
The sqljs/initSqlJs works fine.
The Problem seems to be, that if It gets executed, it's not available yet
I tried alrady
put all code in one file
put the query inside the InitSqlJs(...).then((...)=>{})
do the initSqlJs in the static.html with window.db
It seems I need to get the DB Handle before the ContextProvider, but where?

How to test code that uses a custom hook based on useContext with react-testing-library and jest

I've created a custom context hook - and I'm struggling to figure out how to pass values to its provider during testing.
My hook:
import React, { createContext, useContext, useState } from 'react';
const Context = createContext({});
export const ConfigurationProvider = ({ children }) => {
// Use State to keep the values
const [configuration, setConfiguration] = useState({});
// pass the value in provider and return
return (
<Context.Provider
value={{
configuration,
setConfiguration,
}}
>
{children}
</Context.Provider>
);
};
export const useConfigurationContext = () => useContext(Context);
export const { Consumer: ConfigurationConsumer } = Context;
This is how it's used in the application:
function App() {
return (
<ConfigurationProvider>
<div className="app">
<ComponentA />
</div>
</ConfigurationProvider>
);
}
And in ComponentA:
const ComponentA = () => {
// Get configuration
const configuration = useConfigurationContext();
return (
<div>{JSON.stringify(configuration)}</div>
)
}
This all works fine - considered that I'm calling setConfiguration from another component and set an object. Now for the testing part:
import React, { Component, createContext } from 'react';
import { render, waitFor } from '#testing-library/react';
import ComponentA from 'componentA';
const config = {
propertyA: 'hello',
};
test('renders the config', async () => {
const ConfigurationContext = createContext();
const { queryByText } = render(
<ConfigurationContext.Provider value={config}>
<ComponentA />
</ConfigurationContext.Provider>
);
expect(queryByText('hello')).toBeInTheDocument();
});
This doesn't work - I'm expecting the value that I'm sending in would be rendered in the div, but the context is an empty object. What am I doing wrong?
Thanks to Carle B. Navy I got the reason why it doesn't work. For other people two wonder what the solution is I fixed it by doing the following:
In my context hook, I changed the last line to export the provider as well:
export const { Consumer: ConfigConsumer, Provider: ConfigProvider } = Context;
Then in my test case, instead of creating a new context, I import the ConfigProvider at the top, and then:
const { queryByText } = render(
<ConfigProvider value={config}>
<ComponentA />
</ConfigProvider>
);
Thanks for helping me solve this and hope this helps someone else.

GatsbyJs update provider value from onRouteUpdate

I'm using a provider for holding global location using gatsby's browser API for accessing location by consumer in other components. The problem is i can't change the global location from onRouteUpdate, Here is my code:
gatsby-browser.js:
import React, {useContext} from 'react';
import Provider,{ appContext } from './provider';
export const onRouteUpdate = ({ location, prevLocation }) => {
console.log('new pathname', location.pathname)
console.log('old pathname', prevLocation ? prevLocation.pathname : null)
// wanna set the new location for provider to use in other pages
// this code does not work
return(
<appContext.Consumer>
{context => {
context.changeLocation(location.pathname)
}})
</appContext.Consumer>
)
}
Provider.js:
import React, { useState } from 'react';
import { globalHistory as history } from '#reach/router'
export const appContext = React.createContext();
const Provider = props => {
const [location, setLocation] = useState(history.location);
return (
<appContext.Provider value={{
location,
changeLocation: (newLocation)=> {setLocation({location:newLocation}); console.log('changing')}
}}>
{props.children}
</appContext.Provider>
)
};
export default Provider;
Thanks.
onRouteUpdate isn’t expected to return React nodes, and so the React element you’re returning isn't going to be evaluated like you’d expect.
Since you’re only looking to store the current page, you don’t actually need to do anything manually onRouteUpdate because this functionality is available out-of-the-box with Gatsby.
// gatsby-browser.js
import React from "react"
import { appContext } from "src/provider"
export const wrapPageElement = ({ element, props }) => (
<AppContext.Provider value={{ location: props.location }}>
{React.createElement(element, props)}
</AppContext.Provider>
)

setting and accessing props in react functional components

I am trying to access redux store variable in a react functional components. code below,
import React from "react";
import { connect } from "react-redux";
import {
Redirect,
Route,
RouteComponentProps,
RouteProps
} from "react-router-dom";
import { Wizard } from "../models/wizard";
import { IStoreState } from "../redux/reducers/index";
import { WizardStage } from "../models/enums";
import { PrivateRoute } from "./PrivateRoute";
import { GettingStarted } from "./GettingStarted";
interface IWizardRouteProps extends RouteProps {
wizard: Wizard;
}
export const _WizardRoute = ({ component, ...rest }: IWizardRouteProps) => {
if (!component) {
throw Error("component is undefined");
}
const Component = component;
const render = (props: RouteComponentProps<any>): React.ReactNode => {
**if (props.wizard.wizardStage===WizardStage.InProgress) {
return <PrivateRoute {...props} component={Component} />;
}**
return (
<PrivateRoute
exact
path="/quote/getting-started"
component={GettingStarted}
/>
);
};
return <Route {...rest} render={render} />;
};
const mapStateToProps = ({
wizard
}: IStoreState): {
wizard: Wizard;
} => {
return { wizard };
};
export const WizardRoute = connect(
mapStateToProps,
null
)(_WizardRoute);
Not sure what I am missing. I can't seem to access the wizard from store in the code about and getting a compile time error in the if condtion. Getting the following error,
Property 'wizard' does not exist on type 'RouteComponentProps<any, StaticContext, any>'.ts(2339)
Any suggestions?
UPDATE
Chris Heald suggestion worked. But now I am getting the following error. Also changed the code to a class component. I am passing in the wizard as props
import React from "react";
import { Redirect, Route, RouteProps } from "react-router-dom";
import { Wizard } from "../models/wizard";
import { WizardStage } from "../models/enums";
interface IPrivateWizardRouteProps {
wizard: Wizard;
}
export class PrivateWizardRoute extends React.Component<
IPrivateWizardRouteProps & RouteProps
> {
renderThis = (props: any) => {
if (this.props.wizard.wizardStage === WizardStage.InProgress) {
return <React.Component {...props} />;
} else {
debugger;
return (
<Redirect
to={{
pathname: "/quote/getting-started"
}}
/>
);
}
};
render() {
const { path, exact } = this.props;
console.log(this.props);
return (
<Route
path={path}
exact={exact}
render={props => this.renderThis(props)}
/>
);
}
}
export default PrivateWizardRoute;
Typescript is complaining that your props, defined as a RouteComponentProps, doesn't expect a property named wizard. If you look at the type definition for it, it clearly doesn't!
You should be able to just have your function expect a new interface composed of both RouteComponentProps and IWizardRouteProps:
const render = (props: RouteComponentProps<any> & IWizardRouteProps)
As an aside, you should probably be using useCallback to define your render function, rather than just defining it in place; otherwise, you'll redefine the function each time the component renders.

React New Context API - Access Existing Context across Multiple Files

All the examples I've seen of the new Context API in React are in a single file, e.g. https://github.com/wesbos/React-Context.
When I try to get it working across multiple files, I'm clearly missing something.
I'm hoping to make a GlobalConfiguration component (the MyProvider below) create and manage the values in the context, ready for any child component (MyConsumer below) read from it.
App.js
render() {
return (
<MyProvider>
<MyConsumer />
</MyProvider>
);
}
provider.js
import React, { Component } from 'react';
const MyContext = React.createContext('test');
export default class MyProvider extends Component {
render() {
return (
<MyContext.Provider
value={{ somevalue: 1 }}>
{this.props.children}
</MyContext.Provider >
);
}
}
consumer.js
import React, { Component } from 'react';
const MyContext = React.createContext('test');
export default class MyConsumer extends Component {
render() {
return (
<MyContext.Consumer>
{(context) => (
<div>{context.state.somevalue}</div>
)}
</MyContext.Consumer>
);
}
}
Unfortunately that fails with this in the console:
consumer.js:12 Uncaught TypeError: Cannot read property 'somevalue' of undefined
Have I completely missed the point? Is there documentation or an example of how this works across multiple files?
I think the problem that you are running into is that you are creating two different contexts, and trying to use them as one. It is the Context created by React.createContext that links Provider and Consumer.
Make a single file (I'll call it configContext.js)
configContext.js
import React, { Component, createContext } from "react";
// Provider and Consumer are connected through their "parent" context
const { Provider, Consumer } = createContext();
// Provider will be exported wrapped in ConfigProvider component.
class ConfigProvider extends Component {
state = {
userLoggedIn: false, // Mock login
profile: { // Mock user data
username: "Morgan",
image: "https://morganfillman.space/200/200",
bio: "I'm Mogran—so... yeah."
},
toggleLogin: () => {
const setTo = !this.state.userLoggedIn;
this.setState({ userLoggedIn: setTo });
}
};
render() {
return (
<Provider
value={{
userLoggedIn: this.state.userLoggedIn,
profile: this.state.profile,
toggleLogin: this.state.toggleLogin
}}
>
{this.props.children}
</Provider>
);
}
}
export { ConfigProvider };
// I make this default since it will probably be exported most often.
export default Consumer;
index.js
...
// We only import the ConfigProvider, not the Context, Provider, or Consumer.
import { ConfigProvider } from "./configContext";
import Header from "./Header";
import Profile from "./Profile";
import "./styles.css";
function App() {
return (
<div className="App">
<ConfigProvider>
<Header />
<main>
<Profile />
</main>
<footer>...</footer>
</ConfigProvider>
</div>
);
}
...
Header.js
import React from 'react'
import LoginBtn from './LoginBtn'
... // a couple of styles
const Header = props => {
return (
... // Opening tag, etc.
<LoginBtn /> // LoginBtn has access to Context data, see file.
... // etc.
export default Header
LoginBtn.js
import React from "react";
import Consumer from "./configContext";
const LoginBtn = props => {
return (
<Consumer>
{ctx => {
return (
<button className="login-btn" onClick={() => ctx.toggleLogin()}>
{ctx.userLoggedIn ? "Logout" : "Login"}
</button>
);
}}
</Consumer>
);
};
export default LoginBtn;
Profile.js
import React, { Fragment } from "react";
import Consumer from "./configContext"; // Always from that same file.
const UserProfile = props => {...}; // Dumb component
const Welcome = props => {...}; // Dumb component
const Profile = props => {
return (
<Consumer>
...
{ctx.userLoggedIn ? (
<UserProfile profile={ctx.profile} />
) : (<Welcome />)}
...
</Consumer>
...
Reading the source code of React-Context, they do
<MyContext.Provider value={{
state: this.state,
}}>
and
<MyContext.Consumer>
{(context) => <p>{context.state.age}</p>}
So if you do
<MyContext.Provider value={{ somevalue: 1 }}>
{this.props.children}
</MyContext.Provider>
You should get somevalue like that
<MyContext.Consumer>
{(context) => <div>{context.somevalue}</div>}
</MyContext.Consumer>
EDIT
What if you create a file called myContext.js with:
const MyContext = React.createContext('test');
export default MyContext;
and then import it like :
import MyContext form '<proper_path>/myContext';
As of right now, the two context you created in the files are not the same even thought the name is the same. You need to export the context that you created in one of the files, and use that through out.
so something like this, in your provider.js file:
import React, { Component } from 'react';
const MyContext = React.createContext();
export const MyContext;
export default class MyProvider extends Component {
render() {
return (
<MyContext.Provider
value={{ somevalue: 1 }}>
{this.props.children}
</MyContext.Provider >
);
}
}
then in your consumer.js file
import MyContext from 'provider.js';
import React, { Component } from 'react';
export default class MyConsumer extends Component {
render() {
return (
<MyContext.Consumer>
{(context) => (
<div>{context.somevalue}</div>
)}
</MyContext.Consumer>
);
}
}
I'm gonna throw my solution into the pot - it was inspired by #Striped and simply just renames the exports into something that makes sense in my head.
import React, { Component } from 'react'
import Blockchain from './cloudComputing/Blockchain'
const { Provider, Consumer: ContextConsumer } = React.createContext()
class ContextProvider extends Component {
constructor(props) {
super(props)
this.state = {
blockchain: new Blockchain(),
}
}
render() {
return (
<Provider value={this.state}>
{this.props.children}
</Provider>
)
}
}
module.exports = { ContextConsumer, ContextProvider }
Now it's easy to implement a ContextConsumer into any component
...
import { ContextConsumer } from '../Context'
...
export default class MyComponent extends PureComponent {
...
render() {
return (
<ContextConsumer>
{context => {
return (
<ScrollView style={blockStyle.scrollView}>
{map(context.blockchain.chain, block => (
<BlockCard data={block} />
))}
</ScrollView>
)
}}
</ContextConsumer>
)
}
I'm SO done with redux!
TLDR; Demo on CodeSandbox
My current method of solving the same problem is to use the Unstated library, which as a convenient wrapper around the React Context API. "Unstated" also provides dependency injection allow the creating of discrete instances of a container; which is handy for code reuse and testing.
How to Wrap a React/Unstated-Context as a Service
The following skeleton API Service holds state properties such as loggedIn, as well as two service methods: login() and logout(). These props and methods are now available throughout the app with a single import in each file that needs the context.
For example:
Api.js
import React from "react";
// Import helpers from Unstated
import { Provider, Subscribe, Container } from "unstated";
// APIContainer holds shared/global state and methods
class APIContainer extends Container {
constructor() {
super();
// Shared props
this.state = {
loggedIn: false
};
}
// Shared login method
async login() {
console.log("Logging in");
this.setState({ loggedIn: true });
}
// Shared logout method
async logout() {
console.log("Logging out");
this.setState({ loggedIn: false });
}
}
// Instantiate the API Container
const instance = new APIContainer();
// Wrap the Provider
const ApiProvider = props => {
return <Provider inject={[instance]}>{props.children}</Provider>;
};
// Wrap the Subscriber
const ApiSubscribe = props => {
return <Subscribe to={[instance]}>{props.children}</Subscribe>;
};
// Export wrapped Provider and Subscriber
export default {
Provider: ApiProvider,
Subscribe: ApiSubscribe
}
App.js
Now the Api.js module can be used as global provide in App.js:
import React from "React";
import { render } from "react-dom";
import Routes from "./Routes";
import Api from "./Api";
class App extends React.Component {
render() {
return (
<div>
<Api.Provider>
<Routes />
</Api.Provider>
</div>
);
}
}
render(<App />, document.getElementById("root"));
Pages/Home.js:
Finally, Api.js can subscribe to the state of the API from deep within the React tree.
import React from "react";
import Api from "../Api";
const Home = () => {
return (
<Api.Subscribe>
{api => (
<div>
<h1>🏠 Home</h1>
<pre>
api.state.loggedIn = {api.state.loggedIn ? "👍 true" : "👎 false"}
</pre>
<button onClick={() => api.login()}>Login</button>
<button onClick={() => api.logout()}>Logout</button>
</div>
)}
</Api.Subscribe>
);
};
export default Home;
Try the CodeSandbox demo here: https://codesandbox.io/s/wqpr1o6w15
Hope that helps!
PS: Someone bash me on the head quick if I'm doing this the wrong way. I'd love to learn different/better approaches. - Thanks!

Resources