I am trying to use getStaticProps for my Layout component as described here, but do struggle to solve this for my specific case:
_app.tsx
const NoCheck: React.FC = ({ children }) => <>{children}</>
function App({ Component, pageProps }: AppProps) {
const neoPage = Component as NeoPage
const LayoutComponent = neoPage.Layout || Layout
const { withAuthCheck = true } = neoPage
const CheckAuthComponent = withAuthCheck ? CheckAuth : NoCheck
return (
<CommonProviders pageProps={pageProps} overmindConfig={config}>
<CheckAuthComponent>
<LayoutComponent>
<Component {...pageProps} />
</LayoutComponent>
</CheckAuthComponent>
</CommonProviders>
)
}
export default App
LayoutUnauthorized.tsx
export const LayoutUnauthorized: React.FC<Props> = ({
children,
systemNormal,
}) => {
return (
<Flex>
<SystemStatus systemNormal={systemNormal} />
{children}
</Flex>
)
}
Login.tsx (in /page)
export const Login: NeoPage = () => {
return (
...
)
}
Login.Layout = LayoutUnauthorized
Login.withAuthCheck = false
export async function getStaticProps() {
const res = await fetch("https://company.com")
const systemNormal = await res.ok
return {
props: {
systemNormal,
},
revalidate: 1,
}
}
So Layout has a property I would like getStaticProps to pass. How can this be achieved?
Related
I have two function components with useState in two different files in my project. I want to display the url on my FaceRecognition component if I set fetchSuccess to true.
const ImageLinkForm = () => {
const [url, setUrl] = useState("");
const [fetchSuccess, setFetchSuccess] = useState(false);
const onInputChange = (event) => {
// I get the url and fetchSuccess is true
};
return (
<div>
// I return a form that allowed me to make the fetch call
</div>
);
};
export default ImageLinkForm;
const FaceRecognition = () => {
return (
<div>
{/* if fetchSuccess */}
<img src=url />
</div>
);
};
export default FaceRecognition;
This really depends on how these components are hierarchically related but one easy-ish option is to use the context API
// context/image.js
import { createContext, useState } from "react";
export const ImageContext = createContext({ fetchSuccess: false });
export const ImageContextProvider = ({ children }) => {
const [fetchSuccess, setFetchSuccess] = useState(false);
const setSuccessful = () => {
setFetchSuccess(true);
};
return (
<ImageContext.Provider value={{ fetchSuccess, setSuccessful }}>
{children}
</ImageContext.Provider>
);
};
Your components can then use the context to read the value...
import { useContext } from "react";
import { ImageContext } from "path/to/context/image";
const FaceRecognition = () => {
const { fetchSuccess } = useContext(ImageContext);
return <div>{fetchSuccess && <img src="url" />}</div>;
};
and write the value...
import { useContext, useState } from "react";
import { ImageContext } from "path/to/context/image";
const ImageLinkForm = () => {
const [url, setUrl] = useState("");
const { setSuccessful } = useContext(ImageContext);
const onInputChange = (event) => {
// I get the url and fetchSuccess is true
setSuccessful();
};
return (
<div>{/* I return a form that allowed me to make the fetch call */}</div>
);
};
The only thing you need to do is wrap both these components somewhere in the hierarchy with the provider
import { ImageContextProvider } from "path/to/context/image";
const SomeParent = () => (
<ImageContextProvider>
<ImageLinkForm />
<FaceRecognition />
</ImageContextProvider>
);
I create a context for user authenticated.It like
interface IAuthContext{
isAuthenticated:boolean
setIsAuthenticated:Dispatch<SetStateAction<boolean>>
checkAuth: () => Promise<void>
logoutClient : () =>void,
}
const defaultIsAuthenticated = false
export const AuthContext = createContext<IAuthContext>({
isAuthenticated:defaultIsAuthenticated,
setIsAuthenticated: () =>{},
checkAuth:() => Promise.resolve(),
logoutClient : () =>{}
})
const AuthContextProvider= ({children} : {children:ReactNode}) =>{
const [isAuthenticated,setIsAuthenticated] = useState(defaultIsAuthenticated)
const checkAuth = async () =>{
//check if token is existing
const token = JwtManager.getToken()
if(token) setIsAuthenticated(true)
else{
const success = await JwtManager.getRefreshToken()
if(success) setIsAuthenticated(true)
}
}
const authContextData = {
isAuthenticated,
setIsAuthenticated,
checkAuth,
logoutClient
}
return(<AuthContext.Provider value={authContextData}>
{children}
</AuthContext.Provider>)
}
export default AuthContextProvider
I use it in _app.tsx , but it not work. Whenever i move it to another component, it work.
This is code in _app.tsx
function MyApp({ Component, pageProps }: AppProps) {
const { checkAuth } = useContext(AuthContext);
useEffect(() => {
const authenticate = async () => {
await checkAuth();
};
authenticate();
}, []);
return (
<ApolloProvider client={client}>
<AuthContextProvider>
<ChakraProvider resetCSS theme={theme}>
<Component {...pageProps} />
</ChakraProvider>
</AuthContextProvider>
</ApolloProvider>
);
}
export default MyApp;
I must use CheckAuth() in _app.tsx for user authenticated, so how can I solve this problem?
So i have a few very similar components with the same logic
import { useDetectClick } from 'src/utils/useDetectClick';
import ColumnMenu from 'src/components/presentational/menus/ColumnMenu';
const ColumnMenuContainer = () => {
const [isActive, setIsActive] = useDetectClick(false);
const onClick = (): void => {
setIsActive(!isActive);
};
return (
<>
<ColumnMenu onClick={onClick} isActive={isActive} />
</>
);
};
export default ColumnMenuContainer;
and example of another one:
import { useDetectClick } from 'src/utils/useDetectClick';
import TaskMenu from 'src/components/presentational/menus/TaskMenu';
const TaskMenuContainer = () => {
const [isActive, setIsActive] = useDetectClick(false);
const onClick = (): void => {
setIsActive(!isActive);
};
return (
<>
<TaskMenu onClick={onClick} isActive={isActive} />
</>
);
};
export default TaskMenuContainer;
The only difference is Component name which they are returning. It look's like i'm repeating myself. I am wondering how to make it Reusable (if only it's possible). I thought about passing component (taskMenu etc.) to one universal Menu component as a prop, but i can't find any similar issue anywhere in web and don't know how to do it.
EDIT:
import { useDetectClick } from 'src/utils/useDetectClick';
interface Props{
Component:React.ReactNode;
}
const ClickableContainer : React.FunctionComponent<Props> = ({Component}) => {
const [isActive, setIsActive] = useDetectClick(false);
const onClick = (): void => {
setIsActive(!isActive);
};
return <Component onClick={onClick} isActive={isActive} />;
};
export default ClickableContainer;
When i am trying this solution, this error occurs:
JSX element type 'Component' does not have any construct or call signatures.
And it's underlining "Component".
EDIT - WORKING SOLUTION:
import { useDetectClick } from 'src/utils/useDetectClick';
interface NestedComponentProps {
onClick: () => void;
isActive: boolean;
}
interface Props {
Component: React.FC<NestedComponentProps>;
}
const Menu: React.FC<Props> = ({ Component }) => {
const [isActive, setIsActive] = useDetectClick(false);
const onClick = (): void => {
setIsActive(!isActive);
};
return <Component onClick={onClick} isActive={isActive} />;
};
export default Menu;
You can pass the component to use as a prop. The important (and somewhat silly) detail to remember here is that the variable name needs to be upper-case!
import { useDetectClick } from 'src/utils/useDetectClick';
const ClickableContainer = ({Component}) => {
const [isActive, setIsActive] = useDetectClick(false);
const onClick = (): void => {
setIsActive(!isActive);
};
return <Component onClick={onClick} isActive={isActive} />;
};
export default ClickableContainer;
You can then reuse that:
import TaskMenu from 'src/components/presentational/menus/TaskMenu';
import ColumnMenu from 'src/components/presentational/menus/ColumnMenu';
import ClickableContainer from './ClickableContainer';
const TaskMenuContainer = () => <ClickableContainer Component={TaskMenu} />;
const ColumnMenuContainer = () => <ClickableContainer Component={ColumnMenu} />;
I have contexts/RoomContext.tsx:
import { useState, createContext } from 'react';
const RoomContext = createContext([{}, () => {}]);
const RoomProvider = (props) => {
const [roomState, setRoomState] = useState({ meetingSession: null, meetingResponse: {}, attendeeResponse: {} })
return <RoomContext.Provider value={[roomState, setRoomState]}>
{props.children}
</RoomContext.Provider>
}
export { RoomContext, RoomProvider }
Then in my component, RoomPage.tsx, I have:
const RoomPageComponent = (props) => {
const router = useRouter()
const [roomState, setRoomState] = useContext(RoomContext);
useEffect(() => {
const createRoom = async () => {
const roomRes = await axios.post('http://localhost:3001/live')
console.log('roomRes', roomRes)
setRoomState(state => ({ ...state, ...roomRes.data }))
}
if (router.query?.id) {
createRoom()
}
}, [router])
return <RoomPageWeb {...props} />
}
export default function RoomPage(props) {
return (
<RoomProvider>
<RoomPageComponent {...props} />
</RoomProvider>
)
}
But I get a complaint about the setRoomState:
This expression is not callable.
Type '{}' has no call signatures.
The issue here is that you are trying to use RoomContext in a component(RoomPage) which doesn't have RoomContext.Provider, higher up in the hierarchy since it is rendered within the component.
The solution here to wrap RoomPage with RoomProvider
import { RoomProvider, RoomContext } from '../../contexts/RoomContext'
function RoomPage(props) {
const [roomState, setRoomState] = useContext(RoomContext);
useEffect(() => {
const createRoom = async () => {
const roomRes = await axios.post('http://localhost:3001/live')
console.log('roomRes', roomRes)
setRoomState(state => ({...state, ...roomRes.data}))
}
...
return (
<RoomPageWeb {...props} />
)
export default (props) => (
<RoomProvider><RoomPage {...props} /></RoomProvider>
)
If user is logged in, render the component. If not, render login page. I notice, however, that this function is called twice. The first time, useAuthDataContext() is null. The second time, I get the correct object back.
const PrivateRoute = ({ component, ...options }) => {
const { userData } = useAuthDataContext()
console.log(userData)
const finalComponent = userData != null ? component : Login
return (
<Route {...options} component={finalComponent} />
)
};
export default PrivateRoute
I have rewritten this function as follows. Here, PrivateRoute2 is called only once, and useAuthDataContext() returns null.
const PrivateRoute2 = ({ component: Component, ...rest }) => {
const { userData } = useAuthDataContext()
console.log(userData)
return (
<Route
{...rest}
render={props =>
userData != null ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
)
}
Here is my useAuthDataContext() implementation that is causing the rerender:
export const AuthDataContext = createContext(null)
const initialAuthData = {}
const AuthDataProvider = props => {
const [authData, setAuthData] = useState(initialAuthData)
useLayoutEffect( (props) => {
const getUser = async () => {
try {
const userData = await authService.isAuthenticated()
setAuthData( {userData})
} catch (err) {
setAuthData({})
}
}
getUser()
}, [])
const onLogout = () => {
setAuthData(initialAuthData)
}
const onLogin = newAuthData => {
const userData = newAuthData
setAuthData( {userData} )
}
const authDataValue = useMemo(() => ({ ...authData, onLogin, onLogout }), [authData])
return <AuthDataContext.Provider value={authDataValue} {...props} />
}
export const useAuthDataContext = () => useContext(AuthDataContext)
export default AuthDataProvider
I think i found one solution. See this post https://hackernoon.com/whats-the-right-way-to-fetch-data-in-react-hooks-a-deep-dive-2jc13230