AWS Amplify - How to render components after sign in - reactjs

I have an AWS Amplify app using React. I want to be able to only load (or reload) a TaskList component only when the user has successfully signed in. However, the component gets rendered from the very beginning when page loads and when user fills up form and gets signed up it won't reload. I have been trying multiple workarounds but I can't see how to make my component depend on a successful login. I rely on the default Amplify authenticator functions to sign the user in against Cognito.
const App = () => (
<AmplifyAuthenticator>
<div>
My App
<AmplifySignOut />
<TaskList />
</div>
</AmplifyAuthenticator>
);

I managed to solve it using hints given in this answer AWS Amplify: onStatusChange then render main page.
Basically, I changed my App component to return only sign in form or the whole up based on auth state change.
const App = () => {
const [authState, setAuthState] = useState('');
function handleAuthStateChange(state) {
if (state === 'signedin' || state === 'signedout') {
setAuthState(state);
}
}
return (
<div>
{ authState !== 'signedin' ?
<AmplifyAuthenticator>
<AmplifySignIn handleAuthStateChange={handleAuthStateChange} slot="sign-in"></AmplifySignIn>
</AmplifyAuthenticator>
:
<div>
My App
<AmplifySignOut handleAuthStateChange={handleAuthStateChange} slot="sign-out"/>
<TaskList />
</div>
}
</div>
);
}

This is how I solved a similar issue to manage the states. I was having some problems as it didn't seem to dispatch the events afterwards.
From https://github.com/aws-amplify/amplify-js/issues/5825
import React from 'react';
import { AmplifyAuthenticator, AmplifySignOut, AmplifySignUp, AmplifySignIn} from '#aws-amplify/ui-react';
import { onAuthUIStateChange } from '#aws-amplify/ui-components'
const Panel = () => {
const [setAuthState] = React.useState();
React.useEffect(() => {
return onAuthUIStateChange(newAuthState => {
if(newAuthState === 'signedin'){
// Do your stuff
}
setAuthState(newAuthState)
});
}, []);
return(
<AmplifyAuthenticator>
<AmplifySignIn headerText="Sign In" slot="sign-in"/>
<AmplifySignUp slot="sign-up" formFields={[
{type: 'username'},
{type: 'email'},
{type: 'password'}
]}></AmplifySignUp>
<AmplifySignOut></AmplifySignOut>
</AmplifyAuthenticator>
)
}
export default Panel;

Related

Use context for communication between components at different level

I'm building the settings pages of my apps, in which we have a common SettingsLayout (parent component) which is rended for all the settings page. A particularity of this layout is that it contains an ActionsBar, in which the submit/save button for persisting the data lives.
However, the content of this SettingsLayout is different for each page, as every one of them has a different form and a different way to interact with it. For persisting the data to the backend, we use an Apollo Mutation, which is called in one of the child components, that's why there is no access to the ActionsBar save button.
For this implementation, I thought React Context was the most appropriated approach. At the beginning, I thought of using a Ref, which was updated with the submit handler function in each different render to be aware of the changes.
I've implemented a codesandbox with a very small and reduced app example to try to illustrate and clarify better what I try to implement.
https://codesandbox.io/s/romantic-tdd-y8tpj8?file=/src/App.tsx
Is there any caveat with this approach?
import React from "react";
import "./styles.css";
type State = {
onSubmit?: React.MutableRefObject<() => void>;
};
type SettingsContextProviderProps = {
children: React.ReactNode;
value?: State;
};
type ContextType = State;
const SettingsContext = React.createContext<ContextType | undefined>(undefined);
export const SettingsContextProvider: React.FC<SettingsContextProviderProps> = ({
children
}) => {
const onSubmit = React.useRef(() => {});
return (
<SettingsContext.Provider value={{ onSubmit }}>
{children}
</SettingsContext.Provider>
);
};
export const useSettingsContext = (): ContextType => {
const context = React.useContext(SettingsContext);
if (typeof context === "undefined") {
/*throw new Error(
"useSettingsContext must be used within a SettingsContextProvider"
);*/
return {};
}
return context;
};
function ExampleForm() {
const { onSubmit } = useSettingsContext();
const [input1, setInput1] = React.useState("");
const [input2, setInput2] = React.useState("");
onSubmit.current = () => {
console.log({ input1, input2 });
};
return (
<div className="exampleForm">
<input
placeholder="Input 1"
onChange={(event) => setInput1(event.target.value)}
/>
<input
placeholder="Input 2"
onChange={(event) => setInput2(event.target.value)}
/>
</div>
);
}
function ActionsBar() {
const { onSubmit } = useSettingsContext();
return (
<section className="actionsBar">
<strong>SETTINGS</strong>
<button onClick={() => onSubmit?.current()}>Save</button>
</section>
);
}
export default function App() {
return (
<div className="App">
<SettingsContextProvider>
<ActionsBar />
<ExampleForm />
</SettingsContextProvider>
</div>
);
}
The main caveat I see in this approach is that you change the whole submit function when you need only reaction to submit event. Event is the catch, I think.
Your approach works ok, but has no extension points, for cases such as validation etc.
So I propose to use EventEmitter in any form (better with types support) as a context value e.g. communication channel.
This is a fork of your codesandbox that illustrates this approach:
https://codesandbox.io/s/friendly-fog-qlrusj?file=/src/App.tsx

Match background with users current weather conditions

I am new to React, trying to learn and I have this unsolvable problem. I have developed a weather app, I'm still working on it, but at this moment I am stuck for 3 days trying to have a background image that changes depending on the users weather conditions. I have tried something using the icon, from openweather API. I used the same method to get the icon (image from my folder) to match users weather conditions.
import React from "react";
export default function Background(props) {
const codeMapping = {
"01d": "clear-sky-day",
"01n": "clear-sky-night",
"02d": "cloudy-day",
"02n": "cloudy-night",
"03d": "cloudy-day",
"03n": "cloudy-night",
"04d": "cloudy-day",
"04n": "cloudy-night",
"09d": "shower-rain-day",
"09n": "shower-rain-night",
"10d": "rain-day",
"10n": "rain-night",
"11d": "thunderstorm-day",
"11n": "thunderstorm-night",
"13d": "snow-day",
"13n": "snow-night",
"50d": "fog-day",
"50n": "fog-night",
};
let name = codeMapping[props.code];
return (
<img
className="background"
src={`background/${name}.jpg`}
alt={props.alt}
size="cover"
/>
);
}
So... in order to get "icon" of the input city by the user I have to call "<Background cod={weatherData.icon} alt={weatherData.description} />" from the function "Search" which is the function handling the submit form and running api call for input city. But the image is not showing(img1), but to have the img as a background I would call <Background> from my App function(img2), but in this case I will not have access to the real icon value from the input city. I should mention I have a folder in "src" called background and the images names match the codes name from the mapping.
Thank you in advance!
current preview of my app
how I see in other documentation I should set a background
You can pass the code from Search.js as the state.
App.js
const codeMapping = {
"01d": "clear-sky-day",
"01n": "clear-sky-night",
};
export const App = () => {
const [code, setCode] = useState(null) // <-- We'll update this from Search.js
const [backgroundImage, setBackgroundImage] = useState("")
useEffect(() => {
// Set background value based on the code
setBackgroundImage(codeMapping[`${code}`])
}, [code]); // <-- useEffect will run everytime the code changes
return (
<div style={{
height: '100px',
width: '100px',
backgroundImage: `${backgroundImage || "defaultBackgroundImage"}`
}}>
<Search setCode={setCode} />
</div>
)
}
Search.js
import { WeatherContext } from './App';
export const Search = ({ setCode }) => {
const handleClick = (apiResponse) => {
// Some API call returning the actual code value here //
setCode(apiResponse)
}
return (
<input
onClick={() => handleClick("01n")}
type="button"
value="Change city"
/>
)
}

How to handle deeplinks in react

I am trying to implement deeplinks in my app using firestore's dynamic links. When the app is opened using a deeplink, I want to show a modal to the user. I am new to react and I am not sure how to implement this. When the app is opened using the dynamic link, I can get the link in App.js
useEffect(() => {
const unsubscribe = dynamicLinks().onLink(handleDynamicLink);
return () => unsubscribe();
}, []);
const handleDynamicLink = link => {
//Show Modal
};
return (
<RootStoreProvider value={rootStore}>
<SafeAreaProvider initialSafeAreaInsets={initialWindowSafeAreaInsets}>
<IconRegistry icons={EvaIconsPack} />
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<ApplicationProvider {...eva} theme={eva[theme]}>
{!rootStore.authStore.isLoggedIn && !startedPressed ? <WelcomeSliderScreen pressed={getStartedPressed}></WelcomeSliderScreen> :
<RootNavigator
ref={navigationRef}
initialState={initialNavigationState}
onStateChange={onNavigationStateChange}
/>
}
</ApplicationProvider>
</ThemeContext.Provider>
</SafeAreaProvider>
</RootStoreProvider>
)
}
What is the best way to implement this logic? I started to implement a deepLinkStore but now a start the think that this is not the best solution
const handleDynamicLink = link => {
rootStore && rootStore.linkStore.setDeepLink(link);
};
The problem is that rootStore is sometimes null there, I don't understand why.
Shall I just provide a parameter in RootNavigator? Something like this
const [deepLink, setDeepLink] = React.useState(null)
const handleDynamicLink = (link) => {
setDeepLink(link);
}
<RootNavigator
ref={navigationRef}
initialState={initialNavigationState}
onStateChange={onNavigationStateChange}
deepLink={deepLink}
/>
Is this the way to go? How can I access the deepLink parameter in a functional component?

React Context value gets updated, but component doesn't re-render

This Codesandbox only has mobile styles as of now
I currently have a list of items being rendered based on their status.
Goal: When the user clicks on a nav button inside the modal, it updates the status type in context. Another component called SuggestionList consumes the context via useContext and renders out the items that are set to the new status.
Problem: The value in context is definitely being updated, but the SuggestionList component consuming the context is not re-rendering with a new list of items based on the status from context.
This seems to be a common problem:
Does new React Context API trigger re-renders?
React Context api - Consumer Does Not re-render after context changed
Component not re rendering when value from useContext is updated
I've tried a lot of suggestions from different posts, but I just cannot figure out why my SuggestionList component is not re-rendering upon value change in context. I'm hoping someone can give me some insight.
Context.js
// CONTEXT.JS
import { useState, createContext } from 'react';
export const RenderTypeContext = createContext();
export const RenderTypeProvider = ({ children }) => {
const [type, setType] = useState('suggestion');
const renderControls = {
type,
setType,
};
console.log(type); // logs out the new value, but does not cause a re-render in the SuggestionList component
return (
<RenderTypeContext.Provider value={renderControls}>
{children}
</RenderTypeContext.Provider>
);
};
SuggestionPage.jsx
// SuggestionPage.jsx
export const SuggestionsPage = () => {
return (
<>
<Header />
<FeedbackBar />
<RenderTypeProvider>
<SuggestionList />
</RenderTypeProvider>
</>
);
};
SuggestionList.jsx
// SuggestionList.jsx
import { RenderTypeContext } from '../../../../components/MobileModal/context';
export const SuggestionList = () => {
const retrievedRequests = useContext(RequestsContext);
const renderType = useContext(RenderTypeContext);
const { type } = renderType;
const renderedRequests = retrievedRequests.filter((req) => req.status === type);
return (
<main className={styles.container}>
{!renderedRequests.length && <EmptySuggestion />}
{renderedRequests.length &&
renderedRequests.map((request) => (
<Suggestion request={request} key={request.title} />
))}
</main>
);
};
Button.jsx
// Button.jsx
import { RenderTypeContext } from './context';
export const Button = ({ handleClick, activeButton, index, title }) => {
const tabRef = useRef();
const renderType = useContext(RenderTypeContext);
const { setType } = renderType;
useEffect(() => {
if (index === 0) {
tabRef.current.focus();
}
}, [index]);
return (
<button
className={`${styles.buttons} ${
activeButton === index && styles.activeButton
}`}
onClick={() => {
setType('planned');
handleClick(index);
}}
ref={index === 0 ? tabRef : null}
tabIndex="0"
>
{title}
</button>
);
};
Thanks
After a good night's rest, I finally solved it. It's amazing what you can miss when you're tired.
I didn't realize that I was placing the same provider as a child of itself. Once I removed the child provider, which was nested within itself, and raised the "parent" provider up the tree a little bit, everything started working.
So the issue wasn't that the component consuming the context wasn't updating, it was that my placement of providers was conflicting with each other. I lost track of my component tree. Dumb mistake.
The moral of the story, being tired can make you not see solutions. Get rest.

Hook requires data from another hook, but getting Error: Rendered more hooks than during the previous render

I'm getting Error:
Rendered more hooks than during the previous render.
I found some answers saying I should put all hook calls on the top.
However, in the following code, session prints undefined 3 times while it's loading until it prints the session object the fourth time.
Hence, I added a check for the loading state.
import { signIn, signOut, useSession } from "next-auth/client";
import { request } from "graphql-request";
import useSWR from "swr";
export default function Profile() {
const [session, loading] = useSession();
if (loading) {
return <p className="">loading...</p>;
}
if (!session) {
signIn();
}
const { data: user } = useSWR([USER_QUERY, session.email], (query, email) =>
request("/api/graphql", query, { email })
);
return (
<div className="">
<p className="">{session.user.nickname}</p>
<button onClick={() => signOut()}>Sign out</button>
</div>
);
}
However, this is throwing an error.
A workaround for this particular issue is to define a NextAuth callback for session, but I don't think that's the correct way to fix it, since I definitely will need to make other hook calls based on the value in the session object in the future.
How can I fix this?
Because hooks are tracked by array index internally by React, you can't invoke them conditionally. You need the same hook calls in the same order each time a given component renders.
You could move the useSWR and subsequent markup to a separate component to avoid the conditional hook calls.
In the example below I've moved the signIn call to a separate component too, but that's not strictly necessary. If you wanted to leave that inline you could just return null instead.
export default function Profile() {
const [session, loading] = useSession();
if (loading) {
return <p className="">loading...</p>;
}
return !session ? <SignIn /> : <UserStuff />
}
function SignIn () {
signIn();
return null;
}
function UserStuff () {
const { data: user } = useSWR([USER_QUERY, session.email], (query, email) =>
request("/api/graphql", query, { email })
);
return (
<div className="">
<p className="">{session.user.nickname}</p>
<button onClick={() => signOut()}>Sign out</button>
</div>
);
}

Resources