Uncaught Error: invariant expected app router to be mounted - javascript

I've got an error while do migration to next 13 on my old project written in next 12.
Console Error Log
I can't find fault in my code for that errors.
And I googled it but i can't find any solution for this.
It don't explains any errors for my code.
How can I solve it?
I couldn't try anything because it do not explains any error for my code.
Please let me know what is the origin for that error.
Thank you.
++++++++++
navigation.js
function useRouter() {
const router = (0, _react).useContext(_appRouterContext.AppRouterContext);
if (router === null) {
throw new Error('invariant expected app router to be mounted');
}
return router;
}
i think "next/navigation" contains this file (navigation.js)
this error threw when router is null, but i still can't know why router is null.
+++++++++++ layout.jsx
"use client";
import { motion, AnimatePresence } from "framer-motion";
import "animate.css";
import { useRouter } from "next/navigation";
import LoadingSpinner from "../components/layout/media/LoadingSpinner";
import Users from "../class/Users.class";
import { useEffect } from "react";
import create from "zustand";
import Head from "next/head";
import Image from "next/image";
import NavBar from "../components/layout/NavBar";
import SubTransition from "../components/transition/SubTransition";
import LoginModal from "../components/layout/LoginModal";
import "../styles/betconstruct_icons.css";
import "../styles/global.css";
const useStore = create(() => ({
isShowLoginModal: false,
isLoading: true,
}));
//default layout
function MainLayout({ children }) {
useEffect(() => {
Users.checkToken().then((res) => {
if (res) {
console.log("token is valid");
} else {
console.log("token is invalid");
}
LoadingDone();
});
//router.events.on("routeChangeStart", (url) => {
// LoadingNow();
//});
//router.events.on("routeChangeComplete", () => LoadingDone());
//router.events.on("routeChangeError", () => LoadingDone());
if (router.pathname === "/") {
document.querySelector("body").classList.add("layout-bc");
document.querySelector("body").classList.add("theme-default");
document.querySelector("body").classList.add("smart-panel-is-visible");
document.querySelector("body").classList.add("betslip-Hidden");
document.querySelector("body").classList.add("is-home-page");
}
if (router.pathname !== "/") {
document.querySelector("body").classList.add("layout-bc");
document.querySelector("body").classList.add("theme-default");
document.querySelector("body").classList.add("smart-panel-is-visible");
document.querySelector("body").classList.add("betslip-Hidden");
document.querySelector("body").classList.add("is-home-page");
}
}, []);
const animate = {
initial: {
opacity: 0,
transition: `transform 0.24s ease`,
},
animate: {
opacity: 1,
transition: `transform 0.24s ease`,
},
exit: {
opacity: 0,
transition: `transform 0.24s ease`,
},
};
const animateFlyIn = {
initial: {
opacity: 0,
x: 100,
transition: `transform 0.24s ease`,
},
animate: {
opacity: 1,
x: 0,
transition: `transform 0.24s ease`,
},
exit: {
opacity: 0,
x: 100,
transition: `transform 0.24s ease`,
},
};
const { isShowLoginModal, isLoading } = useStore();
const openLoginModal = () => {
useStore.setState({ isShowLoginModal: true });
};
const hideLoginModal = () => {
useStore.setState({ isShowLoginModal: false });
};
const LoadingNow = () => {
useStore.setState({ isLoading: true });
};
const LoadingDone = () => {
useStore.setState({ isLoading: false });
};
const router = useRouter();
return (
<>
<AnimatePresence exitBeforeEnter mode={"wait"}>
{isLoading ? (
<motion.div
key={router.route}
initial={animate.initial}
animate={animate.animate}
exit={animate.exit}
>
<LoadingSpinner router={router} />
</motion.div>
) : null}
{isShowLoginModal && (
<LoginModal
openLoginModal={openLoginModal}
isShowLoginModal={isShowLoginModal}
hideLoginModal={hideLoginModal}
LoadingNow={LoadingNow}
LoadingDone={LoadingDone}
/>
)}
</AnimatePresence>
<NavBar
isLoading={isLoading}
isShowLoginModal={isShowLoginModal}
openLoginModal={openLoginModal}
hideLoginModal={hideLoginModal}
LoadingNow={LoadingNow}
LoadingDone={LoadingDone}
router={router}
/>
<SubTransition>
<div className="layout-content-holder-bc">{children}</div>
</SubTransition>
</>
);
}
export default MainLayout;
+++ This error not occurs for /pages directory. only occurs in using /app directory

Happened to me when layout.tsx didn't have <body> tag.

While transferring the files to the new app the same error popped up which led me to believe it was something in the code I copied over. The issue was in my layout.tsx file that was causing hydration / mounting issues because of a dom mismatch.
Old layout causing bug
export default function RootLayout({ children}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<div className={styles.container}>
<header className={styles.header}>
<>
<Image
priority
src="/images/profile.jpg"
className={utilStyles.borderCircle}
height={144}
width={144}
alt=""
/>
<h1 className={utilStyles.heading2Xl}>{name}</h1>
</>
</header>
<main>{children}</main>
</div>
</html>
);
Fixed code in layout.tsx
export default function RootLayout({ children}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head className={styles.header}>
</head>
<body>{children}</body>
</html>
);
}
Even better: reference layout from beta docs
export default function RootLayout({ children }) {
return (
<html lang="en">
{
}
<head />
<body>{children}</body>
</html>
);
}
**Errors for reference if anyone else is encountering this problem.
Uncaught Error: invariant expected app router to be mounted
react-dom.development.js:21494 The above error occurred in the component:
at HotReload (webpack-internal:///./node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js:19:11)**

The top-most layout is called the Root Layout. This required layout is shared across all pages in an application. Root layouts must contain html and body tags.
export default function RootLayout({ children }: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
Sources:
https://beta.nextjs.org/docs/routing/pages-and-layouts#layouts
https://beta.nextjs.org/docs/routing/pages-and-layouts#root-layout-required

Related

How do I test the fallback component for the ErrorBoundary?

I have this component:
import React, { lazy, Suspense } from 'react';
import { ErrorBoundary } from '../ErrorBoundary';
const FALLBACK = <svg aria-label="" data-testid="icon-fallback" viewBox="0 0 21 21" />;
const ERROR = (
<svg data-testid="icon-notdef" viewBox="0 0 21 21">
<path d="M0.5,0.5v20h20v-20H0.5z M9.1,10.5l-6.6,6.6V3.9L9.1,10.5z M3.9,2.5h13.2l-6.6,6.6L3.9,2.5z M10.5,11.9l6.6,6.6H3.9 L10.5,11.9z M11.9,10.5l6.6-6.6v13.2L11.9,10.5z" />
</svg>
);
export const Icon = ({ ariaLabel, ariaHidden, name, size }) => {
const LazyIcon = lazy(() => import(`../../assets/icons/${size}/${name}.svg`));
return (
<i aria-hidden={ ariaHidden }>
<ErrorBoundary fallback={ ERROR }>
<Suspense fallback={ FALLBACK }>
<LazyIcon aria-label={ ariaLabel } data-testid="icon-module" />
</Suspense>
</ErrorBoundary>
</i>
);
};
I’m trying to test the condition where an SVG is passed in that doesn’t exist, in turn rendering the <ErrorBoundary /> fallback. The ErrorBoundary works in the browser, but not in my test.
This is the failing test:
test('shows notdef icon', async () => {
const { getByTestId } = render(<Icon name='doesnt-exist' />);
const iconModule = await waitFor(() => getByTestId('icon-notdef'));
expect(iconModule).toBeInTheDocument();
});
I get this error message:
TestingLibraryElementError: Unable to find an element by: [data-testid="icon-notdef"]”.
How do I access ErrorBoundary fallback UI in my test?
Edit
This is the code for the <ErrorBoundary /> component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = {
error: '',
errorInfo: '',
hasError: false,
};
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error({ error, errorInfo });
this.setState({ error, errorInfo });
}
render() {
const { children, fallback } = this.props;
const { error, errorInfo, hasError } = this.state;
// If there is an error AND a fallback UI is passed in…
if (hasError && fallback) {
return fallback;
}
// Otherwise if there is an error with no fallback UI…
if (hasError) {
return (
<details className="error-details">
<summary>There was an error.</summary>
<p style={ { margin: '12px 0 0' } }>{error && error.message}</p>
<pre>
<code>
{errorInfo && errorInfo.componentStack.toString()}
</code>
</pre>
</details>
);
}
// Finally, render the children.
return children;
}
}
ErrorBoundary.propTypes = {
children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
fallback: PropTypes.node,
};
… and this is the full error with DOM that I get for the test:
shows notdef icon
TestingLibraryElementError: Unable to find an element by: [data-testid="icon-notdef"]
<body>
<div>
<i
aria-hidden="false"
class="Icon Icon--sm"
>
<span
aria-label=""
data-testid="icon-module"
/>
</i>
</div>
</body>
<html>
<head />
<body>
<div>
<i
aria-hidden="false"
class="Icon Icon--sm"
>
<span
aria-label=""
data-testid="icon-module"
/>
</i>
</div>
</body>
</html>Error: Unable to find an element by: [data-testid="icon-notdef"]
Lastly, my SVG mock:
import React from 'react';
const SvgrMock = React.forwardRef(
function mySVG(props, ref) {
return <span { ...props } ref={ ref } />;
},
);
export const ReactComponent = SvgrMock;
export default SvgrMock;
As discussed in the comments, it is most likely the mock is avoiding the error. Try re mocking the SVG files with a new mock throwing an error.
// tests that require unmocking svg files
describe('non existent svg', () => {
beforeAll(() => {
jest.mock('.svg', () => {
throw new Error('file not found')
});
});
test('shows notdef icon', async () => {
const { getByTestId } = render(<Icon name='doesnt-exist' />);
const iconModule = await waitFor(() => getByTestId('icon-notdef'));
expect(iconModule).toBeInTheDocument();
});
afterAll(() => jest.unmock('.svg'))
})
It is necessary to wrap it to ensure the SVG files are re-mocked only during the test (beforeAll - afterAll) to not interfere with the rest of the tests.

window is not defined - react-draft-wysiwyg used with next js (ssr)

I am working on a rich text editor used for converting plain html to editor content with next js for ssr. I got this error window is not defined so I search a solution to this github link
It used a dynamic import feature of next js.
Instead of importing the Editor directly import { Editor } from "react-draft-wysiwyg";
It uses this code to dynamically import the editor
const Editor = dynamic(
() => {
return import("react-draft-wysiwyg").then(mod => mod.Editor);
},
{ ssr: false }
);
But still I'm getting this error though I already installed this react-draft-wysiwyg module
ModuleParseError: Module parse failed: Unexpected token (19:9)
You may need an appropriate loader to handle this file type.
| import dynamic from "next/dynamic";
| var Editor = dynamic(function () {
> return import("react-draft-wysiwyg").then(function (mod) {
| return mod.Editor;
| });
And this is my whole code
import React, { Component } from "react";
import { EditorState } from "draft-js";
// import { Editor } from "react-draft-wysiwyg";
import dynamic from "next/dynamic";
const Editor = dynamic(
() => {
return import("react-draft-wysiwyg").then(mod => mod.Editor);
},
{ ssr: false }
);
class MyEditor extends Component {
constructor(props) {
super(props);
this.state = { editorState: EditorState.createEmpty() };
}
onEditorStateChange = editorState => {
this.setState({ editorState });
};
render() {
const { editorState } = this.state;
return (
<div>
<Editor
editorState={editorState}
wrapperClassName="rich-editor demo-wrapper"
editorClassName="demo-editor"
onEditorStateChange={this.onEditorStateChange}
placeholder="The message goes here..."
/>
</div>
);
}
}
export default MyEditor;
Please help me guys. Thanks.
Here is a workaround
import dynamic from 'next/dynamic'
import { EditorProps } from 'react-draft-wysiwyg'
// install #types/draft-js #types/react-draft-wysiwyg and #types/draft-js #types/react-draft-wysiwyg for types
const Editor = dynamic<EditorProps>(
() => import('react-draft-wysiwyg').then((mod) => mod.Editor),
{ ssr: false }
)
The dynamic one worked for me
import dynamic from 'next/dynamic';
const Editor = dynamic(
() => import('react-draft-wysiwyg').then((mod) => mod.Editor),
{ ssr: false }
)
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
const TextEditor = () => {
return (
<>
<div className="container my-5">
<Editor
toolbarClassName="toolbarClassName"
wrapperClassName="wrapperClassName"
editorClassName="editorClassName"
/>
</div>
</>
)
}
export default TextEditor
Draft.js WYSWYG with Next.js and Strapi Backend, Create and View Article with Image Upload
import React, { Component } from 'react'
import { EditorState, convertToRaw } from 'draft-js';
import draftToHtml from 'draftjs-to-html';
import dynamic from 'next/dynamic';
const Editor = dynamic(
() => import('react-draft-wysiwyg').then(mod => mod.Editor),
{ ssr: false })
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
export default class ArticleEditor extends Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty()
};
}
onEditorStateChange = (editorState) => {
this.setState({
editorState,
});
this.props.handleContent(
convertToRaw(editorState.getCurrentContent()
));
};
uploadImageCallBack = async (file) => {
const imgData = await apiClient.uploadInlineImageForArticle(file);
return Promise.resolve({ data: {
link: `${process.env.NEXT_PUBLIC_API_URL}${imgData[0].formats.small.url}`
}});
}
render() {
const { editorState } = this.state;
return (
<>
<Editor
editorState={editorState}
toolbarClassName="toolbar-class"
wrapperClassName="wrapper-class"
editorClassName="editor-class"
onEditorStateChange={this.onEditorStateChange}
toolbar={{
options: ['inline', 'blockType', 'fontSize', 'fontFamily', 'list', 'textAlign', 'colorPicker', 'link', 'embedded', 'emoji', 'image', 'history'],
inline: { inDropdown: true },
list: { inDropdown: true },
textAlign: { inDropdown: true },
link: { inDropdown: true },
history: { inDropdown: true },
image: {
urlEnabled: true,
uploadEnabled: true,
uploadCallback: this.uploadImageCallBack,
previewImage: true,
alt: { present: false, mandatory: false }
},
}}
/>
<textarea
disabled
value={draftToHtml(convertToRaw(editorState.getCurrentContent()))}
/>
</>
)
}
}
Try to return the Editor after React updates the DOM using useEffect hook. For instance:
const [editor, setEditor] = useState<boolean>(false)
useEffect(() => {
setEditor(true)
})
return (
<>
{editor ? (
<Editor
editorState={editorState}
wrapperClassName="rich-editor demo-wrapper"
editorClassName="demo-editor"
onEditorStateChange={this.onEditorStateChange}
placeholder="The message goes here..."
/>
) : null}
</>
)

Framer Motion AnimatePresence don't render content on Next.js

I have an issue with the AnimatePresence,used for page transition.
All work properly,except the fact when i load some pages (not all pages),the content of the page don't display,even if the url is correctly pushed,and i have to manually reload the page to see the content.
Here's my _app.js:
import '../assets/css/style.css';
import { AnimatePresence, motion } from 'framer-motion';
const pageVariants = {
pageInitial: {
backgroundColor: 'black',
opacity: 0
},
pageAnimate: {
backgroundColor: 'transparent',
opacity: 1
},
pageExit: {
backgroundColor: 'black',
opacity: 0
}
}
const pageMotionProps = {
initial: 'pageInitial',
animate: 'pageAnimate',
exit: 'pageExit',
variants: pageVariants
}
function handleExitComplete() {
if (typeof window !== 'undefined') {
window.scrollTo({ top: 0 })
}
}
const App = ({ Component, pageProps, router }) => {
return (
<AnimatePresence exitBeforeEnter onExitComplete={handleExitComplete}>
<motion.div key={router.route} {...pageMotionProps}>
<Component {...pageProps}/>
</motion.div>
</AnimatePresence>
)
export default App
And here the page,created with dinamic routing and a static approach:
import ReactMarkdown from 'react-markdown'
import { getArticles, getArticle, getCategories } from '../../lib/api'
import Layout from '../../components/layout/layout'
const Article = ({ article, categories }) => {
const imageUrl = article.image.url.startsWith('/')
? process.env.API_URL + article.image.url
: article.image.url
return (
<Layout categories={categories}>
<h1>{article.title}</h1>
<div>
<ReactMarkdown source={article.content} />
</div>
</Layout>
)
}
export async function getStaticPaths() {
const articles = (await getArticles()) || []
return {
paths: articles.map((article) => ({
params: {
id: article.id,
},
})),
fallback: false,
}
}
export async function getStaticProps({ params }) {
const article = (await getArticle(params.id)) || []
const categories = (await getCategories()) || []
return {
props: { article, categories },
unstable_revalidate: 1,
}
}
export default Article
Note: Some pages work perfectly,some pages need to be reloaded to show the content,and when loaded,the animation start without problems.
I don't know if it's important,but i'm fetching data with GraphQL

onClick passing in tests, but failing when physically clicked

This is my Accordion component
import React, {Component, Fragment} from 'react';
import ArrowTemplate from "./ArrowTemplate";
import ContentTemplate from "./ContentTemplate";
class Accordion extends Component {
constructor(props) {
super(props);
this.state = {isAccordionExpanded: false};
this.foldAccordion = this.foldAccordion.bind(this);
this.expandAccordion = this.expandAccordion.bind(this);
}
expandAccordion() {
console.log(1);
this.setState({isAccordionExpanded: true});
}
foldAccordion() {
console.log(2);
this.setState({isAccordionExpanded: false});
}
render() {
const {state} = this;
const {isAccordionExpanded} = state;
if (isAccordionExpanded === false) {
return (
<Fragment>
<ArrowTemplate
aria={`aria-expanded="true"`}
onClick={this.expandAccordion}
direction={'down'}
color={'black'}
styles={'background:yellow'}
/>
</Fragment>
);
} else if (isAccordionExpanded === true) {
return (
<Fragment>
<ArrowTemplate
aria={`aria-expanded="true"`}
onClick={this.foldAccordion}
color={'black'}
direction={'up'}
/>
<ContentTemplate/>
</Fragment>
);
}
}
}
export default Accordion;
this is the ArrowTemplate
import React from "react";
import BlackDownArrowSVG from './svgs/black-down-arrow.svg';
import WhiteDownArrowSVG from './svgs/white-down-arrow.svg';
import styled from 'styled-components';
import PropTypes from 'prop-types';
ArrowTemplate.propTypes = {
color: PropTypes.string.isRequired,
direction: PropTypes.string.isRequired,
styles: PropTypes.string,
aria: PropTypes.string.isRequired,
};
function ArrowTemplate(props) {
const {color, direction, styles, aria} = props;
const StyledArrowTemplate = styled.img.attrs({
src: color.toLowerCase() === "black" ? BlackDownArrowSVG : WhiteDownArrowSVG,
aria,
})`
${direction.toLowerCase() === "up" ? "translate: rotate(180deg)" : ""}
${styles}
`;
return <StyledArrowTemplate/>;
}
export default ArrowTemplate;
And here are my related tests
describe("<Accordion/>",
() => {
let wrapper;
beforeEach(
() => {
wrapper = shallow(
<Accordion/>
);
}
);
it('should have one arrow at start',
function () {
expect(wrapper.find(ArrowTemplate)).toHaveLength(1);
}
);
it('should change state onClick',
function () {
wrapper.find(ArrowTemplate).simulate("click");
expect(wrapper.state().isAccordionExpanded).toEqual(true);
}
);
it('should call FoldAccordionMock onClick',
function () {
wrapper.setState({isAccordionExpanded: true});
wrapper.find(ArrowTemplate).simulate("click");
expect(wrapper.state().isAccordionExpanded).toEqual(false);
}
);
it('should display content if isAccordionExpanded = true',
function () {
wrapper.setState({isAccordionExpanded: true});
expect(wrapper.find(ContentTemplate).exists()).toEqual(true);
}
);
it('should hide content if isAccordionExpanded = false',
function () {
expect(wrapper.find(ContentTemplate).exists()).toEqual(false);
}
);
}
);
So the problem is that when I tun the tests .simulate(click) seems to work, and all tests pass. But when I click the component myself, nothing happens. Not even a console log. Changing the onClick to onClick={()=>console.log(1)} doesn't work either. Any ideas what's wrong?
StyledArrowTemplate inner component does not know anything about onClick.
ArrowTemplate doesn't know what onClick means, it's just another arbitrary prop to it.
But, if you do as #ravibagul91 said in their comment, you should pass down onClick again, StyledArrowTemplate might know what onClick means.
So just add <StyledArrowTemplate onClick={props.onClick}/>
Accordion Component
import React, { Component, Fragment } from "react";
import ArrowTemplate from "./ArrowTemplate";
import ContentTemplate from "./ContentTemplate";
class Accordion extends Component {
constructor(props) {
super(props);
this.state = { isAccordionExpanded: false };
}
toggleAccordian = () => {
console.log(1);
this.setState({ isAccordionExpanded: !isAccordionExpanded });
};
render() {
const { state } = this;
const { isAccordionExpanded } = state;
if (isAccordionExpanded) {
return (
<Fragment>
<ArrowTemplate
aria={`aria-expanded="true"`}
onClick={() => this.toggleAccordian()}
color={"black"}
direction={isAccordionExpanded ? "up" : "down"}
styles={`background:{isAccordionExpanded ? 'yellow' : ''}`}
/>
<ContentTemplate />
</Fragment>
);
}
}
}
export default Accordion;

React: How to update one component, when something happens on another component

I have an application with a table, the table has a checkbox to set a Tenant as Active, this variable is a global variable that affects what the user does in other screens of the application.
On the top right of the application, I have another component called ActiveTenant, which basically shows in which tenant the user is working at the moment.
The code of the table component is like this:
import React, { Component } from 'react';
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
class ListTenants extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
fetchData = () => {
adalApiFetch(fetch, "/Tenant", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
const results= responseJson.map(row => ({
key: row.id,
TestSiteCollectionUrl: row.TestSiteCollectionUrl,
TenantName: row.TenantName,
Email: row.Email
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render() {
const columns = [
{
title: 'TenantName',
dataIndex: 'TenantName',
key: 'TenantName',
},
{
title: 'TestSiteCollectionUrl',
dataIndex: 'TestSiteCollectionUrl',
key: 'TestSiteCollectionUrl',
},
{
title: 'Email',
dataIndex: 'Email',
key: 'Email',
}
];
// rowSelection object indicates the need for row selection
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
if(selectedRows[0].TenantName != undefined){
console.log(selectedRows[0].TenantName);
const options = {
method: 'post'
};
adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+selectedRows[0].TenantName.toString(), options)
.then(response =>{
if(response.status === 200){
Notification(
'success',
'Tenant set to active',
''
);
}else{
throw "error";
}
})
.catch(error => {
Notification(
'error',
'Tenant not activated',
error
);
console.error(error);
});
}
},
getCheckboxProps: record => ({
type: Radio
}),
};
return (
<Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} />
);
}
}
export default ListTenants;
And the code of the ActiveTenant component its also very simple
import React, { Component } from 'react';
import authAction from '../../redux/auth/actions';
import { adalApiFetch } from '../../adalConfig';
class ActiveTenant extends Component {
constructor(props) {
super(props);
this.state = {
tenant: ''
};
}
fetchData = () => {
adalApiFetch(fetch, "/Tenant/GetActiveTenant", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
this.setState({ tenant: responseJson.TenantName });
}
})
.catch(error => {
this.setState({ tenant: '' });
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render() {
return (
<div>You are using tenant: {this.state.tenant }</div>
);
}
}
export default ActiveTenant;
The problem is, if I have multiple tenants on my database registered and I set them to active, the server side action occurs, and the state is changed, however on the top right its still showing the old tenant as active, UNTIL I press F5 to refresh the browser.
How can I achieve this?
For the sake of complete understandment of my code I will need to paste below other components:
TopBar which contains the active tenant
import React, { Component } from "react";
import { connect } from "react-redux";import { Layout } from "antd";
import appActions from "../../redux/app/actions";
import TopbarUser from "./topbarUser";
import TopbarWrapper from "./topbar.style";
import ActiveTenant from "./activetenant";
import TopbarNotification from './topbarNotification';
const { Header } = Layout;
const { toggleCollapsed } = appActions;
class Topbar extends Component {
render() {
const { toggleCollapsed, url, customizedTheme, locale } = this.props;
const collapsed = this.props.collapsed && !this.props.openDrawer;
const styling = {
background: customizedTheme.backgroundColor,
position: 'fixed',
width: '100%',
height: 70
};
return (
<TopbarWrapper>
<Header
style={styling}
className={
collapsed ? "isomorphicTopbar collapsed" : "isomorphicTopbar"
}
>
<div className="isoLeft">
<button
className={
collapsed ? "triggerBtn menuCollapsed" : "triggerBtn menuOpen"
}
style={{ color: customizedTheme.textColor }}
onClick={toggleCollapsed}
/>
</div>
<ul className="isoRight">
<li
onClick={() => this.setState({ selectedItem: 'notification' })}
className="isoNotify"
>
<TopbarNotification locale={locale} />
</li>
<li>
<ActiveTenant />
</li>
<li
onClick={() => this.setState({ selectedItem: "user" })}
className="isoUser"
>
<TopbarUser />
<div>{ process.env.uiversion}</div>
</li>
</ul>
</Header>
</TopbarWrapper>
);
}
}
export default connect(
state => ({
...state.App.toJS(),
locale: state.LanguageSwitcher.toJS().language.locale,
customizedTheme: state.ThemeSwitcher.toJS().topbarTheme
}),
{ toggleCollapsed }
)(Topbar);
App.js which contains the top bar
import React, { Component } from "react";
import { connect } from "react-redux";
import { Layout, LocaleProvider } from "antd";
import { IntlProvider } from "react-intl";
import { Debounce } from "react-throttle";
import WindowResizeListener from "react-window-size-listener";
import { ThemeProvider } from "styled-components";
import authAction from "../../redux/auth/actions";
import appActions from "../../redux/app/actions";
import Sidebar from "../Sidebar/Sidebar";
import Topbar from "../Topbar/Topbar";
import AppRouter from "./AppRouter";
import { siteConfig } from "../../settings";
import themes from "../../settings/themes";
import { themeConfig } from "../../settings";
import AppHolder from "./commonStyle";
import "./global.css";
import { AppLocale } from "../../dashApp";
import ThemeSwitcher from "../../containers/ThemeSwitcher";
const { Content, Footer } = Layout;
const { logout } = authAction;
const { toggleAll } = appActions;
export class App extends Component {
render() {
const { url } = this.props.match;
const { locale, selectedTheme, height } = this.props;
const currentAppLocale = AppLocale[locale];
return (
<LocaleProvider locale={currentAppLocale.antd}>
<IntlProvider
locale={currentAppLocale.locale}
messages={currentAppLocale.messages}
>
<ThemeProvider theme={themes[themeConfig.theme]}>
<AppHolder>
<Layout style={{ height: "100vh" }}>
<Debounce time="1000" handler="onResize">
<WindowResizeListener
onResize={windowSize =>
this.props.toggleAll(
windowSize.windowWidth,
windowSize.windowHeight
)
}
/>
</Debounce>
<Topbar url={url} />
<Layout style={{ flexDirection: "row", overflowX: "hidden" }}>
<Sidebar url={url} />
<Layout
className="isoContentMainLayout"
style={{
height: height
}}
>
<Content
className="isomorphicContent"
style={{
padding: "70px 0 0",
flexShrink: "0",
background: "#f1f3f6",
position: "relative"
}}
>
<AppRouter url={url} />
</Content>
<Footer
style={{
background: "#ffffff",
textAlign: "center",
borderTop: "1px solid #ededed"
}}
>
{siteConfig.footerText}
</Footer>
</Layout>
</Layout>
<ThemeSwitcher />
</Layout>
</AppHolder>
</ThemeProvider>
</IntlProvider>
</LocaleProvider>
);
}
}
export default connect(
state => ({
auth: state.Auth,
locale: state.LanguageSwitcher.toJS().language.locale,
selectedTheme: state.ThemeSwitcher.toJS().changeThemes.themeName,
height: state.App.toJS().height
}),
{ logout, toggleAll }
)(App);
I think this should be enough to illustrate my question.
You're not using redux correctly there. You need to keep somewhere in your redux state your active tenant; That information will be your single source of truth and will be shared among your components throughout the app. Every component that will need that information will be connected to that part of the state and won't need to hold an internal state for that information.
Actions, are where you would call your API to get your active tenant information or set a new tenant as active. These actions wil call reducers that will change your redux state (That includes your active tenant information) and those redux state changes will update your app components.
You should probably take some time to read or re-read the redux doc, it's short and well explained !

Categories

Resources