pass data to a Layout component gatsbyJS - reactjs

I want to pass data to layout Component, the data is from an API, cockpitCMS to be exact and the data is slug to be exact too.
io have tried this
import { useStaticQuery, graphql } from "gatsby"
export const slugs = () => {
const { data } = useStaticQuery(
graphql`
query slug{
allCockpitPost {
edges {
node {
title {
slug
}
}
}
}
}
`
)
return data.allCockpitPost.edges.node.title
}
but, I get this instead... React Hook "useStaticQuery" is called in function "slugs" which is neither a React function component or a custom React Hook function
maybe because we can't use usestaticQuery twice, and it is already been used in the SEO component.

Your problem here is the way you use hook. Basically you have some ways to use useStaticQuery or any hook in a function:
That function NEED to return a React Component.
Other wise, that should be a HOC.
Example code for first way:
import AnyComponent from '../some-where';
const MockComponent = (props) => {
const { mockData } = useStaticQuery(anyQuery);
return (
<AnyComponent mockData={mockData}>
)
}
export default MockComponent
Example code for second way:
const withMockHOC = (AnyOtherComponent) => (props) => {
const { mockData } = useStaticQuery(anyQuery);
return <AnyOtherComponent {...props} mockData={mockData} />;
};
Hope this help.

Related

mapStateToProps react router dom v6 useParams()

BlogDetailsPage.js
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
const BlogDetailsPage = (props) => {
const { id } = useParams();
return <div>Blog Details: {}</div>;
};
const mapStateToProps = (state, props) => {
const { id } = useParams();
return {
blog: state.blogs.find((blog) => {
return blog.id === id;
}),
};
};
export default connect(mapStateToProps)(BlogDetailsPage);
How to use mapStateToProps in "useParams()" react-router-dom ?
and whatever links that navigate to /slug path are ended up in BlogDetailsPage.js, Since BlogDetailsPage.js is being nested nowhere else so i couldn't get specific props pass down but route params. From my perspective this is completely wrong but i couldn't figure out a better way to do it.
Compiled with problems:X
ERROR
src\components\BlogDetailsPage.js
Line 11:18: React Hook "useParams" is called in function "mapStateToProps" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use" react-hooks/rules-of-hooks
Search for the keywords to learn more about each error.```
Issue
React hooks can only be called from React function components or custom React hooks. Here it is being called in a regular Javascript function that is neither a React component or custom hook.
Solutions
Preferred
The preferred method would be to use the React hooks directly in the component. Instead of using the connect Higher Order Component use the useSelector hook to select/access the state.blogs array.
Example:
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
const BlogDetailsPage = () => {
const { id } = useParams();
const blog = useSelector(state => state.blogs.find(
blog => String(blog.id) === id
));
return <div>Blog Details: {}</div>;
};
export default BlogDetailsPage;
Alternative/Legacy
If you have the need to access path params in any mapStateToProps function, if you are using a lot of oder code for example, then you'll need to create another HOC to access the path params and have them injected as props so they are available in the mapStateToProps function.
Example:
import { useParams, /* other hooks */ } from "react-router-dom";
const withRouter = Component => props => {
const params = useParams();
// other hooks, useLocation, useNavigate, etc..
return <Component {...props} {...{ params, /* other injected props */ }} />;
};
export default withRouter;
...
import { compose } from 'redux';
import { connect } from 'react-redux';
import withRouter from '../path/to/withRouter';
const BlogDetailsPage = ({ blog }) => {
return <div>Blog Details: {}</div>;
};
const mapStateToProps = (state, { params }) => {
const { id } = params || {};
return {
blog: state.blogs.find((blog) => {
return String(blog.id) === id;
}),
};
};
export default compose(
withRouter, // <-- injects a params prop
connect(mapStateToProps) // <-- props.params accessible
)(BlogDetailsPage);
I think, react hook functions are allowed to use inside of react component.
Outside of react components, it's not allowed to use react api hook functions.
Thanks, I'd liked to help you my answer.

can i memoize custom hook which is graphql query in react?

// useMe.tsx //
import { gql, useQuery } from "#apollo/client";
import { meQuery } from "../__generated__/meQuery";
export const ME_QUERY = gql`
query meQuery {
me {
id
email
my_workspaces {
title
}
}
}
`;
export const useMe = () => {
return useQuery<meQuery>(ME_QUERY, {
fetchPolicy: "cache-first"
});
};
This is my custom hook. I am trying to get user info with graphql query and made it to custom hook.
But there was some problem, whenever the high-level components state changed,
the low-level component which calling custom-hook(useMe) always rerendered
so i wanna memoize custom hook with useCallback or useMemo but i can't find right answer
please help me, Thank you
Just wrap the value with useCallback is fine
const useMeQuery = () => {
return useQuery<meQuery>(ME_QUERY, {
fetchPolicy: "cache-first"
});
export const useMe = useCallbak(useMeQuery)
}

Transform Function Component to a Class Component

I am still learning React and I am wondering if it is possible to make the function component below into a class component.
Component:
import React, { useState } from "react"
import { useStaticQuery, graphql } from "gatsby"
import Navbar from "./navbar"
const Layout = ({ location, title, children }) => {
const rootPath = `${__PATH_PREFIX__}/`
const [classNames, setClassNames] = useState('')
const updateClasses = (classNames) => {
setClassNames(classNames)
}
const data = useStaticQuery(graphql`
{
site {
siteMetadata {
menuLinks {
link
name
}
}
}
}
`)
return (
<div>
<Navbar pages={ data.site.siteMetadata.menuLinks } updateClassNames={updateClasses} />
<main className={classNames}>{children}</main>
</div>
)
}
export default Layout
My biggest issue is with the parameters that are passed to the function location, title, children. What will happen with them. I am not using them at the moment, but will need them later.
Class or function component is not much different except using hooks.
With your current function component, just use the location, title, etc props like other variables in a normal function.
Why do you need to convert into class component?
You don't need a class component to lift up a class, value, or any data in a child component, it's the same behavior rather than class-based component or functional component. You just need to pass via props a function that it will be triggered in a child component to lift up some data again to the parent.
In your parent component, you need to set the function. Without knowing its structure, it would look like:
someFunction= value => {
console.log('I have the value: ', value)
}
return <Layout someFunction={someFunction}
Disclaimer: you may need to adapt the code to your component. The idea is to set a function and pass it via props in the return.
Then, in your <Layout> component, you can destructure the function as you do with location, title and children, and trigger when you need it:
import React, { useState } from "react"
import { useStaticQuery, graphql } from "gatsby"
import Navbar from "./navbar"
const Layout = ({ location, title, children, someFunction }) => {
const rootPath = `${__PATH_PREFIX__}/`
const [classNames, setClassNames] = useState('')
const updateClasses = (classNames) => {
setClassNames(classNames)
}
const handleClick=()=>{
someFunction('hello')
}
const data = useStaticQuery(graphql`
{
site {
siteMetadata {
menuLinks {
link
name
}
}
}
}
`)
return (
<div>
<Navbar pages={ data.site.siteMetadata.menuLinks } updateClassNames={updateClasses} onClick={()=>handleClick} />
<main className={classNames}>{children}</main>
</div>
)
}
export default Layout
In this dummy example, you will be passing 'hello' to the parent component when the <Navbar> is clicked, of course, you can pass any desired value or use a useEffect hook or whatever you need. This is the way to pass data from child to parent component.

Define a functional component inside storybook preview

I have a custom modal component as functional component and in typescript. This modal component exposes api's through context providers and to access them, I'm using useContext hook.
const { openModal, closeModal } = useContext(ModalContext);
Example code on how I use this api's:
const TestComponent = () => {
const { openModal, closeModal } = useContext(ModalContext);
const modalProps = {}; //define some props
const open = () => {
openModal({...modalProps});
}
return (
<div>
<Button onClick={open}>Open Modal</Button>
</div>
)
}
And I wrap the component inside my ModalManager
<ModalManager>
<TestComponent />
</ModalManager>
This example works absolutely fine in my Modal.stories.tsx
Problem:
But this doesn't work inside my Modal.mdx. It says I cannot access react hooks outside functional component. So, I need to define a TestComponent like component to access my modal api's from context. How to define it and where to define it so that below code for preview works?
import {
Props, Preview, Meta
} from '#storybook/addon-docs/blocks';
<Meta title='Modal' />
<Preview
isExpanded
mdxSource={`
/* source of the component like in stories.tsx */
`}
>
<ModalManager><TestComponent /></ModalManager>
</Preview>
I'm not sure if this is a hack or the only way. I created the TestComponent in different tsx file and then imported it in mdx. It worked.
You may have a utility HOC to render it inside a MDX file as below
HOCComp.tsx in some Utils folder
import React, { FunctionComponent, PropsWithChildren } from 'react';
export interface HOCCompProps {
render(): React.ReactElement;
}
const HOCComp: FunctionComponent<HOCCompProps> = (props: PropsWithChildren<HOCCompProps>) => {
const { render } = props;
return render();
};
export default HOCComp;
Inside MDX File
import HOCComp from './HOC';
<HOCComp render={()=> {
function HOCImpl(){
const [count,setCount] = React.useState(180);
React.useEffect(() => {
const intId = setInterval(() => {
const newCount = count+1;
setCount(newCount);
},1000)
return () => {
clearInterval(intId);
}
})
return <Text>{count}</Text>
}
return <HOCImpl />
}}
/>

Trying to use a function that is in my context provider file

I am using Context-Api and am trying to use a function provided from my file in a lifecycle method. the function isnt wrapped in a consumer of course so i looked at the documentation and set value to context. this still isnt working.Everyting is working in my return of my class component but component did mount does not work.
import { ProductConsumer } from '../context';
export default class Details1 extends Component
componentDidMount() {
let value = this.context;
let id = this.props.match.params.id;
value.handleDetail(id);
}
render() {
{value => {
const {
id,...} = value.detailProduct;
return (
<ProductConsumer>
{value => {
My Component
</ProductConsumer>
export const Details = () => (
<Product.Consumer>
{context =>
<Details1 context={context}/>
}
</Product.Consumer>
)
You can either wrap the component with the consumer, passing it the function as a prop, or (better - ) convert your component to a functional component, using the useContext hook to get the values from your context.
import React, { useContext } from "react";
import someContext from "./context-path";
const MyComponent = () => {
const { myFunction } = useContext(someContext);
...
};

Resources