React hooks - state in useState() is not reset when route is changed - javascript

const Create = () => {
console.log('rerender !!')
const [parcelType, setParcelType] = useState('paper')
console.log('parcelType =', parcelType)
return (
<Container onClick={() => setParcelType('plastic')}>
<BookingList />
<Card title="Business">
<p>Header</p>
</Card>
</Container>
)
}
export default Create
I want to change parcelType state to 'plastic' when click on Container in Create component. and I want to reset parcelType state to 'paper' when route is change ( Create component re-render ). But when component re-render state is not set to paper
For more details: CreateComponent is re-render when route is change in BookingList component
const BookingList = props => {
const { id } = props.match.params
const containerStyle = useTranslateSpring('-100px', '0')
const itemList = items.map((item, idx) => {
const itemStyle = useTranslateSpring('-100px', '0', '0', 200 + 200 * idx)
const url = `/booking/${item.id}/create`
return (
<ItemContainer
onClick={() => props.history.push(url)}
style={itemStyle}
key={item.id}
isactive={id === item.id}
>
{item.id}
</ItemContainer>
)
})
return <Container style={containerStyle}>{itemList}</Container>
}
export default withRouter(BookingList)
Create Component is render in route by routeTemplate
const Routes = () => (
<Router basename={process.env.REACT_APP_BASE_URL}>
<>
<RouteTemplate
exact
path="/booking/:id/create"
component={Booking.create}
title="Booking"
/>
</>
</Router>
)
and RouteTemplate is render Component wrapped by PageTemplate component
const RouteTemplate = props => {
const {
component: Component,
title,
query,
isAuthenticated,
isLanding,
...rest
} = props
return (
<Route
{...rest}
render={matchProps =>
isAuthenticated ? (
<PageTemplate title={title} isLanding={isLanding}>
<Component {...matchProps} query={query} />
</PageTemplate>
) : (
<Redirect
to={{
pathname: '/',
state: { from: props.location },
}}
/>
)
}
/>
)
}

So I assume you want to reset component's state once route is changed.
This should happen wherever you use functional component + hooks or class-based component with explicit this.state. It's how React works under the hood.
You already have <Create> rendered at the page
Once route is changed <Route> tries to render <Create> element
React sees there is already existing <Create> element and tries to update that instead of re-creating(typically update is much more efficient than re-creating). That's why state is not reset - since it should not reset for updates.
There are different way to handle that.
If such a case happen outside react-router's <Route> I'd suggest use key prop to reset state. But for <Route> it would mean replacing more clear/straightforward <Route path="..." component={Create} /> with more verboose <Route path="..." render={({match}) => <Create match={match} key={match.params.id} />}
So instead let's apply useEffect hook to reset state once props.match.params.id is changed:
const Create = ({ match: {params: {id} } }) => {
useEffect(() => {
setParcelType('paper');
}, [id]);
That should be equal to class-based
state = {
typeOfWhatEver: 'paper'
};
componentDidUpdate(prevProps) {
if(prevProps.match.params.id !== this.props.match.params.id) {
this.setState({
typeOfWhatEver: 'paper'
});
}
}

Related

load routes after getting roles from an api

I created a react app, I added role based mechanism, the idea is that after the athentication, directly I send a request to an api to get the roles of the user, because the token contains only the username and I can not add any information on the payload.
so while getting thr roles from the api, I added a loder component that will block the user from using the app until the roles are loaded, at this point everything is working well, but when I reloaded the page, the app redirect me to the default route everytime, because the routes are not created yet, I would like to know how to block the app until the routes also are loaded? :
App.tsx :
const App: React.FC = () => {
const useRoles = useRoleBased(); // hook to get roles
return (
<>
{useRoles?.loading(
<Loader open backgroundColor="#ffffff" circleColor="primary" />
)}
<Box className={`mainSideBar ${openSideBar && 'openSideBar'}`}>
<Router />
</Box>
</>
);
};
export default App;
Router.tsx :
const routes = [
{ path: '/logout', element: <ConfirmLogout /> },
{
path: '/dashboard-page',
element: <DashboardPage />,
allowedRoles: [Roles.Director, Roles.RequestFullAccess],
},
{
path: '/profil',
element: <RequestPage />,
allowedRoles: [Roles.Manager],
},
];
const Router: React.FC = () => {
return <RolesAuthRoute routes={routes}></RolesAuthRoute>;
};
export default Router;
RolesAuthRoute.tsx :
export function RolesAuthRoute({ routes }: { routes: IRoutes[] }) {
const userRoles = useSelector((state: any) => state?.roles?.roles);
const isAllowed = (
allowedRoles: Roles[] | undefined,
userRoles: string[]) =>
process.env.REACT_APP_ACTIVATE_ROLE_BASED_AUTH === 'false' ||
!allowedRoles ||
allowedRoles?.some((allowedRole) => userRoles?.includes(allowedRole)
);
return (
<Routes>
{routes.map((route) => {
if (isAllowed(route?.allowedRoles, userRoles))
return (
<Route
path={route?.path}
element={route?.element}
key={route?.path}
/>
);
else return null;
})}
<Route path="*" element={<Navigate to="/" replace />} /> //this route is created in all cases
</Routes>
);
}
You could return early (conditional rendering) to stop the router from rendering prematurely. You'll need to modify the hook to return the loading state as boolean instead of rendering the component as it seems to be currently implemented.
const App: React.FC = () => {
const useRoles = useRoleBased(); // hook to get roles
if(useRoles.isLoading){
return <Loader open backgroundColor="#ffffff" circleColor="primary" />
};
return (
<>
<Box className={`mainSideBar ${openSideBar && 'openSideBar'}`}>
<Router />
</Box>
</>
);
};
export default App;

React context api lose auth data when react router dom push page

I have an context where i save the user data, and i have another component when verify the context user is null, if the context user is null my component should redirect the user to the login page, if not should render the component. My routers is inside my Authprovider, but still losing the user data when reload the router. I found another posts with the same issue, and the instruction is to keep the routers inside the useauthprovider, but doesn't work with my app.
My code
function App() {
let header = window.location.pathname === '/login' || '/cadastro' ? <Header /> : null;
let footer = window.location.pathname === '/login' || '/cadastro' ? <Footer /> : null;
return (
<UseAuthProvider> // My use AuthProvider
<Router>
<div className='app-container' >
<Switch>
<Cart>
<Header />
<NavbarMenu />
<div className='app-body'>
<UseCampanhaProvider>
<PublicRoute exact path='/' component={Home} />
<PrivateRoute exact path='/cupom/:campaignId' component={CupomScreen} />
<PrivateRoute exact path='/carrinho' component={CartScreen} />
</UseCampanhaProvider>
<PublicRoute exact path='/login' restricted={true} component={Login} />
<PublicRoute path='/cadastro' restricted={true} component={Cadastro} />
</div>
<AuthModal />
{footer}
</Cart>
</Switch>
</div>
</Router >
</UseAuthProvider>
);
}
export default App;
My component where i verify the user context
const PrivateRoute = ({ component: Component, ...rest }) => {
const { user } = useAuth();
return (
<Route {...rest} render={props => (
!user ?
<Redirect to='/login' />
:
<Component {...props} />
)} />
);
};
export default PrivateRoute;
My context where i load the user
const UseAuthProvider = ({ children }) => {
const [user, setUser] = useState();
const [open, setOpen] = useState(false)
useEffect(() => {
verifyUser(); //here i call the function when verify the localstorage
}, [])
const verifyUser = async () => {
let tokenHeader = authHeader();
if (tokenHeader) {
await Api.post('/cliente/index', {}, {
headers: {
...tokenHeader
}
}).then((response) => {
setUser(response.data.cliente)
})
}
}
const handleModal = () => {
setOpen((state) => !state)
}
const Logout = async () => {
localStorage.clear('acessToken-bolao')
setUser(null)
}
return (
<useAuthContext.Provider value={{ Auth, verifyUser, user, Register, Logout, open, handleModal }}>
{children}
</useAuthContext.Provider>
)
}
I tried to debug my application and when i redirect my user to another router, before the component render my user return undefined, and after my component is rendered the context load the user data.
It sounds like your entire application is unmounting and remounting.
In this case the state will be lost as it is not simply a re-render.
By what mechanism are you navigating to the new page?
If I remember React-Router correctly you need to use
If you try navigating the url itself with window.location or href then you are reloading the entire page (not using the router in the SPA)
If routed correctly I would expect that only data inside the Switch would be re-loaded.

How to pass props to route components when using useRoutes?

I'm trying out the new useRoutes hook from react-router-dom and it seems to be pretty interesting. The only problem, is that I can't figure out how I would pass props down to the components.
Before, I'd have a Route component, and I would select parts of local or global state there and pass it on, but how would I do it with useRoutes?
In the below sandbox, I'm trying to change the background based on the boolean value of isLoading. How would I pass isLoading on?
Edit
Here's the code:
import React from "react";
import { BrowserRouter as Router, Outlet, useRoutes } from "react-router-dom";
const Main = ({ isLoading }) => (
<div
style={{
height: "40vh",
width: "50vw",
backgroundColor: isLoading ? "red" : "pink"
}}
>
<Outlet />
</div>
);
const routes = [
{
path: "/",
element: <Main />
}
];
const App = ({ isLoading }) => {
const routing = useRoutes(routes);
return (
<>
{routing}
{JSON.stringify(isLoading)}
</>
);
};
export default function Entry() {
const [isLoading, setIsLoading] = React.useState(false);
React.useEffect(() => {
setInterval(() => {
setIsLoading(!isLoading);
}, 3000);
}, [isLoading]);
return (
<Router>
<App isLoading={isLoading} />
</Router>
);
}
Edit
I've considered passing in an isLoading argument to routes, but I feel like that won't be an efficient approach, because the whole tree will rerender at any route, regardless of whether or not it depends on isLoading or doesn't. Would a better approach be to use Switch and a custom Route component for routes that depend on isLoading and just use useSelector in that custom Route component?
I think this would work:
const routes = (props) => [
{
path: "/",
element: <Main {...props} />
}
];
const App = ({ isLoading }) => {
const routing = useRoutes(routes({isLoading}));
return (
<>
{routing}
{JSON.stringify(isLoading)}
</>
);
};
Instead of passing the array, you can create a function that returns a constructed array to the useRoutes hook:
const routes = (isLoading) => [
{
path: "/",
element: <Main isLoading={isLoading} />
}
];
code sandbox:
https://codesandbox.io/s/dark-dream-hx1y9
more pretty would be:
const routes = (props) => [
{
"/": () => <Main {...props} />
}
];
const App = ({ isLoading }) => {
const routing = useRoutes(routes(isLoading));
return (
<>
{routing}
{JSON.stringify(isLoading)}
</>
)
};

ReactRouterDom, AuthRoute returns react render functions are not valid as react child warning

My Router is a simple component containing public and private routes. I have created an AuthRoute referring to the great tutorial from here
So, my Router looks like:
<Router>
<div>
<Navigation />
<Route exact path={ROUTES.LANDING} component={Landing} />
<Route path={ROUTES.SIGN_UP} component={SignUp} />
<Route path={ROUTES.SIGN_UP_SUCCESS} component={SignUpSuccess} />
<AuthenticationRoute path={ROUTES.HOME} component={Home} />
</div>
</Router>
and my AuthenticationRoute looks like this:
export const AuthenticationRoute = ({ component: Component, ...rest }) => {
const [authChecking, setAuthChecking] = useState(true);
const [{ isAuth }, dispatch] = useStateValue();
useEffect(() => {
checkLoggedIn().then(res => {
setAuthChecking(false);
dispatch({
op: 'auth',
type: 'toggleSessionAuth',
toggleSessionAuth: res
});
});
}, [])
if(authChecking)
return null;
if(!isAuth) {
return <Redirect to='/' />;
}
return <Route {...rest} render={(props) => (
<Component {...props} />
)
} />
}
Everything looks fine, however, my console returns such warning:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from the render. Or maybe you meant to call this function rather than return it.
I have tried different solutions using component/render etc, however, I could not find a solution to this problem and I have no idea what I am doing wrong.
For testing purposes, instead of rendering Component, I tried to render simple <div>test</div> and it worked fine. However, when I am passing a JSX component in props, it returns the warning shown above.
Implementation oh Home Component (Home.js):
export const Home = () => {
const [{ user }, dispatch] = useStateValue();
const { history } = useReactRouter();
const moveTo = path => {
dispatch({
op: 'other',
type: 'setView',
setView: path
});
history.push(path);
}
return (
<div className="pageMenuWrapper">
<h1 className="homeTitle">Hi {() => user ? `, ${user.username}` : ``}.</h1>
<div className="wrapper">
<Tile image={leagueico} alt="text" onClick={() => moveTo(ROUTES.TEST)}/>
<Tile comingSoon />
</div>
</div>
);
}
export default Home;
Could anyone help me solve this little problem?

Handle 'lambda in JSX attribute' case

There is an existing code:
const FooRoute: React.SFC<RouteProps> =
({ component: Component, ...rest }) => {
if (!auth.isFoo()) {
return <Redirect to={{ pathname: '/404' }} />;
}
if ('render' in rest) {
return (
<Route {...rest} render={rest.render} />
);
} else {
return (
Component
?
<Route
{...rest}
render={
(props: RouteComponentProps<{}>) => <Component {...props} />}
/>
: null
);
}
};
There is an error here:
Now how does the getComponent function will look like in this case?
Thought about smth like:
const getComponent = (props: RouteComponentProps<{}>) => () => (
<Component {...props} />
)
Then one can simply:
<Route
{...rest}
render={getComponent}
/>
But in this case Component is undefined. Any clues?
EDIT: Important Note - using TypeScript. So have to pass the Component somehow down into getComponent.
EDIT2: the reason I've used double lambda is because it allows handling situations like this:
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => (foo: string) => {
...
}
with
<button onClick={handleClick('bar')} ... // foo === 'bar'
In your case getComponent is a lambda function which returns another lambda function but not component because of (props) => () => .... Modify your getComponent function to receive Component from render function like below.
const getComponent = (Component: Component) => (props: RouteComponentProps<{}>) => (
<Component {...props} />
)
and modify this 'render' function too.
<Route
{...rest}
render={getComponent(Component)}
/>
Note: You are receiving component as component with small c and using it as Component with capital C.

Categories

Resources