I currently have an application using redux and I have it set that every time the app loads it checks the JWT token is valid and adds the user data to the state.
I was wondering what the differences are between calling the api and then storing data in the state every reload or storing the data once in localStorage?
How the code is setup with calling the api and storing with redux.
CHECK TOKEN
const token = localStorage.UserIdToken;
if (token) {
const decodedToken = jwtDecode(token);
if (decodedToken.exp * 1000 < Date.now()) {
store.dispatch(logoutUser());
} else {
store.dispatch({ type: SET_AUTHENTICATED });
axios.defaults.headers.common['Authorization'] = token;
store.dispatch(getUserData());
}
}
getUserData()
export const getUserData = () => async dispatch => {
try {
const res = await axios.get('/user');
dispatch({
type: SET_USER,
payload: res.data,
});
}
...
};
First of all, storing the data once in localStorage means that the user will not receive updates to his data, but will always receive the same. Think of seeing the same social media feed every time you log in, which would be the case if it would be saved in localStorage and not requested from the api every reload.
Second, storing data in localStorage instead of the redux state means you use the benefits of using state and redux - redux ensures that components will rerender when state they depend on changes. This ensures that components are responsive to user actions. localStorage won't do that.
Following your comment, I think there is another reason you should consider:
Using localStorage might pose problems if you want to change the user data (add a field for instance). If the data was in 1 place, you could change all user data and let users pull the new data on the next reload. If the data is in localStorage, you will need to add code to your app that will change the existing data on first reload, and then do nothing on other times. This is not a good pattern, and has a better chance of having bugs and problems.
Related
In my project I use ReactJS in combination with redux and firebase.
Creating a thunk to make async calls to firebase and store the data in redux.
When I'm fetching my files from firebase storage.
Using this method:
try {
let list = [];
await storage
.ref()
.child(path)
.listAll()
.then((res) => {
res.items.forEach((item) => {
storage
.ref()
.child(item.fullPath)
.getDownloadURL()
.then((urlRes) => {
list.push({
name: item.name,
url: urlRes,
});
});
});
});
dispatch(getFileActionSuccess(list));
This method works as intended.
It returns an array of files with their url to view/download them.
The problem is when I try to access this object in my state, it returns an empty array.
Even though when checking using Redux Devtools, I can clearly see that after the list was dispatched. And I could see the correct data.
Devtools image
Note: this is not the real code but a representation
function page() {
getFiles();
<filesList/>
}
function filesList() {
const files = useSelector((state) => state.files, _.isEqual);
console.log(files);
return (..insert render..);
}
But when logging the files. It shows an empty array at first. But when expanding it, it shows the correct data. But it doesn't render it. As I don't understand why it isn't showing like it is supposed to I no longer know what to do and how to fix this.
Simply fetch the data on component mount and component update, and update your state accordingly.
If you’re using React Hooks, you can use React.useState() and give it a dependency. In this case the dependency would be the part of your state which will update upon completion of your HTTP request.
In my React project, I've implemented authentication using Firebase. The workflow is as follows - The UID which is received upon signing in with Google OAuth is used to query the users in the firestore, and accordingly, the user is fetched. This user is then updated to the redux state by the appropriate handler functions. This is the implementation of the sign-in process. The setUser function does the task of updating the redux state
googleAuth = async () => {
firebase.auth().signInWithPopup(provider).then(async (res) => {
const uid = res.user.uid;
const userRef = firestore.collection('users').where('uid', '==', uid);
const userSnap = await userRef.get();
if(!userSnap.empty) {
const user = userSnap.docs[0].data();
this.props.setUser(user);
}
this.props.history.push('/');
}).catch(err => {
alert(err.message);
})
}
The presence of "currentUser" field in the user state of redux, as a result of the aforementioned setUser function, is the basis of opening the protected routes and displaying relevant user-related details in my project. This currentUser is persisted in the state using Redux Persist, so as long as I don't sign out, the localStorage has the "currentUser" object stored, thus meaning that I am signed in.
This implementation, however, causes a problem, that if I update the localStorage using the console and set the currentUser without actually logging in using my Google credentials, the app recognises me as logged in, and I am able to browse all protected routes. Thus, if someone copies some other user's details from his device's localStorage, then he/she can easily access their account and change/delete important information.
What is the correct way to implement authentication in React then, assuming that I do want login state to persist until the user does not sign out manually
I am writing simple blog collection. I have newest react-router and react-redux. I have all blogs in my store. When I navigate to single blog page it works fine at first time, but when I refresh browser or enter dynamic url straight to address bar, component loose data. What kind of hook I should use to fetch data? I have only this in my SingleBlog to fetch data:
const id = useParams().id
const blog = useSelector(state => state.blogs.find(b => b.id === id))
When you refresh the browser, your whole app restarts, that includes your state that holds the data.
If you want to keep the data even after the browser refreshes, you should then save your data in the localStorage and modify the code so that it gets the state from the data saved in the localStorage.
How you might do that:
When you fetch the data
...
// Where data is the variable that holds your fetched data
localStorage.setItem('data_array', JSON.stringify(data));
In the blog reducer
...
// Instead of having the initial state as empty array `[]`, you make it equal
// to whichever data is stored in the localStorage. Because if the localStorage
// have no data, it will still return an empty array `[]`
const initialState = JSON.parse(localStorage.getItem('data_array'));
export function blogReducer(state = initialState, action) {
switch (action.type)
...
}
In the blog page
...
// You keep blog const as it is since we took care of the localStorage in
// the reducer
const blog = useSelector(state => state.blogs.find(b => b.id === id))
...
Quick note:
You shouldn't rely on refreshing the browser to fetch a new blog post/data. You could, however, use setInterval() to send a fetch request every x time to see if there are any new posts/data, Or you could add a refresh button to your app that fires a request to get any new data
I am working on a React.JS project, based on the react-boilerplate templates. Unfortunately, react-boilerplate only has an example of loading remote data into redux. There is no save example.
I was able to write the save actions, reducer and saga, no problem. It is all pretty standard stuff. However, one issue holding me back, which I was unable to resolve - reloading the store after saving.
I did the below:
const mapDispatchToProps = dispatch => {
return {
loadEvent: eventId => dispatch(loadEvent(eventId)),
saveEvent: values => {
const event = dispatch(saveEvent(values))
return dispatch(loadEvent(event.id || values.id))
}
}
}
I want the above code to work as a promise - reload the event by id after save finished to completion.
It is not working like I need it to. I get load invoked, yet there is no new data in the store.
You should create some xxxx_REQUEST and xxxx_SUCCESS|FAILURE action types to each request (not important it is saving or not).
I don't know you are redux-saga or redux-thunk but after your request fetch finished, you should dispatch xxxx_SUCCESS|FAILURE then in your reducer, get data and store it on you store.
Then you could use a selector to get data from redux store in your component.
I resolved this issue by sticking everything inside my saga as below:
try {
// Call our request helper (see 'utils/request')
const createdEvent = yield call(request, requestURL, {
method: !isNaN(id) && id !== undefined && id !== null ? 'PUT' : 'POST',
body: JSON.stringify(event)
})
yield put(eventLoaded(createdEvent, id))
yield put(loadEvent(createdEvent['id']))
} catch (err) {
yield put(eventLoadingError(err))
}
Now, the thing works as I need it to.
Will Redux store be cleaned after reloading the browser?
And can i use redux instead of cookie to save user info and token?
Redux is a state management library so,on refresh ,the redux store contains only the initialstate of the reducers.If you want to save tokens or authenticated user info then save it in localStorage.And also make sure,you un set the local storage after logging out of the app.
Use Redux Persist or build a middleware that will save the store everytime an action is dispatched and then create a HOC that when the app is reloaded (a.k.a the page is refreshed), it'll check the local storage for the item and then restore it to the store before the app is loaded and then render the application.
Depending on the complexity and if you want to blacklist certain reducers, I would use Redux-Persist. If you want to something simple and built by yourself use the middleware option.
Redux store gets initial state upon app reload.
Try this:
Make a dump component for local storage and use it anywhere you want.
Constants.js
export const USER_MODEL = {
set: ({ token, userInfo }) => {
localStorage.setItem('token', token);
localStorage.setItem('userInfo', userInfo);
},
remove: () => {
localStorage.removeItem('token');
localStorage.removeItem('userInfo');
},
get: () => ({
token: localStorage.getItem('token'),
userInfo: localStorage.getItem('userInfo’),
})
};
User.js
import { USER_MODEL } from './Constants';
// just an example how you can set localStorage anywhere in your component
USER_MODEL.set({
token: “abc”,
userInfo: {name: ‘foo’, city: ‘bar’},
});
// get user model from localStorage
const token = localStorage.get().token;
const userInfo = localStorage.get().userInfo;