Passing location and pageContext from page to child components in Gatsby - reactjs

My page layout for a Gatsby site looks like this.
const Container = ({location, children, pageContext}) => {
return (
<>
<Header location={location} />
<Breadcrumbs pageContext={pageContext} />
{children}
<Footer />
</>
)
}
I need to pass location and pageContext from the page to the child components. I have tried to add location and pageContext to the DataProvider like this:
export const DataContext = React.createContext();
const DataProvider = ({ children, location, pageContext }) => {
return (
<DataContext.Provider value={{
location,
pageContext
}}>
{children}
</DataContext.Provider>
)
};
export default DataContext
export { DataProvider }
Then I use DataProvider in gatsby-ssr.js and gatsby-browser.js like this:
export const wrapRootElement = ({ element }) => (
<ThemeProvider theme={theme}>
<DataProvider>
{element}
</DataProvider>
</ThemeProvider>
);
In the child component:
const HeaderLinks = () => {
return (
<DataContext.Consumer>
{
context => (
<Menu
theme="light"
mode="horizontal"
selectedKeys={[context.location.pathname]}
>
<Menu.Item key={key}>
<Link to={url}>{name}</Link>
</Menu.Item>
</Menu>
)
}
</DataContext.Consumer>
)
}
But it doesn't seem to work, as it is not getting updated when I move to another page. (I also have wrapPageElement with Container, may be that's reasons.)
How can I pass location and pageContext to the child components? Is it better to use React Context or simply pass them as props? If I should use React Context, how can I correct my code to make it work?

Instead of using wrapRootElement to use ContexProvider you can make use of wrapPageElement where you can get the page props and pass them on to the DataProvider. This will make sure that pageContext and location change on each page
export const wrapRootElement = ({ element }) => (
<ThemeProvider theme={theme}>
{element}
</ThemeProvider>
);
export const wrapPageElement = ({ element, props }) => (
<DataProvider value={props}>
{element}
</DataProvider>
);
export const DataContext = React.createContext();
const DataProvider = ({ children, value }) => {
const {location, pageContext} = value;
return (
<DataContext.Provider value={{
location,
pageContext
}}>
{children}
</DataContext.Provider>
)
};
export default DataContext
export { DataProvider }

I ended up using useLocation from #reach/router to return location in child components. And I simply pass pageContext as a prop to <Breadcrumbs />, as it is used only once and is not passed down to any child components.

Related

How to update context state variable rendering across screens in React native?

new to React Native.
I'm using Context hook state to use an array in two tab screens of an application.
One of the screens displays the context array in the form of a FlatList, the other one inserts items into the array. What would be the proper way to rerender when the array changes?
Here's the code:
APP.JS
const App = () => {
const Tab = createBottomTabNavigator();
return(
<NavigationContainer>
<Tab.Screen name="HomeScreen" component={Home} />
<Tab.Screen name="DiscoverScreen" component={Discover} />
</NavigationContainer>
);
}
export default function AppWrapper() {
return (
<AuthProvider>
<App />
</AuthProvider>
);
}
AUTHCONTEXT.JS
const AuthContext = createContext()
const AuthProvider = ({ children }) => {
const [array, setArray] = useState([]);
return (
<AuthContext.Provider value={{ array, setArray }}>
{children}
</AuthContext.Provider>
)
}
export {AuthContext, AuthProvider}
HOME.JS
export default Home = () => {
const {array} = useContext(AuthContext);
return(
<View>
<FlatList
data={array}
renderItem={(item)=>{
return(
<Text>{item}</Text>
)}}
/>
</View>
)
}
DISCOVER.JS
export default Discover = () => {
const {setArray} = useContext(AuthContext);
setArray((currentArray)=>[...currentArray,'test']);
return(
<View></View>
);
}
You shouldn't invoke setArray unconditionally in Discover function component. If you need to add value to the array when component appears, use useEffect hook (and don't forget to setup second argument, probably it will be empty array, otherwise you get into infinite loop and component crash with an error)
export default Discover = () => {
const {setArray} = useContext(AuthContext);
useEffect(() => {
setArray((currentArray)=>[...currentArray,'test']);
}, [])
return(
<View></View>
);
}
And you don't need to worry about rerender the component, React handle this for you. Once setArray invokes it automatically rerender all component that use AuthContext and their children.

React pass multiple properties and children

I'm new to React, typescript and nextjs and I'm trying to pass multiple properties to a component. But, I seem to override them.
For example I have this in my _app.tsx
function App({ Component, myProps }: MyAppProps): JSX.Element {
const { ...props } = myProps;
return (
<MyStateProvider>
<Component {...props} />{' '}
</MyStateProvider>
);
}
In MyStateProvider, I have this in my-state-context.tsx
export const MyStateProvider: React.FC<any> = ( {children}) => {
const contextValue = doSomething();
return (
<MyStateContext.Provider value={contextValue}>
{children}
</MyStateContext.Provider>
);
};
However, I wish to something like this in my _app.tsx
function App({ Component, myProps }: MyAppProps): JSX.Element {
const { ...props } = myProps;
return (
<MyStateProvider {...props}>
<Component {...props} />{' '}
</MyStateProvider>
);
}
And this MyStateProvider in my-state-context.tsx
export const MyStateProvider: React.FC<any> = ( props, {children }) => {
const contextValue = doSomething(props); // <-- trying to accomplish passing props
return (
<MyStateContext.Provider value={contextValue}>
{children}
</MyStateContext.Provider>
);
};
What is the correct way to go about this?
You can do the following:
function App({ Component, ...myProps }: MyAppProps): JSX.Element {
return (
<MyStateProvider {...myProps}>
<Component {...myProps} />
</MyStateProvider>
);
}
and in your provider, supposing that you are passing the component like this <MyStateProvider Children={SomeComponent}/>
// note that first letter of Children should be uppercase to behave as a component
export const MyStateProvider: React.FC<any> = ({Children, ...props}) => {
const contextValue = doSomething({...props});
return (
<MyStateContext.Provider value={contextValue}>
<Children />
</MyStateContext.Provider>
);
};
See the example

How to define more layouts for subdirectories in NextJS?

I have the main layout that wraps whole app:
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<Layout>
<Component {...pageProps} />
</Layout>
</>
);
}
I want to have the main layout and another layout to wrap, for example localhost:3000/subdirectory/... subdirectory.
You can do it simply like below:
First, remove the layout from your _app.js
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
</>
);
}
Second, create you first layout component:
const MainLayout = ({ children }) => {
return (
<>
<main>{children}</main>
</>
);
};
export default MainLayout;
Then Second layout:
const Dashboard = ({ children }) => {
return (
<>
<main>{children}</main>
</>
);
};
export default Dashboard ;
Then you can import your layout and use it like in your component:
import Dashboard from './Dashboard ';
export default function Card(){
return(
<Dashboard><div>your code</div><Dashboard>
)
}
And:
import MainLayoutfrom './MainLayout';
export default function Card(){
return(
<MainLayout><div>your code</div><MainLayout>
)
}

How to convert HOC to react custom hook

I have below code snippets, just wondering apart from passing a component to withGroupInput, do we have another way to re-use this GroupedInputWithLabel with different components? Thanks
export const GroupedInputWithLabel = (props) => {
const { required, children, fieldName } = props;
const inputComponent = (
<>
<ControlLabel htmlFor={fieldName} required={required} />
{children}
</>
);
return <GroupedInput {...props}>{inputComponent}</GroupedInput>;
};
export const withGroupInput = (props, Component) => (
<GroupedInputWithLabel {...props}>
<Component {...props} />
</GroupedInputWithLabel>
);

Cannot get redux state inside the provider file

I'm trying to get the redux state value in the same file as where I use the provider.
For some reason it seems it cannot find the value.
const MyApp = ({ Component, pageProps }: AppProps)=> {
const isDark = useSelector<ThemeState, ThemeState["isDark"]>(state => state.isDark)
const dispatch = useDispatch()
return (
<>
<Provider store={ThemeStore}>
<div className={isDark ? 'dark' : 'white'}>
<Player />
<Component {...pageProps} />
</div>
</Provider>
</>
)
}
export default MyApp
This gives an error:
Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider>
When using the same useSelector and dispatch inside my nav component, it does work.
Any idea how I could make it work inside the _app.js file?
You can not use state in the provider like that, you need to go at least one layer deeper or just use what you're passing to the state directly not from calling to useSelector, try this:
function Child({ children }) {
const isDark = useSelector<ThemeState, ThemeState["isDark"]>(state => state.isDark)
return <div className={isDark ? "dark" : "white"}>{children}</div>;
}
const MyApp = ({ Component, pageProps }: AppProps) => {
return (
<Provider store={ThemeStore}>
<Child>
<Player />
<Component {...pageProps} />
</Child>
</Provider>
);
};
export default MyApp;

Resources