How and where do I use the data from an Apollo Client? - reactjs

How and where do I have to put that query so that I can map through it and display the categories in a header. I'm a noob and all the apollo documentation is made with hooks and functional components, but I have to do this assignment with class based components and I just can't figure it out.
index.js:
const client = new ApolloClient({
uri: "http://localhost:4000/graphql",
cache: new InMemoryCache(),
});
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById("root")
);
App.js:
class App extends Component {
render() {
return (
<>
<Header />
</>
);
}
}
export default App;
Header.js:
class Header extends Component {
render() {
return (
<header>
<ul className="nav-list">
//Display categories here with map in a <li className="nav-item">
</ul>
</header>
)
}
}
query I need for header elements :
const QUERY = gql`
query getCategories {
categories {
name
}
}
`;

Hooks don't work with class components - but you can wrap your class components and pass the hook result as props to the class component:
import React from 'react';
import { useScreenWidth } from '../hooks/useScreenWidth';
export const withQueryHOC = (Component, query) => {
return (props) => {
const { loading, error, data } = useQuery(query);
return <Component loading={loading} error={error} data={data} {...props} />;
};
};
This is called a Higher-Order Component
Where Component is the class component you want to wrap. This way you can use class components but still have access to the hooks.
You should export you component like so:
export default withQueryHOC(YourComponentHere);
In your component you should access loading, error and data through this.props

Related

Catch Data from URL params in react class Compoent

First of all I like to convey thanks all the wise programmer. After updating react react-router-dom i am facing this problem. Here i want to mention one thing that, i am a "class component" lover.
However, This is my base component in react.
import React, { Fragment, Component } from 'react'
import axios from 'axios'
import { Col , Row} from 'react-bootstrap'
import { Link } from 'react-router-dom'
export default class Blog extends Component {
constructor(props) {
super(props)
this.state = {
data:[]
}
}
componentDidMount()
{
axios.get("https://jsonplaceholder.typicode.com/posts")
.then((response)=>{
if(response.status===200)
{
this.setState({
data:response.data
})
}
})
.catch((error)=>{})
}
render() {
const allData = this.state.data;
const blogFull = allData.map((val)=>{
var title = val.title;
var body = val.body;
var id = val.id;
return(
<Col key={id} lg={4}>
<Link to={"/post/"+id}><h1>{title}</h1></Link>
<p>{body}</p>
</Col>
)
})
return (
<Fragment>
<Row>
{blogFull}
</Row>
</Fragment>
)
}
}
and this is my next component
import axios from 'axios'
import React, { Component, Fragment } from 'react'
import { useParams } from 'react-router'
export default class Post extends Component {
constructor(props) {
super(props)
this.state = {
mydata:[],
}
}
componentDidMount()
{
axios.get("https://jsonplaceholder.typicode.com/posts/")
.then((response)=>{
if(response.status===200)
{
this.setState({
mydata:response.data
})
}
})
.catch((error)=>{
})
}
render() {
const dataAll = this.state.mydata;
return (
<Fragment>
data retriving
<h1>{dataAll.title}</h1>
<p>{dataAll.body}</p>
</Fragment>
)
}
}
My Route is here :
<Routes>
<Route exact path="/" element={<Blog/>}/>
<Route exact path="/post/:id" element={<Post/>}/>
</Routes>
Can anyone tell me that how can i get data in post component from base component via its url parameter? The "match" object is not working in current update of react-router-dom. I want help for class component.
Issue
In react-router-dom v6 the Route components no longer have route props (history, location, and match), and the current solution is to use the React hooks "versions" of these to use within the components being rendered. React hooks can't be used in class components though.
To access the match params with a class component you must either convert to a function component, or roll your own custom withRouter Higher Order Component to inject the "route props" like the withRouter HOC from react-router-dom v5.x did.
Solution
I won't cover converting a class component to function component. Here's an example custom withRouter HOC:
const withRouter = WrappedComponent => props => {
const params = useParams();
// etc... other react-router-dom v6 hooks
return (
<WrappedComponent
{...props}
params={params}
// etc...
/>
);
};
And decorate the component with the new HOC.
export default withRouter(Post);
This will inject a params prop for the class component.
this.props.params.id

ApolloProvider client - how to access from React Class component?

I am building React App and need to use class components.
I have an ApolloClient set up in index.js and it is passing the client to the ApolloProvider. How do I access it in my App component (class component)? Is it even possible? I also have setup Redux and mapped state to props with connect (I thought about using export default withApollo(App) but then I lose state to props mapping with connect()).
Can someone help/explain how to correctly implement apollo-client with react class components? Should I create new ApolloClient in each class component?
index.js
const apolloClient = new ApolloClient({
uri: "http://localhost:4000/",
cache: new InMemoryCache(),
});
...
<ApolloProvider client={apolloClient}>
<App />
</ApolloProvider>
App.js
class App extends Component {
render() {
...
}
}
const mapStateToProps = (state) => {
return {
...
};
};
export default connect(mapStateToProps)(App);
To use ApolloClient in a class-based React component, you will need to make sure that your component is wrapped in an ApolloProvider component. You can use the ApolloConsumer component to get access to the ApolloClient instance.
import React from 'react';
import { ApolloConsumer } from '#apollo/client';
class MyComponent extends React.Component {
render() {
return (
<div>
<ApolloConsumer>
{client => (
<button onClick={() => client.writeData({ data: { isLoggedIn: true } })}>
Log in
</button>
)}
</ApolloConsumer>
</div>
);
}
}

How to navigate to other page using react router

I have a onClick function to navigate to other page. I tried this.props.history.push("/SecondPage/ID/") and some examples but nothing worked out.
I have the component like this:
export class MainPage extends Component {
constructor(props) {
super(props);
}
render(){
return (
<div id="main" onClick={this.NavigatetoOtherPage.bind(this)}>
)
}
NavigatetoOtherPage(){
let ID = this.props.ID; // I need to pass the ID as a parameter.
//Here I need to navigate to other page using. I can use window.location.href but I need to use react router.
}
}
export default connect(state => {
return {
ID: state.Reducer.ID,
};
})(MainPage)
My app.js file like this
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Route exact path='/' component={MainPage}/>
<Route path='/SecondPage/:ID/' component = {SecondPage} />
</Provider>
);
}
}
My index.js page like this
export function renderPage() {
ReactDOM.render(
<Router>
<App />
</Router>
, document.getElementById('root'));
}
renderPage();
How can I navigate to second page without window.location.href
You can use the useHistory hook or the Link component given you are using react-router-dom
import React from "react";
import { useHistory, Link } from "react-router-dom";
// Then in your component
const MainPage = (props) => {
/**
* hooks
*/
const history = useHistory();
/**
* function
*/
const handleNavigation = () => {
let ID = props.ID; // I need to pass the ID as a parameter.
history.push(`/dashboard/${ID}`)
}
return (
<button id="main" onClick={() => history.push("/")}> Go to / </button>
<button id="main" onClick={() => handleNavigation()}> Go to dynamic page
</button>
<Link to={`/dashboard/${props.ID}`} className="some-styling">
Using Link
</Link>
);
};
// I have merged both implementations
export default MainPage;
// Edited: Based on the comment, the issue is "The history is not coming in the props."
// Then you could use `withRouter` HOC, and then there will be
// the `history` object in the wrapped component's props.
import {withRouter} from 'react-router-dom';
class MainPage extends React.Component {
render(){
console.log(this.props.history) // history object
return(<div />)
}
}
export default withRouter(MainPage)`
Wrote down a small sandbox. I guess this is what you are trying to achieve.
https://codesandbox.io/s/practical-tereshkova-ilbig?file=/src/App.js

How to solve 'TypeError: Cannot read property 'params' of undefined' with Apollo Graphql Client and React?

I'm trying to connect a deck URL to link to the cards list, similar to how I see it was done on Traversy's SpaceX graphql demo. I'm receiving the error TypeError: Cannot read property 'params' of undefined when clicking a link to either of the demo decks.
This is a React client I have built on top of the server, using Apollo with GraphQL connected to my functional PostgreSQL DB. I've pinpointed the issue to what appears to be these lines, and how the props are being passed through React and Apollo. Do note I am a beginner with GraphQL and Apollo, and this is my first real project with React, so any guidance to understanding the process would be appreciated.
let { id } = this.props.match.params;
id = parseInt(id);
Decks index.js
import React, { Component, Fragment } from "react";
import gql from "graphql-tag";
import { Query } from "react-apollo";
import Loading from "../../Loading";
import DeckItem from "./DeckItem";
const GET_DECKS = gql`
query DeckQuery {
decks #connection(key: "DeckConnection") {
edges {
id
deckName
createdAt
cards {
id
front
}
}
}
}
`;
class Decks extends Component {
render() {
return (
<Fragment>
<h1>Deck</h1>
<Query query={GET_DECKS}>
{({ data, error, loading }) => {
if (loading) {
return <Loading />;
}
if (error) {
return <p>Error</p>;
}
const decksToRender = data.decks.edges;
return (
<Fragment>
{console.log(data)}
{decksToRender.map(deck => (
<DeckItem key={deck.id} deck={deck} />
))}
</Fragment>
);
}}
</Query>
</Fragment>
);
}
}
export default Decks;
DeckItem index.js
import React from "react";
import Moment from "react-moment";
import { Link } from "react-router-dom";
export default function DeckItem({ deck: { id, deckName, createdAt } }) {
return (
<div>
<div>
<h2>
<Link to={`/deck/${id}`}>{deckName}</Link>
</h2>
<p>Description coming soon...</p>
<h5>
Created on <Moment format="YYYY-MM-DD HH:mm">{createdAt}</Moment>
</h5>
</div>
</div>
);
}
Cards index.js
import React, { Component, Fragment } from "react";
import gql from "graphql-tag";
import { Query } from "react-apollo";
import { Link } from "react-router-dom";
import Loading from "../../../Loading";
const CARDS_QUERY = gql`
query CardsQuery($id: ID!) {
deck(id: $id) {
id
deckName
cards {
id
front
back
}
}
}
`;
export class Cards extends Component {
render() {
let { id } = this.props.match.params;
id = parseInt(id);
return (
<Fragment>
<Query query={CARDS_QUERY} variables={{ id }}>
{({ data, error, loading }) => {
if (loading) {
return <Loading />;
}
if (error) {
return <p>Error</p>;
}
const {
id,
card: { front, back }
} = data.deck;
return (
<div>
<ul>
<h1>
<li>
<span>Id:</span> {id}
</li>
</h1>
<h4>Details</h4>
<li>Front: {front}</li>
<li>Back: {back}</li>
</ul>
<hr />
<Link to="/">Back</Link>
</div>
);
}}
</Query>
</Fragment>
);
}
}
export default Cards;
Relevant React router routes
```
import { Router, Route } from "react-router-dom";
```
import FlashCardPage from "../FlashCards";
import Cards from "../FlashCards/Cards/CardsItem";
import * as routes from "../../constants/routes";
<Router>
```
<Route
exact
path={routes.FLASHCARDS}
component={() => <FlashCardPage />}
/>
<Route exact path={routes.CARDS} component={() => <Cards />} />
</div>
</Router>
the path in my routes file is "/deck/:id"
Console Error log
react-dom.development.js:19782 Uncaught TypeError: Cannot read property 'params' of undefined
at Cards.render (index.js:23)
index.js:1452 The above error occurred in the <Cards> component:
in Cards (at App/index.js:42)
in component (created by Route)
in Route (at App/index.js:42)
in div (at App/index.js:19)
in Router (at App/index.js:18)
in App (at withSession.js:8)
in Query (at withSession.js:6)
in Unknown (at src/index.js:84)
in ApolloProvider (at src/index.js:83)
The result should be a deck showing a list of the cards, '/deck/1' should show 2 or 3 cards from my PostgreSQL server. Currently I can only see the decks with a url, but upon clicking, it throws up the error immediately. Other Graphql functions are working correctly, and using Playground the query works just fine, so it seems I'm not passing props correctly. Any additional information needed, I'll be happy to provide. Thank you!
The Route component from react-router-dom uses the render-prop pattern to extend the component it renders, providing it props. [1]
It forwards these props (match, location, history, and staticContext) to the React.Component that is passed for its component prop when it renders. [2]
In the routes you defined this isn't passed to the FlashCardPage or Cards component because there is stateless function component that wraps them.
<Route exact path={routes.CARDS} component={() => <Cards />} />
This stateless function component is passed these props; you can take responsibility to forward these props down to FlashCardPage and Cards components.
<Route exact path={routes.CARDS} component={(props) => <Cards {...props} />} />
However, I recommend to get rid of the stateless component which wraps the component rendered in the Route since it's redundant.
<Route exact path={routes.CARDS} component={Cards} />

React-Redux Provider not working

My project uses React-Redux Provider.
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
, document.getElementById('root'));
and
class App extends Component {
componentDidMount(){
API.getCategories().then((categories)=>{
this.props.dispatch(addCategories(categories))
})
API.getAllPosts().then(posts => {
console.log('getAllPosts', posts)
})
}
render() {
return (
<div className="App">
<Route exact path="/" render={()=>{
return (
<div>
{
this.props.categories.map((category)=>{
return (
<Link key={category.name} to={`/category/${category.name}`} params={{category: category.name}} >{category.name}</Link>
)
})
}
</div>
)
}}
/>
<Route path="/category/:category" component={Category} />
</div>
);
}
}
function mapStateToProps(x) {
return {
categories: x.categories
}
}
// export default App;
export default withRouter(connect(
mapStateToProps,
)(App))
From the above code and based on my experience from a previous project, the Category component's this.props should have a dispatch method that I can call the actions with but for some reason it is not there.
This is my Category Component:
class Category extends Component {
componentDidMount(){
console.log('this.props of Category', this.props)
var category = this.props.match.params.category
API.getPosts(category).then((posts)=>{
console.log('after getPosts', posts)
this.props.dispatch(addAllPosts(posts))
})
}
render(){
return <p>Category</p>
}
}
export default Category
What am I missing here???
You need to use the connect function from react-redux on your Category component so it has access to dispatch.
export default connect()(Category)
Also, it might just be simplified for SO, but App does not need to be wrapped in withRouter. This is only required if you need the router props injected into the component. Route does this automatically for any component it renders, which is why you don't need it on Category.
export default connect(mapStateToProps)(App)

Resources