How can I pass setState function between contextProvider? - reactjs

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>
);
}

Related

Hiding Banner at a certain page

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.

props return undefined from NextJs

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;

Type '{ children: Element; }' has no properties in common with type 'IntrinsicAttributes' React -typescript Context

I'm currently coding a React -typescript App for practising FluentUI (a.k.a Fabric). Issue appears
with my App.tsx component.
import React, { useContext, useState } from 'react';
import logo from './logo.svg';
import './App.css';
import Search from './components/Search';
//import CategoriasProvider from './components/Context/CategoriasContext';
import Title from './components/Title';
import { ListGhostingExample } from '../src/components/DrinkList';
import { PrimaryButton } from 'office-ui-fabric-react';
import { CategoriasContext, ICategoriasContextInterface } from './components/Context/CategoriasContext';
import CategoriasProvider from './components/Context/CategoriasContext';
import axios from 'axios';
import './components/DrinkList.css'
import './components/Search.css'
interface IApp{
items:ICategoriasContextInterface[],
renderList:boolean
}
const App =()=> {
const contextValues=useContext(CategoriasContext);
return(
<CategoriasProvider>
<div className="App">
<div className="search">
<Search name={contextValues?.name} image={contextValues?.image} thumbnail={contextValues?.thumbnail} />
</div>
</div>
</CategoriasProvider>
);
}
export default App;
CategoriasProvider comes from a Context (CategoriasContext.tsx ). CategoriasProvider has the mentioned error Inside of CategoriasProvider there's a Search.tsx Component.Search's works as a "wrapper". Code is:
import React, { useEffect, useState } from 'react';
import { SearchBox,ISearchBoxStyles } from 'office-ui-fabric-react/lib/SearchBox';
import { PrimaryButton, IContextualMenuProps, Stack, IStackTokens, StackItem, initializeIcons } from 'office-ui-fabric-react';
import { ComboBox, DefaultPalette, Dropdown, DropdownMenuItemType, IComboBoxOption, IDropdownOption, IDropdownStyles, IStackItemStyles, SelectableOptionMenuItemType, Toggle } from '#fluentui/react';
import { getGlassesOriginal } from './Utils/Utils';
import axios from 'axios';
import '../Search.css';
import { CategoriasContext, ICategoriasContextInterface } from './Context/CategoriasContext';
initializeIcons();
const Search = (props:ICategoriasContextInterface) => {
//State
const [textContent, setTextContent] = useState("");
const [textBoxDisabled,disableTextBox]=useState(false);
const [comboBoxDisabled,disableComboBox]=useState(true);
const CategoriasContextInSearch=React.useContext(CategoriasContext);
const setTextContentInstate = (e: any) =>{
console.log("Contenido de e" + e.target.value);
setTextContent(e.target.value);
}
const showMessageInConsole = ():void => {
console.log(textContent);
setTextContent("");
}
// Example formatting
const stackTokens: IStackTokens = { childrenGap: 20 };
const searchBoxStyles: Partial<ISearchBoxStyles> = { root: { width: 200 } };
const dropdownStyles: Partial<IDropdownStyles> = {
dropdown: { width: 200 },
};
const options: IDropdownOption[] = [
{ key: 'glasses', text: 'Glasses', itemType: DropdownMenuItemType.Header },
];
function getGlasses () {
let outputArray:string[] = [];
console.log("getGlasses");
axios
.get("https://www.thecocktaildb.com/api/json/v1/1/list.php?g=list")
.then((response)=>{
let responseDataJson=response.data.drinks;
for (let element in responseDataJson) {
options.push({key:responseDataJson[element].strGlass,text:responseDataJson[element].strGlass});
}
}
)
return outputArray;
}
function selectSearch(){
if(textBoxDisabled){
disableTextBox(false);
disableComboBox(true);
} else {
disableTextBox(true);
disableComboBox(false);
};
}
useEffect(() => {
//TODO: No se debería llamar siempre a esta función. Solamente cuando se activa el sistmea de búsqueda (y además, cachearlo)
getGlasses()
});
return(
<div className="wrapper">
<div className="one"> <Toggle onClick={selectSearch}/></div>
<div className="two">
{
<SearchBox
name="searchBox"
className="searchBox"
styles={searchBoxStyles}
placeholder="Cheers!"
onChange={setTextContentInstate}
value={textContent}
disabled={textBoxDisabled}
/>
}
</div>
<div className="three">
<Dropdown
placeholder="Select a glass"
options={options}
styles={dropdownStyles}
disabled={comboBoxDisabled}
/>
</div>
<div className="four">
<div className="primaryButton">
<PrimaryButton text="Search" onClick={showMessageInConsole}/>
</div>
</div>
</div>
);
}
export default Search;
Hope you can help me!!! Thanks in advance!
The code which is causing the error in your title is in your comment. It's this line:
export const CategoriasProvider = () => {
You are defining CategoriasProvider as a component which takes no props. It can only accept IntrinsicAttributes which is basically just the key property.
But when you use CategoriasProvider in App you are calling it with JSX element children. You get an error because you have not said that the CategoriasProvider component can accept a children prop.
Any of the following types will solve your problem:
export const CategoriasProvider: React.FC = ({children}) => {
export const CategoriasProvider = ({children}: {children: React.ReactNode}) => {
export const CategoriasProvider = ({children}: React.PropsWithChildren<{}>) => {
Regardless, you'll want to pass the children down as children of the inner Provider component.
return (
<CategoriasContext.Provider value={hola}>
{children}
</CategoriasContext.Provider>
);
Your App component is not going to work as expected because the useContext hook which accesses the CategoriasContext is located outside of the CategoriasProvider. It will just get the default value for the context -- not the value from the provider.
You need to rearrange your components such that the hook call occurs in a component that is rendered inside of the CategoriasProvider.
Try this:
const Search = () => {
const contextValues = useContext(CategoriasContext);
return (
<div className="search">
<Search
name={contextValues?.name}
image={contextValues?.image}
thumbnail={contextValues?.thumbnail}
/>
</div>
);
};
const App = () => {
return (
<CategoriasProvider>
<div className="App">
<Search />
</div>
</CategoriasProvider>
);
};
export default App;

How to Access React Context Inside a WebView in React Native?

I would like to be able to access React Context inside my WebView but the following error occurs:
TypeError: undefined is not an object (evaluating 'Context._context')
Here is my code:
// App.js
import React, { createContext } from 'react'
import { renderToString } from 'react-dom/server'
import { WebView } from 'react-native-webview'
export const AppContext = createContext({
value: '',
setValue: () => { }
})
export default function App() {
const [value, setValue] = useState('Hello there')
return (
<AppContext.Provider value={{ value, setValue }}>
<WebView source={{ html: renderToString(<WebViewComponent />) }} />
</AppContext.Provider>
)
}
// WebViewComponent.js
export default function WebViewComponent() {
const { value, setValue } = useContext(AppContext)
return (
<div>
{value}
</div>
)
}
So a simple solution to the problem is to just pass anything as the props to renderToString(<WebViewComponent />) instead of using context.
So in my example it will be like this:
// App.js
import React from 'react'
import { renderToString } from 'react-dom/server'
import { WebView } from 'react-native-webview'
export default function App() {
const [value, setValue] = useState('Hello there')
return (
<WebView
source={{
html: renderToString(<WebViewComponent value={value} setValue={setValue} />)
}}
/>
)
}

Why component with Context Provider doesn't re-render

I was looking for the answer why react component with Context Provider doesn't re-render but i couldn't find answer proper for me to understand why.
Moreover i want to mention Im using GatsbyJS.
Here's App.context.js:
const defaultValue = {
menu: false,
handleMenu: () => { },
}
const AppContext = createContext(defaultValue);
export default AppContext;
export { defaultValue };
Next, down below there's Provider element App.provider.js:
import AppContext, { defaultValue } from './App.context';
class AppProvider extends Component {
constructor(props) {
super(props);
this.state = defaultValue
}
handleMenu = () => {
if (this.state.menu) {
this.setState({
menu: false
})
} else {
this.setState({
menu: true
})
}
}
render() {
return (
<AppContext.Provider value={{
menu: this.state.menu,
handleMenu: this.handleMenu,
}}>
{this.props.children}
</AppContext.Provider>
);
}
}
export default AppProvider;
Then, I'm using this provider at the beginning of elements tree:
//Components
import Header from '../components/header';
import Footer from '../components/footer';
import MainWrap from '../components/mainWrap';
//Context
import AppProvider from '../context/App.provider';
const Layout = ({ children }) => {
return (
<AppProvider>
<MainWrap>
<Header />
{children}
<Footer />
</MainWrap>
</AppProvider>
);
}
export default Layout;
Here's MainWrap component:
//Styles
import wrapStyles from '../styles/wrapper.module.scss';
//Context
import AppContext from '../context/App.context';
const MainWrap = ({children}) => {
const {menu} = useContext(AppContext);
return (
<div className={menu?wrapStyles.wrap:wrapStyles.wrapActive}>{children}</div>
);
}
export default MainWrap;
When context value change, child components like MainPage re-render properly, but why component with Provider does not, so i can't instead of using next wrap component (MainPage) just put a div in component with Provider:
//Components
import Header from '../components/header';
import Footer from '../components/footer';
//Styles
import wrapStyles from '../styles/wrapper.module.scss';
//Context
import AppProvider from '../context/App.provider';
import AppContext from '../context/App.context';
const Layout = ({ children }) => {
const {menu} = useContext(AppContext);
return (
<AppProvider>
<div className={menu?wrapStyles.wrap:wrapStyles.wrapActive}>
<Header />
{children}
<Footer />
</div>
</AppProvider>
);
}
export default Layout;
I hope it will be understandable.

Resources