I am try to use apollo-client with nextjs. Here I want to fetch data in getServerSideProps. Suppose I have 2 components and one page-
section.tsx this is component-1
const Section = () => {
return (
<div>
Section
</div>
);
};
export default Section;
mobile.tsx this is component 2
const Mobile = () => {
return (
<div>
Mobile
</div>
);
};
export default Mobile;
Now I call this two component into home page.
index.tsx-
const Home: NextPage = () => {
return (
<Container disableGutters maxWidth="xxl">
<Section />
<Mobile />
</Container>
);
};
export default Home;
export const getServerSideProps: GetServerSideProps = async () => {
const { data } = await client.query({ query: GET_USER_LIST })
return { props: {} }
}
Here you can see that in getServerSideProps I already fetch my data.
My question is How can I directly access this data form Section component and Mobile component without passing props. I don't want to pass props, because if my component tree will be more longer, then it will be difficult to manage props.
From appollo docs, I alreay know that apollo client do the same with redux state manager. So please tell me how can I access this data from any component that already fetched in getServerSideProps. Is it possible?. If not then how can what is the solutions.
How about using context api if you want to avoid prop drilling? By putting data into context, you can access it from any child component. Get the data from the SSR and put it into the context.
Below is the example
import React, {createContext, useContext} from "react";
export default function Home({data}) {
return <DataContext.Provider value={{data}}>
<div>
<Section/>
<Mobile/>
</div>
</DataContext.Provider>
}
export async function getServerSideProps() {
const data = 'hello world' //Get from api
return {
props: {data},
}
}
function Section() {
return <div>
Section
</div>
}
function Mobile() {
const context = useContext(DataContext);
return <div>
Mobile {context.data}
</div>
}
const DataContext = createContext({});
Now, as long as your tree structure grows within the DataContext provider, each child node will have access to data in the context.
Hope this helps.
Related
I have the following where I am able to print the data coming from getServerSideProps.
But when I pass it on to the component section, the prop value posts is undefined. Why?
The following is under /pages and is inside the base index.tsx file.
import type { NextPage } from 'next'
import Post from '../components/Post'
const Home: NextPage = ({ posts }) => {
// This prints undefined. This is the issue.
console.log(posts)
return (
<div>
<Post posts={posts}/>
</div>
)
}
export default Home
export const getServerSideProps = async () => {
const response = await fetch('http://localhost:8080/posts/all')
const data = await response.json()
// this prints correctly
console.log(data)
return {
props: {
posts: data
}
}
};
Updates to show _app.tsx file
import Home from '../pages';
const TestApp = () => {
return <>
<Home />
</>
}
export default TestApp
Remove the custom _app.tsx file or update it to accept a Component prop and render it as shown in the Nextjs documentation for a Custom App.
The problem is this custom App is always rendering the Home component (with no props) so it's printing undefined. Then, try navigating to / on localhost. It should render the index.tsx file and properly invoke getServerSideProps and pass the result to the component as you expect.
The version of react-router-dom is v6 and I'm having trouble with passing values to another component using Navigate.
I want to pass selected rows to another page called Report. But, I'm not sure I'm using the right syntax for navigate method and I don't know how to get that state in the Report component.
Material-ui Table: I'm trying to use redirectToReport(rowData) in onClick parameter.
function TableRows(props){
return (
<MaterialTable
title="Leads"
columns={[
...
]}
data = {props.leads}
options={{
selection: true,
filtering: true,
sorting: true
}}
actions = {[{
position: "toolbarOnSelect",
tooltip: 'Generate a report based on selected leads.',
icon: 'addchart',
onClick: (event, rowData) => {
console.log("Row Data: " , rowData)
props.redirect(rowData)
}
}]}
/>
)}
LeadTable component
export default function LeadTable(props) {
let navigate = useNavigate();
const [leads, setLeads] = useState([]);
const [loading, setLoading] = useState(true);
async function fetchUrl(url) {
const response = await fetch(url);
const json = await response.json();
setLeads(json[0]);
setLoading(false);
}
useEffect(() => {
fetchUrl("http://localhost:5000/api/leads");
}, []);
function redirectToReport(rowData) {
navigate('/app/report', { state: rowData }); // ??? I'm not sure if this is the right way
}
return(
<div>
<TableRows leads={leads} redirect={redirectToReport}></TableRows>
</div>
)}
Report component
export default function ReportPage(state) {
return (
<div>
{ console.log(state) // This doesn't show anything. How to use the state that were passed from Table component here?}
<div className = "Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);}
version 6 react-router-dom
I know the question got answered but I feel this might be helpful example for those who want to use functional components and they are in search of passing data between components using react-router-dom v6.
Let's suppose we have two functional components, first component A, second component B. The component A wants to share data to component B.
usage of hooks: (useLocation,useNavigate)
import {Link, useNavigate} from 'react-router-dom';
function ComponentA(props) {
const navigate = useNavigate();
const toComponentB=()=>{
navigate('/componentB',{state:{id:1,name:'sabaoon'}});
}
return (
<>
<div> <a onClick={()=>{toComponentB()}}>Component B<a/></div>
</>
);
}
export default ComponentA;
Now we will get the data in Component B.
import {useLocation} from 'react-router-dom';
function ComponentB() {
const location = useLocation();
return (
<>
<div>{location.state.name}</div>
</>
)
}
export default ComponentB;
Note: you can use HOC if you are using class components as hooks won't work in class components.
Your navigate('/app/report', { state: rowData }); looks correct to me.
react-router-v6
If you need state, use navigate('success', { state }).
navigate
interface NavigateFunction {
(
to: To,
options?: { replace?: boolean; state?: any }
): void;
(delta: number): void;
}
Your ReportPage needs to be rendered under the same Router that the component doing the push is under.
Route props are no longer passed to rendered components, as they are now passed as JSX literals. To access route state it must be done so via the useLocation hook.
function ReportPage(props) {
const { state } = useLocation();
console.log(state);
return (
<div>
<div className="Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);
}
If the component isn't able to use React hooks then you still access the route state via a custom withRouter Higher Order Component. Here's an example simple withRouter HOC to pass the location as a prop.
import { useLocation, /* other hooks */ } from 'react-router-dom';
const withRouter = WrappedComponent => props => {
const location = useLocation();
// other hooks
return (
<WrappedComponent
{...props}
{...{ location, /* other hooks */ }}
/>
);
};
Then access via props as was done in pre-RRDv6.
class ReportPage extends Component {
...
render() {
console.log(this.props.location.state);
return (
<div>
<div className="Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);
}
}
2 things (just a suggestion):
Rather than a ternary use &&
{location && <div>{location.state.name}</div>}
Why are you checking location and rendering location.state.name? I would use the check on the data you are fetching or make sure the data returns null or your value.
On Sabaoon Bedar's Answer, you can check if there is any data or not before showing it :
Instead of this <div>{location.state.name}</div>
Do this { location != null ? <div>{location.state.name}</div> : ""}
if you want to send data with usenavigate in functional component you can use like that
navigate(`/take-quiz/${id}`, { state: { quiz } });
and you can get it with uselocation hook like this
const location = useLocation();
location.state.quiz there is your data
But you cannot get this data in props it;s tricky part ;)!!
on SABAOON BEDAR answer,
from component A: navigate('/', {state:"whatever"}
in component B: console.log(location.state) //output = whatever
I was wondering how to refetch a query from another sibling component with react-query.
Lets say I have Component A
import {useQuery} from "react-query";
function ComponentA(){
const getData = async () => data //returns api data
const {data, refetch} = useQuery(["queryA"], async () => await getData())
return(
<div>
{data}
</div>
)}
And in Component B
import {useQuery, QueryClient} from "react-query";
function ComponentB(){
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
},
},
})
const refreshQueryFromComponentA = async () => {
await queryClient.refetchQueries(["queryA"], { active: true })
}
return(
<div>
<button onClick={refreshQueryFromComponentA}/>
</div>
)}
And in Page.js
import ComponentA from "./componentA";
import ComponentB from "./componentB";
function Page(){
return(
<div>
<ComponentA/>
<ComponentB/>
</div>
)}
When I call the refreshQueryFromComponentA function in ComponentB I do not see the query refresh in ComponentA or a new call to the backend in the network tab. I also use the correct query-key but I am only able to refetch the query in ComponentA with the refetch() function which comes from the useQuery function.
I think it's possible what I'm trying to do, since react-query uses context and should be available throughout the whole application. But maybe I'm using the wrong method or misinterpreted it.
Thanks in advance everyone!
There needs to be one QueryClient at the top of your app. The QueryClient holds the queryCache, which stores your data. If you create a new one in componentB, it won’t share anything with componentA. Also, make sure to add it to the QueryClientProvider and retrieve it via useQueryClient(). The client also needs to be stable, so don’t create a new one each render. This is from the first page of the docs:
import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
2
3 const queryClient = new QueryClient()
4
5 export default function App() {
6 return (
7 <QueryClientProvider client={queryClient}>
8 <Example />
9 </QueryClientProvider>
10 )
11 }
In my application on Next.Js i use redux and redux saga. I want to use ssr making http requests:
export const getStaticProps = wrapper.getStaticProps(async ({ store }) => {
store.dispatch(getDataRequest());
store.dispatch(END);
await store.sagaTask.toPromise();
});
In the same time i want to get data of the above result:
const selector = useSelector((s) => s);
console.log(selector);
The issue is that, when i run the application i get an error:
Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider>
I used the provider but the data doesn't appear. Question: How to solve the issue in my application?
demo: https://codesandbox.io/s/awesome-butterfly-f7vgd?file=/pages/store/saga.js
this is your _app component:
function App({ Component, pageProps }) {
return (
<div>
<Provider store={makeStore}>
<Component {...pageProps} />
</Provider>
</div>
);
}
you dont need to wrap it with Provider. this is the only thing you need to do in _app.
export default wrapper.withRedux(App)
this is getStatisProps in your pages/index.js
export const getStaticProps = wrapper.getStaticProps(async ({ store }) => {
store.dispatch(getDataRequest());
store.dispatch(END);
await store.sagaTask.toPromise();
});
see store is already passed here. You will be accesing state via store.getState(). this is how it should be
export const getStaticProps = wrapper.getStaticProps(async ({ store }) => {
store.dispatch(getDataRequest());
store.dispatch(END);
await store.sagaTask.toPromise();
const state = store.getState();
return {props:{data:state}} // since you have only one reducer
});
now data will be passed as a prop to your component. if you console.log(props.data) inside the component, you should be seeing your dataReducer
Gatsby noob here so please bear with me. I have a component that accepts props from the index.js where it is supposed to receive data from an array of objects but will always receive the error TypeError: Cannot read property 'map' of undefined where it's referring to the Hero.js component index.js is calling for.
My assumption is that the data being queried in index.js is either not specific enough or that it is rendering the component before data is received. Here is the index.js file:
import { graphql } from 'gatsby';
import { Layout, SEO, Hero } from 'components';
const IndexPage = ({ data }) => {
const dataFetch = data.contentfulTemplateIndex.heroes;
let tester = () => {
for (let count = 0; count < dataFetch.length; count++) {
return <Hero {...props} />;
}
};
console.log(dataFetch);
let props = {
impactText: dataFetch.impactText,
labels: dataFetch.labels,
primaryLabel: dataFetch.primaryLabel,
location: dataFetch.location
// supportingText: dataFetch.supportingText.json
};
return (
<Layout>
{dataFetch && tester()}
</Layout>
);
};
export const query = graphql`
query {
contentfulTemplateIndex {
heroes {
image {
fluid {
src
}
}
impactText
labels
location
primaryLabel
supportingText {
json
}
}
}
}
`;
export default IndexPage;
Here is the Hero.js component which index.js is calling:
import { Link } from 'gatsby';
import { documentToReactComponents } from '#contentful/rich-text-react-renderer';
import cx from 'classnames';
import styles from './hero.module.scss';
const Hero = (props) => {
return (
<div>
<ul>
<Link className={styles.pills}>{props.primaryLabel}</Link>
{props.labels.map((label) => {
return <Link className={styles.pills}>{label}</Link>;
})}
</ul>
<div className={styles.grid}>
<h1>{props.impactText}</h1>
</div>
</div>
);
};
export default Hero;
It's impossible for an outsider to debug your code without a minimum reproducable example.
The best way to debug GraphQL is to use the GraphiQL interface of your browser.
Run gatsby develop. If it fails because of the TypeError remove the lines of code that cause the type error (but not the code of your GraphQL query!). You need to get your development server runnning.
Open your browser, use the URL: http://localhost:8000/___graphql
Copy your graphQL query from your code and paste it into the GraphiQL query window.
Can you access your data there? If not you made a mistake writing your query or the data is not where it's supposed to be.
This way you can make sure the data exists.
It also helps to console.log(props) so you can examine the data object:
const Hero = (props) => {
console.log(props);
return (