I have a login flow in my react native app. When the user enters its credentials, an action is dispatch that verifies them and then dispatch an other action in order to modify the state by the reducer.
In my component, I register to changes this way:
function mapStateToProps(state) {
return { userConnected: state.user.connected }
}
export default connect(mapStateToProps)(LoginScreen)
And I perform action when new props are received with that function:
async componentWillReceiveProps(newProps){
if(newProps.userConnected){
this.props.navigation.navigate("Home");
}else{
this.showWrongCredentials();
}
}
The first time when the user enters wrong credentials, the props are updated with connected = false, so it shows the wrong credentials message. If the user clicks another time with some wrong credentials, the state received the same values connected = false, so the method componentWillReceiveProps is not called and I cannot show the wrong credentials message again.
How can I do that ? Everytime the state is updated, even if the values are the same, I would like the method componentWillReceiveProps to be fired.
Yes, such behaviour is by design.
From react-redux doc
If performance is a concern, the best way to improve performance is to skip unnecessary re-renders, so that components only re-render when their data has actually changed
Or if you like source
Note that selectorFactory is responsible for all caching/memoization of inbound and outbound props. Do not use connectAdvanced directly without memoizing results between calls to your selector, otherwise the Connect component will re-render on every state or props change.
You mentioned that user clicks when entered credentials. I suggest to change some property of store as a result of user click. So for every click credentials will be verified and message printed.
Introduce additional variable to store which will be used as flag for every user click after credential has been entered. It can be named credentialEnteredFlag and set to true every time user entered credentials. Then have action to reset credentialEnteredFlag to false from componentWillReceiveProps.
componentWillReceiveProps will look like
async componentWillReceiveProps(newProps){
if(newProps.credentialEnteredFlag){
this.props.resetCredentialEnteredFlag();
if(newProps.userConnected){
this.props.navigation.navigate("Home");
}else{
this.showWrongCredentials();
}
}
}
Related
It should be noted that I am using Redux with React-Native.
For simplicity sake, lets say I have an email viewer. There's a homepage with a massive list of emails and when you tap on an email, you are navigated to another screen. Here, the contents of the email, title, subject, body, is all held in a piece of state managed by redux. This all works fine.
Lets say I back out of that page, and now navigate to another email. The previous email pops up for a split second before the new email is shown. It should be noted here that I am storing the data in AsyncStorage as a sort of "cache?". The issue here is that since I only re-update the state whenever I tap on an email, the state which is the body of the email viewing page gets updated a split second after the user is navigated to it. This, is annoying.
The heart of the question is this
How can I store the body of my data in another piece of state, functionally identical to the current-email-viewing-state without overwriting the currently active state?
or
is this even the best way to do this?
Thanks
You could use Redux's lifecycle methods to handle this. Let's say the state for your email detail component looks something like this:
export const initialState: StateShape = {
loading: false,
readOnlyEmailData: {
recipientEmails: null,
senderEmail: null,
body: null,
},
};
When the email detail component (let's call it EmailDetail.jsx) is loading, you can use ComponentDidMount() to get and set your values.
You'll probably have actions and actionCreators like getEmail, getEmailSuccess, and getEmailError. Set loading to true in getEmail, and then false again on success or error. You can add a conditionally rendered spinner component (easy to borrow from something like MaterialUI) to EmailDetail, which is visible only when loading is true, and render the rest of your content when loading is false.
When the user hits the back button or otherwise navigates away from the component, componentWillUnmount() can be given a list of things to do as the component prepares to unmount. In this case you could use a reset method to reset loading and readOnlyEmailData to initial state on unmount.
When the user clicks on a new email, it will load the new email data on mount and show the spinner until the new email data is available.
There are other ways to do this, this is not the most optimized, but it should work quite a bit better than what you've tried so far. I hope it helps :)
I'm writing a component that uploads parameters to the server in promises. While the parameters are uploading I redirect to another component and send the parameters object as props, like this:
this.props.history.push('/new-page/createForm', {
parameters: this.state.parameters
});
At the create Form component, I'm receiving the props and rendering the state of the upload of the parameters, and they render as uploading (because they haven't finished uploading).
Back to the previous component, once the parameters upload promise finishes, I update the parameters object to reflect the new state, i.e, uploaded.
ParamsService.uploadParam(parameter, uploadRoot)
.then(() => {
parameter.status = 'Uploaded';
})
Now, once the parameters are uploaded, I want the create form to automatically re-render in order to reflect the new state of the parameters, but this is not happening automatically.
However, if I perform an action in the form, such as filling one of the input fields, which trigger a React rendering, the parameters show as uploaded.
Is there a way to recognize the props change in the new create form and re-render once the parameters state change to uploaded?
I've tried componentDidUpdate and getDerivedStateFromProps but such functions do not trigger when the parameters status change to uploaded.
Any help or guidance is appreciated.
see if react-url-query solve your problems.
as the example in readme, once you use the HOC like
export default addUrlProps({
urlPropsQueryConfig: {
foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }
},
})(MyComponent);
MyComponent will receive 2 props this.props.foo and this.props.onChangeFoo which allow you to get the value and allow you to change the url params ?fooInUrl
Solution for React hooks: use-query-params
I am trying to use react hooks to make a Table component that displays rows of data from an API based on a set of filters that the user can choose. I want to make a new call to fetch data whenever the user clicks an 'Apply Filters' button, not when the user makes changes to the filters.
I am using context to manage the 'filters' state and a 'lastFetched' state which tracks when the user last clicked the 'Apply Filters' button (as well as other states on the page). Updates to the context are made via the useReducer hook and its dispatch method (see here).
The data fetching occurs in a useEffect hook that reruns whenever the 'lastFetched' state changes. This appears to be working correctly; however, the effect references other values from the context (i.e. the filters) that are not included in the dependencies. I am aware of the exhaustive-deps eslint rule, and I am concerned that I am not handling the hook's dependencies correctly.
const Table = () => {
const [context, dispatch] = useTableContext(); // implemented with createContext and useReducer
const { filters, lastFetched } = context;
useEffect(() => {
if (!filters.run) {
return;
}
dispatch({ type: 'FETCH_DATA_BEGIN' });
const params = convertContextToParams(context); // this is lazy, but essentially just uses the the filters and some other state from the context
API.fetchData(params)
.then((data) => {
dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data.results });
})
.catch((e) => {
dispatch({ type: 'FETCH_DATA_FAILURE', payload: e.response.data.message });
});
return () => { ... some cleanup... };
}, [lastFetched]); // <== This is the part in question
return <...some jsx.../>
};
Again, this appears to be working, but according to the react docs, it seems I should be including all the values from the context used in the hook in the hook's dependencies in order to prevent stale references. This would cause the logic to break, since I don't want to fetch data whenever the filters change.
My question is: when the user clicks 'Apply Filters', updates context.lastFetched, and triggers the useEffect hook, will the hook be referencing stale filter state from the context? If so, why? Since the effect is rerun whenever the button is clicked, and all the state updates are done via a reducer, does the usual danger of referencing stale variables in a closure still apply?
Any guidance appreciated!
Note: I have thought about using useRef to prevent this issue, or perhaps devising some custom async middleware to fetch data on certain dispatches, but this is the solution I currently have.
I am not an expert but I would like to provide my takes. According to my understanding of how Context works, you will not get stale filter data with the current implementation. useReducer updates the state with a new object which will trigger Table to be re-render.
Also, Table component doesn't really care about filter data unless lastFetched is changed by a click event. If lastFetched is changed, all the Consumer of TableContext will be re-render again. You should not get stale filter data either.
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, and I wanted to re-request data to a REST server after login in case of "session expired error".
When the component mounts, it fetches the data from the network. However, if session has expired, I receive an unauthorized error. This error is managed by a parent component, which shows the login form to reactivate session. I did that like this because I have many components that should behave like that (re-request after login).
<Parent>
<Component1/>
<Component2/>
...
</Parent>
If a "session expired error" is triggered Parent's state changes and it shows the login form. After login, Parent's state changes again and all visible components should update their data. The ComponentX method called when Parent's state is changed is ComponentWillUpdate and ComponentDidUpdate, so I placed the following code in ComponentWillUpdate:
if (!this.state.gotData && !this.updating){
this.updating = true;
this.setState({gotData:true},
()=>{this.updating=false; this.getData();});
}
which does the following: if it was not able to get the data from the network and it is not getting data at the moment (!this.updating flag), it requests the data through this.getData method. gotData is updated in optimistic assumption, and it is set to false if getData method fails.
I know that setState should not be used in componentWillUpdate method, but I have not found a better solution to this. Maybe I could add a validSession prop to all components, then set it to false when "Parent" gets the "expiration" error, and set it to true when login is done. And re-request data from in componentWillReceiveProps instead of componentWillUpdate.
Another possible solution would be to subscribe components to a global "re-login" event, and do the data request inside the listener.
How would you do that? Maybe my approach is completely wrong from the beginning...