I have been trying to build a little site using GatsbyJS with protected content using Firebase, following this example:https://github.com/taming-the-state-in-react/gatsby-firebase-authentication. I am using DatoCMS as a source. I have built blogs before where everything was the same except for the firebase authentication part, and everything worked perfectly fine.
Somehow, I get the error of data not being defined inside my template file out of which the article-pages are generated.
Here is my code:
import React from 'react'
import AuthUserContext from '../components/Session/AuthUserContext';
import withAuthorization from '../components/Session/withAuthorization';
import { graphql } from 'gatsby';
const authCondition = authUser => !!authUser;
const WikiPageSingle = withAuthorization(authCondition)(() => (
<AuthUserContext.Consumer>
{authUser => (
<div>
<h1>{this.props.data.datoCmsArticle.id}</h1>
</div>
)}
</AuthUserContext.Consumer>
))
export default withAuthorization(authCondition)(WikiPageSingle);
export const query = graphql`
query ArticlQuery ($slug: String!) {
datoCmsArticle (slug: { eq: $slug }) {
id
title
slug
}
}
`;
I know it might be hard to get help on this since its kind of a very specific issue but any ideas or pointers why this wouldn't work on a template file would be much appreciated!
I managed to get it working by creating a new layout for those articles and basically protecting the whole content inside that layout. I still feel there must be a more elegant solution so any ideas are more than welcome. In the meantime, here is how I got it working in case someone else has this issue in the future:
New Layout component:
import React from 'react';
import AuthUserContext from '../components/Session/AuthUserContext';
import withAuthorization from '../components/Session/withAuthorization';
import withAuthentication from './Session/withAuthentication';
const authCondition = authUser => !!authUser;
const ArticleLayout = withAuthorization(authCondition)(({children}) => (
<AuthUserContext.Consumer>
{authUser => (
<div>
{children}
</div>
)}
</AuthUserContext.Consumer>
));
export default withAuthentication(ArticleLayout);
And here my template in the templates folder:
import React from 'react';
import { graphql } from 'gatsby';
import ArticleLayout from '../components/articlelayout';
class BlogPost extends React.Component {
render() {
return (
<ArticleLayout>
<div>
<h1>{this.props.data.datoCmsArticle.title}</h1>
</div>
</ArticleLayout>
)
}
}
export default BlogPost
export const query = graphql`
query PostQuery ($slug: String!) {
datoCmsArticle (slug: { eq: $slug }) {
id
title
slug
}
}
`;
Related
The Load product is maybe we call it hook whose purpose of the life is fetch the data from a graphql backend
Here its how LOAD_PRODUCT LOOKS LIKE
import { gql } from '#apollo/client'
export const LOAD_PRODUCTS = gql`
query{
categories {
name
products {
id,
name,
inStock,
gallery,
category
}
}
}
`
import React, { Component } from 'react'
import { useQuery,gql } from '#apollo/client'
import { LOAD_PRODUCTS } from '../../graphql/productAction'
export class ProductListing extends Component {
constructor(){
super();
const {error,loading,data} = useQuery()
}
render() {
return (
<div>ProductListing</div>
)
}
}
export default ProductListing
for now i just want to fire the load user hook and save set the data to the different state there must be a method to do this i search on google but nothing help i just cant use fetch method to get the result
You can also query a graphql endpoint using the Query component or the HOC. But note that since class based components are considered legacy those do not receive any updates anymore.
Using the Query component:
import { Query } from '#apollo/client/react/components';
export class ProductListing extends Component {
render() {
return (
<Query query={LOAD_PRODUCTS}>
{({data}) => <div>Render data here ...</div>}
</Query>
)
}
}
Using the HOC:
import { graphql } from '#apollo/client/react/hoc';
class ProductListing extends Component {
render() {
const data = this.props.data;
return <div>Render data here ...</div>;
}
}
export default graphql(LOAD_PRODUCTS)(ProductListing);
I am trying to find the most efficient way to add syntax highlighting to my react sanity.io blog. Here's the article component that I created using react:
import React, {useEffect, useState} from "react";
import {useParams} from "react-router-dom";
import sanityClient from "../../client";
import BlockContent from "#sanity/block-content-to-react";
import imageUrlBuilder from "#sanity/image-url";
import Prism from "prismjs";
const builder = imageUrlBuilder(sanityClient);
function urlFor(source) {
return builder.image(source);
}
const serializers = {
types: {
code: (props) => (
<pre data-language={props.node.language}>
<code>{props.node.code}</code>
</pre>
),
},
};
export default function SinglePost() {
const [postData, setPostData] = useState(null);
const {slug} = useParams();
useEffect(() => {
sanityClient
.fetch(
`*[slug.current == $slug]{
title,
slug,
mainImage{
asset->{
_id,
url
}
},
body,
"name": author->name,
"authorImage": author->image
}`,
{slug}
)
.then((data) => setPostData(data[0]))
.catch(console.error);
Prism.highlightAll();
}, [slug]);
if (!postData) return <div>Loading...</div>;
return (
<article>
<h2>{postData.title}</h2>
<div>
<img
src={urlFor(postData.authorImage).width(100).url()}
alt="Author is Kap"
/>
<h4>{postData.name}</h4>
</div>
<img src={urlFor(postData.mainImage).width(200).url()} alt="" />
<div>
<BlockContent
blocks={postData.body}
projectId={sanityClient.clientConfig.projectId}
dataset={sanityClient.clientConfig.dataset}
serializers={serializers}
/>
</div>
</article>
);
}
I am importing article data from Sanity and rendering it as a component. I tried using prism.js but I am having issues getting it to work.
What's the best and most efficient way to to enable syntax highlighting?
Well, I'd use react-syntax-highlighter package on NPM. It's pretty easy to add to your project. Basically a plug-and-play. With no awkward configurations.
I have a site built with React Static that has a Header component that is always present. Depending on if the current page has a hero component or not, the Header should be either light or dark.
The Header is rendered outside of the routes and the useEffect is triggered before the children is rendered. This is probably because of the routing.
This is the current code:
// App.js
import React, { useState, useEffect } from 'react'
import { Root, Routes } from 'react-static'
export default () => {
const [useDarkTheme, setUseDarkTheme] = useState(false);
useEffect(() => {
if (typeof document !== "undefined") {
const heroPresent = document.querySelectorAll(".o-hero").length > 0;
console.log("The hero is present: " + heroPresent);
setUseDarkTheme(!heroPresent);
}
})
return (
<Root>
<React.Suspense fallback={ <em>Loading...</em> }>
<Header useDarkTheme={ useDarkTheme } />
<Routes default />
</React.Suspense>
</Root>
);
}
What will be rendered at <Routes default /> is the static pages configured in React Static's static.config.js.
Below is an example of the Hero component:
// Hero.js
import React from "react";
export default () => {
console.log("This is the Hero rendering. If this exist, the Header should be dark.");
return (
<div className="o-hero">
<p>Hero!</p>
</div>
);
}
When I run the application and look at the logs this is what I get:
The hero is present: false
This is the Hero rendering. If this exist, the Header should be dark.
How could I somehow detect the presence of the Hero from the Header although the Hero is in a router and the Header is not? This feels like quite a common use case, but I could not find any info on the interwebs.
Thanks in advance!
So I ended up using useContext to provide all children with a getter and a setter for the Header's theme (dark or light). The solution is very much inspired from this answer. The solution looks like this:
// App.js
import React, { useState, useContext } from 'react'
import { Root, Routes } from 'react-static'
import { HeaderThemeContext } from "./context";
export default () => {
const { theme } = useContext(HeaderThemeContext);
const [headerTheme, setHeaderTheme] = useState(theme);
return (
<Root>
<React.Suspense fallback={ <em>Loading...</em> }>
<HeaderThemeContext.Provider value={ { theme: headerTheme, setTheme: setHeaderTheme } }>
<Header theme={ headerTheme } />
<Routes default />
</HeaderThemeContext.Provider>
</React.Suspense>
</Root>
);
}
// Hero.js
import React from "react";
import { headerThemes, setHeaderTheme } from "./context";
export default () => {
setHeaderTheme(headerThemes.DARK);
console.log("This is the Hero rendering. If this exist, the Header should be dark.");
return (
<div className="o-hero">
<p>Hero!</p>
</div>
);
}
// context.js
import React, { createContext, useContext } from "react";
export const headerThemes = {
LIGHT: "light",
DARK: "dark",
};
export const HeaderThemeContext = createContext({
theme: headerThemes.LIGHT,
setTheme: () => {}
});
// This is a hook and can only be used in a functional component with access to the HeaderThemeContext.
export const setHeaderTheme = theme => useContext(HeaderThemeContext).setTheme(theme);
This gives global access to set and get the header theme, which might not be optional, but it works for now and I think it's fine. Please let me know if there is a better way of doing this.
I have recently started building a big project on React using also a Firebase with authentication and I cannot quite understand the relation between the react-router-dom links and React components.
I am struggling with getting the
this.props.match.params // which is going to be 2018 / 2019 / 2020... etc
in the component, which renders as a dynamic route (like unique post component).
I have tried to use only a simple class component and this works but the problem is, without the authentication everyone can access this admin route and everyone would be allowed to edit and delete data there. I want it to be accessed only by authenticated users. (Admins)
So this is how my piece of code looks like:
Main component: (where the link is)
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
class SeasonBox extends Component {
render() {
return (
<Link className='seasonbox' to={`/adminseason/${this.props.season}`}>
<p className='seasonbox__season'>{this.props.season}/{this.props.season+1}</p>
</Link>
)
}
}
export default SeasonBox;
And the component that renders after the link is clicked:
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import { compose } from 'recompose'
import { withAuthorisation } from '../Session'
import { withFirebase } from '../Firebase'
const AdminMatchesBox = ({authUser}) => (
<div>{authUser ? <AdminMatchesBoxAuth /> : <AdminMatchesBoxNonAuth />} </div>
)
class AdminMatchesBoxAuth extends Component {
render() {
return (
<div>
Hey I am the season {this.props.match.params}!
<Link to={'/adminmatches'}>Wróć</Link>
</div>
)
}
}
const AdminMatchesBoxNonAuth = () => (
<div>
<h1>You do not have permission to visit this page.</h1>
</div>
)
const mapStateToProps = state => ({
authUser: state.sessionState.authUser
});
const condition = authUser => !!authUser
export default compose(withAuthorisation(condition), connect(mapStateToProps),withFirebase)(AdminMatchesBox);
So if I don't use authorisation, and I use only a single class component I can get this.props.match.params -> which is the id of the website and I need it to access data from the database.
However, I want it to not be visible by not logged users and I had to process it through the authorisation process.
I am receiving an error
Cannot read property 'params' of undefined.
I have no clue how to pass match.params into the AdminMatchesBoxAuth component.
Could anyone advice?
By wrapping withRouter you able to access params
Try this
import { withRouter } from "react-router";
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import { compose } from 'recompose'
import { withAuthorisation } from '../Session'
import { withFirebase } from '../Firebase'
const AdminMatchesBox = ({authUser}) => (
<div>{authUser ? <AdminMatchesBoxAuth /> : <AdminMatchesBoxNonAuth />} </div>
)
class AdminMatchesBoxAuth extends Component {
constructor (props){
super(props)
}
render() {
return (
<div>
Hey I am the season {this.props.match.params}!
<Link to={'/adminmatches'}>Wróć</Link>
</div>
)
}
}
const AdminMatchesBoxNonAuth = () => (
<div>
<h1>You do not have permission to visit this page.</h1>
</div>
)
const mapStateToProps = state => ({
authUser: state.sessionState.authUser
});
const condition = authUser => !!authUser
export default compose(withRouter, withAuthorisation(condition), connect(mapStateToProps),withFirebase)(AdminMatchesBox)
i need to integrate my react application using google analytics. I found the react-ga library which looks reliable and "easy" to use. But i didn't use google analytics in the past and i'm having some dificulties. First of all i use the withTracker component i the demo page of react-ga github project and then in my router i wrap my homepage component with this wrapper. I need to make a pretty simple task but i can't find how. I need to track the number of visitors that hit the homepage. The withTracker component is this:
import React, { Component } from 'react';
import ReactGA from 'react-ga';
const withTracker = (WrappedComponent, options = {}) => {
const trackPage = (page) => {
ReactGA.set({
page,
options
});
ReactGA.pageview(page);
};
class HOC extends Component {
componentDidMount() {
const page = this.props.location.pathname;
trackPage(page);
}
componentWillReceiveProps(nextProps) {
const currentPage = this.props.location.pathname;
const nextPage = nextProps.location.pathname;
if (currentPage !== nextPage) {
trackPage(nextPage);
}
}
render() {
return <WrappedComponent {...this.props} />;
}
}
return HOC;
};
export default withTracker;
and my homePage is this:
import React, { PropTypes } from 'react';
import Footer from './header/footer';
const Main= props => (
<div>
<Footer/>
</div>
);
export default MainExperienceComponent;
Can you help me deal with this issue? Thanks a lot