React first gives null values and after a while values are initialized - reactjs

I am trying to create a component to make the validation if the user is authenticated or not.
It is not I am redirecting to log in, however, when I am checking if is it authenticated, first the session is null and after 2000ms says that is authenticated.
App.js
function App() {
const session = useSelector((state) => state.user.user);
useEffect(() => {
if (token) {
axios
.get(`http://localhost:4500/api/user/current`, {
headers: {
token: token,
},
})
.then((res) => {
dispatch(login(res.data));
});
} else {
dispatch(logout());
console.log("no token");
}
}, []);
console.log(session?.role + " XX S"); //first goes null and then after it is initialized
return (
<Route
element={
<RequireAuth allowedRoles={[3]} session_role={session?.role} /> //session?.role first is null and it is a problem with the logic of validation
}
>
<Route path="/protected" element={<Protected />} />
</Route>
)
RequireAuth.js
const RequireAuth = ({ allowedRoles, session_role }) => {
const session = useSelector((state) => state.user.user);
const location = useLocation();
console.log(session_role + " ROLE ");
return session_role?.find((role) => allowedRoles?.includes(role)) ? (
<Outlet />
) : session ? (
<Navigate to="/unauthorised" state={{ from: location }} replace />
) : (
<Navigate to="/login" state={{ from: location }} replace />
);
};
export default RequireAuth;
Reducer
export const userSlice = createSlice({
name: "user",
initialState: {
user: null,
isLoggedIn: false,
},
reducers: {
login: (state, action) => {
state.user = action.payload;
state.isLoggedIn = true;
},
logout: (state) => {
state.user = null;
state.isLoggedIn = false;
},
},
});

function App() {
const dispatch = useDispatch();
let token = 1;
useEffect(() => {
async function initializeToken() {
if (token) {
setTimeout(() => {
dispatch(login({
user: {
id: 'id',
name: 'name',
surname: 'surname',
email: 'email',
role: 'role',
},
}))
}, 2000);
} else {
dispatch(logout());
console.log("no token");
}
}
initializeToken();
}, [dispatch]);
const session = useSelector((state) => state.user);
const sessionObject = session ? JSON.parse( JSON.stringify(session)) : null ;
console.log(sessionObject, 'sessionObject');
const isAuth = sessionObject ? sessionObject.isLoggedIn : null ;
console.log(isAuth + "isAuth");
const role = sessionObject ? sessionObject.user && session.user.user ? session.user.user.role : null : null
console.log(role, 'role')
return (
<div className="App">
<Router>
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="login" element={<Login />} />
<Route exact path="unauthorised" element={<Unauthorised />} />
<Route
element={
<RequireAuth allowedRoles={[3]} session_role={session?.role} />
}
>
<Route path="/protected" element={<Protected />} />
</Route>
</Routes>
</Router>
</div>
);
}
export default App;
Have used setTimeout instead of api , it can simulate same stuff. The reason behind null is parsing the data, not the initial state i thought it would be. The above code should do the trick.

Related

Relay request for `MySheetsQuery` failed

I have MySheets where I need to show some info. This info is fetched from a graphql endpoint. The problem is that the component is loading some time and then the following error occurs:
Relay request for MySheetsQuery failed by the following reasons: 1.
System cannot complete request. Something went wrong trying to
retrieve My Asset Allocations for {(R1) RSA 11% 2012} from external
sources sheets(first: $count, after: $cursor, q: $search, id^^^.
The status code is 200 OK. ,
but the response I am getting in Network is:
{"errors":[{"message":"System cannot complete request. Something went
wrong trying to retrieve My Asset Allocations for {(R1) RSA 11% 2012}
from external
sources","locations":[{"line":25,"column":3}],"path":["viewer","sheets"],"extensions":{"type":"BUSINESS_CONSTRAINT_VIOLATION","classification":"DataFetchingException"}}],"data":null}
When I copy the request payload from the Inspect -> Network and the values I pass and paste them in Postman, I get the response successfully. Why is relay causing an error?
I am getting this warnings from relay in console.
const MySheets: React.FC<IProps> = ({
viewer,
id,
name,
}: IProps) => {
const [localSearch, setLocalSearch] = React.useState<string>();
const [search, setSearch] = React.useState<string>();
return (
<>
<div>
<HeaderActionBar title={"Sheets"} />
<Paper elevation={6}>
<Box display={"flex"} margin={2.5}>
<div>
<div>
<SearchIcon fontSize={"medium"} color={"action"} />
</div>
<InputBase
onChange={(event) => {
setLocalSearch(event.target.value);
}}
placeholder={"Search..."}
onKeyDown={(e) => {
if (e.keyCode === 13) {
setSearch(localSearch);
}
}}
inputProps={{ "aria-label": "search" }}
/>
</div>
<Button
variant={"contained"}
size={"small"}
color={"secondary"}
startIcon={
<SearchIcon fontSize={"large"} color={"inherit"} />
}
onClick={() => {
setSearch(localSearch);
}}
>
Search
</Button>
</Box>
<Divider/>
<Suspense fallback={<TableSkelton />}>
<SheetsTable
viewer={viewer}
search={search}
id={id}
/>
</Suspense>
</Paper>
</div>
</>
);
};
const query = graphql`
query MySheetsQuery(
$count: Int!
$cursor: String
$search: String
$clientId: ID!
$supplierIds: [ID!]
$supplierProductIds: [ID!]
$instrumentName: String
$sort: SheetSort
) {
viewer {
...SheetsTable_sheets
}
node(id: $clientId) {
... on Client {
id
displayName
}
}
}
`;
export function SheetsWrapper() {
const { id } = useParams();
const { data, isLoading, error } = useQuery<MySheetsQuery>(query, {
count: 10,
search: null,
id: id ?? null,
});
if (isLoading) {
return <EnhancedCircularProgress />;
}
if (error) {
return <><div>{error.message}</div>
<div>{error.stack}</div></>;
}
if (!data) {
return <EnhancedCircularProgress />;
}
return (
<Suspense fallback={<EnhancedCircularProgress />}>
<MySheets
viewer={data?.viewer}
clientId={id}
clientName={data.node?.displayName ?? ""}
/>
</Suspense>
);
}
interface IProps {
viewer: SheetsTable_sheets$key;
clientId: string;
clientName: string;
}
export default SheetsWrapper;
This is my RelayEnvironment:
type HandleLogoutFn = () => void;
type GetAuthTokenFn = () => string;
function createNetworkLayer(
handleLogout: HandleLogoutFn,
getAuthTokenFn: GetAuthTokenFn
) {
return new RelayNetworkLayer(
[
cacheMiddleware({
size: 100, // max 100 requests
ttl: 900000, // 15 minutes
}),
urlMiddleware({
url: () => `${ConfigService.serverUri}/graphql`,
}),
retryMiddleware({
fetchTimeout: 5000,
retryDelays: [30000, 60000],
forceRetry: (cb, delay) => {
window.forceRelayRetry = cb;
console.log(
`call \`forceRelayRetry()\` for immediately retry! Or wait ${delay} ms.`
);
},
statusCodes: [500, 503, 504],
}),
authMiddleware({
token: getAuthTokenFn,
}),
(next) => async (req) => {
req.fetchOpts.headers.Accept = "application/json";
req.fetchOpts.headers["Content-Type"] = "application/json";
// req.fetchOpts.headers['X-Request-ID'] = uuid.v4(); // add `X-Request-ID` to request headers
req.fetchOpts.credentials = "same-origin"; // allow to send cookies (sending credentials to same domains)
try {
return await next(req);
} catch (ex) {
if (ex.res && ex.res.status === 401) {
handleLogout();
}
throw ex;
}
},
],
{}
);
}
export function createRelayEnv(
handleLogout: HandleLogoutFn,
getAuthTokenFn: GetAuthTokenFn
) {
const network = createNetworkLayer(handleLogout, getAuthTokenFn);
return new Environment({
network: network,
store: new Store(new RecordSource()),
});
}

Ternary operator shows for milliseconds the truthy part even when it's false

(sorry for my English)
I'm new in Reactjs and I'm trying to do an application where the user can create Flashcards.
The flashcards must have a category so, when the user enter in the menu to list and create flashcards, if there isn't any category created, I want to show a phrase for the user to access before the category menu to create a category.
To do that, my component that lists de flashcards receive an Prop with the list of categories and if the list is empty, it show the banner, if not, it show the rest of the component:
type Props = {
categorias: Categoria[];
};
const TarjetasList = ({ categorias }: Props) => { ...
return (
<>
{!categorias.length ? (
<h1>Debe crear una categoria</h1>
) : (
<>
<TarjetaFilter onSubmitFilter={handleSubmitFilter} />
<div>
{page?.content.map((tarjeta) => (
<div key={tarjeta.id}>
<TarjetaCard tarjeta={tarjeta} onDelete={getTarjetas} />
</div>
))}
</div>
<Pagination
pageCount={page ? page?.totalPages : 0}
range={2}
onChange={handlePageChange}
/>
</>
)}
</>
);
};
The problem is that when the user accesses the menu that lists the cards, it shows the banner with the phrase "You must create a category" for a few milliseconds, even though the "!categoria.length" is false, and then it stops display it and it displays the rest of the component as it should, but even though it's only for a few milliseconds, I want to avoid that. Is it possible?
What am I doing wrong?
here is the code of the component of the menu that opens de list of cards:
const Tarjetas = () => {
const [categorias, setCategorias] = useState<Categoria[]>([]);
const getCategorias = useCallback(() => {
const config: AxiosRequestConfig = {
method: "GET",
url: "/categorias",
withCredentials: true,
};
requestBackend(config).then((response) => {
setCategorias(response.data.content);
});
}, []);
useEffect(() => {
getCategorias();
}, [getCategorias]);
return (
<Routes>
<Route index element={<TarjetasList categorias={categorias} />} />
<Route path=":tarjetaId" element={<TarjetasForm />} />
</Routes>
);
};
Here is the complete code of the TarjetasList component:
type ControlComponentsData = {
activePage: number;
filterData: TarjetaFilterData;
};
type Props = {
categorias: Categoria[];
};
const TarjetasList = ({ categorias }: Props) => {
const [page, setPage] = useState<SpringPage<Tarjeta>>();
const [controlComponentsData, setControlComponentsData] =
useState<ControlComponentsData>({
activePage: 0,
filterData: { texto: "", categoria: null },
});
const getTarjetas = useCallback(() => {
const config: AxiosRequestConfig = {
method: "GET",
url: "/tarjetas",
withCredentials: true,
params: {
page: controlComponentsData.activePage,
size: 3,
categoriaId: controlComponentsData.filterData.categoria?.id,
texto: controlComponentsData.filterData.texto,
},
};
requestBackend(config).then((response) => {
setPage(response.data);
});
}, [controlComponentsData]);
useEffect(() => {
getTarjetas();
}, [getTarjetas]);
const handlePageChange = (pageNumber: number) => {
setControlComponentsData({
activePage: pageNumber,
filterData: controlComponentsData.filterData,
});
};
const handleSubmitFilter = (data: TarjetaFilterData) => {
setControlComponentsData({
activePage: 0,
filterData: data,
});
};
return (
<>
{!categorias.length ? (
<h1>Debe crear una categoria</h1>
) : (
<>
<TarjetaFilter onSubmitFilter={handleSubmitFilter} />
<div>
{page?.content.map((tarjeta) => (
<div key={tarjeta.id}>
<TarjetaCard tarjeta={tarjeta} onDelete={getTarjetas} />
</div>
))}
</div>
<Pagination
pageCount={page ? page?.totalPages : 0}
range={2}
onChange={handlePageChange}
/>
</>
)}
</>
);
};
Your categories categorias are initially empty. Until you load them, you will be displaying the text.
The simplest possible solution is not to initialise categorias to empty list but to null:
const [categorias, setCategorias] = useState<Categoria[] | null>(null);
Then you can add an additional rendering state until categorias are loaded:
if (!categorias) {
return <h1>Cargando…</h1>
}
return (
<>
{!categorias.length ? (
<h1>Debe crear una categoria</h1>
) : (
...
However, ideally you would handle loading state using complex structures similar to:
{ isLoading: true, error: null, categorias: [] }
Then you would be able to correctly distinguish when to display loading indication and when to display loading error.

React Router v5.1.2 Public & Protected Authenticated & Role Based routes

Goal is to have /login as the only public route, once logged in user has routes based on user role.
Authentication is done with Keycloak I get users from keycloak.idTokenParsed.preferred_username: admin, manager, engineer, operator.
If operator tries to go to role restricted route gets redirected to /notauthorized page. (This part not done)
If not logged in user gets redirected to /login page. (This part is done/works)
Is there a better way to do this? Not repeating routes & adding additional users in Routes.jsx kind of a mess.
How do I implement role restricted redirect to /notauthorized?
App.js (does not have all the imports and missing bottom part with mapStateToProps, mapDispatchToProps & export default App )
import React, { useEffect } from "react";
import { Route, Redirect, Switch } from "react-router-dom"
let routeWithRole = [];
let user = '';
const AppContainer = ({ keycloak }) => {
if(keycloak && keycloak.token) {
user = keycloak.idTokenParsed.preferred_username
if( user === 'admin') {
routeWithRole = admin;
} else if( user === 'engineer') {
routeWithRole = engineer
} else if(user === 'manager') {
routeWithRole = manager
} else {
routeWithRole = operator
}
}
return (
<div>
{(keycloak && keycloak.token) ?
<React.Fragment>
<Switch>
{routeWithRole.map((prop, key) => {
console.log('App.js Prop & Key ', prop, key)
return (
<Route
path={prop.path}
key={key}
exact={true}
component={prop.component}
/>
);
})}
<Redirect from={'/'} to={'/dashboard'} key={'Dashboard'} />
</Switch>
</React.Fragment>
:
<React.Fragment>
<Switch>
{publicRoutes.map((prop, key) => {
return (
<Route
path={prop.path}
key={key}
exact={true}
component={(props) =>
<prop.component
keycloak={keycloak}
key={key} {...props} />
}
/>
);
})}
<Redirect from={'/'} to={'/login'} key={'login'} />
</Switch>
</React.Fragment>
}
</div>
)
}
Routes.jsx (missing all the impotrs)
export const publicRoutes = [
{ path: "/login", type: "public", name: "landing page", component: LandingPageContainer },
]
export const admin = [
{ path: "/createUser", name: "Create User", component: CreateUser},
{ path: "/editUser", name: "Edit User", component: EditUser},
{ path: "/createdashboard", name: "Create Dashboard", component: CreateDashboard },
{ path: "/editashboard", name: "Edit Dashboard", component: EditDashboard },
{ path: "/createcalendar", name: "Create Calendar", component: CreateCalendar },
{ path: "/editcalendar", name: "list of factories", component: EditCalendar },
{ path: "/dashboard", name: "Dashboard", component: Dashboard }
]
export const engineer = [
{ path: "/createdashboard", name: "Create Dashboard", component: CreateDashboard },
{ path: "/editashboard", name: "Edit Dashboard", component: EditDashboard },
{ path: "/dashboard", name: "Dashboard", component: Dashboard },
{ path: "/notauthorized", name: "Not Authorized", component: Notauthorized }
]
export const manager = [
{ path: "/createcalendar", name: "Create Calendar", component: CreateCalendar },
{ path: "/editcalendar", name: "Edit Calendar", component: EditCalendar },
{ path: "/dashboard", name: "Dashboard", component: Dashboard },
{ path: "/notauthorized", name: "Not Authorized", component: Notauthorized }
]
export const operator = [
{ path: "/dashboard", name: "Dashboard", component: Dashboard },
{ path: "/notauthorized", name: "Not Authorized", component: Notauthorized }
]
I will consider the option when we have known "keycloak" before react initialization (not async loading data for "keycloak"). You will be able to improve if you understand the idea
The main idea is to show all routes but almost all of them will be protected routes. See the example:
render (
<Switch>
<Route exact path="/login"> // public route
<LandingPageContainer />
</Route>
<AuthRoute exact path="/dashboard"> // for any authorized user
<Dashboard />
</AuthRoute>
<AdminRoute path="/create-user"> // only for admin route
<CreateUser />
</AdminRoute>
<AdminOrEngineerRoute path="/create-dashboard"> // only for admin or engineer route
<CreateDashboard />
</AdminOrEngineerRoute>
<Redirect to="/dashboard" /> // if not matched any route go to dashboard and if user not authorized dashboard will redirect to login
</Switch>
);
Then you can create list of components like this:
const AVAILABLED_ROLES = ['admin', 'engineer'];
const AdminOrEngineerRoute = ({ children, ...rest }) {
const role = keycloak && keycloak.token ? keycloak.idTokenParsed.preferred_username : '';
return (
<Route
{...rest}
render={({ location }) =>
AVAILABLED_ROLES.includes(role) && ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)
}
/>
);
}
As a result AdminOrEngineerRoute will allow to pass to this route only admin or engineer in other case you will get /login page
Always yours "IT's Bruise"

drawer with logout in react navigation

I am facing one issue in my drawer. I am looking for some help in this. People who are using react navigation 5 version.
i am using the latest react navigation version and my code for custom drawer is below
function CustomDrawerContent(props) {
//
return (
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
<DrawerItem label="Logout" onPress={()=>
Alert.alert(
'Log out',
'Do you want to logout?',
[
{text: 'Cancel', onPress: () => {return null}},
{text: 'Confirm', onPress: () => {
AsyncStorage.clear();
//props.navigation.navigate('Home')
}},
],
{ cancelable: false }
)
} />
</DrawerContentScrollView>
);
}
And my route.js is mentioned below.
export default function Routes(username, password) {
// eslint-disable-next-line no-shadow
const Stack = createStackNavigator();
const [loader, setloader] = useState('');
const [state, dispatch] = React.useReducer(
(prevState, action) => {
switch (action.type) {
case 'RESTORE_TOKEN':
return {
...prevState,
userToken: action.token,
isLoading: false,
};
case 'SIGN_IN':
return {
...prevState,
isSignout: false,
userToken: action.token,
};
case 'SIGN_OUT':
return {
...prevState,
isSignout: true,
userToken: undefined,
};
}
},
{
isLoading: true,
isSignout: false,
userToken: null,
},
);
React.useEffect(() => {
// Fetch the token from storage then navigate to our appropriate place
const bootstrapAsync = async () => {
let userToken;
try {
userToken = await AsyncStorage.getItem('#kiklee-user-id');
} catch (e) {
// Restoring token failed
}
// After restoring token, we may need to validate it in production apps
// This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away.
dispatch({type: 'RESTORE_TOKEN', token: userToken});
};
bootstrapAsync();
}, []);
const authContext = React.useMemo(
() => ({
signIn: async data => {
try {
let config_api_url = Config.api_login_url;
if (data.username === '' && data.password === '') {
Alert.alert(
'Error : ',
'Please enter your email address and password.',
);
} else if (data.username === '') {
Alert.alert('Error : ', 'Please enter your email address.');
} else if (
!(data.username === '') &&
Validate.isEmailValid(data.username) === true
) {
Alert.alert('Error : ', 'Please enter the correct email address.');
} else if (password.length < 5) {
Alert.alert(
'Error : ',
'Please enter your password with a minimum length of 5.',
);
} else {
// seterror(''); //empty all errors
setloader(true);
await fetch(config_api_url, {
method: 'POST',
body: JSON.stringify({
username: data.username,
password: data.password,
}),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
})
.then(response => response.text())
.then(async responseData => {
// parse the json values
var responsevalue = JSON.parse(responseData);
if (responsevalue.login_status === 'Active') {
await AsyncStorage.removeItem('#kiklee-user-id');
await AsyncStorage.setItem(
'#kiklee-user-id',
responsevalue.id.toString(),
);
// const value = await AsyncStorage.getItem('#kiklee-user-id');
dispatch({type: 'SIGN_IN', token: 'dummy-auth-token'});
setloader(false);
// eslint-disable-next-line eqeqeq
} else if (responsevalue.login_status == 'Deactive') {
Alert.alert('Error : ', 'Userid is deactive.');
setloader(false);
} else {
Alert.alert('Error : ', 'Invalid username or password.');
setloader(false);
}
})
.catch(err => {
Alert.alert(err);
});
}
} catch (e) {
// saving error
Alert.alert('Please try your login after some time.');
}
},
signOut: async () => {
AsyncStorage.removeItem('#kiklee-user-id');
dispatch({type: 'SIGN_OUT'});
},
signUp: async data => {
dispatch({type: 'SIGN_IN', token: 'dummy-auth-token'});
},
}),
[],
);
if (state.isLoading) {
// We haven't finished checking for the token yet
return <SplashScreen />;
}
// Loader
if (loader == true) {
return (
<View style={styles.container}>
<Spinner
visible={true}
textContent={'Loading...'}
textStyle={styles.spinnerTextStyle}
/>
</View>
);
}
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer>
{state.userToken == null ? (
<>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
headerShown: false,
animationTypeForReplace: state.isSignout ? 'pop' : 'push',
}}
/>
<Stack.Screen
name="Privacy"
component={Privacy}
options={{headerShown: false}}
/>
<Stack.Screen
name="ForgetPassword"
component={ForgetPassword}
options={{headerShown: true}}
/>
<Stack.Screen
name="SignUp"
component={Signup}
options={{headerShown: false}}
/>
</Stack.Navigator>
</>
) : (
<>
<Drawer.Navigator
initialRouteName="Dashboard"
drawerContent={props => CustomDrawerContent(props)}>
<Drawer.Screen name="Dashboard" component={MainRoot} />
</Drawer.Navigator>
</>
)}
</NavigationContainer>
</AuthContext.Provider>
);
}
i can call authcontext inside the custom drawer because it will display a error called "Hooks rule"
i am looking for a help only with react navigation 5.
Try defining const Stack = createStackNavigator(); above the Routes component.
Your useState and useReducer hooks need to come first.
I had the same issue ,but eventually I created a work around because I tried passing the function as props from different roots and when i logged props from the customer drawer item , it was undefined... so that lead me to a previous solution that I had used with react navigation v4.
Solution:
create your own component for signing out and other functionalities like links etc.
const DrawerMeta = () => {
const { signOut } = useContext(AuthContext);
return (
<View style={{ flex: 1, justifyContent: "flex-end" }}>
<Button
onPress={async () => {
await signOut();
}}
title="sign out"
/>
</View>
);
};
insert into your drawer component
function CustomDrawerContent(props) {
//
return (
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
<DrawerMeta />
</DrawerContentScrollView>
);
}

react update state inside custom hook, but didn't return update value

i'm trying to create a component which uses sort of stepbar, and this stepbar needs some data ( like current step, methods for change steps etc. )
I am creating a custom hook with all these methods
export const useSteps = () => {
const history = useHistory();
const orderId = useSelector(getCheckinOrderId);
const checkinHasStarted = useSelector(getCheckinHasStarted);
const hasMeals = useSelector(hasMealServices);
const hasBaggage = useSelector(hasBaggageServices);
const { t } = useTranslation('Checkin');
const steps: Steps = {
findBooking: {
label: t('Find booking')
},
baggage: {
isHidden: !hasBaggage,
label: t('Baggage')
},
meal: {
isHidden: !hasMeals,
label: t('Meal')
},
seats: {
label: t('Seats')
},
payment: {
label: t('Payment')
},
boardingPass: {
label: t('Boarding pass')
}
};
const [activeStep, setActiveStep] = useState(
Object.keys(steps).findIndex(stepKey => history.location.pathname.includes(stepKey))
);
const updateActiveStep = () => {
setActiveStep(Object.keys(steps).findIndex(stepKey => history.location.pathname.includes(stepKey)));
};
console.log(activeStep); // properly updates when updateActiveStep() called
return {
steps,
activeStep,
setStep: (step: CheckinStep) => {
history.push(`/${orderId}/${step}`);
updateActiveStep();
},
nextStep: (currentStep: CheckinStep, replace = false) => {
// omitted next step logic
updateActiveStep();
},
previousStep: (currentStep: CheckinStep, replace = false) => {
//omitted prev step logic
updateActiveStep();
}
};
};
but when i'm trying to get the activeStep variable inside my component in order to pass it to <Stepbar /> component, i see that activeStep is always equal to the initial value
But inside my custom hook, activeStep is properly updated
I use this hook like so:
const Order: React.FC = () => {
const isCheckinStarted = useSelector(getCheckinHasStarted);
const isCheckinCompleted = useSelector(getCheckinHasCompleted);
const hasBaggage = useSelector(hasBaggageServices);
const hasMeals = useSelector(hasMealServices);
const { steps, activeStep, nextStep, setStep } = useSteps();
console.log(activeStep); // didn't udpate, always initial
return (
<div>
<Stepbar variant="blue" steps={steps} />
<Switch>
<Route
path="/:id(\d+)"
exact
render={() => {
if (isCheckinStarted) {
nextStep(CheckinStep.Passengers, true);
return null;
}
return <Passengers />;
}}
/>
<Route
path="/:id(\d+)/baggage"
render={() => {
if (isCheckinCompleted) {
nextStep(CheckinStep.Passengers, true);
return null;
}
if (!hasBaggage) {
nextStep(CheckinStep.Baggage, true);
return null;
}
return <Baggage />;
}}
/>
<Route
path="/:id(\d+)/meal"
render={() => {
if (isCheckinCompleted) {
nextStep(CheckinStep.Passengers, true);
return null;
}
if (!hasMeals) {
nextStep(CheckinStep.Meal, true);
return null;
}
return <Meal />;
}}
/>
<Route
path="/:id(\d+)/seats"
render={() => {
if (isCheckinCompleted) {
nextStep(CheckinStep.Passengers, true);
return null;
}
return <Seats />;
}}
/>
<Route
exact
path="/:id(\d+)/payment"
render={() => {
if (isCheckinCompleted) {
nextStep(CheckinStep.Passengers, true);
return null;
}
return <Payment />;
}}
/>
<Route
path="/:id(\d+)/payment/successfull"
render={() => {
if (isCheckinCompleted) {
nextStep(CheckinStep.Passengers, true);
return null;
}
return (
<PaymentResult
result="success"
onClick={reload => {
if (reload) {
location.reload();
} else {
setStep(CheckinStep.Passengers);
}
}}
/>
);
}}
/>
<Route
path="/:id(\d+)/payment/failed"
render={() => {
if (isCheckinCompleted) {
nextStep(CheckinStep.Passengers, true);
return null;
}
return <PaymentResult result="fail" onClick={() => setStep(CheckinStep.Payment)} />;
}}
/>
</Switch>
</div>
);
};
And I can't figure out why.
You can pay attention to lines with comments, other code works great.
Something strange goes inside my hook, i'm print some debug info when initialize state
const [activeStep, setActiveStep] = useState(() => {
console.log('init for first time');
return Object.keys(steps).findIndex(stepKey =>
history.location.pathname.includes(stepKey));
});
when i'm change active step from parent component, i see this line (init for first time ) every time
const updateActiveStep = useCallback(() => {
console.log('here');
const newStep = Math.random();
console.log('before set new step', newStep); // set random float
setActiveStep(newStep);
}, [activeStep]);
useEffect(() => {
console.log(activeStep, 'active step inside hook'); // always Int
}, [activeStep]);

Resources