I call action creator using componentWillMount method, then it gets called twice. look at below screenshot of the result.
this is my action creator :
export function fetchAdmin(){
return (dispatch)=>{
axios.get(`${Config.API_URL}/admin`,{
headers: { authorization: localStorage.getItem('token')}
})
.then(response => {
return dispatch({
type: FETCH_DATA,
payload: response.data
});
}
)
.catch(response => {
console.log(response);
});
}
}
and this is my reducer :
import { FETCH_DATA } from '../actions/types';
export function admin(state= {}, action){
switch(action.type){
case FETCH_DATA:
return {...state, lists: action.payload };
default:
return { state, lists: 'YEY!'}
}
}
and this is my container and the way i call my action creator using componentWillMount.
import React, { Component } from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import * as actions from 'actions/admin_action';
class Admin extends Component{
componentWillMount(){
this.props.fetchAdmin()
}
renderData(){
var lists = this.props.lists;
console.log(lists);
}
render(){
return(
<div>
{this.renderData()}
</div>
)
}
const mapStateToProps = (state) => {
return { lists: state.admin.lists };
}
export default connect(mapStateToProps, actions)(Admin);
from the screenshot i assume that render() gets called twice. the first call goes to default action creator so that the result is "YEY!" and the second is the valid one.
can someone explain it to me why componentWillMount perform like this and actually in the first render i just wanna my array not "YEY!" how to solve this problem? Thank you.
There is no problem here! When your component render first time, it reached the rendering before the logic componentWillMount is finished, so:
First time rendering: Your component is rendered while the lists in global state is the default which equals YEY!.
Second time rendering: When data is fetched successfully, after that the reducer change in the lists in the global state with the fetched ones, after that your component detects the change due to mapStateToProps that got the lists from the global state.
Needless to say that React component is re-rendered on propsChange, that's why there is a method componentWillReceiveProps so that you can add some logic (if you need) on each time your props change.
This behavior allows you to handle waiting the data to be fetched by:
Add a loading spinner.
Get your layout rendered.
Get the other components in your page rendered.
Javascript is asynchronous. The HTTP request you are performing is asynchronous. This means that Javascript execution continues while the HTTP request is still pending.
So React already renders while the state is still 'Yey'. This is how it should be and also the beauty of Redux, actions are fire and forget, they are not blocking. If React would wait for the HTTP request to finish, user experience would be horrible!
If you don't want it to render 'Yey'. You should show a loader or something while the request is still pending.
Related
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;
I have a route (using React-Router) with component which it renders. Every time this route opened and its component created I need to reset some part of Redux state (one reducer's state in fact), used in this component. This reducer is shared in some other parts of the app, so I use Redux state and not local component's state. So how can I reset the reducer's state every time my component created? I am wondering about best practice to do this.
I think if I'll dispatch actions in componentDidMount method, there will be blinking of previous state for some second.
Can I dispatch action to reset some reducer's state in component's constructor?
Is there any better approach? Can I somehow to set initial state in connect() function, so component will have resetted state each time it created? I check the docs, but I cannot find some argument for this.
Yes, you can dispatch action in constructor to change reducer state
constructor(prop){
super(prop);
prop.dispatch(action);
}
Another approach you can try is setting default props so that you don't need to call reducer(dispatch action)
ButtonComponent.defaultProps = {
message: defaultValue,
};
One possible solution I can think of...
If you could go with the first approach, you can try to stop the previous state being shown while component is being re-rendered with reset state.
The only phase during which you would see the prevState is during the initial render. How about an instance variable to track the render count.
A rough draft.
import React from "react";
import { connect } from "react-redux";
import { add, reset } from "./actions";
class Topics extends React.Component {
renderCount = 0;
componentDidMount() {
// Dispatch actions to reset the redux state
// When the connected props change, component should re-render
this.props.reset();
}
render() {
this.renderCount++;
if (this.renderCount > 1) {
return (
<div>
{this.props.topics.map(topic => (
<h3 id={topic}>{topic}</h3>
))}
</div>
);
} else {
return "Initializing"; // You can return even null
}
}
}
const mapStateToProps = state => ({ topics: state });
const mapDispatchToProps = (dispatch) => {
return {
add(value){
dispatch(add(value));
},
reset(){
dispatch(reset());
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Topics);
Here renderCount is a class variable, that keeps incrementing on component render. Show a fallback UI on first render to avoid previous state from being shown and on second render (due to redux store update), you could display the store data.
A working example added below. I have added an approach to avoid the fallback UI as well. Have a look if it helps.
https://stackblitz.com/edit/react-router-starter-fwxgnl?file=components%2FTopics.js
This is my component's class:
import React, { Component } from 'react';
import {connect} from 'react-redux';
import Button from '../UI/Button/Button';
import * as actions from '../../store/actions';
class Password extends Component {
submitPassword=(e)=>{
e.preventDefault();
this.props.submitPassword(this.state.password, this.props.levelNumber);
}
render() {
<Button clicked={this.submitPassword} >
Submit password
</Button>
}
}
const mapStateToProps = state => {
return {
};
}
const mapDispatchToProps = dispatch => {
return {
submitPassword: (password,levelNumber) => dispatch(actions.submitPassword(password,levelNumber))
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Password);
and this is my action:
export const submitPassword = () => {
// HERE ALL MY LOGIC !!!
return {
level:undefined,
type:actions.PASSWORD
}
}
The code working all correctly including params and logic.
I wanna that every time that finish to execute the function submitPassword A third component refresh/reload with the new props. Attention! My third component is parent, not child!
It's possibile to send a command from action to component? How can I do it? I have already tried with:
componentWillReceiveProps() {
console.log("new props");
}
in my component but he can not take the event.
normally a structure my redux store as follows
{
entities: {},
ui: {},
domain:{}
}
so for example when you execute submitPassword you execute all the logic and depending on the result you can dispatch another action to update the ui or the domain part so the componentes that are connected respond to those changes.
The UI holds information about UI changes, like if you are submiting info display a progress bar or if it has to display a dialog.
The domain part holds information related to the whole webapp, like loggin info and notifications.
You don't need always to pass new props for redux state to be accessed.
As redux is having immutable state, you'll always be getting new updated state no matter the previous one. So this will enforce your component to update props to get latest state from redux. This is done by <Provider> wrapper attached on root level.
So hence your props will be having new values whenever redux state gets updated.
The lifecycle you are looking is static getderivedstatefromprops(). This lifecycle gets executed whenever props are changed/updated.
I made visual implementation on fly that can aid you the picture. Here Redux State means Redux Store
Update at the bottom of post
I have a React container component, AppContainer that detects if the user is authenticated. If the user is authenticated, it displays the routes, app, header, etc. If the user is un-authenticated, it displays a Login component.
The AppContainer is a connected component (using react-redux). The mapStateToProps and mapDispatchToProps are as follows:
const mapStateToProps = function(state) {
return {
isAuthenticated: state.Login.isAuthenticated,
}
}
const mapDispatchToProps = function(dispatch, ownProps) {
return {
loginSuccess: (user) => {
console.log("before dispatch")
dispatch(loginSuccess(user))
},
}
}
The loginSuccess function that is being dispatched is an action creator that simply stores the user information in the redux store. The default state of Login.isAuthenticated is false.
In componentDidMount() I check if this.props.isAuthenticated (from the user information in the redux store) is true. If not, I check if the tokenId is in the localStorage. If the token is in localStorage, I dispatch the loginSuccess action to add that information to the redux store.
Then, since that info is in the Redux store, the component will update and show the protected material. This works fine.
My componentDidMount function is as follows:
componentDidMount() {
if (this.props.isAuthenticated) {
console.log("REDUX AUTH'D")
} else {
if (localStorage.getItem("isAuthenticated") && !this.props.isAuthenticated) {
console.log("BROWSER AUTHD, fire redux action")
this.props.loginSuccess({
profileObj: localStorage.getItem("profileObj"),
tokenObj: localStorage.getItem("tokenObj"),
tokenId: localStorage.getItem("tokenId"),
})
}
}
}
The only issue is that I am getting the following warning:
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. Please check the code for the t component.
Though the error given indicates a problem with setState(), I am not calling setState() anywhere in my entire program, so... But removing the this.props.loginSuccess({ ... in componentDidMount also removes the error.
The log statements in my code print before the error and the component does render the protected information as intended if the auth is present. Why does this error occur if the component seems to be working?
Update:
Looking at the stack trace shows that it is coming from the google-login utility I am using.
This is the code for that component: https://github.com/anthonyjgrove/react-google-login/blob/master/src/google.js
This was a problem with the google-login React component provided by a NPM package. I fixed this by rendering the google-login component conditionally (in its own container component, not featured in the original question) based on the isAuthenticated value in the Redux state.
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.