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 :)
Related
I am creating an easy chat app, with different text channels. I am facing an infinite loop issue when using the useEffect hook to update the messagesList on real time. You can see the code below but this is what I am trying to achieve:
First useEffect is for the chat window to scroll to the last message every time there is a change in the messagesList array. This means: I am in the middle of the messages window, I write a new message and it takes me to the bottom. This is working fine.
Second useEffect is for the messagesList to be rendered whenever the channel is changed or there is any change in the messagesList. Adding the messagesList as a dependency is causing the infinite loop... but I think I need it cause otherwise the following happens: user1 is inside the chat channel and user2 writes a new message. User1 wont see the new message displayed as his chat is not being re-rendered. How would you make it for the new message to be displayed for user1?
Sorry for the confusing question and thanks a lot in advance!
useEffect(() => {
anchor.current.scrollIntoView(false);
}, [messagesList]);
useEffect(() => {
const unsubscribe = onSnapshot(
collection(firestore, `channels/${activChannel}/messages`),
(snapshot) => {
console.log(snapshot.docs);
getMessagesList();
}
);
return () => unsubscribe();
}, [activChannel, messagesList]);
I am not familiar with firestore, but perhaps you could tie the updating of the messages to the event that an user submits his message or use useSyncExternalStore. This piece of documentation on useEffect use cases might help you.
an excerpt from the docs:
Here, the component subscribes to an external data store (in this
case, the browser navigator.onLine API). Since this API does not exist
on the server (so it can’t be used to generate the initial HTML),
initially the state is set to true. Whenever the value of that data
store changes in the browser, the component updates its state.
Although it’s common to use Effects for this, React has a
purpose-built Hook for subscribing to an external store that is
preferred instead. Delete the Effect and replace it with a call to
useSyncExternalStore:
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();
}
}
}
I have dropdown filter to show items by date, for example and show data for last 24 hours, show data for last 3 days.
I have defaultState in my reducer
const defaultState = {
dataArray: [],
a: true,
b: false
}
By default dataArray is empty.
And I have reducer and action. In componentDidMount method I fetch data from server by dispatching some actions.
If I refresh page, default page that list last items for 24 hours is empty because dataArry comes from defaultState in my reducer. But if I change page to list data for last 3 days then componentWillReceiveProps works and inside this method I fetch data and it reduce my state and returns new one with
dataArray = [{some data}]
How to fetch data and set it to state to render it after page was refreshed?
Add to defaultState a 'loading' variable and initialize it to true. When fetching of data is completed, set it to false.
In your component check this variable. If it is true display a spinner and/or a loading message, otherwise display the data.
In addition to that, every time you start fetching, before fetching, fire an action called 'FETCH_START' which will set the loading variable to true.
If there is an error in fetching, you can set another state variable to the error message. This variable will be initialized (every time you start fetching) to an empty string. If loading is completed you can check this error variable, and display the error message if there was an error, instead of displaying the data.
This process is useful for various cases, such as authentication, etc.
Yossi's answer is pretty spot on. I just want to take it a step further and mention that you can also enhance your users' experience by knowning when to fetch data. For instance, if you're on a landing page and know most of your users are going to be browsing your store, start fetching that data on the landing page while nothing is happening.
To go along with that, take a look at this: Google Dev's requestIdleCallback()
I have an application which searches for flights using Vue.js and Vue Router.
I have two components, first one is search, which is on the base route '/'. When user clicks on search, it will send a request to server and gets a huge list of flights.
Then I need to call the result component on '/results' route and show the results using v-for.
I have two questions, first, how can I manually redirect to '/results' after I get the results.
Second and more important, what is the proper way of passing the results data to results component to use?
Inside your results components, you can put transition hooks in the route object. Read here: http://vuejs.github.io/vue-router/en/pipeline/hooks.html
The activate hook runs when a component is activated, and the component wont appear until the activate hook has run. Here's the example on that page, which would be similar to yours:
route:{
activate: function (transition) {
return messageService
.fetch(transition.to.params.messageId)
.then((message) => {
// set the data once it arrives.
// the component will not display until this
// is done.
this.message = message
})
}
}
So basically when they click search you send them to /results and this hook will handle loading the data in between.
Here's an advanced example of a mail app using vue-router that shows off a lot of the transition hooks in action: https://github.com/vuejs/vue-router/tree/dev/example/advanced
It seems pretty common in react/flux to respond to trigger actions that set specific status attributes on your state data for your components to react to by rendering this vs. that.
Simple example: User clicks the "complete" button on a to-do list item component, it fires an action that sets complete: true on that item in the store, the component re-renders and reacts to that "complete" status attribute by rendering a friendly check-mark.
Not-so-simple example: Application starts loading collection of data from your API, so you init your store with loading: true, and your component reacts by rendering a spinner. When the load is complete, an action sets loading: false so the component will go ahead and render the items...but if there was an error, the action will also trigger error: true prompting the component to render some red warning text. Now you've got two toggles to manage, un-set the error flag if you give it another go, etc.
Really-not-so-simple example: User clicks the "edit" button on a to-do list item, so you set editing: true on that item so the component renders the form instead of the display. Then, the user hits a submit button. Now you've got editing: true and submitting: true in case you want to prevent a double-submit while the update is in transit. Then, if there's an error, you need to shut submitting off but keep editing true and add an error flag with a message and now if the user just navigates away from the page you've got to clean all that up...
So...how do we keep all this stuff sane? Consistent? Readable? Are there patterns/conventions/utilities for this kind of status data? How do we keep all these status flags separate from our actual data - the stuff we loaded and may want to submit back to the API, without it having to swat away all these additional bits of information we tacked on?
Maybe it all just comes down to writing clean code, but it seems like the kind of domain that there might be more to thank I'm aware of...
Instead of using flags (booleans), I would recommend having a currentStatus field. You can then use constants to give that field a value. Something like...
var Status = {
LOADING: 1,
EDITING: 2
};
and so on. In the case that you might have more than one state at once (can't come up with a decent example right now), you can use JS' Bitwise ops.
Status = {
LOADING: 1,
EDITING: 2,
SUBMITTING: 4,
SOMETHING_ELSE: 8
};
// Let's say I'm loading and doing something_else
var currentStatus = Status.LOADING + Status.SOMETHING_ELSE;
// Am I editing?
console.log(Status.EDITING & currentStatus); // 0, so no
// Am I doing something_else?
console.log(Status.SOMETHING_ELSE & currentStatus); // non-zero? Then true