I made a simple React Redux application that fetches a list of posts from jsonplaceholder and within it is a form that allows users to send a POST request. When I send a post request according to Redux DevTools extension it is added successfully marked as post number 101. Here is its snapshot
but the problem is after clicking the submit button 3 times it shows up on the screen.
The first two clicks show neither the title nor its body but it starts showing up on the third click.
This is Posts.jsx file and here is how I used componentDidUpdate to update the component after post request.
class Posts extends Component {
componentDidMount(){
this.props.fetchPosts();
}
componentDidUpdate(nextProps) {
if (nextProps.newPost) {
this.props.posts.unshift(nextProps.newPost);
}
}
renderPosts(){ // cutted for brevity }
render() {
return (
{this.renderPosts()}
)
}
}
const mapStateToProps = (state) => {
return {
posts: state.posts.items,
newPost: state.posts.item,
}
}
export default connect(mapStateToProps, { fetchPosts })(Posts);
Here is its GitHub link repository.
The only error I am getting is the below error.
index.js:1 Warning: Each child in a list should have a unique "key" prop.
I don't believe this has anything to do with rendering the new post, but I already specified a "key" while looping through components.
What I am doing wrong during the course of this post request? Thank You.
You are using the wrong lifeCycle method. in order to get the nexProps you have to use componentWillReceiveProps instead of componentDidUpdate.
componentDidUpdate will give you the previous Props and previous State.
componentWillReceiveProps(nextProps) {
if (nextProps.newPost) {
this.props.posts.unshift(nextProps.newPost);
}
}
The above snippet should work.
But this method is deprecated. react introduced an alternative (kind of) of this. which is called getDerivedStateFromProps. The problem is it is a static method and you can't access previous props (this.props) inside this method.
If you need it you did something wrong as it is an anti-pattern.
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 component called <Header> where I have a log-in form that makes an API call to log a user in.
I have a function in my header component that makes some API calls to fetch some data and update the menu items in the header once successfully logged in:
componentDidMount() {
const { auth, actions, children } = this.props;;
if (auth.isLoggedIn) {
actions.getAssessmentRequest();
actions.getFirstSubsectionRequest();
}
}
The problem I am having is that the very first time the user logs in the above componentDidMount function does not trigger because the header component was already mounted the first time the page was loaded.
I have tried to use componentDidUpdate and componentWillReceiveProps but they get triggered multiple times and I get request timeout errors.
Any ideas which lifecycle method I could use to achieve this?
Yes you are on the right path, you should use the componentWillReceiveProps lifecycle method. The trick to prevent infinite loops and constantly making requests, you have to perform a check to test whether the props you care about actually changed or not:
componentDidMount() {
this.fetchData(this.props);
}
componentWillReceiveProps(nextProps) {
if(nextProps.auth.isLoggedIn !== this.props.auth.isLoggedIn) {
this.fetchData(nextProps);
}
}
fetchData(props) {
const { auth, actions, children } = props;
if (auth.isLoggedIn) {
actions.getAssessmentRequest();
actions.getFirstSubsectionRequest();
}
}
I can't tell you why it is called multiple times, but I can tell you that it should not matter. The problem is that you're not comparing the props for what has changed. If you do this, the code will behave the way you want:
componentWillReceiveProps(newProps) {
const { auth, actions, children } = newProps;
if (auth.isLoggedIn !== this.props.auth.isLogin) {
actions.getAssessmentRequest();
actions.getFirstSubsectionRequest();
}
}
See also the official ReactJS documentation, which states: https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops
Hope this helps.
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)),
}
}
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.
Let me explain the problem that I've faced recently.
I have React.js + Flux powered application:
There is a list view of articles (NOTE: there are multiple of of different lists in the app) and article details view inside it.
But there is only one API endpoint per each list which returns array of articles.
In order to display the details I need to find article by id in array. That works pretty fine. I trigger action which makes request to server and propagates store with data, when I go to details screen then I just get the necessary article from that array in store.
When user lands on article details view before list (stores are empty) then I need to make a request.
Flow looks like: User loads details view -> component did mount -> stores are empty -> rendered empty -> fetchArticles action is triggered -> request response is 200 -> stores now have list of articles -> component did update -> rendered with data successfully
Component could look as follows:
let DetailsComponent = React.createClass({
_getStateFromStores() {
let { articleId } = this.getParams();
return {
article: ArticleStore.getArticle(articleId)
};
},
componentDidMount() {
// fire only if user wasn't on the list before
// stores are empty
if (!this.state.article) {
ArticleActions.fetchArticles('listType');
}
},
render() {
return <ArticleDetails article={this.state.article} />;
}
});
The interesting part comes next:
Now I need to make another request to server but request options depend on the article details. That's why I need to make second request after the first one on the details view.
I've tried several approaches but all of them look ugly. I don't like calling actions from stores that makes stores too complicated. Calling action inside action in this case doesn't work well because I will need to find article from store inside that action.
Solution (?!)
What I've came up with is to use callback in action inside component and it feels much more cleaner:
let DetailsComponent = React.createClass({
_getStateFromStores() {
let { articleId } = this.getParams();
return {
article: ArticleStore.getArticle(articleId)
};
},
componentDidMount() {
if (!this.state.article) {
ArticleActions.fetchArticles('listType', () => {
this._requestAdditionalData();
});
}
this._requestAdditionalData();
},
_requestAdditionalData() {
if (this.state.article) {
ArticleActions.fetchAdditional(this.state.article.property);
}
},
render() {
return <ArticleDetails article={this.state.article} />;
}
});
What's your input?
Consider move the second call to get a detail article to the ArticleDetails component componentDidMount() life cycle method.
So if the article is not set, do not render the ArticleDetails component at all by return null / false.