Warning Prop `href` did not match. using react server-side-rendering - javascript

I am using react-router-dom and I am guessing that this is causing the problem, but I have no clue where to even start looking or how to fix it. I also am getting errors like Warning: Did not expect server HTML to contain a <nav> in <div>.
As I stated, I'm not really sure where to look so if you think there is certain code that would be helpful please let me know and I will post it. Otherwise, I can post my code that I use to do SSR.
EDIT: Exact error: Warning: Prophrefdid not match. Server: "/profile/5a073dc44cb45b00125e5c82" Client: "profile/5a073dc44cb45b00125e5c82"
I have checked the client and it has /profile/:id so not sure where it says there is not a /, as for the other error with the <nav> in <div> , I have a nav inside my header , but I'm not really sure how to go about "fixing" that.
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { renderRoutes } from 'react-router-config';
import serialize from 'serialize-javascript';
import { Helmet } from 'react-helmet';
import { matchRoutes } from 'react-router-config';
import routes from './src/routes';
import createStore from './src/stores';
function handleRender(req, res) {
let initial = {};
if (req.vertexSession != null && req.vertexSession.user != null) {
initial.user = { currentUser: req.vertexSession.user };
}
const store = createStore.configure(initial); // create Store in order to get data from redux
const promises = matchRoutes(routes, req.path)
.map(({ route, match }) => {
// Matches the route and loads data if loadData function is there
return route.loadData
? route.loadData(store)
: route.loadDataWithMatch ? route.loadDataWithMatch(store, match) : null;
})
.map(promise => {
if (promise) {
return new Promise((resolve, reject) => {
promise.then(resolve).catch(resolve); // lets all data load even if route fails
});
}
});
Promise.all(promises).then(() => {
const context = {};
if (context.url) {
return res.redirect(301, context.url); // redirect for non auth users
}
if (context.notFound) {
res.status(404); // set status to 404 for unknown route
}
const content = renderToString(
<Provider store={store}>
<StaticRouter location={req.path} context={context}>
<div>{renderRoutes(routes)}</div>
</StaticRouter>
</Provider>
);
// console.log(store.getState());
const initialState = serialize(store.getState());
const helmet = Helmet.renderStatic();
res.render('index', { content, initialState, helmet });
});
}
module.exports = handleRender;

Did you fix this already? I had the similar problem with my react app and fixed it. Here was my problem:
<Link to="./shop">Shop</Link>
my fix:
<Link to="/shop">Shop</Link>
Whatever you are rendering with the server is the issue. I suggest to comb through your routes module and see if you forgot a forward slash somewhere. If that doesn't work look at through the components your importing in the routes file.

To add to Kevorkian answer:
Make sure to prefix all the routes with a /
Note the /update
<Link href={{ pathname: '/update', query: { id: product.id } }}>

This happened to me in nextjs. It was because i had something like
Link=/auth/${isLogin?"sign-up":"login"}. This was an issue because there is no reference at compile time. The should have been isLogin ? "/auth/sign-up" : "/auth/login"}

This happened to me in a React/Next project because of next links.
This is the component
const Breadcrumb = (props) => {
return (
<Row>
<Col xs="12">
<div className="page-title-box d-sm-flex align-items-center justify-content-between">
<h4 className="mb-0 font-size-18">{props.breadcrumbItem}</h4>
<div className="page-title-right">
<ol className="breadcrumb m-0">
<BreadcrumbItem>
<Link href="#">{props.title}</Link>
</BreadcrumbItem>
<BreadcrumbItem active>
<Link href="#">{props.breadcrumbItem}</Link>
</BreadcrumbItem>
</ol>
</div>
</div>
</Col>
</Row>
);
};
Here we have Link href="#" (This is the server side)
<li class="breadcrumb-item">
<a href="/search?searchTerm=a00&codeSets=1%2C2%2C3%2C4%2C5%2C6%2C7#">Home
</a>
</li>
After the page loaded, it becomes something like that above (This is the client side)
So we have a mismatch here in the DOM and we get this warning
SOLUTION
I was rendering this component on my pages and getting this warning. As a solution, i have implemented dynamic content approach checking some state values before rendering the element.
For instance in my code detail page, I have done that like below.
const CodeDetail = (props) => {
const [code, setCode] = useState<any>(null);
useEffect(() => {
if (props.router.query.code) {
if (codesData.filter(codeData => codeData.code == props.router.query.code).length > 0) {
setCode(codesData.find(codeData => codeData.code == props.router.query.code));
}
else {
setCode({});
}
}
}, [props.router]);
let pageContent;
if (code !== null) {
pageContent = (<Container fluid={true}><Breadcrumbs title="Codes" breadcrumbItem="Code Detail" /></Container>)
}
else {
pageContent = null;
}
return(
<React.Fragment>{pageContent}</React.Fragment>
);
};

I just want to chip in that with Next's Link component it NOT will accept an empty string for an href property like it does in native HTML. If you really want a link that links to nowhere you HAVE to use #. That's how I got my error.

Related

error while go back to previous pages using breadcrumbs with reactjs and react-router-v5

I'm make a breadcrumb using reactjs and react-router v5. I'm getting error when i'm click to previous page in breadcumbs. the application becomes hang. here's my breadcrumb.
showing the recent url pages is work. But the problem is when i want to back to previous page from breadcrumbs, my apps become hang, and i'm getting this error.
And then i'm getting the weird things about url. when i'm done in the last page. when i'm hover the link in breadcrumbs , it looks same.
it's weird , because the url of dashboard is '/'.
Here's my breadcrumbs code.
import React from "react";
import { Breadcrumb } from "react-bootstrap";
import { Link, withRouter } from "react-router-dom";
const Breadcrumbs = (props) => {
const {
history,
location: {pathname}
} = props;
const pathnames = pathname.split("/").filter(x => x);
return (
<Breadcrumb aria-label="breadcrumb">
<Link onClick={() => history.push("/")}> Dashboard</Link>
{
pathnames.map((name, index) => {
const routeTo = `/${pathnames.slice(0, index + 1).join("/")}`;
const isLast = index === pathnames.length - 1;
return isLast ? (<>
<Breadcrumb.Item key={name}>{name}</Breadcrumb.Item> </>
) : (
<Link key={name} onClick={() => history.push(routeTo)}>{name}</Link>
)
})
}
</Breadcrumb>
);
};
export default withRouter(Breadcrumbs);
I want my breadcrumbs work like this.
Demo Breadcrumbs
i'm stuck. any help will be appreciated. Thank you.
The react-router-dom Link component requires a to prop with a valid string or Path object with pathname property.
Instead of
<Link onClick={() => history.push("/")}>Dashboard</Link>
...
<Link key={name} onClick={() => history.push(routeTo)}>{name}</Link>
use
<Link to="/">Dashboard</Link>
...
<Link key={name} to={routeTo}>{name}</Link>

How to dynamically import a MDX Component in React?

I created an Article component which gets its title through the route parameters like this:
const Article = () => {
const params = useParams()
const dashedTitle = params.title.replace(/ /g, '-')
return (
<article>
<MyMDX />
</article>
);
}
I want to return a MDX file with the same name as the provided title. Simply returning the <MyMDX /> component works fine if I manually import it at the top of the article with import MyMDX from '../markdowns/mymdx.mdx. However, I don't see a way to import this file dynamically, depending on the tile.
Is there a way to do this or could I do this in a better way?
I managed to find a solution to this:
const Article = () => {
const params = useParams()
const [article, setArticle] = useState()
dashedTitle = params.title.replace(/ /g, '-')
useEffect(() => {
import(`../markdowns/${dashedTitle}.mdx`).then(module => {
setArticle(module.default)
}).catch(err => {
console.log(err)
setArticle(undefined)
})
}, [dashedTitle])
return (
<article>
{article ? article : <NotFound />}
</article>
);
}
With useEffect, import the MDX module and then set my article variable to the module's default export (which is exactly the generated JSX component). Thus, I can use it in the return segment in case it's not undefined.
Additionally, I found a similar answer that could help.

React: Persisting State Using Local Storage

I am coding an app in which there is a collection of reviews and a person can respond to a review, but each review can only have one response. So far, I am doing this by rendering a ReviewResponseBox component in my ReviewCardDetails component and passing the review_id as props.
I have implemented the logic so that once there is one ReviewResponse, the form to write another will no longer appear. However, before I was initializing the state in this component with an empty array, so when I refreshed my page the response went away and the form came back up. (This is now commented out)
I am trying to resolve this by persisting my state using React LocalStorage but am having trouble writing my method to do this. Here is what I have so far:
Component that renders ReviewResponseBox and passes review_id as props:
import React from "react";
import './Review.css';
import { useLocation } from "react-router-dom";
import StarRatings from "react-star-ratings";
import ReviewResponseBox from "../ReviewResponse/ReviewResponseBox";
const ReviewCardDetails = () => {
const location = useLocation();
const { review } = location?.state; // ? - optional chaining
console.log("history location details: ", location);
return (
<div key={review.id} className="card-deck">
<div className="card">
<div>
<div className='card-container'>
<h4 className="card-title">{review.place}</h4>
<StarRatings
rating={review.rating}
starRatedColor="gold"
starDimension="20px"
/>
<div className="card-body">{review.content}</div>
<div className="card-footer">
{review.author} - {review.published_at}
</div>
</div>
</div>
</div>
<br></br>
<ReviewResponseBox review_id={review.id}/>
</div>
);
};
export default ReviewCardDetails;
component that I want to keep track of the state so that it can render the form or response:
import React from 'react';
import ReviewResponse from './ReviewResponse';
import ReviewResponseForm from './ReviewResponseForm';
import { reactLocalStorage } from "reactjs-localstorage";
class ReviewResponseBox extends React.Component {
// constructor() {
// super()
// this.state = {
// reviewResponses: []
// };
// }
fetchResponses = () => {
let reviewResponses = [];
localStorage.setResponses
reviewResponses.push(reviewResponse);
}
render () {
const reviewResponses = this.getResponses();
const reviewResponseNodes = <div className="reviewResponse-list">{reviewResponses}</div>;
return(
<div className="reviewResponse-box">
{reviewResponses.length
? (
<>
{reviewResponseNodes}
</>
)
: (
<ReviewResponseForm addResponse={this.addResponse.bind(this)}/>
)}
</div>
);
}
addResponse(review_id, author, body) {
const reviewResponse = {
review_id,
author,
body
};
this.setState({ reviewResponses: this.state.reviewResponses.concat([reviewResponse]) }); // *new array references help React stay fast, so concat works better than push here.
}
getResponses() {
return this.state.reviewResponses.map((reviewResponse) => {
return (
<ReviewResponse
author={reviewResponse.author}
body={reviewResponse.body}
review_id={this.state.review_id} />
);
});
}
}
export default ReviewResponseBox;
Any guidance would be appreciated
You would persist the responses to localStorage when they are updated in state using the componentDidUpdate lifecycle method. Use the componentDidMount lifecycle method to read in the localStorage value and set the local component state, or since reading from localStorage is synchronous directly set the initial state.
I don't think you need a separate package to handle this either, you can use the localStorage API easily.
import React from "react";
import ReviewResponse from "./ReviewResponse";
import ReviewResponseForm from "./ReviewResponseForm";
class ReviewResponseBox extends React.Component {
state = {
reviewResponses: JSON.parse(localStorage.getItem(`reviewResponses-${this.props.review_id}`)) || []
};
storageKey = () => `reviewResponses-${this.props.review_id}`;
componentDidUpdate(prevProps, prevState) {
if (prevState.reviewResponses !== this.state.reviewResponses) {
localStorage.setItem(
`reviewResponses-${this.props.review_id}`,
JSON.stringify(this.state.reviewResponses)
);
}
}
render() {
const reviewResponses = this.getResponses();
const reviewResponseNodes = (
<div className="reviewResponse-list">{reviewResponses}</div>
);
return (
<div className="reviewResponse-box">
{reviewResponses.length ? (
<>{reviewResponseNodes}</>
) : (
<ReviewResponseForm addResponse={this.addResponse.bind(this)} />
)}
</div>
);
}
addResponse(review_id, author, body) {
const reviewResponse = {
review_id,
author,
body
};
this.setState({
reviewResponses: this.state.reviewResponses.concat([reviewResponse])
}); // *new array references help React stay fast, so concat works better than push here.
}
getResponses() {
return this.state.reviewResponses.map((reviewResponse) => {
return (
<ReviewResponse
author={reviewResponse.author}
body={reviewResponse.body}
review_id={this.state.review_id}
/>
);
});
}
}

React-Markdown Custom Component Declaration, How Can I Declare in the Renderer to Use a Custom Component?

Problem
Using React-Markdown I can fully use my custom built components. But this is with specific pre-built keywords in the markdown. Like paragraph or images. That works PERFECTLY. But the problem is that these seem to all be pre-built words/conditions like paragraphs, headers, or images.
I can't find a way to add something new key word in my markdown like "CustomComponent" to be used. That's all I need at this point ><
This works just fine for me to make the markdown's image into a custom "footer" component I made elsewhere. I know it's ridiculous but it works. But I have no idea how to make this renderer accept/create a new keyword like "emoji" or "customComponent" or "somethingSilly".
let body =
`![Fullstack React](https://dzxbosgk90qga.cloudfront.net/fit-in/504x658/n/20190131015240478_fullstack-react-cover-medium%402x.png)`;
const renderers = {
image: () => <Footer/>
};
<ReactMarkdown source={body} renderers={renderers} />;
Some past work I did:
Some documentation:
https://reposhub.com/react/miscellaneous/rexxars-react-markdown.html
https://github.com/rexxars/commonmark-react-renderer/blob/master/src/commonmark-react-renderer.js#L50
Examples:
https://codesandbox.io/s/react-markdown-with-custom-renderers-961l3?from-embed=&file=/src/App.js
But nothing indicates how I can use "CustomComponent" to indicate to inject a custom component.
Use Case / Background
I'm trying to retrieve an article from my database that is formatted like so in markdown (basically a giant string). I'm using regular react with typescript and redux-- this is the only portion of my application that needs this.
"
# Title
## Here is a subtitle
Some text
<CustomComponentIMade/>
Even more text after.
<CustomComponentIMade/>
"
I know its most likely a little late for your purposes, but I've managed to solve this issue using a custom remark component.
Essentially you'll need to use the remark-directive plugin as well as a small custom remark plugin (I got this plugin straight from the remark-directive docs)
Then in react markdown you can specify the plugins, custom renderers and custom tags for eg.
import React from 'react'
import ReactMarkdown from 'react-markdown'
import {render} from 'react-dom'
import directive from 'remark-directive'
import { MyCustomComponent } from './MyCustomComponent'
import { visit } from "unist-util-visit"
import { h } from "hastscript/html.js"
// react markdown components list
const components = {
image: () => <Footer/>,
myTag: MyCustomComponent
}
// remark plugin to add a custom tag to the AST
function htmlDirectives() {
return transform
function transform(tree) {
visit(tree, ['textDirective', 'leafDirective', 'containerDirective'], ondirective)
}
function ondirective(node) {
var data = node.data || (node.data = {})
var hast = h(node.name, node.attributes)
data.hName = hast.tagname
data.hProperties = hast.properties
}
}
render(
<ReactMarkdown components={components} remarkPlugins={[directive, htmlDirectives]}>
Some markdown with a :myTag[custom directive]{title="My custom tag"}
</ReactMarkdown>,
document.body
)
So in your markdown wherever you have something like :myTag[...]{...attributes} you should render the MyCustomComponent with attributes as props.
Sorry I haven't tested the code, but hopefully it communicates the gist of things, if you need a working example let me know and I'll do my best to set one up.
I have tried this way and it worked for me
import CustomNextImage from '#commonComponentsDependent/CustomNextImage';
import dynamic from 'next/dynamic';
import Link from 'next/link';
import { FC } from 'react';
import ReactMarkdown from 'react-markdown';
import { Options } from 'react-markdown/lib/ast-to-react';
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
import remarkGfm from 'remark-gfm';
const SyntaxHighlighterDynamic = dynamic(() => import('./SyntaxHighlighter'));
import classes from './styles.module.css';
interface Props {
content: string;
}
type TCustomComponents = Options['components'];
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
const MdToHTML: FC<Props> = ({ content }) => {
const customComponents: TCustomComponents = {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
img(image) {
if (!image.src) return <></>;
return (
<div className={classes['img-container']}>
<CustomNextImage
src={image.src}
alt={image.alt}
/>
</div>
);
},
a({ href, children, node }) {
if (!href) return <></>;
if (
href.startsWith('/') ||
href.startsWith('https://lognmaze.com')
) {
return (
<Link href={href} prefetch={false} passHref>
<a>{children}</a>
</Link>
);
}
return (
<a
href={href}
target='_blank'
rel='noopener noreferrer'
>
{children}
</a>
);
},
code({ node, inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<SyntaxHighlighterDynamic language={match[1]} PreTag='div' {...props}>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighterDynamic>
) : (
<code className={className} {...props} data-code-inline='true'>
{children}
</code>
);
},
};
return (
<ReactMarkdown
components={customComponents}
remarkPlugins={[remarkGfm]}
>
{content}
</ReactMarkdown>
);
};
export default MdToHTML;

How to take items from an array that is in a React component's props and render to the page?

I am trying to make my navbar dynamically show different things depending on whether the user is signed in or not using React and Redux. And actually, that has proven to be manageable, I hooked up action creators that check whether there is a user object and if there is then to switch the isSignedIn property to true and show content accordingly.
The challenge has been that I want to show that user's profile photo in the navbar too. In order to do that, my plan was to have an action creator fire off a function that will get the user's documents form the database (firebase) and return the details in state to the navbar component. Then mapStateToProps would take that content and move it into props for me to manipulate onto the users view.
I can get this all to work to a point, the component fires up and it takes a bit of time to get the users details form firebase, so theres a couple of seconds where the props.user property is empty. That's fine but then when it does return with the users content, they are stored in object in an array and I have no idea how to access them.
So this.props.user is an array with one object that has key-value pairs like userDateOfBirth: "12-12-2020" and so on. I need to access those values. I thought either this.props.user.userDateOfBirth or this.props.user[0].userDateOfBirth would work but neither does. The first just returns 'undefined' and the second returns an error "Cannot read property 'userDateOfBirth' of undefined.
Please help, this is driving me insane.
I've updated the question to include some of the code that should make this a bit more understandable. I've left the render function out of the navbar component for the sake of brevity.
The Action Creators signedIn and signedOut work as I expected. It's the currentUser Action Creator which I'm having difficulties with. I've shared as much as I think necessary, and maybe a little too much. Thanks for any help.
Code Below:
* Navbar Component *
import './NavBar.css';
import {Link} from "react-router-dom";
import React from "react";
import {connect} from "react-redux";
import * as firebase from "firebase";
import {signedIn, signedOut, currentUser} from "../actions";
componentDidMount()
{
this.unregisterAuthObserver = firebase.auth().onAuthStateChanged((user) => {
let currentUser = user;
if (user) {
this.props.signedIn()
} else this.props.signedOut();
if (this.props.auth.isSignedIn) {
this.props.currentUser(currentUser.uid);
}
});
}
render() {
return (
this.props.auth.isSignedIn ?
//If the user is signed in, show this version of the navbar
//Overall NavBar
<div id="navBar">
{/*Left side of the NavBar*/}
<div id="navBarLeft">
<Link to ={"/"}
>
{/* OLAS Logo*/}
<img
alt={"Olas Logo"}
id="logo"
src={require("../img/Olas_Logo&Brandmark_White.png")}
/>
</Link>
</div>
{/*Right side of the NavBar*/}
<div id="navBarRight">
<div id="home" className="navBarItem">
<Link to={"/"} >
HOME
</Link>
</div>
<div id="careerPaths" className="navBarItem">
<Link to={"/careerPath"} >
CAREER PATHS
</Link>
</div>
<div id="jobPostInsights" className="navBarItem">
<Link to={"/jobPostInsights"} >
JOB POST INSIGHTS
</Link>
</div>
<div id="careerQ&AForum" className="navBarItem">
<Link to={"/forum"} >
CAREER Q&A FORUM
</Link>
</div>
<div id="userProfile" className="navBarItem">
<Link to={"/userprofile"}>
{this.props.user.length ? (<div>{this.props.user.user[0].userFirstName}</div>): <div>PROFILE</div>}
</Link>
</div>
</div>
</div>
//This is a critical part of the ternary operator - DO NOT DELETE
:
//If the user is not signed in, show this version of the navbar
<div id="navBar">
{/*Left side of the NavBar*/}
<div id="navBarLeft">
<Link to ={"/"}
>
{/* OLAS Logo*/}
<img
alt={"Olas Logo"}
id="logo"
src={require("../img/Olas_Logo&Brandmark_White.png")}
/>
</Link>
</div>
{/*Right side of the NavBar*/}
<div id="navBarRight">
<div/>
<div/>
<div id="about" className="navBarItem">
<Link to={"about"}>
<span className="navBarItemInner">ABOUT OLAS</span>
</Link>
</div>
<div id="home" className="navBarItem">
<Link to={"/"} >
<span className="navBarItemInner">HOME</span>
</Link>
</div>
<div id="signIn" className="navBarItem" >
<Link to={"/signIn"} >
<span className="navBarItemInner">SIGN IN / REGISTER</span>
</Link>
</div>
</div>
</div>
)};
const mapStateToProps = state => {
console.log(state);
return {auth: state.auth, user: state.user}
};
export default connect(mapStateToProps, {signedIn, signedOut, currentUser})(NavBar);
* currentUser Action Creator *
export const currentUser = (currentUserUid) => {
return async (dispatch) => {
const response = await getUserDocFromDB(currentUserUid);
dispatch({
type: GET_CURRENT_USER,
payload: response,
});
}
};
* getUserDocFromDB *
import getFirestoreDb from '../getFirestoreDb';
let db = getFirestoreDb();
const getUserDocFromDB = async (currentUserUid) => {
let userDoc = [];
await
db.collection("users")
.where("userFirebaseUid", "==", currentUserUid)
.onSnapshot(
(querySnapshot) => {
if (querySnapshot.empty) {
return;
}
querySnapshot.forEach((doc) => {
const {
userDateOfBirth,
userGender,
userState,
userCity,
userTitle,
userDescription,
userFirstName,
userLastName,
userProfileImageUrl,
} = doc.data();
userDoc.push({
userDateOfBirth,
userGender,
userState,
userCity,
userTitle,
userDescription,
userFirstName,
userLastName,
});
});
},
function(error) {
console.log("Error getting document Error: ", error);
},
);
return userDoc;
};
export default getUserDocFromDB;
* User Reducer *
import {GET_CURRENT_USER} from "../actions/ActionTypes";
const INITIAL_STATE = {
user: null,
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_CURRENT_USER:
return {user: action.payload};
default:
return INITIAL_STATE;
}
};
* Combine Reducers *
import { combineReducers } from "redux";
export default combineReducers({
user: UserReducer,
});
The second way, this.props.user[0].userDateOfBirth is the correct way of accessing user properties after the data is loaded. What is happening is the array is empty while the data is being fetched and populated in app state. Using a guard will prevent the undefined error.
Assuming the default state is an empty array:
this.props.user.length && this.props.user[0].userDateOfBirth
In react this is a pattern called Conditional Rendering, and would likely look more like:
{this.props.user.length ? (
/* Render user component using this.props.user[0] */
) : null}
To make consuming the user object a little cleaner in components I'd convert the default state to be null, and have the reducer that handles the fetch response do the unpacking from the response array. Then the check is reduced to something like:
{this.props.user ? (
/* Render user component using this.props.user */
) : null}
I hope you are using private route then simply get pathname by using window.location.pathname === "user/dashboard" ? print username : "login "
Minimize tge filtration ..
Happy coding
Drew Reese's answer is spot on and I just wanted to add to it. You can check if the property exists, or simply render nothing if does not, as his examples show. When the state finally loads, it will force a re-render, the conditional will then be true, and you can then show the user profile photo or whatever other components require info from the returned api call.
This is why you might have seen a pattern of dispatching LOADING reducers, setting loading to true until the api returns and then it is set to false. You can check if loading is true in the component, and choose to render a spinner or something else instead.

Categories

Resources