Hope you all are fine. I am new to react redux world. I am learning and working on a call logging project. I have a few questions and it would great if someone can guide me whether I am doing it wrong or tell me the alternative.
I am using JWT to authenticate a user. Once the user details are verified. I am dispatching success action and in the reducer, I am setting the state to authenticated true and the response. I am also storing the token and expiryTime in localStorage
In the root file which is index file. I am checking if the token exists in localStorage and if so then dispatching sign in action.
Everything is working. But I am losing other values like a response from a server. When he logged in for the first time. How can I tackle this problem ?
Secondly, there is a User initial icon on the top right corner. I get the initial when a user logs in and it gets stored in auth state. it works fine but once again if I refresh the page it becomes null and I lose that initial.
so I tried another way and stored the initial in localStorage. But the navbar already rendered on the screen and I don't see any initial until I refresh the page.
I have passed new key in mapStateToProps. ii and stored initial from localStorage in it and it working fine. Is this a valid way of doing it ???
Regards
Meet
const SignedInLinks = (props) => {
return (
<ul className="right">
<li><NavLink to="/signin" onClick=
{props.signOut}>Log Out</NavLink></li>
<li><NavLink className="btn btn-floating pink lighten-1" to="/">
{props.ii ? props.ii : null }
</NavLink></li>
</ul>
)}
const mapStateToProps = state => {
return {
auth: state.auth,
ii: window.localStorage.getItem('ui')
}
}
export default connect(mapStateToProps, { signOut })(SignedInLinks);
Rather than using localStorage in mapStateToProps, intialize your ii state in your reducer corresponding to that state and then pass it to your component via mapStateToProps. Something like this.
const iiReducer = (state = window.localStorage.getItem('ui') || false, action) => {
/*.
.
. Other Logic
.
.*/
return state
}
and then use it normally as you would from a store's state
const mapStateToProps = state => {
return {
auth: state.auth,
ii: state.ii
}
}
Hope this helps !
I believe I have an idea of what the problem is (I'm kind of a beginner in react and redux aswell, so tell me if I'm speaking nonsense).
You say that you store the token in localstorage (it is recommended not to do that btw, the recommended way is to store it in a cookie), and if a valid token is found in localstorage, you log in. I'm guessing that you store the response from the server (including the icon) in the app's state, or in the redux store? If that is the case, this information will be removed when you update the browser (f5),therefore not being loaded anymore.
The solution is to make sure to load the relevant data when the component mounts, so in your componentDidMount method (if you don't have one, make one), and set the state in that method.
Related
In my react app I use the following pattern quite a bit:
export default function Profile() {
const [username, setUsername] = React.useState<string | null>(null);
React.useEffect(()=>{
fetch(`/api/userprofiles?username=myuser`)
.then(res=>res.json())
.then(data => setUsername(data.username))
},[])
return(
<div>
{username}'s profile
</div>
)
}
When the page loads, some user data is fetched from the server, and then the page updates with that user data.
One thing I notice is that I only really need to call setUsername() once on load, which makes using state seem kinda excessive. I can't shake the feeling that there must be a better way to do this in react, but I couldn't really find an alternative when googling. Is there a more efficient way to do this without using state? Or is this the generally agreed upon way to load data when it only needs to be done once on page load
Without using any external libraries, no - that is the way to do it.
It would be possible to remove the state in Profile and have it render the username from a prop, but that would require adding the state into the parent component and making the asynchronous request there. State will be needed somewhere in the app pertaining to this data.
The logic can be abstracted behind a custom hook. For example, one library has useFetch where you could do
export default function Profile() {
const { data, error } = useFetch('/api/userprofiles?username=myuser');
// you can check for errors if desired...
return(
<div>
{data.username}'s profile
</div>
)
}
Now the state is inside useFetch instead of in your components, but it's still there.
In following code, Sample uses a async function to get some data. I will be using that data to update the Redux store so any component can access the username within the application.
const resource = fetchData();
function Sample() {
// throws a promise when retrieving data
const name = resource.read();
const dispatch = useDispatch();
dispatch(setUsername(name));
const username = useSelector((state) => state.username);
return <div>User: {username}</div>;
}
<Suspense fallback="loading....">
<Sample />
</Suspense>
Here, lets assume there is no way my application can proceed without the username. Well, <Suspense> at parent level achieves that up until data are fetched from the resource. However, there is a slight gap from Redux event dispatch to <Sample> component re-render where is displays User: instead of loading.... (event don't force the re-render immediately so username is empty). So I will see following content in the web page in the same order
loading.... --> User: --> User: Srinesh
So my requirement is to show loading.... until store is updated. On the web page, I'm expecting to see,
loading.... --> User: Srinesh
Is there a way to achieve this without using another condition at <Sample> component level to show loading.... if the username is null?
The first issue is that dispatching in the middle of component rendering logic is a side effect, and you must never do that.
A safer place to put that would be in a useLayoutEffect hook, which will dispatch as soon as the component is done rendering, but force a synchronous re-render before the browser has a chance to paint. That way you won't see the flash.
I have an app which header contains icon which should be shown when the user is logged in. I keep my logged in info in sessionStorage but when it changes my component is not rendered again. I tried to use useEffect for that and useMemo but it doesn't worked.
The updating part:
const isLoggedIn = useMemo(() => sessionStorage.getItem('isLogged'), [sessionStorage.getItem('isLogged')]);
The usage:
{isLoggedIn === 'true' ? ['left'].map((anchor) => (
...some jsx
)) : null}
The sessionStorage value is a string: "false" or "true".
I have routes and constant header, the header is not a part of routes so when it changes my header is not rerenders so I tried to use useMemo for that.
Posting my answer as per clarification gained through comments.
If you are using Redux:
I would recommend to store the user logged-in information in redux store and connect to the isolated Header component via connect HOC and mapStateToProps. Whenever you update (upon successful user login) the user login status the component will listen to store updates.
Or
You can use React context approach if there is no redux used
// Declare it outside of your App component/any other file and export it
const GlobalState = React.createContext();
// Declare state variable to store user logged in info inside of your App component
const [isLoggedIn, setIsLoggedIn] = useState(false);
// Add them to context to access anywhere in your components via useContext
// In App render or where you have route mapping
<GlobalState.Provider value={{
isLoggedIn,
setIsLoggedIn
}}>
....
</GlobalState.Provider>
// Update the status using setIsLoggedIn upon successful login where you are making login call
// In your Header get it via useContext
const context = useContext(GlobalState);
`context.isLoggedIn` is what you need.
// You can use useEffect/useMemo approach to get the login status updates
Find more about React context and useContext
sessionStorage is not an observer object and you have to store the current authentication state into a variable or React state and use that variable in your component. And when you authenticated the user, you should update the variable to true and change that to false when the user logged out.
To implement what I said, you can get help from these ways:
Redux
React context
You can implement the React context by your self from scratch or using the React-hooks-global-state
UseMemo is used for memoizing calculated values. You should be using useCallback.useCallback is used for memoizing function references.
Refer this
const isLoggedIn = useCallback(() => sessionStorage.getItem('isLogged'), [sessionStorage.getItem('isLogged')]);
Can you try to put your sessionStorage data into State and update that state? As far as I know, react will not know about the session storage. So even if you change the manipulate the data in the sessionStorage directly it won't gonna update your UI.
let [storeData, setStoreData] = useState(true);
let isLoggedIn = useMemo(() => ({ sessionData: storeData }), [storeData]);
{isLoggedIn === 'true' ? ['left'].map((anchor) => (
...some jsx
)) : null}
<button
onClick={() => {
sessionStorage.setItem("isLogged", !storeData);
setStoreData(sessionStorage.getItem("isLogged"));
}} > Update Store </button>
I am currently working on a simple React app with a very common workflow where users trigger Redux actions that, in turn, request data from an API. But since I would like to make the results of these actions persistent in the URL, I have opted for React Router v4 to help me with the job.
I have gone through the Redux integration notes in the React Router documentation but the idea of passing the history object to Redux actions just doesn't feel like the most elegant pattern to me. Since both Redux and Router state changes cause React components to be re-rendered, I'm a little worried the component updates could go a bit out of control in this scenario.
So in order to make the re-rendering a bit more predictable and sequential, I have come up with the following pattern that attempts to follow the single direction data flow principle:
Where I used to trigger Redux actions as a result of users' interactions with the UI, I am now calling React Router's props.history.push to update the URL instead. The actual change is about updating a URL parameter rather than the whole route but that's probably not that relevant here.
Before:
// UserSelector.jsx
handleUserChange = ({ target: selectElement }) => {
// Some preliminary checks here...
const userId = selectElement.value
// Fire a Redux action
this.props.setUser(userId)
}
After:
// UserSelector.jsx
handleUserChange = ({ target: selectElement }) => {
// Some preliminary checks here...
const userId = selectElement.value
// Use React Router to update the URL
this.props.history.push(`/user-selector/${userId}`)
}
The userId change in the URL causes React Router to trigger a re-render of the current route.
Route definition in App.jsx:
<Route path="/user-selector/:userId?" component={UserSelector} />
During that re-render, a componentDidUpdate lifecycle hook gets invoked. In there I am comparing the previous and current values of the URL parameter via the React Router's props.match.params object. If a change is detected, a Redux action gets fired to fetch new data.
Modified UserSelector.jsx:
componentDidUpdate (prevProps) {
const { match: { params: { userId: prevUserId } } } = prevProps
const { match: { params: { userId } } } = this.props
if (prevUserId === userId) {
return
}
// Fire a Redux action (previously this sat in the onChange handler)
this.props.setUser(userId)
}
When the results are ready, all React components subscribed to Redux get re-rendered.
And this is my attempt to visualise how the code's been structured:
If anyone could verify if this pattern is acceptable, I would be really grateful.
For step 3, I suggest a different approach which should be more in line with react-router:
react-router renders a component based on a route
this component should act as the handler based on the particular route it matches (think of this as a container or page component)
when this component is mounted, you can use componentWillMount to fetch (or isomorphic-fetch) to load up the data for itself/children
this way, you do not need to use componentDidUpdate to check the URL/params
Don't forget to use componentWillUnmount to cancel the fetch request so that it doesn't cause an action to trigger in your redux state
Don't use the App level itself to do the data fetching, it needs to be done at the page/container level
From the updated code provided in the question:
I suggest moving the logic out, as you would most likely need the same logic for componentDidMount (such as the case when you first hit that route, componentDidUpdate will only trigger on subsequent changes, not the first render)
I think it's worth considering whether you need to store information about which user is selected in your Redux store and as part of URL - do you gain anything by structuring the application like this? If you do, is it worth the added complexity?
I am new to react js.
I have two routes like A & B. Now i am passing some values from A to B as props. If B page is refreshed, then all props values from A is gone and B page is not rendering. I am using react with redux.
mapDispatchToProps & mapStateToProps functions are used to pass values between A & B routes as props.
For example: Route A has done some calculations and store the values in redux state and Route B is exported as connect(mapStateToProps, mapDispatchToProps)(B), by using mapStateToProps in which A's state values are passed to B as props.
Please suggest me the best way to handle browser refresh on above mentioned use case and also if any other best way to pass the values between routes. Thanks in advance.
Your question talks about two different concerns. First is passing props from one page to another in a React/Redux application, and second is maintaining the application state when the page is refreshed.
You've described the correct method of passing data between two routes in a redux based application.
Which brings us to the second concern.
How to maintain the state of a React/Redux application when the page is refreshed?
When a React/Redux application is refreshed, it gets initialised again and the redux store gets it's default values.
If you wish to maintain the app state across page refreshes or across different sessions, you need to store the state somewhere, and load it when the app initialises.
We can divide this problem into three parts:
Where to store the data
How to store redux state
How to reload the data when the application is initialised
Let's look at each sub-problem individually.
Where to store the data?
You can use the Web Storage API to store data within the user's browser. This API provides 2 mechanisms to store data:
sessionStorage: Stored data is preserved as long as the browser is open, including page reloads and restores.
localStorage: Data is preserved until it is cleared by the user or the application. It persists even if the browser is closed and reopened.
Both sessionStorage and localStorage allow you to store key-value pairs in the browser, and both provide the same set of functions to manage data.
For sessionStorage (example taken from MDN):
// Save data to sessionStorage
window.sessionStorage.setItem('key', 'value');
// Get saved data from sessionStorage
var data = window.sessionStorage.getItem('key');
// Remove saved data from sessionStorage
window.sessionStorage.removeItem('key');
// Remove all saved data from sessionStorage
window.sessionStorage.clear();
For localStorage:
// Save data to localStorage
window.localStorage.setItem('key', 'value');
// Get saved data from localStorage
var data = window.localStorage.getItem('key');
// Remove saved data from localStorage
window.localStorage.removeItem('key');
How to store redux state?
As you are already aware, Redux provides a createStore function which takes our root reducer and returns the application store.
The store object holds the entire application store, and provides a few methods including one to register a listener.
store.subscribe(listener) can be used to add a change listener to the store, which will get called every time the store gets updated.
We will add a listener to the store, which will save the application state to localStorage.
Try adding this in the file where you create your store using createStore:
/**
* This function accepts the app state, and saves it to localStorage
* #param state
*/
const saveState = (state) => {
try {
// Convert the state to a JSON string
const serialisedState = JSON.stringify(state);
// Save the serialised state to localStorage against the key 'app_state'
window.localStorage.setItem('app_state', serialisedState);
} catch (err) {
// Log errors here, or ignore
}
};
/**
* This is where you create the app store
*/
const store = createStore(rootReducer);
/**
* Add a change listener to the store, and invoke our saveState function defined above.
*/
store.subscribe(() => {
saveState(store.getState());
});
How to reload the stored data, and restore the application state when the app is initialised again?
When we create our app store using createStore, we have the option to pass an initial state to the store using the second parameter to the function.
When the application starts up, we will check the localStorage for any saved data. If we find it, we will send it as the second parameter to createStore.
This way, when the app finishes initialising, it will have the same state as it did before the page was refreshed or the browser was closed.
Try adding this in the file where you create your store using createStore:
/**
* This function checks if the app state is saved in localStorage
*/
const loadState = () => {
try {
// Load the data saved in localStorage, against the key 'app_state'
const serialisedState = window.localStorage.getItem('app_state');
// Passing undefined to createStore will result in our app getting the default state
// If no data is saved, return undefined
if (!serialisedState) return undefined;
// De-serialise the saved state, and return it.
return JSON.parse(serialisedState);
} catch (err) {
// Return undefined if localStorage is not available,
// or data could not be de-serialised,
// or there was some other error
return undefined;
}
};
/**
* This is where you create the app store
*/
const oldState = loadState();
const store = createStore(rootReducer, oldState);
That's it! Now, combine the last two blocks of code, and your application has the ability to maintain state across page refreshes, or even across browser restarts.
Hope this helps.
Cheers!
:)
you can try redux-persist or redux-storage ,
when you initialize the store
createStore(reducer, [preloadedState], [enhancer]),
you can get the data and assign it to preloadedState
Try using react-router and this will render components based on route.
When the app is initialized, app should fetch data and update the store with the required information.
Eg: In the below example, When IntilizeApp component is mounting compute the information required and update the store by dispatching the actions. Use react's life cycle method like componentWillMount to compute.
import {Router, Route, hashHistory} from 'react-router'
// import initializeApp
// import ComponentA
// import ComponentB
<Router history={hashHistory}>
<Route path="/" component={InitializeApp}>
<Route name="A" path="A" component={ComponentA} />
<Route name="B" path="B" component={ComponentB} />
</Route>
</Router>