I'm building a web app that has role permissions based, admin, user and also products, product A, B, C, etc. I get these data from a backend api.
Currently, I'm using local storage and useContext hook to save and manipulate these data, but an user that knows it, can easily change this information and manipulate the front end, so I'm wondering now which approach I can use here.
My wish (if it's possible) is to get these information by backend after the login, and reuse the data freely in other components, just importing it, like an useContext hook.
I know that there is Redux, but since I'm using next.js, from what I saw, every rendering it will lose/refresh data, so it won't be usefull here.
I'm also using SWR, so, I tried to get these data from cache.get('key'), but the SWR call must be on the same component to get the data properly from the key cached. It's not working if a call the SWR on the e.g home page, and try to get it in other generic component.
What do you people suggest to go here?
Thanks!
I think you should authenticate your user, then store their access key and identifier in localStorage and global state.
When users access an authorization required page.
You'll check for the access token if it doesn't exist on both global state and localStorage. Redirect (or alert) the authorization error.
If there is an access token. Then send a request to the server with that access token. The server will be in charge of authorizing progress. And you will handle the logic on the front end based on the response from the server.
The thing is the authorization (checking permission for instance) should be done in the backend, not on the frontend
I don't know whether you can manipulate the implementation of the backend API or not. Hope it helps
Following the answers, I created a useContext to use in any component that I need.
Here is what I have in my context:
const [userRoles, setUserRoles] = useState<string[] | undefined>([])
const getUsersRoles = useCallback(async () => {
const getUserRoles = await UsersService.getUsersRoles(userInfo.idUsuario)
setUserRoles(getUserRoles.data)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
Note: UsersService.getUsersRoles function is integration with the API
And how I use it in the component I need:
const { userRoles, getUsersRoles } = useAuth()
if (userRoles?.length === 0) {
getUsersRoles()
return <LoadingGIf tip="Carregando opções..." />
}
With this, I have the data I need here userRoles. And IF the user reload/refresh the page, getUsersRoles is requested and set the data to the userRoles
The downside to this, at least for me, is that I have to add this:
const { userRoles, getUsersRoles } = useAuth()
if (userRoles?.length === 0) {
getUsersRoles()
return <LoadingGIf tip="Carregando opções..." />
}
for every component I need to use the roles data, but I believe that's it. It's working fine and isn't request extra any endpoints of API.
If anyone has a contribuitions or improves to do in the code, fell free to do it.
Related
I have to do full offline functionality like all redux store data should remain after page refresh/offline(network connection lost).
When user goes offline previously stored data should be used in viewing pages & for suppose i have big forms(ex: employee form with all work experience and personal details)suddenly if user loose network, data should not be lost.. should be saved somehow & after that when user is online have to store data to DB
I am using react -18.0.0 version with react-redux & redux-saga.What are the possible ways?
lets say users are required to log in to system .once login is successfull save that
data to local storage then dispatch loginsuccess with user and token as payloads
execute a function inside useEffect in app.js to get back saved data from local storage and dispatch
loginsuccess with those data
inside signup function:
let res=await axios.post("/signin",{user});
let{user,token}= res.data;
localStorage.setItem("user", JSON.stringify(user));
localStorage.setItem("token",token);
dispatch({ type: "loginsuccess",payload:{user,token}});
inside app.js :
useEffect(() => {
dispatch(isUserLoggedIn());
}, []);
inside isUserLoggedIn :
const localstorage = localStorage.getItem("user");
if (localstorage) {
const user = JSON.parse(localStorage.getItem("user"));
dispatch({ type: "loginsuccess",payload:{user}});
} else {
dispatch({ type: "loginfailed",payload:{error:"please log in"}});
}
One way can be to store your form(or state) in localstorage. Another way can be use library redux-persist .
I think your question is similar to the one asked here. Have a look here for more ideas.
Another approach to store your data, is to make use of the IndexedDB API, it already has a great support on the latest browser versions caniuse.
There is a really good library that make use of this api but
combined with promises idb.
After picking up how you're gonna do it, you can have a watcher saga that reacts to the online / offline status and do the appropriated synchronization.
Also I think it's important to mention that all of the above will only take care of the data, you still need to make sure to make the JS and CSS to be available offline. on web.dev there is a great induction on this subject.
I'm tinkering with NextJS' getServerSideProps. I see that when I request a page from scratch, I receive the fully hydrated content. Then when I navigate to a new page, an API call is made, which receives some JSON data that is used to re-populate the page.
What I don't like is that the new API call is actually making two calls. For example my getServerSideProps has an axios.get() call. So on that click to the new page, I'm getting:
a call to something like example.com/_next/data/1231234/....
that call, behind the scenes, must be running my getServerSideProps() with its axios.get() to retrieve the new JSON data.
So is there a way to avoid the double-API call? I'd prefer that after the first page load, clicks to new pages would just skip to step two.
On a non-NextJS app I'd have something like a useEffect() that ran on page load, but obviously then the first run of the page would not return the full content, and for search-engine purposes I'd like to return the full content. I've seen some lectures where Google says they do run javascript and see the full content, but might as well be on the safe side for all other engines.
getServerSideProps will always run at request time--whenever you hit the page (or possibly using prefetch, the default, of next/link) This will result in pre-render of the page using the data from getServerSideProps Side-note: If you using next API middleware, then you can avoid the ajax call and simply import the method to run directly in getServerSideProps.
It sounds like you want to fetch the data at build time and could render the page statically? If so, rather look to use getStaticProps.
You can also avoid both and make an API call in useEffect if you prefer, but code will be run at the client, once the page loads, of course. getServerSideProps will pre-render the page with the data before it renders to the client.
So, the goal is to determine ways of getting props for:
the initial (direct) page request,
in-app navigation request
To solve this we have two options. And unfortunately, both of them are not perfect.
First option:
Check if the request has header x-nextjs-data. NextJS adds this header for fetching data from getServerSideProps:
export const isInitialPageRequest = (req: GsspRequest) => {
return !req.headers['x-nextjs-data'];
}
export const getServerSideProps: GetServerSideProps = async (context: GetServerSidePropsContext) => {
if (isInitialPageRequest(context.req)) {
// this code runs only on the initial request, on the server side
}
return {
props: {
// props
}
}
}
In this case, the request to /_next/data/development/xxx.json?...' is still executed every time. But at least you can control behavior depending on the case (and avoid redundant API calls for example).
Second option:
Use getInitialProps and check if property context.req is defined or not. You already mentioned it in the comments, just added it as an answer option with an example:
page.getInitialProps = async (context: NextPageContext) => {
if (context.req) {
// this code runs only on the initial request, on the server side
}
return {
// props
}
}
The NextJS team is recommending to use getServerSideProps instead
I am currently working on a authentication project within React Native using the react-navigation package. So far so good, I have 3 Navigators setup, one for loading, one for auth and one for the application.
I have also got the navigation setup with redux so I can access it from any component via a prop, which is exactly what I wanted to do.
However, I have a question. I have done some research and can't seem to figure out the best way to do this.
I am dispatching an action from a press of a button attemptLogin() the attempt login then does what it says on the tin, it attempt to login.
After a successful login, I store the access_token in SecureStore using expo.
Now, upon the successful login I need to navigate away from the current stack onto the new one.
What would be the best way to do this, can you return a promise from a redux action? If so would the best way to be inside of the component and then inside of the component do something like
this.props.login(username, password).then(() => { this.props.navigation... });
Or would you do it inside of the action?
A recommended way as below:
componentDidUpdate(prevProps) {
if(this.props.loggedInSuccessfully && !prevProps.loggedInSuccessfully) {
this.props.navigation.navigate....
}
}
onLoginButtonPress = () => {
const { username, password } = this.state;
this.props.login(username, password);
}
Upon successful logged in, update a state loggedInSuccessfully in your reducer and implement logic in componentDidUpdate. This way is the clearest for whoever gonna maintaining your code, in my humble opinion
you can also navigate from redux action or everywhere you want.
read the official docs about navigation service.
I am trying to display an admin Dashboard in react and Meteor only if the current user is an admin.
I am calling a server method that checks the user permission and rendering the admin component only if this method returns true.
This server call is asynchronous and thus the component is not rendering, usually I manage this asynchronous call with state, but I don't want to expose anything in state here (wouldn't like someone to change the state and access the admin dashboard).
Here is the code:
export default class AdminChecker extends Component {
isItAdmin() {
// Get the id of current user
const userId = Meteor.userId();
if (userId) {
// call a server method which returns true if current user is Admin
Meteor.call('checkAdminId', userId, (err, authorized) => {
if (err) {
console.log(err);
return null;
}
return (authorized) ? <AdminDashboard /> : null;
});
}
}
render() {
return (
<div className="admin-temp-container">
{this.isItAdmin()}
</div>
);
}
}
I think I can have this logic in a parent component and send the result of isItAdmin to adminDashboard as a prop (the adminDashboard component would display information only if its props is true).
But I am insure if this is safe. Could one change the props with Chrome react developer tools or something like that?
Thanks a lot
I think there are two parts to this (and the comments are hinting at both):
First, you should not expect to be able to enforce security on the client. You must implement access control logic on the server (i.e., any API that performs an admin action must check that the user performing the action is an admin).
Once you've done that (and perhaps you already have), then you likely need to use props or state to store whether or not the user is an admin (just like you would store any other data in your app).
The key point is that once you enforce security on the server, then it doesn't really matter if a user manipulates the state to get to the admin dashboard: the server will not let the user view any real data or take any actions anyway.
This is completely unrelated to server-side rendering. You can most certainly build a secure admin dashboard without server-side rendering, as long as the APIs used to fetch the admin data and perform admin actions are implementing their own access control checks.
For example I have two components - ListOfGroupsPage and GroupPage.
In ListOfGroupsPage I load list of groups from the server and store it to the state.groups
In route I have mapping like ‘group/:id’ for GroupPage
When this address is loaded, the app shows GroupPage, and here I get the data for group from state.groups (try to find group in state via id).
All works fine.
But if I reload page, I'm still on page /group/2, so GroupPage is shown. But state is empty, so the app can't find the group.
What is the proper way to load data in React + Redux? I can see this ways:
1) Load all data in root component. It will be very big overhead from traffic side
2) Don't rely on store, try to load required data on each component. It's more safe way. But I don't think that load the same data for each component - it's cool idea. Then we don't need the state - because each component will fetch the data from server
3) ??? Probably add some kind of checking in each component - first try to find required data in store. If can't - load from the server. But it requires much of logic in each component.
So, is there the best solution to fetch data from server in case of usage Redux + ReactJS?
One approach to this is to use redux-thunk to check if the data exist in the redux store and if not, send a server request to load the missing info.
Your GroupPage component will look something like
class GroupPage extends Component {
componentWillMount() {
const groupId = this.props.params.groupId
this.props.loadGroupPage(groupId);
}
...
}
And in your action...
const loadGroupPage = (groupId) => (dispatch, getState) => {
// check if data is in redux store
// assuming your state.groups is object with ids as property
const {
groups: {
[groupId]: groupPageData = false
}
} = getState();
if (!groupPageData) {
//fetch data from the server
dispatch(...)
}
}
I recommend caching the information on the client using localstorage. Persist your Redux state, or important parts of it, to localstorage on state change, and check for existing records in localstorage on load. Since the data would be on the client, it would be simple and quick to retrieve.
The way I approach this is to fetch from the server straight after the store has been created. I do this by dispatching actions. I also use thunks to set isFetching = true upon a *_REQUEST and set that back to false after a *_SUCCESS or *_FAILURE. This allows me to display the user things like a progress bar or spinner. I think you're probably overestimating the 'traffic' issue because it will be executed asynchronosly as long as you structure your components in a way that won't break if that particular part of the store is empty.
The issue you're seeing of "can't get groups of undefined" (you mentioned in a comment) is probably because you've got an object and are doing .groups on it. That object is most likely empty because it hasn't been populated. There are couple of things to consider here:
Using ternary operators in your components to check that someObject.groups isn't null; or
Detailing in the initialState for someObject.groups to be an empty array. That way if you were to do .map it would not error.
Use selectors to retrieve the list of groups and if someObject.groups is null return an empty array.
You can see an example of how I did this in a small test app. Have a look at specifically:
/src/index.js for the initial dispatch
/src/redux/modules/characters.js for the use of thunks
/src/redux/selectors/characters.js for the population of the comics, series, etc. which are used in the CharacterDetails component