Event like componentDidUpdate, but fired only once - javascript

I have some components that should do some work as soon as their data has arrived and rendered for the first time, but not for future rerenderings. For example: Comments are loaded and rendered, now 1. load social media libraries and 2. load some Google Analytics.
Right now I'm doing it like that:
componentDidUpdate: function (prevProps, prevState) {
if (this.hasAlreadyUpdatedOnce) {
// ... do some stuff...
} else {
// ... do some stuff that should happen only once...
// 1. load social media libraries
// 2. load some Google Analytics
this.hasAlreadyUpdatedOnce = true;
}
}
But I'm asking myself if there's a more elegant way than setting a property like that.

Assuming you're responding to a state change, you should pass a callback as the second argument to setState.
componentDidMount: function(){
ajaxyThing(function(data){
this.setState({data: data}, function(){
// this.state is updated, the component has rerendered
// and the dom is current
});
}.bind(this));
}

You want componentDidMount(). Details here.

Have you tried updating state once the ajax call has finished?
Or you can return false for componentShouldUpdate and once the ajax call promise has resolved call forceUpdate.

I can't give you a definitive answer because I don't know if your ajax call is in the parent or child component but either way you should be able to leverage shouldComponentUpdate() to accomplish your goals. If you really don't ever want to update your component after the ajax call comes in then you can do something like this:
shouldComponentUpdate() {
return false;
}
and then when your ajax call comes back just run this.forceUpdate(). returning false will make it so that your component never updates unless you run this.forceUpdate(). However this is not the best solution to the problem I just can't give a better one without more information.

The React docs have a good example on how to handle this using isMounted().
isMounted() returns true if the component is rendered into the DOM,
false otherwise. You can use this method to guard asynchronous calls
to setState() or forceUpdate().
Example
First, initialize your state variables in `getInitialState()':
getInitialState: function() {
return {
username: '',
lastGistUrl: ''
}
}
In componentDidMount() make the ajax call ($.get in this case) then re-set the state variables:
componentDidMount: function() {
$.get(this.props.source, function(result) {
var lastGist = result[0];
if (this.isMounted()) {
this.setState({
username: lastGist.owner.login,
lastGistUrl: lastGist.html_url
});
}
}.bind(this));
}

Related

Vue.js component method on creation

I'm new to Vue and I'd like to make an AJAX call every time my component is rendered.
I have a vue component lets say "test-table" and Id like to fetch the contents via an AJAX call. There are many such tables and I track the active one via an v-if/v-else-if etc.
Currently I have a cheaty solution: in the template for the component I call a computed property called getData via {{ getData }} which initiates the Ajax call but does only return an empty string. Id like to switch to the proper way but dont know how.
My code is like so: (its typescript)
Vue.component("test-table", {
props: ["request"],
data () {
return {
tableData: [] as Array<TableClass>,
}
},
template: `{{ getData() }} DO SOME STUFF WITH tableData...`,
computed: {
getData() : string {
get("./foo.php", this.request, true).then(
data => this.tableData = data.map(element => new TableClass(data))
)
return "";
}
}
}
HTML:
<test-table v-if="testcounter === 1" :request="stuff...">
<test-table v-else-if="testcounter === 2" :request="other stuff...">
...
get is an async method that just sends a GET request with request data to the server. The last parameter is only for saying the method to expect a JSON as answer. Similar to JQuerys getJSON method.
the "created" method does NOT work! It fires only one time when the component is first created. If I deactivate and activate again (with v-if) the method is not called again.
Btw: I'm using Vue 2.6.13
Lifecycle hooks won't fire every time if the component is cached, keep-alive etc
Add a console.log in each of the lifecycle hooks to see.
Change to use a watcher which handles firing getData again if request changes.
...
watch: {
request: {
handler: function() {
this.getData()
},
deep: true
}
},
created() {
this.getData()
},
methods: {
getData(): string {
// do request
}
}
#FlorianBecker try the lifecycle hook updated(). It may be a better fit for what you're trying to achieve. Docs here.
You should be able to use the mounted hook if your component is continuously rendered/unrendered using v-if, like so:
export default {
mounted() {
// do ajax call here
this.callAMethod();
},
...
}
Alternatively, you could use the created() hook but it is executed earlier in the chain, so this means the DOM template is not created yet so you cant refer to it. mounted usually is the way to go.
More info on these hooks can be found here.

React Redux - quick flash of previous state before dispatch issue

I'm building a React + Redux app with a Node.js backend, and one of the features is that a user can view profiles of other users. To do this, I have a section in the Redux state called users that looks like:
{
...,
users: {
user: {},
isLoading: true
}
}
Every time the /users/:id route is rendered, a getUser(id) action is dispatched and fills the state with the received information.
The main issue is when a user views user1's profile page (therefore redux state is now filled with user1's profile) and then views user2's profile page, the dispatch getUser(2) action is called right after the page is rendered. Since user1's info is still in the state, it will flash their info for a very short time, and then show the loading spinner until the new user is loaded.
I read about dispatching a resetUser(id) action on every unmount of the page, but I'm not sure if this is the right way to go. Also, one of the features is if a user is viewing their own page, they have an edit button which redirects them to /edit-profile. If I reset the state on every unmount, I'll have to fetch their profile again (in the edit page), even though I just did that when they viewed their page.. And that doesn't seem like it makes sense.
Any ideas how to solve this? Thanks!
The render phase runs after mounting. And you stated that previous data is being shown before new data. It seems that you have asynchronous mounting:
async componentDidMount() {}
It will continue rendering even before mounting phase is completed. To avoid issue, you may use synchronous nature of mounting:
componentDidMount(){}
Where you'll call api data.
Now, when you reach to rendering phase it will have new data available before rendering and won't flash you old data.
You now may be wondering how to call api asynchronously. You can create a asynchronous function and call that function inside the synchronous componentDidMount.
componentDidMount() {
yourAsyncFunc()
}
async yourAsyncFunc() {} // api call
How can I do this with React hooks?
While using useEffect, don't implement async:
useEffect(async () =>
Implement it simply:
useEffect(() => {
// you can still use asynchronous function here
async function callApi() {}
callApi()
}, []) // [] to run in similar to componentDidMount
If you miss second parameter to useEffect then you are not ensuring it to run on mounting phase. It will run before, after, and in the rendering phase depending on case.
Implementing something like resetUser(id) seems to be the right way here. I use this approach in my current project and this does solve the problem. The other approach of removing async keyword from useEffect callback as mentioned in another answer didn't work for me (I use hooks, redux-toolkit, Typescript).
After dispatching this action, your state should look something like
{
...,
users: {
user: null,
isLoading: false,
}
}
If you are using hooks, you can dispatch the action this way:
useEffect(() => {
const ac = new AbortController();
...
return () => {
dispatch(resetUser(null));
ac.abort();
};
}, []);
Action could look something like this:
resetListingDetails(state, action) {
// Immer-like mutable update
state.users = {
...state.users,
user: null,
};
}

Best way to run a function when page refresh

I am trying to call a function when the page is refreshed. I adding a state if the page is rendered with the data I got from my backend end but I get an warning message "Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state." Even though it works fine (except with the warning message), I dont think this is the best, most efficient way to do it?
If this is the best, most efficient way, how do I fix the waring message?
function Demo() {
constructor(){
this.state = {
username: "unknown",
rendered: false,
}
this.renderUserProfile = this.renderUserProfile.bind(this);
}
update(){
//code to retrieve data from backend node.js *
this.setState({ username: data });
this.setState({ rendered: true });
}
render(){
if (!this.state.rendered) {
this.update();
}
return (<p>demo</p>)
}
}
Thank you for your help!
Do never change state inside render, because every state (or prop) change will call render again. That is what the warning is telling you: you risk having infinite loops.
There is no need of a state param for "rendered", because your component will call render anyway as soon as this.setState({username: data}); executes. If you want something to happen then, add it in update just after the setState line.
Now let's imagine that you still really want it. If you don't want your component to render when the rendered state changes, then just don't use the React Component state, but any standard class attribute:
class MyComponent extends React.Component {
rendered = false
...
render() {
this.rendered = true
....
}
}
Just be aware that this looks super wrong (and useless) since it tries to go around what the React framework is good at.
Finally, from this code there is no way to know how you intend you have new data coming in. If it is an Ajax call, then you will call this.update with that data in the callback of your Ajax call - certainly not in render.

How to make a react component that can be rendered with no data and later rendered with data?

I have a situation that I'm sure is common and I just haven't learned the react way for accomplishing it. Let's say I have this:
var appView = new React.createClass({
render: function() {
return (
<div>
<SomeSubview/>
</div>
)
}
});
React.render(
React.createElement(appView),
$('#app').get(0)
);
My question is how should I create the SomeSubView react component so that it can render properly without any data, and then later render showing some data when the data is available. I have pub/sub system set up, so I'd like to be able to subscribe to an event and get the data to SomeSubView that way. SomeSubView might look something like this:
SomeSubView = new React.createClass({
componentDidMount: function() {
pubsub.subscribe({
callback: function() {
// something the sets the state or a prop of this component
}
});
},
render: function() {
// something that renders properly when
// there is no data and renders the data when there is data
return (
<div></div>
)
}
});
I can't tell if this a case for state or props on the react component? I don't know if it's best practice to put conditionals in the render function?
In your SomeSubView just check if data is available in your render function, but before returning the markup.
Like this:
SomeSubView = new React.createClass({
componentDidMount: function() {
pubsub.subscribe({
callback: function() {
// something the sets the state or a prop of this component
}
});
},
render: function() {
// something that renders properly when
if( this.state.data.length > 0 ){
var data = <li>{this.state.data}</li>;
}
return (
<div>{data}</div>
)
}
});
If the variable data is not set, React will simply pass over it as non-existent.
You can of course also use .map() on your state data to loop out markup just like in most render examples.
You have to use state like user3728205 said, especifically setState().
setState(function|object nextState[, function callback])
Merges nextState with the current state. This is the primary method
you use to trigger UI updates from event handlers and server request
callbacks.
The first argument can be an object (containing zero or more keys to
update) or a function (of state and props) that returns an object
containing keys to update.
Here is the simple object usage...
setState({mykey: 'my new value'});
What this says is that "whenever" you update your state via setState, React will execute the method render again for you. So, you should put yor display logic based on the state, when it changes the view displayed will change too.
I say "whenever" because React doesn't fire re-render immediatily, but creates a pending state transition.
NEVER mutate this.state directly, as calling setState() afterwards may
replace the mutation you made. Treat this.state as if it were
immutable.
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value.
There is no guarantee of synchronous operation of calls to setState
and calls may be batched for performance gains.
setState() will always trigger a re-render unless conditional
rendering logic is implemented in shouldComponentUpdate(). If mutable
objects are being used and the logic cannot be implemented in
shouldComponentUpdate(), calling setState() only when the new state
differs from the previous state will avoid unnecessary re-renders.
For more information about the magic of React you should read this.
https://facebook.github.io/react/docs/reconciliation.html
A simple example that maybe can help.
And i recommend read the flux architecture that is very easy to understand and implement (is about utilizing a unidirectional data flow), and you have implementations like Fluxxor that facilitates the use of flux. This is for your pubsub part.

Hold off React Render until after request

I have a component that's being loaded with the initial state:
getInitialState: function() {
return {
panel: "deals",
showExtension: false,
person: {}
};
},
And I have this to find a person:
componentDidMount: function() {
this.findPersonFromBackground();
},
findPersonFromBackground: function() {
chrome.runtime.sendMessage({ action: "findPerson", email: this.props.currentEmail.from_email }, function(person) {
this.setState({person: person});
}.bind(this));
},
So all works fine. If the person is found, I render one thing, and if not, something else.
When the first is found, the view goes from non-found state to found state real fast, since the API calls are pretty quick.
What's the best way to wait to render until that first call comes back?
There's no good way to do what you're asking, which is to keep a component from rendering at all until some asynchronous operation that the component initiates completes; the best you can do is use techniques from other answers to this question to render something slightly different in the case that it's not complete.
However, the problem you're actually trying to solve is preventing the brief flash of alternatively-rendered content if the API request starts and the completes very quickly. If you move the asynchronous operation to the parent of your component, you can ensure that the async operation completes before you ever even render your child.
If the Chrome runtime requests are consistently fast, this may be fine, but in the general case, it's worth considering what the behavior will be if the async operation takes a longer time to complete.
One way you can handle it is with the enum pattern. In this code, this.state.person is either the loading sentinel, the notFound sentinel, or the actual person.
var status = {loading: {}, notFound: {}};
// ...
getInitialState: function(){
return {person: status.loading}
},
fetchPerson: function(){
doFetchPerson(function(person){
if (person) this.setState({person: person})
else this.setState({person: status.notFound})
}.bind(this))
},
render: function(){
var person = this.state.person)
if (person === status.loading) ...
else if (person === status.notFound) ...
else ...
}
Is this more of a design question? You could show a spinner, an empty box, a shapes only preview (like Facebook feed), or nothing at all.
If this is a technical question then return null in render().
maybe you need this project: https://github.com/toplan/react-hold
You can use the placeholder to keep the shape of your component.

Categories

Resources