React-tooltip and Next.js SSR issue - css

I use the react-tooltip library in my Next.js app.
I noticed that every time I refresh a website while visiting a page that uses the tooltip I get an error:
react-dom.development.js:88 Warning: Prop `dangerouslySetInnerHTML` did not match.
CSS classes are different on the client and on the server
The weird part is I do not get that error while navigating from a random page to a page that uses the react-tooltip.
The tooltip related code:
<StyledPopularityTooltipIcon src="/icons/tooltip.svg" alt="question mark" data-tip="hello world" />
<ReactTooltip
effect="solid"
className="tooltip"
backgroundColor="#F0F0F0"
arrowColor="#F0F0F0"
clickable={true}
/>

I had the same issue, I had to use state to detect when component has been mounted, and show the tooltip only after that.
P.S. You don't see the error when navigating, because the page is not rendered on server when you navigate, it's all front-end :)

In case you are using any server-side rendering (like Next.js) - you will need to make sure your component is mounted first before showing the react-tooltip.
I fixed this by using the following:
import React, { useEffect, useState } from 'react';
const [isMounted,setIsMounted] = useState(false); // Need this for the react-tooltip
useEffect(() => {
setIsMounted(true);
},[]);
return (<div>
{isMounted && <ReactTooltip id={"mytip"} effect={"solid"} />}
<span data-tip={"Tip Here"} data-for={"mytip"}>Hover me</span>
</div>)

You should wrap your JSX in the following component:
import React, { useEffect, useState } from 'react';
const NoSsr = ({ children }): JSX.Element => {
const [isMounted, setMount] = useState(false);
useEffect(() => {
setMount(true);
}, []);
return <>{isMounted ? children : null}</>;
};
export default NoSsr;
Like this:
<NoSsr>
<YourJSX />
</NoSsr>

If you are working with NEXTJS this might be a good approach, you can check the documentation here as well, also if you are working with data-event, globalEventOff or any other prop and is not hiding or not working in your localhost, this only occurs in Development Strict Mode. ReactTooltip works fine in Production code with React 18. So you can set reactStrictMode : false, in your next.config.js to test it locally and then set it back to true, hope this helps :) info reference here
import dynamic from 'next/dynamic'
const ReactTooltip = dynamic(() => import('react-tooltip'), { ssr : false });
function Home() {
return (
<div>
<Button
data-tip
data-event="click focus"
data-for="toolTip"
onClick={():void => ()}
/>
<ReactTooltip id="toolTip" globalEventOff="click"/>
</div>
)
}
export default Home

Related

NextJS - ReactDOMServer does not yet support Suspense

I'm currently trying to incorporate a loader component to a site built with NextJS. I would like to use Suspense to show a loading screen may it be after refreshing the page or changing routes.
This is how my code goes:
import Head from 'next/head'
import { Loader } from '../components/loader'
const { Suspense } = require('React')
function MyApp({ Component, pageProps }) {
return (
<>
<Suspense fallback={<Loader />}>
<Head>
.... some codes such as meta tags, title tags ....
</Head>
<Component {...pageProps} />;
</Suspense>
</>
)
}
My problem is I get an error that says ReactDOMServer does not yet support Suspense. but I would like to use Suspense to enable a loading screen on my page. Much like this website
You can use React 18 features like suspense in Next.js Advanced Features. Obviously it's still experimental and might cause issues with you application.
npm install next#latest react#rc react-dom#rc
To enable, use the experimental flag concurrentFeatures: true
// next.config.js
module.exports = {
experimental: {
concurrentFeatures: true,
},
}
Once enabled, you can use Suspense and SSR streaming for all pages.
import dynamic from 'next/dynamic'
import { lazy, Suspense } from 'react'
import Content from '../components/content'
// These two ways are identical:
const Profile = dynamic(() => import('./profile'), { suspense: true })
const Footer = lazy(() => import('./footer'))
export default function Home() {
return (
<div>
<Suspense fallback={<Spinner />}>
{/* A component that uses Suspense-based */}
<Content />
</Suspense>
<Suspense fallback={<Spinner />}>
<Profile />
</Suspense>
<Suspense fallback={<Spinner />}>
<Footer />
</Suspense>
</div>
)
}
I had a similar issue. I ended up simulating the Suspense with a combination of setState & componentDidMount
render(){
return this.state.browser ? <Component/> : <Placeholder/>
}
componentDidMount(){
this.setState({browser: true})
}
I hope it helps.

Theme with React and Redux

i am trying to make a theme system on a react project that uses redux with a reducer that manages themes according to the user's local storage. But here my problem is that I used css files to define my styles on all of my components. However, I have all my logic with 2 javascript objects for light or dark mode. So I can't use js in css files unless I use css variables but I don't know how.
Here is my structure :
In my app.js I imported useSelector and useDispatch from react redux to access the global state :
import React from 'react';
import './App.css';
import Footer from './components/Footer';
import Header from './components/Header';
import Presentation from './components/Presentation';
import Projects from './components/Projects';
import Skills from './components/Skills';
import Timeline from './components/Timeline';
import { switchTheme } from './redux/themeActions';
import { useSelector, useDispatch } from 'react-redux';
import { lightTheme, darkTheme } from './redux/Themes';
function App() {
const theme = useSelector(state => state.themeReducer.theme);
const dispatch = useDispatch();
return (
<div className="App">
<Header />
<input type='checkbox' checked={theme.mode === 'light' ? true : false}
onChange={
() => {
if(theme.mode === 'light') {
dispatch(switchTheme(darkTheme))
} else {
dispatch(switchTheme(lightTheme))
}
}} />
<div className="top">
<div className="leftPart">
<Presentation />
<Skills />
</div>
<Timeline />
</div>
<Projects />
<Footer />
</div>
);
}
export default App;
and in themes.js I have my 2 objects which represent the themes :
export const darkTheme = {
mode: 'dark',
PRIMARY_BACKGROUND_COLOR: '#171933',
SECONDARY_BACKGROUND_COLOR: '#1e2144',
TERTIARY_BACKGROUND_COLOR: '#0a0c29',
PRIMARY_TEXT_COLOR: '#eee',
SECONDARY_TEXT_COLOR: '#ccc',
PRIMARY_BORDER_COLOR: '#aaa'
}
export const lightTheme = {
mode: 'light',
PRIMARY_BACKGROUND_COLOR: '#D3CEC8',
SECONDARY_BACKGROUND_COLOR: '#E5DFD9',
TERTIARY_BACKGROUND_COLOR: '#C1BFBC',
PRIMARY_TEXT_COLOR: '#222',
SECONDARY_TEXT_COLOR: '#333',
PRIMARY_BORDER_COLOR: '#555'
}
You can make use of data attributes.
I have done the same in one my project like so :-
[data-color-mode="light"] {
--color-focus-ring: #7daee2;
--color-link-hover: #0039bd;
--color-primary-bg: #eef6ff;
--color-primary-text: #212121;
--color-primary-border: #98b2c9;
--color-secondary-bg: #c3d7f0;
--color-secondary-text: #1a1a1a;
}
[data-color-mode="dark"] {
--color-focus-ring: #5355d4;
--color-link-hover: #4183c4;
--color-primary-bg: #080808;
--color-primary-text: #f1f1f1;
--color-primary-border: #525252;
--color-secondary-bg: #191919;
--color-secondary-text: #d8d5d5;
}
You can add the attribute to your top-level element (assuming div) like so:-
<div className="appContainer" data-color-mode="light" ref={appRef}> ></div>
Now use that appRef to change the data-color-mode attribute as well update the localstorage in one function. Updating the data-color-mode allows you to toggle between css variable colors easily. In my code, I have done this the following way:-
const toggleColorMode = () => {
const nextMode = mode === "light" ? "dark" : "light";
// container is appRef.current only
container?.setAttribute("data-color-mode", nextMode);
setMode(nextMode);
};
I am not using redux for this. Simply React Context API is being used by me but it's doable in your scenario as well.
You can take more reference from the repo - https://github.com/lapstjup/animeccha/tree/main/src
Note - I think there are other routes where people go with CSS-IN-JS but I haven't explored them yet. This solution is one of the pure css ways.
Fun fact - Github has implemented their newest dark mode in a similar way and that's where I got the idea as well. You can inspect their page to see the same attribute name :D.

Meteor and withTracker: why is a component rendered twice?

I have created a bare-bones Meteor app, using React. It uses the three files shown below (and no others) in a folder called client. In the Console, the App prints out:
withTracker
rendering
withTracker
rendering
props {} {}
state null null
In other words, the App component is rendered twice. The last two lines of output indicate that neither this.props nor this.state changed between renders.
index.html
<body>
<div id="react-target"></div>
</body>
main.jsx
import React from 'react'
import { render } from 'react-dom'
import App from './App.jsx'
Meteor.startup(() => {
render(<App/>, document.getElementById('react-target'));
})
App.jsx
import React from 'react'
import { withTracker } from 'meteor/react-meteor-data'
class App extends React.Component {
render() {
console.log("rendering")
return "Rendered"
}
componentDidUpdate(prevProps, prevState) {
console.log("props", prevProps, this.props)
console.log("state", prevState, this.state)
}
}
export default withTracker(() => {
console.log("withTracker")
})(App)
If I change App.jsx to the following (removing the withTracker wrapper), then the App prints only rendering to the Console, and it only does this once.
import React from 'react'
import { withTracker } from 'meteor/react-meteor-data'
export default class App extends React.Component {
render() {
console.log("rendering")
return "Rendered"
}
componentDidUpdate(prevProps, prevState) {
console.log(prevProps, this.props)
console.log(prevState, this.state)
}
}
What is withTracker doing that triggers this second render? Since I cannot prevent it from occurring, can I be sure that any component that uses withTracker will always render twice?
Context: In my real project, I use withTracker to read data from a MongoDB collection, but I want my component to reveal that data only after a props change triggers the component to rerender. I thought that it would be enough to set a flag after the first render, but it seems that I need to do something more complex.
This a "feature", and it's not restricted to Meteor. It's a feature of asynchronous javascript. Data coming from the database arrives after a delay, no matter how quick your server is.
Your page will render immediately, and then again when the data arrives. Your code needs to allow for that.
One way to achieve this is to use an intermediate component (which can display "Loading" until the data arrives). Let's say that you have a component called List, which is going to display your data from a mongo collection called MyThings
const Loading = (props) => {
if (props.loading) return <div>Loading...</div>
return <List {...props}></List>
}
export default withTracker((props) => {
const subsHandle = Meteor.subscribe('all.myThings')
return {
items: MyThings.find({}).fetch(),
loading: !subsHandle.ready(),
}
})(Loading)
It also means that your List component will only ever be rendered with data, so it can use the props for the initial state, and you can set the PropTypes to be isRequired
I hope that helps
Unsure if you're running into the same error I discovered, or if this is just standard React behavior that you're coming into here as suggested by other answers, but:
When running an older (0.2.x) version of react-meteor-data on the 2.0 Meteor, I was seeing two sets of distinct renders, one of which was missing crucial props and causing issues with server publications due to the missing data. Consider the following:
// ./main.js
const withSomethingCount = (C) => (props) => <C { ...props } count={ ... } />
const withPagination = (C) => (props) => <C { ...props } pagination={ ... } />
const withSomething = withTracker((props) => {
console.log('withSomething:', props);
});
// Assume we're rending a "Hello, World" component here.
export const SomeComponent = withSomethingCount(withPagination(withSomething(...)));
// Console
withSomething: { count: 0 }
withSomething: { count: 0, pagination: { ... } }
withSomething: { count: 0 }
withSomething: { count: 0, pagination: { ... } }
For whatever reason, I was seeing not only N render calls but I was seeing N render calls that were missing properties in a duplicate manner. For those reading this and wonder, there was one and only one use of the component, one and only one use of the withTracker HoC, the parent HoCs had no logic that would cause conditional passing of props.
Unfortunately, I have not discovered a root-cause of the bug. However, creating a fresh Meteor application and moving the code over was the only solution which removed the bug. An in-place update of the Meteor application (2.0 to 2.1) and dependencies DID NOT solve the issue... however a fresh installation and running a git mv client imports server did solve my problems.
I've regrettably had to chalk this up to some form of drift due to subsequent Meteor updates over the two years of development.

Set up Storybook to work with Next.js's Link tag

I'm trying to set up Storybook for a Next.js project. I have a component that render the Link tag from Next.js. My problem is that when I load this component, Storybook throws the following error:
Cannot read property 'pageLoader' of null
at Link.handleRef
What does one have to do to get Storybook working with Next.js Routing, specifically rendering the Link tag?
Update: Code that causes the error:
// button-component.js
import Link from 'next/link.js';
import t from 'prop-types';
import React from 'react';
function Button({ as, children, href, ...props }) {
const isExternal = href && href.startsWith('http');
const a = (
<a href={href} {...props}>
{children}
</a>
);
if (href) {
return isExternal ? (
a
) : (
<Link href={href} as={as}>
{a}
</Link>
);
}
return (
<button type="button" {...props}>
{children}
</button>
);
}
Button.propTypes = {
as: t.string,
children: t.node,
href: t.string,
};
export default React.memo(Button);
// button.stories.js
import React from 'react';
import Button from './button-component';
export default {
title: 'Button',
};
export const standardButton = () => <Button>Standard Button</Button>;
export const internalLink = () => <Button href='/buy'>
Internal Link
</Button>;
export const externalLink = () => (
<Button target="_blank" href="https://www.hopin.to">
External Link
</Button>
);
I found an issue reported about this on Next.js's github: https://github.com/zeit/next.js/issues/9951
It was reported only 5 days ago, so you could be having the same issue. The resolution is to upgrade to nextjs v9.1.8-canary.6. Reading more about this and looking at the source code, this is likely your problem. Also, there are more recent canary builds of nextjs, if you want to try something newer.
If that doesn't resolve it, my other guess is that you're getting errors because you're using Link outside of a Next.js page. Next.js may include dependencies for pages, behind the scenes. Link may rely on those dependencies and is throwing an error when they aren't found. If you want to test your components outside of Next.js pages, you could create a custom Link component that tests whether you're in Next.js and only renders Link if you are. For example:
import Link from 'next/link'
import Router from 'next/router'
const CustomLink = ({children, ...otherProps}) => {
const isPage = () => {
// if we're in a next.js route, then Router.router will be set
return Boolean(Router.router)
}
return isPage()
? (<Link {...otherProps}>{children}</Link>)
: children
}
Then use CustomLink instead of Link.
Another solution I found works similar as with next/image. To your .storybook/preview.js add following:
import Link from "next/link";
Object.defineProperty(Link, "default", {
configurable: true,
value: (props) => <a {...props} />,
});

Dynamically load .css based on condition in reactJS application

I have a reactJS application that I want to make available to multiple clients. Each clients has unique color schemes. I need to be able to import the .css file that corresponds to the specific client.
For example, if client 1 logs into the application, I want to import client1.css. if client 2 logs into the application, I want to import client2.css. I will know the client number once I have validated the login information.
The application contains multiple .js files. Every .js file contains the following at the top of the file
import React from 'react';
import { Redirect } from 'react-router-dom';
import {mqRequest} from '../functions/commonFunctions.js';
import '../styles/app.css';
Is there a way to import .css files dynamically for this scenario as opposed to specifying the .css file in the above import statement?
Thank you
Easy - i've delt with similar before.
componentWillMount() {
if(this.props.css1 === true) {
require('style1.css');
} else {
require('style2.css');
}
}
Consider using a cssInJs solution. Popular libraries are: emotion and styled-components but there are others as well.
I generally recommend a cssInJs solution, but for what you are trying to do it is especially useful.
In Emotion for example they have a tool specifically build for this purpose - the contextTheme.
What cssInJs basically means is that instead of using different static css files, use all the power of Javascript, to generate the needed css rules from your javascript code.
A bit late to the party, I want to expand on #Harmenx answer.
require works in development environments only, once it goes to production you're likely to get errors or not see the css file at all. Here are some options if you, or others, encounter this:
Option 1: Using css modules, assign a variable of styles with the response from the import based on the condition.
let styles;
if(this.props.css1 === true) {
//require('style1.css');
import("./style1.module.css").then((res) => { styles = res;});
} else {
//require('style2.css');
import("./style2.module.css").then((res) => { styles = res;});
}
...
<div className={styles.divClass}>...</div>
...
Option 2: using Suspend and lazy load from react
// STEP 1: create components for each stylesheet
// styles1.js
import React from "react";
import "./styles1.css";
export const Style1Variables = (React.FC = () => <></>);
export default Style1Variables ;
// styles2.js
import React from "react";
import "./styles2.css";
export const Style2Variables = (React.FC = () => <></>);
export default Style2Variables ;
// STEP 2: setup your conditional rendering component
import React, {lazy, Suspense} from "react";
const Styles1= lazy(() => import("./styles1"));
const Styles2= lazy(() => import("./styles2"));
export const ThemeSelector = ({ children }) => {
return (
<>
<Suspense fallback={null} />}>
{isClient1() ? <Styles1 /> : <Styles2/>}
</Suspense>
{children}
</>
);
};
// STEP 3: Wrap your app
ReactDOM.render(
<ThemeSelector>
<App />
</ThemeSelector>,
document.getElementById('root')
);
Option 3: Use React Helm which will include a link to the stylesheet in the header based on a conditional
class App extends Component {
render() {
<>
<Helmet>
<link
type="text/css"
rel="stylesheet"
href={isClient1() ? "./styles1.css" : "./styles2.css"}
/>
</Helmet>
...
</>
}
}
Personally, I like option 2 because you can set a variable whichClientIsThis() then modify the code to:
import React, {lazy, Suspense} from "react";
let clientID = whichClientIsThis();
const Styles= lazy(() => import("./`${clientID}`.css")); // change your variable filenames to match the client id.
export const ThemeSelector = ({ children }) => {
return (
<>
<Suspense fallback={null} />}>
<Styles />
</Suspense>
{children}
</>
);
};
ReactDOM.render(
<ThemeSelector>
<App />
</ThemeSelector>,
document.getElementById('root')
);
This way you don't need any conditionals. I'd still recommend lazy loading and suspending so the app has time to get the id and make the "decision" on which stylesheet to bring in.

Resources