Why componentWillMount is called after rendering? - javascript

I am working with React and I am trying to understand the lifecycle. I am doing a componentWillMount method in order to get the props I need before the render occurs. I need to know how to update the state when the view loads.
All I am trying to do is a GET request in order to get a list of dealers for a Casino Game. Basically, I am missing 1 or 2 steps which are for render the dealers's list in the DOM
I will show what I am doing with my code and after that I will explain what I want
Actions part
getDealerActions.js
class GetDealersActions {
constructor () {
this.generateActions('dealerDataSuccess', 'dealerDataFail');
}
getDealers (data) {
const that = this;
that.dispatch();
axios.get('someroute/get-dealers/get-dealers')
.then(function success (response) {
that.actions.dealerDataSuccess({...response.data});
})
}
};
then we move to the stores
getDealersStore.js
class GetDealersStore {
constructor () {
this.state = {
dealerData : null,
};
}
#bind(GetDealersActions.dealerDataSuccess)
dealerDataSuccess (data) {
this.setState({
dealerData : data,
});
console.log(this.state.dealerData);
}
}
in this case that console.log(this.state.dealerData); returns something like this which is exactly what I need
Object {dealersData: Array[3]}
the problems comes in the component part, honestly because I don't know how to handle the data here
#connectToStores
export default class Dealers extends Component {
static contextTypes = {
router : React.PropTypes.func,
}
constructor (props) {
super(props);
this.state = {}
}
static getStores () {
return [ GetDealersStore ];
}
static getPropsFromStores () {
return GetDealersStore.getState();
}
componentWillMount () {
console.log('###', this.props);
GetDealersActions.getDealers();
}
render () {
console.log('>>>', this.props);
let content;
if (this.state.dealerData) {
content = this.state.dealerData.map((item) => {
return <div key={item.CardId}>{item}</div>;
});
} else {
content = <div>Loading . . .</div>;
}
return (
<div>
<div>{content}</div>
</div>
);
}
}
all I get here <div>{content}</div> is Loading . . . because this.state is coming like this Object {}
A weird situation I am getting here, is that this view is rendering twice, the 1st time is rendering, and the console.log('>>>', this.props); returns this >>> Object {params: Object, query: Object} and the second time it renders, fires this >>> Object {params: Object, query: Object, dealerData: Object} which is what I need.
So, why componentWillMount is waiting the render method in order to get fired ?

It's not weird at all. componentWillMount will fire before render, and in the first-pass you are invoking an action to get the dealers GetDealersActions.getDealers(); which is basically an async command. Since it is async, the component will render once before it gets data, and then again after the store publishes a changed event, which will re-trigger rendering.
Here is an approximation of the sequence of actions happening in your example:
componentWillMount invokes getDealers command (which is async)
initial render with default component state
Async operation completed in action creator and store is set with dealer data
store publishes a changed event, which re-triggers rendering
second render invoked with the dealer data in component state.
The problem is that React will run it's lifecycle methods in a certain sequence, not caring about you invoking some async method. So basically you don't have a way to stop rendering just because you invoked a command to get the dealers. That is a limitation of react (or a feature), which surfaces when combined with async programming and you should accept it as is.
If you accept the fact that React will render twice, you can utilize that in your favor, so on first render you could just show a loading indicator (e.g. a spinning wheel) and when the data loads you just display it in the second render.
However, if you are not convinced and still want to avoid double-rendering in the initial load, you could do prefetching of the data before you mount the application component, which would ensure that initial data is loaded in the store before the first render, which would mean that you wouldn't have to invoke getDealers in componentWillMount since the data would already be in the store on the first render.
As a reminder, double-rendering is not a significant performance problem, like it would be in Angular.js or Ember.js, since React is very efficient at DOM manipulation, but it could produce some UX issues if not handled properly.

Related

need to pass an effect via props or force component reload from outside the root component in preact/react

I have a situation that an item outside the component might influence the item in the backend. ex: change the value of one of the properties that are persisted, let's say the item status moves from Pending to Completed.
I know when it happens but since it is outside of a component I need to tell to the component that it is out of sync and re-fetch the data. But from outside. I know you can pass props calling the render method again. But the problem is I have a reducer and the state will pick up the last state and if I use an prop to trigger an effect I get into a loop.
Here is what I did:
useEffect(() => {
if (props.effect && !state.effect) { //this runs when the prop changes
return dispatch({ type: props.effect, });
}
if (state.effect) { // but then I get here and then back up and so on
return ModelEffect[state.effect](state?.data?.item)}, [state.status, state.effect, props.effect,]);
In short since I can't get rid of the prop the I get the first one then the second and so on in an infinite loop.
I render the root component passing the id:
render(html`<${Panel} id=${id}/>`,
document.getElementById('Panel'));
Then the idea was that I could do this to sync it:
render(html`<${Panel} id=${id} effect="RELOAD"/>`,
document.getElementById('Panel'));
any better ways to solve this?
Thanks a lot
I resolved it by passing the initialized dispatch function to a global.
function Panel (props) {
//...
const [state, dispatch,] = useReducer(RequestReducer, initialData);
//...
useEffect(() => {
//...
window.GlobalDispatch = dispatch;
//...
}, [state.status, state.effect,]);
with that I can do:
window.GlobalDispatch({type:'RELOAD'});

How do i fetch json from a url and display json content on my js page

Im making a custom MS Teams app and in the app im trying to fetch a json from a url to then display the contents later. However, the fetch is failing. My goal is to fetch a list of data from a supplied url and then displaying it in the Tab within my teams app, which would be here: Where i want my json content to show up
As you can probably tell, i dont have any experience with javascript at all, but the custom MS teams app wants javascript...
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import React from 'react';
import './App.css';
import * as microsoftTeams from "#microsoft/teams-js";
/**
* The 'GroupTab' component renders the main tab content
* of your app.
*/
class Tab extends React.Component {
constructor(props){
super(props)
this.state = {
context: {}
}
}
//React lifecycle method that gets called once a component has finished mounting
//Learn more: https://reactjs.org/docs/react-component.html#componentdidmount
componentDidMount(){
// Get the user context from Teams and set it in the state
microsoftTeams.getContext((context, error) => {
this.setState({
context: context
});
});
// Next steps: Error handling using the error object
}
componentDidMount() {
fetch('http://example.com/movies.json')
.then(response => response.json())
.then(data => console.log(data));
}
render() {
var jsondata = data;
let userName = Object.keys(this.state.context).length > 0 ? this.state.context['upn'] : "";
return (
<div>
</div>
);
}
}
export default Tab;
So to sum it all up, how do i fetch my json from a url and display the content of that json on my tab.js page within teams?
Thanks in advance for any help.
While I can't speak to how the Teams API works, I can help you understand how to render things from a json API in your react component.
In your componentDidMount function, your example is sending and receiving the response from the API. To render this response, we need to assign the data to your component's "state" and then use that to render it in HTML.
This will be pretty simple. First, you need to extend your component's state, in a similar manner as you've done for context. Do this first in the constructor, where we'll declare an initial state of an empty object (I'll name it content but you can use whatever name makes most sense):
// inside the constructor() function
this.state = {
context: {},
content: {}
}
In React, we use setState to update this state object state when something changes, like on a lifecycle method such as componentDidMount. You just need to call this setState again when you want to change the state object from its initial value to something else. In our case, when we receive the data from the API.
setState takes whatever you provide it and merges it into the state object, so you only should declare anything you want to change. Anything else not declared will remain unchanged.
So, in componentDidMount, we can make a small change to do something with data when it arrives:
componentDidMount() {
fetch('http://example.com/movies.json')
.then(response => response.json())
.then(data => {
this.setState({
content: data
})
});
}
This is basically saying:
once the component has mounted, make a call to fetch from the API
then, with that response, take the json from the body
and then, assign the json "data" into our component's state object under the key of content.
You can then do things with this data by calling this.state.content. I'm not sure what format the data will come in, but whatever json object arrives back from the API will be stored under this.state.content.
As an example, imagine we get a simple object back from the API that looks like this { title: "Tab title" }. It means that, on a successful call to the API, our state object will look like this:
{
context: "whatever you have here", // whatever you have here, I don't know this
content: { title: "Tab title" }
}
When this state object is updated, react will trigger a new render of the component.
So, to make it appear in our component, we need to use this state in our render function (we wrap things in curly braces if they need to be dynamically rendered rather than hardcoded):
render() {
return (
//... the rest of your function
<div>{this.state.content.title}</div>
);
}
As you might have guessed, this will show the title inside a div, if the title exists.
Eventually, you should consider handling the state of the component before that API call has resolved itself. The lifecycle method componentDidMount will be called after the component is mounted, and because you're hitting an API, there will be something rendered to the DOM before the API call resolves itself. In my example, it'll be just be an empty div, and then it'll appear when the state updates and the render function is called again.
You could do this more effectively by extending your state object to see whether the API response is done (you'd update this in the same place you set the content), and you could render the UI conditionally on this.
The official docs on lifecycle methods will help you understand this pattern more.
Good luck!
Well, you can rely on #Josh Vince for an explanation as he did it perfectly, I will just write the code a bit cleaner for you.
Let's create 2 methods that will set the state with respective data and then simply call the state values and render the following as per your wish.
import React, {Component} from 'react';
import './App.css';
import * as microsoftTeams from "#microsoft/teams-js";
class Tab extends Component {
this.state = {
context: {},
movies: null
}
getContext = () => {
try {
microsoftTeams.getContext((context) => this.setState({context}));
}
catch(error){
console.log('ERROR ->', error);
throw error;
}
}
fetchMoviesData = () => {
try {
fetch('http://example.com/movies.json')
.then(response => response.json())
.then(data => this.setState({movies: data}));
} catch(error){
console.log('ERROR ->', error);
throw error;
}
}
componentDidMount() {
this.getContext();
this.fetchMoviesData();
}
render() {
let {movies, context} = this.state;
const jsondata = movies;
let userName = Object.keys(this.state.context).length ? context['upn'] : "";
return <>JSONDATA: {jsondata}, USERNAME: {userName}</>
}
}
export default Tab;

Set initial state for material ui dialog

I have a MaterialUI dialog that has a few text fields, drop downs, and other things on it. Some of these elements need to be set to some value every time the dialog opens or re-opens. Others elements cannot be loaded until certain conditions exist (for example, user data is loaded).
For the 'resetting', I'm using the onEnter function. But the onEnter function doesn't run until entering (duh!)... but the render function, itself, still does - meaning any logic or accessing javascript variables in the JSX will still occur. This leaves the 'onEnter' function ill-equipped to be the place I set up and initialize my dialog.
I also can't use the constructor for setting/resetting this initial state, as the data I need to construct the state might not be available at the time the constructor loads (upon app starting up). Now, I could super-complicate my JSX in my render function and make conditionals for every data point... but that's a lot of overhead for something that gets re-rendered every time the app changes anything. (the material UI dialogs appear run the entire render function even when the 'open' parameter is set to false).
What is the best way to deal with initializing values for a material ui dialog?
Here is a super-dumbed-down example (in real life, imagine getInitialState is a much more complex, slow, and potentially async/network, function) - let's pretend that the user object is not available at app inception and is actually some data pulled or entered long after the app has started. This code fails because "user" is undefined on the first render (which occurs BEFORE the onEnter runs).
constructor(props) {
super(props);
}
getInitialState = () => {
return {
user: {username: "John Doe"}
}
}
onEnter = () => {
this.setState(this.getInitialState())
}
render() {
const { dialogVisibility } = this.props;
return (
<Dialog open={dialogVisibility} onEnter={this.onEnter}>
<DialogTitle>
Hi, {this.state.user.username}
</DialogTitle>
</Dialog> );
}
My first instinct was to put in an "isInitialized" variable in state and only let the render return the Dialog if "isInitialized" is true, like so:
constructor(props) {
super(props);
this.state = {
isInitialized: false
};
}
getInitialState = () => {
return {
user: {username: "John Doe"}
}
}
onEnter = () => {
this.setState(this.getInitialState(),
() => this.setState({isInitialized:true})
);
}
render() {
const { dialogVisibility } = this.props;
if(!this.state.isInitialized) {
return null;
}
return (
<Dialog open={dialogVisibility} onEnter={this.onEnter}>
<DialogTitle>
Hi, {this.state.user.username}
</DialogTitle>
</Dialog> );
}
As I'm sure you are aware... this didn't work, as we never return the Dialog in order to fire the onEnter event that, in turn, fires the onEnter function and actually initializes the data. I tried changing the !this.state.inInitialized conditional to this:
if(!this.state.isInitialized) {
this.onEnter();
return null;
}
and that works... but it's gives me a run-time warning: Warning: Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state.
That brought me to a lot of reading, specifically, this question: Calling setState in render is not avoidable which has really driven home that I shouldn't be just ignoring this warning. Further, this method results in all the logic contained in the return JSX to still occur... even when the dialog isn't "open". Add a bunch of complex dialogs and it kills performance.
Surely there is a 'correct' way to do this. Help? Thoughts?
What you need conceptually is that when you are freshly opening the dialog, you want to reset some items. So you want to be able to listen for when the value of open changes from false to true.
For hooks, the react guide provides an example for keeping the "old" value of a given item with a usePrevious hook. It is then simply a matter of using useEffect.
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
function MyDialog({ dialogVisibility }) {
const prevVisibility = usePrevious(dialogVisibility);
useEffect(() => {
// If it is now open, but was previously not open
if (dialogVisibility && !prevVisibility) {
// Reset items here
}
}, [dialogVisibility, prevVisibility]);
return <Dialog open={dialogVisibility}></Dialog>;
}
The same thing can be achieved with classes if you use componentDidUpdate and the previousProps parameter it receives.
export class MyDialog extends Component {
public componentDidUpdate({ dialogVisibility : prevVisibility }) {
const { dialogVisibility } = this.props;
if (dialogVisibility && !prevVisibility) {
// Reset state here
}
}
public render() {
const { dialogVisibility } = this.props;
return <Dialog open={dialogVisibility}></Dialog>;
}
}
You should use componentDidUpdate()
This method is not called for the initial render
Use this as an opportunity to operate on the DOM when the component has been updated
If you need data preloaded before the dialog is opened, you can use componentDidMount():
is invoked immediately after a component is mounted (inserted into the tree)
if you need to load data from a remote endpoint, this is a good place to instantiate the network request
React guys added the useEffect hook exactly for cases like the one you are describing, but you would need to refactor to a functional component.
Source: https://reactjs.org/docs/hooks-effect.html
This can be solved by doing leaving the constructor, getInitialState, and onEnter functions as written and making the following addition of a ternary conditional in the render function :
render() {
const { dialogVisibility } = this.props;
return (
<Dialog open={dialogVisibility} onEnter={this.onEnter}>
{this.state.isInitialized && dialogVisibility ?
<DialogTitle>
Hi, {this.state.user.username}
</DialogTitle> : 'Dialog Not Initialized'}
</Dialog> );
)}
It actually allows the dialog to use it's "onEnter" appropriately, get the right transitions, and avoid running any extended complex logic in the JSX when rendering while not visible. It also doesn't require a refactor or added programming complexity.
...But, I admit, it feels super 'wrong'.

Updating component state in React-Redux with API calls

I'm trying to set up a React app where clicking a map marker in one component re-renders another component on the page with data from the database and changes the URL. It works, sort of, but not well.
I'm having trouble figuring out how getting the state from Redux and getting a response back from the API fit within the React life cycle.
There are two related problems:
FIRST: The commented-out line "//APIManager.get()......" doesn't work, but the hacked-together version on the line below it does.
SECOND: The line where I'm console.log()-ing the response logs infinitely and makes infinite GET requests to my database.
Here's my component below:
class Hike extends Component {
constructor() {
super()
this.state = {
currentHike: {
id: '',
name: '',
review: {},
}
}
}
componentDidUpdate() {
const params = this.props.params
const hack = "/api/hike/" + params
// APIManager.get('/api/hike/', params, (err, response) => { // doesn't work
APIManager.get(hack, null, (err, response) => { // works
if (err) {
console.error(err)
return
}
console.log(JSON.stringify(response.result)) // SECOND
this.setState({
currentHike: response.result
})
})
}
render() {
// Allow for fields to be blank
const name = (this.state.currentHike.name == null) ? null : this.state.currentHike.name
return (
<div>
<p>testing hike component</p>
<p>{this.state.currentHike.name}</p>
</div>
)
}
}
const stateToProps = (state) => {
return {
params: state.hike.selectedHike
}
}
export default connect(stateToProps)(Hike)
Also: When I click a link on the page to go to another url, I get the following error:
"Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op."
Looking at your code, I think I would architect it slightly differently
Few things:
Try to move the API calls and fetch data into a Redux action. Since API fetch is asynchronous, I think it is best to use Redux Thunk
example:
function fetchHikeById(hikeId) {
return dispatch => {
// optional: dispatch an action here to change redux state to loading
dispatch(action.loadingStarted())
const hack = "/api/hike/" + hikeId
APIManager.get(hack, null, (err, response) => {
if (err) {
console.error(err);
// if you want user to know an error happened.
// you can optionally dispatch action to store
// the error in the redux state.
dispatch(action.fetchError(err));
return;
}
dispatch(action.currentHikeReceived(response.result))
});
}
}
You can map dispatch to props for fetchHikeById also, by treating fetchHikeById like any other action creator.
Since you have a path /hike/:hikeId I assume you are also updating the route. So if you want people to book mark and save and url .../hike/2 or go back to it. You can still put the the fetch in the Hike component.
The lifecycle method you put the fetchHikeById action is.
componentDidMount() {
// assume you are using react router to pass the hikeId
// from the url '/hike/:hikeId'
const hikeId = this.props.params.hikeId;
this.props.fetchHikeById(hikeId);
}
componentWillReceiveProps(nextProps) {
// so this is when the props changed.
// so if the hikeId change, you'd have to re-fetch.
if (this.props.params.hikeId !== nextProps.params.hikeId) {
this.props.fetchHikeById(nextProps.params.hikeId)
}
}
I don't see any Redux being used at all in your code. If you plan on using Redux, you should move all that API logic into an action creator and store the API responses in your Redux Store. I understand you're quickly prototyping now. :)
Your infinite loop is caused because you chose the wrong lifecycle method. If you use the componentDidUpdate and setState, it will again cause the componentDidUpdatemethod to be called and so on. You're basically updating whenever the component is updated, if that makes any sense. :D
You could always check, before sending the API call, if the new props.params you have are different than the ones you previously had (which caused the API call). You receive the old props and state as arguments to that function.
https://facebook.github.io/react/docs/react-component.html#componentdidupdate
However, if you've decided to use Redux, I would probably move that logic to an action creator, store that response in your Redux Store and simply use that data in your connect.
The FIRST problem I cannot help with, as I do not know what this APIManager's arguments should be.
The SECOND problem is a result of you doing API requests in "componentDidUpdate()". This is essentially what happens:
Some state changes in redux.
Hike receives new props (or its state changes).
Hike renders according to the new props.
Hike has now been updated and calls your "componentDidUpdate" function.
componentDidUpdate makes the API call, and when the response comes back, it triggers setState().
Inner state of Hike is changed, which triggers an update of the component(!) -> goto step 2.
When you click on a link to another page, the infinite loop is continued and after the last API call triggered by an update of Hike is resolved, you call "setState" again, which now tries to update the state of a no-longer-mounted component, hence the warning.
The docs explain this really well I find, I would give those a thorough read.
Try making the API call in componentDidMount:
componentDidMount() {
// make your API call and then call .setState
}
Do that instead of inside of componentDidUpdate.
There are many ways to architect your API calls inside of your React app. For example, take a look at this article: React AJAX Best Practices. In case the link is broken, it outlines a few ideas:
Root Component
This is the simplest approach so it's great for prototypes and small apps.
With this approach, you build a single root/parent component that issues all your AJAX requests. The root component stores the AJAX response data in it's state, and passes that state (or a portion of it) down to child components as props.
As this is outside the scope of the question, I'll leave you to to a bit of research, but some other methods for managing state and async API calls involved libraries like Redux which is one of the de-facto state managers for React right now.
By the way, your infinite calls come from the fact that when your component updates, it's making an API call and then calling setState which updates the component again, throwing you into an infinite loop.
Still figuring out the flow of Redux because it solved the problem when I moved the API request from the Hike component to the one it was listening to.
Now the Hike component is just listening and re-rendering once the database info catches up with the re-routing and re-rendering.
Hike.js
class Hike extends Component {
constructor() {
super()
this.state = {}
}
componentDidUpdate() {
console.log('dealing with ' + JSON.stringify(this.props.currentHike))
}
render() {
if (this.props.currentHike == null || undefined) { return false }
const currentHike = this.props.currentHike
return (
<div className="sidebar">
<p>{currentHike.name}</p>
</div>
)
}
}
const stateToProps = (state) => {
return {
currentHike: state.hike.currentHike,
}
}
And "this.props.currentHikeReceived()" got moved back to the action doing everything in the other component so I no longer have to worry about the Hikes component infinitely re-rendering itself.
Map.js
onMarkerClick(id) {
const hikeId = id
// Set params to be fetched
this.props.hikeSelected(hikeId)
// GET hike data from database
const hack = "/api/hike/" + hikeId
APIManager.get(hack, null, (err, response) => {
if (err) {
console.error(err)
return
}
this.props.currentHikeReceived(response.result)
})
// Change path to clicked hike
const path = `/hike/${hikeId}`
browserHistory.push(path)
}
const stateToProps = (state) => {
return {
hikes: state.hike.list,
location: state.newHike
}
}
const dispatchToProps = (dispatch) => {
return {
currentHikeReceived: (hike) => dispatch(actions.currentHikeReceived(hike)),
hikesReceived: (hikes) => dispatch(actions.hikesReceived(hikes)),
hikeSelected: (hike) => dispatch(actions.hikeSelected(hike)),
locationAdded: (location) => dispatch(actions.locationAdded(location)),
}
}

Assigning a property with ES6 in a React Component

I am new to ES6 and still trying to grasp the concepts of the new specifications, i am currently working on a component in React where i need to make an ajax call and store this response in an object. Then use this object to the map the necessary elements
My component looks like the following
export class App extends Component {
search(){
//make ajax call
response = obj.responseText;
}
getValues(){}
render(){
let result = response.data.map(this.getValues);
return(
<div onKeyDown={this.search.bind(this)}>{result}</div>
)
}
}
How do i declare the "response" variable globally which gets assigned the data from ajax call "obj.responseText"?
It seems like you know what you want to achieve, but are a little confused about how to get there.
I would highly recommend reading the React documentation before you go any further.
Why not global variables?
How do I declare the response variable globally?
In short, don't. Global variables are well-documented as being evil. One instance of this component in a page with a global variable to store its search results would be fine, but imagine if you had two or more instances - they would all share/overwrite each other's search results.
Introducing state
Instead, you want to use React's component state functionality to store your search results.
You can set an initial state by setting a component's this.state in its constructor, (or in ES5, define a getInitialState method on the component).
Then, any time you want to update the component's state, you can call its this.setState(...) method, passing in a new state object. This will also trigger a re-render of the component.
Example
Here is a simple implementation following the above pattern:
export class App extends Component {
// Set the initial state of the component in the constructor
constructor(props) {
super(props);
this.state = {};
}
// This gets called when your component is mounted
componentDidMount() {
// Here we make our AJAX call. I'll leave that up to you
performMyAjaxMethodDefinedSomewhereElse(result => {
// We call this method to update `this.state` and trigger re-rendering
this.setState({ result });
});
}
render() {
// If we haven't received any results yet, display a message
if (!this.state.result) {
return (
<div>No results!</div>
);
}
// Iterate over the results and show them in a list
const result = this.state.result.map(text => (<li>{text}</li>));
// Display the result
return (
<ul>{result}</ul>
);
}
}
Naturally, if you don't want the AJAX call to fire off immediately, you can use a very similar approach, replacing componentDidMount with an event handler which looks almost identical.

Categories