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

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

Related

Uncaught Error: invariant expected app router to be mounted

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

Dynamic table of contents won't deploy on Vercel (... but works on localhost)

I'm trying to add a dynamic table of contents to my blogpage in next.js.
The code is running perfectly on my localhost, but as soon as I'm deploying it to vercel I got this error:
TypeError: Cannot read properties of undefined (reading 'content')
at BlogPost (/vercel/path0/.next/server/pages/posts/[slug].js:111:23)
at Jc (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:64:191)
at Mc (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:66:253)
at Z (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:71:89)
at Nc (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:73:98)
at Mc (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:67:131)
at Z (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:71:89)
at Mc (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:70:13)
at Z (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:71:89)
at Nc (/vercel/path0/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:73:98)
I found out that the build failure is produced by the command .processSync on line 85 (I wrote a comment there). Sadly I'm unable to fix this...
Any suggestions and help why this happens?
Here is the full code:
( I delete the grahpcms route when creating the GraphQLClient for safety, so that's not the failure here.)
import { GraphQLClient, gql } from "graphql-request";
import { useRouter } from "next/router";
import { unified } from "unified";
import rehypeParse from "rehype-parse/lib";
import rehypeStringify from "rehype-stringify/lib";
import { visit } from "unist-util-visit";
import parameterize from "parameterize";
const graphcms = new GraphQLClient();
const QUERY = gql`
query Post($slug: String!) {
post(where: { slug: $slug }) {
title
id
content {
html
}
datePublish
coverPhoto {
url
}
datePublish
}
}
`;
const SLUGLIST = gql`
{
posts {
slug
}
}
`;
export async function getStaticPaths() {
const { posts } = await graphcms.request(SLUGLIST);
return {
paths: posts.map((post) => ({ params: { slug: post.slug } })),
fallback: true,
};
}
export async function getStaticProps({ params }) {
const slug = params.slug;
const data = await graphcms.request(QUERY, { slug });
const post = data.post;
return {
props: {
post,
},
};
}
export default function BlogPost({ post }) {
const router = useRouter();
var toc = [];
//Forms the HTML String into a tree that we can add logic too
//Then forms that tree back into html string
const newContent = unified()
.use(rehypeParse, {
fragment: true,
})
.use(() => {
return (tree) => {
visit(tree, "element", (node) => {
if (node.tagName === "h2") {
const id = parameterize(node.children[0].value);
node.properties.id = id;
toc.push({
id: node.properties.id,
title: node.children[0].value,
});
console.log("id", id);
}
});
};
})
.use(rehypeStringify)
//THIS IS WHERE THE DELPLOYMENT FAILS
.processSync(post.content.html)
.toString();
if (router.isFallback) {
return <h2>Loading</h2>;
}
return (
<div>
<header>
<h1>{post.title}</h1>
<img
src={post.coverPhoto.url}
width="100%"
style={{ borderRadius: "1rem" }}></img>
<span>Published: {post.datePublish}</span>
</header>
<main>
<div>
{toc.map(({ id, title }) => {
return (
<li style={{ listStyle: "none" }} key={id}>
<a style={{ fontSize: "1.1rem" }} href={`#${id}`}>
<b> {title}</b>
</a>
</li>
);
})}
</div>
<div
className="blogpost"
dangerouslySetInnerHTML={{ __html: newContent }}
/>
</main>
</div>
);
}
Thank you very much!
I would try to use operator of optional changing like this:
post?.content?.html
Second step is to build project on your computer not to wait for building on vercel and detect another error.
P. S. You can handle undefined props by if statement but only don't forget to place it before main return statement and after all hooks.

User preferred language saved but the page content language didn't change only after I reload

I'm using ant design pro.
The idea is In the platform we have 2 languages to choose from Fr(French) and En(English),
I want the user when he logs in and change the language to English for example when he logs out and log in again the language should be saved to English so he would be able to see the content in English, I managed to do it, when i login in the backend the preferedLanguage = en, the language toggle in the also changes to en, the only problem the web page content stays in French it's only change in English when i reload the page.
I think the issue is related to the login page, the login page is set to French as default , let's say my preferred language now is English, if i login from the login page. The page content loaded in French only changes when i reload.
-This is the umijs documentation : https://umijs.org/docs/max/i18n#setlocale-%E8%AE%BE%E7%BD%AE%E8%AF%AD%E8%A8%80
-LanguageDropdown (the toggle where you select the language (Fr or En)
import type { FC } from 'react';
import React, { useState, useEffect } from 'react';
import { Menu, Dropdown } from 'antd';
import { CaretDownOutlined, CaretUpOutlined, GlobalOutlined } from '#ant-design/icons';
import styles from './index.less';
import { setLocale, getLocale, getAllLocales } from 'umi';
interface Props {
setUpdateLang: any;
currentLang: string;
}
const LanguageDropdown: FC<Props> = ({ currentLang, setUpdateLang }) => {
const [langvisible, setLangVisible] = useState<boolean>(false);
const [localesList, setLocalesList] = useState<any[]>([]);
const [currentLocale, setCurrentLocale] = useState<string>('');
// useEffect(() => {
// if (currentLang) {
// setLocalesList(getAllLocales());
// setCurrentLocale(getLocale());
// setLocale(currentLang === 'fr' ? 'fr-FR' : 'en-US');
// }
// alert(currentLang);
// alert(getLocale());
// }, [currentLang]);
useEffect(() => {
setLocalesList(getAllLocales());
setCurrentLocale(getLocale());
if (currentLang) {
// alert(222);
const selectedLang = currentLang === 'fr' ? 'fr-FR' : 'en-US';
// setNewLang(selectedLang);
setLocale(selectedLang, false);
setCurrentLocale(getLocale());
}
}, [currentLang]);
const onLangVisibleChange = (visibleLang: boolean) => {
setLangVisible(visibleLang);
};
const langNameHandler = (lang: string) => {
if (lang === 'en-US') return 'EN';
else if (lang === 'fr-FR') return 'FR';
return 'FR';
};
const setNewLang = (lang: string) => {
setUpdateLang({ lang: langNameHandler(lang).toLocaleLowerCase(), updated: true });
setLocale(lang);
};
const Langmenu = (
<Menu>
{localesList?.map((lang: any) => (
<Menu.Item key={lang}>
<a onClick={() => setNewLang(lang)}>{langNameHandler(lang)}</a>
</Menu.Item>
))}
</Menu>
);
return (
<div className={styles.profileDropdownContainer}>
<Dropdown
overlay={Langmenu}
placement="bottomLeft"
trigger={['click']}
onVisibleChange={onLangVisibleChange}
className={styles.dropdown}
>
<div className={styles.langContainer}>
<span>
<GlobalOutlined /> {langNameHandler(currentLocale)}
</span>
{!langvisible ? <CaretDownOutlined /> : <CaretUpOutlined />}
</div>
</Dropdown>
</div>
);
};
export default LanguageDropdown;
-RightContext
import { Tag } from 'antd';
import type { Settings as ProSettings } from '#ant-design/pro-layout';
import React, { useEffect, useState } from 'react';
import type { ConnectProps } from 'umi';
import type { Dispatch } from 'umi';
import { connect } from 'umi';
import type { ConnectState } from '#/models/connect';
import Avatar from './AvatarDropdown';
import styles from './index.less';
import LanguageDropdown from '../languageDropdown';
import moment from 'moment';
export type GlobalHeaderRightProps = {
dispatch: Dispatch;
theme?: ProSettings['navTheme'] | 'realDark';
auth: any;
users: any;
platformLanguage: any;
data: any;
} & Partial<ConnectProps> &
Partial<ProSettings>;
const ENVTagColor = {
dev: 'orange',
test: 'green',
pre: '#87d068',
};
const GlobalHeaderRight: React.FC<GlobalHeaderRightProps> = (props) => {
const [updateLang, setUpdateLang] = useState<{ lang: string; updated: boolean }>({
lang: '',
updated: false,
});
const [currentLang, setCurrentLang] = useState<any>(null);
const { theme, layout, auth, platformLanguage, data, dispatch } = props;
let className = styles.right;
useEffect(() => setCurrentLang(platformLanguage), [platformLanguage]);
useEffect(() => {
if (updateLang.updated) {
const {
organization,
roles,
email,
deleted,
department,
createdById,
organizationId,
...rest
} = data;
const birthdate = moment(rest.birthdate).format('YYYY-MM-DD');
const workversary = moment(rest.workversary).format('YYYY-MM-DD');
dispatch({
type: 'users/updateMe',
payload: {
data: { ...rest, birthdate, workversary, platformLanguage: updateLang.lang },
userId: auth?.currentUser.id,
},
});
setUpdateLang({ ...updateLang, updated: false });
setCurrentLang(updateLang.lang);
}
}, [updateLang]);
if (theme === 'dark' && layout === 'top') {
className = `${styles.right} ${styles.dark}`;
}
return (
<div className={className}>
{currentLang ? (
<LanguageDropdown currentLang={currentLang} setUpdateLang={setUpdateLang} />
) : null}
<Avatar />
{REACT_APP_ENV && (
<span>
<Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag>
</span>
)}
</div>
);
};
export default connect(({ settings, auth, users }: ConnectState) => ({
theme: settings.navTheme,
layout: settings.layout,
auth,
users,
platformLanguage: auth?.currentUser?.membership?.platformLanguage,
data: auth?.currentUser?.membership,
}))(GlobalHeaderRight);
-LoginLayout
import React, { useEffect, useState } from 'react';
import type { ConnectState } from '#/models/connect';
import type { MenuDataItem } from '#ant-design/pro-layout';
import { getMenuData, getPageTitle } from '#ant-design/pro-layout';
import { useIntl, connect } from 'umi';
import type { ConnectProps } from 'umi';
import { Col, Row } from 'antd';
import { Helmet, HelmetProvider } from 'react-helmet-async';
import LoginImage from '../assets/loginImage.png';
import SignUpAdminImage from '../assets/adminSignup.svg';
import styles from './LoginLayout.less';
import LanguageDropdown from '#/components/languageDropdown';
import SignupSideText from '#/components/SignupSideText';
export type UserLayoutProps = {
breadcrumbNameMap: Record<string, MenuDataItem>;
} & Partial<ConnectProps>;
const LoginLayout: React.FC<UserLayoutProps> = (props) => {
const [layoutImage, setLayoutImage] = useState(LoginImage);
const [updateLang, setUpdateLang] = useState<{ lang: string; updated: boolean }>({
lang: '',
updated: false,
});
const [currentLang, setCurrentLang] = useState<any>('');
useEffect(() => {
if (updateLang.updated) {
setUpdateLang({ ...updateLang, updated: false });
setCurrentLang(updateLang.lang);
}
}, [updateLang]);
useEffect(() => {
if (window.location.pathname === '/user/adminSignup/step1') {
setLayoutImage(SignUpAdminImage);
} else if (window.location.pathname === '/user/adminSignup/step2') {
setLayoutImage('TextSignup');
} else setLayoutImage(LoginImage);
}, [window.location.pathname]);
const {
route = {
routes: [],
},
} = props;
const { routes = [] } = route;
const {
children,
location = {
pathname: '',
},
} = props;
const { formatMessage } = useIntl();
const { breadcrumb } = getMenuData(routes);
const title = getPageTitle({
pathname: location.pathname,
formatMessage,
breadcrumb,
...props,
});
return (
<>
<HelmetProvider>
<Helmet>
<title>{title}</title>
<meta name="description" content={title} />
</Helmet>
<div className={styles.container}>
<Col
xl={{ span: 12, order: 1 }}
xs={{ span: 0, order: 2 }}
md={{ span: 0, order: 2 }}
lg={{ span: 0, order: 2 }}
style={{ backgroundColor: '#00bfa5' }}
>
{layoutImage === 'TextSignup' ? (
<SignupSideText />
) : (
<img alt="logo" width="100%" height="100%" src={layoutImage} />
)}
</Col>
<Col
xl={{ span: 12, order: 2 }}
lg={{ span: 24, order: 2 }}
sm={{ span: 24, order: 1 }}
xs={{ span: 24, order: 1 }}
>
{' '}
<Row justify="end" className="languageRow">
<LanguageDropdown currentLang={currentLang} setUpdateLang={setUpdateLang} />
</Row>
{children}
</Col>
</div>
</HelmetProvider>
</>
);
};
export default connect(({ settings }: ConnectState) => ({ ...settings }))(LoginLayout);

Redux store isn't getting updated

I have built this app using create-react-native-app, the action is dispatched but the state isn't being updated and I'm not sure why.
I see the action being logged (using middleware logger) but the store isn't getting updated, I am working on Add_Deck only for now
Here is my reducer:
// import
import { ADD_CARD, ADD_DECK } from './actions'
// reducer
export default function decks(state ={}, action){
switch(action.type){
case ADD_DECK:
return {
...state,
[action.newDeck.id]: action.newDeck
}
case ADD_CARD:
return {
...state,
[action.deckID]: {
...state[action.deckID],
cards: state[action.deckID].cards.concat([action.newCard])
}
}
default: return state
}
}
Actions file:
// action types
const ADD_DECK = "ADD_DECK";
const ADD_CARD = "ADD_CARD";
// generate ID function
function generateID() {
return (
"_" +
Math.random()
.toString(36)
.substr(2, 9)
);
}
// action creators
function addDeck(newDeck) {
return {
type: ADD_DECK,
newDeck
};
}
// export
export function handleAddDeck(title) {
return dispatch => {
const deckID = generateID();
// const newDeck = { id: deckID, title, cards: [] };
dispatch(addDeck({ id: deckID, title, cards: [] }));
};
}
function addCard(deckID, newCard) {
// { question, answer }, deckID
return {
type: ADD_CARD,
deckID,
newCard
};
}
// export
export function handleAddCard(deckID, content) {
// { question, answer }, deckID
return dispatch => {
const newCard = { [generateID()]: content };
dispatch(addCard(deckID, newCard));
};
}
And react-native component:
import React, { Component } from 'react';
import { View, Text, StyleSheet, TextInput, TouchableOpacity } from "react-native";
import {red, white} from '../utils/colors'
import { connect } from 'react-redux'
import { handleAddDeck } from '../redux/actions'
class AddDeck extends Component {
state = {
text:""
}
handleSubmit = () => {
this.props.dispatch(handleAddDeck(this.state.text))
this.setState(()=>{
return { text: ""}
})
}
render() {
return (
<View style={styles.adddeck}>
<Text> This is add deck</Text>
<TextInput
label="Title"
style={{ height: 40, borderColor: "gray", borderWidth: 1 }}
onChangeText={text => this.setState({ text })}
placeholder="Deck Title"
value={this.state.text}
/>
<TouchableOpacity style={styles.submitButton} onPress={this.handleSubmit}>
<Text style={styles.submitButtonText}>Create Deck</Text>
</TouchableOpacity>
</View>
);
}
}
function mapStateToProps(decks){
console.log("state . decks", decks)
return {
decks
}
}
export default connect(mapStateToProps)(AddDeck);
const styles = StyleSheet.create({
adddeck: {
marginTop: 50,
flex: 1
},
submitButton: {
backgroundColor: red,
padding: 10,
margin: 15,
height: 40,
},
submitButtonText: {
color: white
}
});
I guess you forgot to export your types from the actions file thus the switch(action.type) does not trigger the needed case statement.
Maybe try to add as the following:
export const ADD_DECK = "ADD_DECK";
export const ADD_CARD = "ADD_CARD";
Or further debugging just to see if the values are the ones what you are looking for:
export default function decks(state = {}, action) {
console.log({type:action.type, ADD_DECK}); // see what values the app has
// further code ...
}
I hope that helps! If not, let me know so we can troubleshoot further.

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