Should i use Router.back for default function value in my prop?
Hi, I'm working with Next.js and came across the following problem
I have a component with optional prop onPageBack and wondering if using Router.back as default value for it instead of the hook one is the right choice
import Router from 'next/router';
const MessagePage = ({ onPageBack = Router.back }) => {
return (
<button onClick={onPageBack}>Back</button>
);
};
vs
import { useRouter } from 'next/router';
const MessagePage = ({ onPageBack }) => {
const router = useRouter();
const handlePageBack = onPageBack || router.back;
return (
<button onClick={handlePageBack}>Back</button>
);
};
The first one looks more JS native, but I wonder if using global Router object here, which is not mentioned in the Next.js documentation, is the right choice, as well as I'm not sure how good it's going to do with other native stuff Next.js does with routing under the hood
I should say that this prop is required, as onPageBack has a bit different behavior depends on where the component's used
Related
As the title suggests I'm having difficulty setting up React Testing Library on a project that uses NextJS, GraphQL, Apollo and i18n
Terminal was complaining about the router not being set up so I used next-router-mock to mock it as explained in their documentation and now I'm getting this error which I can't wrap my head around
TypeError: Cannot destructure property 'pageTitle' of '_languageContent.default[locale]' as it is undefined.
// __tests__/index.test.jsx
import { render, screen } from '#testing-library/react'
import Home from '../src/pages/index'
import '#testing-library/jest-dom'
import { QueryClient, QueryClientProvider } from "react-query"
import mockRouter from 'next-router-mock';
jest.mock('next/router', () => require('next-router-mock'));
const queryClient = new QueryClient()
describe('Home', () => {
it('renders a heading', () => {
mockRouter.push("/en-US");
render(
<QueryClientProvider client={queryClient} >
<Home />
</QueryClientProvider>
)
const heading = screen.getByText('stuff');
expect(heading).toBeInTheDocument()
})
})
My assumption is that my test is not receiving the language strings it requires - the language strings are determined by the URL - http://localhost:3000/en-US for English http://localhost:3000/fr-FR for French.
On the page component itself, a languageContent const is fed a variable from useRouter to determine which strings to use
But I have tried to set the URL with mockRouter but this did nothing
Here is the component I am trying to test
const Component = () => {
const [newName, setnewName] = useState('');
const updateName = (max: number) => {
//logic to update newName
};
return (
<>
<div>{newName}</div>
</>
);
};
export default Component;
When ever the newName variable's value changes I want to add some effect in the ui for it. Is there any way that this can be done?
Yes. You can use the useEffect hook to achieve this, and add the newName state as a dependency.
For example, take a look at the following. I will demonstrate through console.log("hey, newName changed") every time the variable state changes.
const Component = () => {
const [newName, setnewName] = useState('');
useEffect(() => {console.log("hey, newName changed!"}, [newName])
const updateName = (max: number) => {
};
return (
<>
<div>{newName}</div>
</>
);
};
export default Component;
Import it with useState.
Now, you may ask "yes, but youre only consoling something out, not actually doing anything with the CSS transition". Rest assured, you can take a similar approach.
The useEffect hook is simply a function that watches for state change. From the callback function, just add your custom css transition class, or fire a function that changes the CSS.
As I am not sure what kind of transition effect you want as you did not specify it in your question, so forgive me for not being able to provide a specific example. I hope this helps you.
use useEffect and dependancy in to change state and using that state you can update class, and then write aniamtion css for that class , hope this will help...
import React from "react";
import "./App.css";
function App() {
const [newName, setnewName] = React.useState('Lorem');
const [transition, setTransition] = React.useState(false)
React.useEffect(()=>{
setnewName('ipsum')
setTransition(true)
},[newName])
return (
<>
<h1 classNane={`${transition?'animate':''}`} >{newName}</h1>
</>
);
}
export default App;
In ReactJs project you can use .storybook/preview.js file to add global decorators and parameters. How to achieve this same behaviour with #storybook/react-native?
What I need is to wrap all my stories with ThemeProvider but the unique way that I found is to wrap individual stories with .addDecorator().
Edit storybook/index.js, by using addDecorator on it.
Example:
import React from 'react'
import { getStorybookUI, configure, addDecorator } from '#storybook/react-native'
import Decorator from './Decorator'
addDecorator(storyFn => (
<Decorator>
{storyFn()}
</Decorator>
))
// import stories
configure(() => {
require('../stories')
}, module)
const StorybookUI = getStorybookUI({ onDeviceUI: true })
export default StorybookUI;;
Found an updated answer in Storybook's own documentation.
// .storybook/preview.js
import React from 'react';
export const decorators = [
(Story) => (
<div style={{ margin: '3em' }}>
<Story />
</div>
),
];
As of June 2021, using storybook v5.3.25, the above answer does not work. However I have managed to figure out a solution.
Decorators must be added to the storybook/index.js file in the following format:
import { ThemeDecorator } from './storybook/ThemeDecorator';
addDecorator(withKnobs); // inbuilt storybook addon decorator
addDecorator(ThemeDecorator);// custom decorator
configure(() => {
loadStories();
}, module);
in this instance, ThemeDecorator.js is a simple wrapper component that renders your story, it would look something like this:
import React from 'react';
import { Provider } from 'theme-provider';
export const ThemeDecorator = (getStory) => (
<Provider>{getStory()}</Provider>
);
Importantly, the addDecorator function expects a React component (not a wrapper function as other examples claim), that it will render, with its props being a reference to an individual story at runtime.
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
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.