React set provider variables on application load - reactjs

In the root of my app I need to make calls to an api when the app loads and use that data in the provider.
const App = () => {
const [userRoles] = useState(['SuperUser']);
const [selectedProfileId, setSelectedProfileId] = useState(null);
const { isLoading, user, error } = useAuth0();
const { data, loading: profileLoading, error: profileError } = useQuery(
GET_PROFILES
);
useEffect(() => {
setProfileId(data?.items?.[0]?.id);
}, [data]);
if (isLoading || profileLoading) return <Loading />;
if (error)
return (
<Alert />
);
if (searchCustomersError) {
return (
<Alert />
);
}
// const getUserRole = () => {return role} ??? How can I call this in the roles prop in the provider?
return (
<ProfileProvider
id={1}
name={user?.name}
roles={userRoles}
setSelectedProfile={handleProfielChange}
selectedProfileId={selectedProfileId}
>
... App routes
</ProfileProvider>
);
};
The problem is that selectedProfileId is null initially and my app uses that null value in the provider for some reason then once it gets the data it gives the provider the proper value. I dont get it because if the profileLoading value is true then the Provider shouldnt render right?
Also, I need to have a function that sets the roles variable right now I have it static but I need to adjust the the user data I get from user Auth0 to get the userRole var. How can I do this?

Have you tried doing something like so, it'll check to see if the profileLoading is false, then render ProfileProvider, otherwise you create some sort of loading screen or something before the render.
{!profileLoading?
<>
<ProfileProvider
id={1}
name={user?.name}
roles={userRoles}
setSelectedProfile={handleProfielChange}
selectedProfileId={selectedProfileId}
>
... App routes
</ProfileProvider>
</> :
<Loading />
}

Related

Simulate user navigation with React Testing Library and React Router

I have a component that is meant to trigger when the user navigates away from a page. It wraps a formik form; if a user has unsaved changes, it attempts to save those changes as soon as the user attempts to navigate away. While the save is resolving, users will see a modal that says "saving..." If the save is successful, the user continues on to the next page. If it is unsuccessful, it displays a modal prompting them to either stay or move along. The component works fine, but I'm struggling to test it.
Component in question:
const AutoSaveUnsavedChangesGuard: React.FC<Props> = ({
when,
onLeave,
children,
ignoreRoutes = [],
submitForm,
}) => {
const { push } = useHistory();
const { error, savingStatus } = useSavingStatusContext();
const [nextLocation, setNextLocation] = React.useState<string>();
const [isShowing, setIsShowing] = React.useState<boolean>(false);
const [showUnsavedChangesModal, setShowUnsavedChangesModal] = React.useState<boolean>(false);
const [showSavingModal, setShowSavingModal] = React.useState<boolean>(false);
const handleBlockNavigation = (nextLocation: Location) => {
if (!!matchPath(nextLocation.pathname, ignoreRoutes)) {
return true;
}
setNextLocation(nextLocation.pathname);
setIsShowing(true);
submitForm();
return false;
};
React.useEffect(() => {
// Proceed to next location when there has been a navigation attempt and client no longer blocks it
if (!when && nextLocation) {
push(nextLocation);
}
}, [when, nextLocation, push]);
React.useEffect(() => {
// If we have an error and we have triggered the Prompt, display the unsaved changes guard.
setShowUnsavedChangesModal(!!error)
}, [error]);
React.useEffect(() => {
setShowSavingModal(savingStatus=== SavingStatusType.SAVING)
}, [savingStatus]);
return (
<React.Fragment>
<Prompt when={when} message={handleBlockNavigation}/>
<UnsavedChangesModal
show={showUnsavedChangesModal && isShowing}
onLeave={() => {
onLeave && onLeave();
}}
onStay={() => {
setNextLocation(undefined);
}}
onHide={() => {
setIsShowing(false);
setShowUnsavedChangesModal(false);
}}
/>
<SavingModal show={showSavingModal && isShowing} />
{children}
</React.Fragment>
);
};
export default AutoSaveUnsavedChangesGuard;
I'm trying to test behavior with react-testing-library. I'd like to simulate a user navigating away (IE call the message method in the rendered component with a new location), but I am struggling to do so. We had a function like the one below when we tested using enzyme.
const changeRouteLocation = (nextLocation: Location, wrapper: ShallowWrapper) => {
const prompt = wrapper.find(ReactRouter.Prompt);
const onNavigate = prompt.props().message as (location: Location) => string | boolean;
onNavigate(nextLocation);
};
Unfortunately, this component uses useEffect hooks that don't play nice with enzyme, and I must test it using react-testing-library. How can I simulate a user attempting to navigate to a new location with react-testing-library?
Edit: Adding what I have for testing code per a request. This code does not produce the desired outcome, and I honestly didn't expect it to.
const RenderingComponent = ({initialEntries})=>{
return(
<ThemeProvider>
<MemoryRouter initialEntries={initialEntries}>
<AutoSaveUnsavedChangesGuard {...defaults} />
</MemoryRouter>
</ThemeProvider>
)
}
beforeEach(() => {
jest.spyOn(ReactRouter, 'useHistory').mockReturnValue(makeHistory());
useSavingStatusContextSpy = jest.spyOn(useAutoSaveContextModule, 'useSavingStatusContext')
});
it('should render default. It should not show any modals when there are no errors and the route has not changed.', async () => {
// Default rendering. Works fine, because it's not meant to display anything.
const wrapper = render(
<RenderingComponent initialEntries={['/initial-value']} />
)
expect(screen.queryByText('Saving...')).toBeNull();
expect(screen.queryByText('Unsaved Changes')).toBeNull();
expect(wrapper).toMatchSnapshot()
});
it('should show the saving modal when the route location changes and saving status context is of type SAVING',()=>{
useSavingStatusContextSpy.mockReturnValueOnce(makeAutoSaveContext({savingStatus: SavingStatusType.SAVING}))
const {rerender, debug} = render(
<RenderingComponent initialEntries={["initial-value"]} />
)
rerender(<RenderingComponent initialEntries={['/mock-value','/mock-some-new-value']} />)
// I had hoped that re-rendering with new values for initial entries would trigger a navigation event for the prompt to block. It did not work.
debug()
const savingModal = screen.getByText('Saving...');
expect(savingModal).toBeVisible();
})
})

Call hook only if prop that it requires exist if not return default values

In Next.js during SSR I get user object from session and ip address of client that performed request.
return {
props: {
user,
ipAddress,
},
};
User can exist but it can also be undefined if request is unauthenticated. I want to be able to:
If user is authenticated run react-query hook that uses user.id and fetches user settings.
If user is not authenticated provide default settings values that I have stored in config file.
const LandingPage = ({
user,
ipAddress,
}: LandingPageProperties): JSX.Element => {
// User exist
const { config } = useGetOwnSettingsQuery(
{
endpoint: 'http://localhost:3000/api/graphql',
fetchParams: {
headers: setFetchHeaders(),
},
},
{ userWhere: { id: user?.id } },
);
// User does not exist
imported config from config.ts
return <LandingPageMap className="map" { config goes here }/>;
};
I really dont have good idea how to perform what I want, hooks do not like if I call them inside useEffect and they dont like if I return early to check if user exist.
So, I guess I get your point, if user exist (means if the props is not undefined) then fetch config else use the default one right?
If you think this can will be used in other places in your application then I suggest go for building your own hook.
Ref: https://reactjs.org/docs/hooks-custom.html
function useUserConfig({user}) {
let [config, setConfig] = useState(defaultConfig)
useEffect(() => {
//...
setConfig(..code )
}, [user])
}
Then in your app, you can use it
let config = useUserConfig(props.user)
Solved by introducing one more component that receives filtered id. Then that component fetch data and render authenticated version of needed component.
const LandingPage = ({
session,
ipAddress,
}: LandingPageProperties): JSX.Element => {
const id = session?.user.id;
return (
<Fragment>
{id ? (
<AuthenticatedMap id={id} ipAddress={ipAddress} />
) : (
<LandingPageMap
className="map"
ipAddress={ipAddress}
config={{ storage: appOptions.storage, plugins: appOptions.plugins }}
/>
)}
</Fragment>
);
};

React Component is rendering twice

I have no idea why, the first render shows an empty object and the second shows my data:
function RecipeList(props) {
return (
<div>
{console.log(props.recipes)}
{/*{props.recipes.hits.map(r => (*/}
{/* <Recipe initial="lb" title={r.recipe.label} date={'1 Hour Ago'}/>*/}
</div>
)
}
const RECIPES_URL = 'http://cors-anywhere.herokuapp.com/http://test-es.edamam.com/search?i?app_id=426&q=chicken&to=10'
export default function App() {
const classes = useStyles();
const [data, setData] = useState({});
useEffect(() => {
axios.get(RECIPES_URL)
.then(res => {
setData(res.data);
})
.catch(err => {
console.log(err)
})
}, []);
return (
<div className={classes.root}>
<NavBar/>
<RecipeList recipes={data}/>
<Footer/>
</div>
);
}
I don't know why and I have struggled here for over an hour (React newbie), so I must be missing something.
This is the expected behavior. The reason you see two console logs is because, the first time RecipeList is called with no data (empty object), and the second time when the data becomes available. If you would like to render it only when the data is available you could do something like {Object.keys(data).length > 0 && <RecipeList recipes={data}/>}. By the way this is called conditional rendering.
This is perfectly normal, React will render your component first with no data. Then when your axios.get returns and update data, it will be rendered again with the new data

AWS Amplify - How to render components after sign in

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;

Calling a non-returning JSX function inside of render() in React

I got a quick question on calling a function inside of the render method or some potential way to update a method when a user decides to go to the next screen via clicking "Send".
My goal is to change the old created "selectedExchange" to an updated "selectedExchange" as the user taps the "Send" arrow to go to the next screen.
// On Send Functionality
onSend = () => {
const { exchanges } = this.props
// Testing to see if hard coded values get sought out response
// Hard code values work
// this.props.selectExchange updates the selectedExchange
this.props.selectExchange(exchanges.exchanges[0])
this.props.navigation.navigate('selectRecipient', { transition: 'slideToLeft' })
//return ( exchange )
}
// Additional helper method, able to replace the above "this.props.selectExchange()"
// if necessary
updateExchange = (value, exchange) => {
this.props.selectExchange(exchange)
this.setState({ selectedDisplayName: value })
// Render call
render() {
const { navigation, account, exchanges } = this.props
const { selectedExchange } = exchanges
const { status, symbolPriceTicker, dayChangeTicker } = selectedExchange
const loading = status === 'pending' && !symbolPriceTicker && !dayChangeTicker
const avatar = account.profile_img_url ? { uri: account.profile_img_url } : IMAGES.AVATAR
return (
<View style={globalStyles.ROOT}>
<Loading loading={loading} />
<Image style={globalStyles.backgroundImg} source={IMAGES.BACKGROUND} />
<TopArea navigation={navigation} avatar={avatar} />
<ScrollView>
{
exchanges.exchanges.length > 0 &&
exchanges.exchanges.map(exchange => (
<View style={screenStyles.container}>
<ServiceInfo
account={account}
balances={exchange.balances}
displayName={exchange.label}
symbolPriceTicker={symbolPriceTicker}
// onRequest={this.onRequest}
onSend={this.onSend}
/>
...
...
What I've tried:
Passing an exchange prop via "onSend={this.onSend(exchange)}" to see if I could pass the necessary object that would be used to update selectedExchange. This didn't work as it required me to return something from onSend.
Directly calling the helper method in the JSX between various views. Also didn't work as it required I returned some form of JSX.
Not sure how else I could tackle this. Thanks for any help!

Resources