I'm trying to implement a custom select which displays a list of languages fetched from an API.
I make the api call in the componentDidMount lifecycle hook and then update the state of my component according to the data I fetched.
Everything seems to work, yet I always get this 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. Please check the code for the
LanguageSelect component.
Here's a snippet of my code:
export default class LanguageSelect extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
languages: []
};
}
// ...
componentDidMount() {
http.get('/api/parameters/languages')
.then(
// the Array.map is just to restructure the data fetched form the API
data => this.setState({ languages : data.map(l => ({ label: l.LocalName, value: l.Code, })) }),
// Error case
error => console.error('Error: Languages data could not be fetched', error)
)
}
// Render function
}
I don't understand, The only setState call I make is inside a componentDidMount() lifecycle thus is only executed when the component is mounted ?
Thanks in advance
You could use isMounted() to check if it's mounted
if (this.isMounted()) {
this.setState({...});
}
Although it's an anti pattern and you can find some proposals on best practice solutions here: https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html
Related
Please how can I implement exactly this code if I use the useEffect hook in a functional component?
componentDidUpdate(prevProps) {
if (this.props.user.id !== prevProps.user.id) {
this.setState({
isLoggedIn: true,
});
}
}
componentDidMount() {
this.props.fetchConversations();
}
const {fetchConverstaions, user} = props
useEffect(() => {
fetchConverstaions()
}, [])
useEffect(() => {
setIsLoggedIn(true) //useState
}, [user?.id])
The useEffect with empty dependencies [] will only run once, hence, componentDidMount. The second useEffect will run whenever user.id changes in value.
From the React documentation - Using the Effect Hook
Tip
If you’re familiar with React class lifecycle methods, you can think of >useEffect Hook as componentDidMount, componentDidUpdate, and ?>componentWillUnmount combined.
so in your case, you can add all the logics in the useEffect hook.
I've tested in various ways... Still, It isn't working.
I don't seem to doing anything wrong
exactly same code as reselect doc
redux store is all normalized
reducers are all immutable
From parent component, I just pass down a prop with id and from child component, connected with redux and used selector to get that exact item by id(from parent component)
### This is what Parent components render looks like
render() {
return (
<div>
<h4>Parent component</h4>
{this.props.sessionWindow.tabs.map(tabId =>
<ChildComponentHere key={tabId} tabId={tabId} />
)}
</div>
);
}
### This is what Child component looks like
render() {
const { sessionTab } = this.props (this props is from connect() )
<div>
<Tab key={sessionTab.id} tab={sessionTab} />
</div>
))
}
### Selectors for across multiple components
const getTheTab = (state: any, ownProps: IOwnProps) => state.sessionWindows.sessionTab[ownProps.tabId];
const makeTheTabSelector = () =>
createSelector(
[getTheTab],
(tab: object) => tab
)
export const makeMapState = () => {
const theTabSelector = makeTheTabSelector();
const mapStateToProps = (state: any, props: IOwnProps) => {
return {
sessionTab: theTabSelector(state, props)
}
}
return mapStateToProps
}
Weirdly Working solution: just change to deep equality check.(from anywhere)
use selectors with deep equality works as expected.
at shouldComponentUpdate. use _.isEqual also worked.
.
1. const createDeepEqualSelector = createSelectorCreator(
defaultMemoize,
isEqual
)
2. if (!_isEqual(this.props, nextProps) || !_isEqual(this.state, nextState)){return true}
From my understanding, my redux is always immutable so when something changed It makes new reference(object or array) that's why react re-renders. But when there is 100 items and only 1 item changed, only component with that changed props get to re-render.
To make this happen, I pass down only id(just string. shallow equality(===) works right?)using this id, get exact item.(most of the components get same valued input but few component get different valued input) Use reselect to memoize the value. when something updated and each component get new referenced input compare with memoized value and re-render when something trully changed.
This is mostly what I can think of right now... If I have to use _isEqual anyway, why would use reselect?? I'm pretty sure I'm missing something here. can anyone help?
For more clarification.(hopefully..)
First,My redux data structure is like this
sessionWindow: {
byId: { // window datas byId
"windowId_111": {
id: "windowId_111",
incognito: false,
tabs: [1,7,3,8,45,468,35,124] // this is for the order of sessionTab datas that this window Item has
},
"windowId_222": {
id: "windowId_222",
incognito: true,
tabs: [2, 8, 333, 111]
},{
... keep same data structure as above
}
},
allIds: ["windowId_222", "windowId_111"] // this is for the order of sessionWindow datas
}
sessionTab: { // I put all tab datas here. each sessionTab doesn't know which sessionWindow they are belong to
"1": {
id: 1
title: "google",
url: "www.google.com",
active: false,
...more properties
},
"7": {
id: 7
title: "github",
url: "www.github.com",
active: true
},{
...keep same data structure as above
}
}
Problems.
1. when a small portion of data changed, It re-renders all other components.
Let's say sessionTab with id 7's url and title changed. At my sessionTab Reducer with 'SessionTabUpdated" action dispatched. This is the reducer logic
const updateSessionTab = (state, action) => {
return {
...state,
[action.tabId]: {
...state[action.tabId],
title: action.payload.title,
url: action.payload.url
}
}
}
Nothing is broken. just using basic reselect doesn't prevent from other components to be re-rendered. I have to use deep equality version to stop re-render the component with no data changed
After few days I've struggled, I started to think that the problem is maybe from my redux data structure? because even if I change one item from sessionTab, It will always make new reference like {...state, [changedTab'id]: {....}} In the end, I don't know...
Three aspects of your selector definition and usage look a little odd:
getTheTab is digging down through multiple levels at once
makeTheTabSelector has an "output selector" that just returns the value it was given, which means it's the same as getTheTab
In mapState, you're passing the entire props object to theTabSelector(state, props).
I'd suggest trying this, and see what happens:
const selectSessionWindows = state => state.sessionWindows;
const selectSessionTabs = createSelector(
[selectSessionWindows],
sessionWindows => sessionWindows.sessionTab
);
const makeTheTabSelector = () => {
const selectTabById = createSelector(
[selectSessionTabs, (state, tabId) => tabId],
(sessionTabs, tabId) => sessionTabs[tabId]
);
return selectTabById;
}
export const makeMapState() => {
const theTabSelector = makeTheTabSelector();
const mapStateToProps = (state: any, props: IOwnProps) => {
return {
sessionTab: theTabSelector(state, props.tabId)
}
}
return mapStateToProps
}
No guarantees that will fix things, but it's worth a shot.
You might also want to try using some devtool utilities that will tell you why a component is re-rendering. I have links to several such tools in the Devtools#Component Update Monitoring section of my Redux addons catalog.
Hopefully that will let you figure things out. Either way, leave a comment and let me know.
I've got a fairly simple react container component that attempts to call set state in an ajax callback called from componentDidMount. The full error is:
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 UserListContainer component.
the order of operations from my console.log are:
render
child-render
componentDidMount
ajax-data
[Big ol Error Message]
I started out using async/await but when I received the error I went back to callbacks with the same result. This is the relevant code:
export class UserListContainer extends React.Component<any, any>
{
constructor() {
super();
this.state = {
users: [], request: {}
};
}
//componentDidMount = async () => {
componentWillMount = () => {
console.log('componentWillMount');
//var response: Models.IUserQueryResponse = await Api.UserList.get(this.state.request);
Api.UserList.get(this.state.request).then((response) => {
console.log('ajax-data');
if (response.isOk) {
this.setState({ users: response.data, request: response.state });
}
});
}
render() {
console.log('render');
return <UserList
request={this.state.request}
users={this.state.users}
onEditClick={this.edit}
onRefresh={this.refresh}
/>;
}
Any help would be appreciated.
you cannot set state in componentWillMount because your component could be in a transitioning state.. also it will not trigger a re-rendering. Either use componentWillReceiveProps or componentDidUpdate.
Now that aside your issue is that you are calling setState in the callback from an API request. and the issue with that is you probably have unmounted that component and dont want to setState anymore.
you can fix this with a simple flag
constructor() {
super();
this.state = {
users: [], request: {}
};
this.isMounted = false;
}
componentDidMount(){
this.isMounted = true
}
componentWillUnmount(){
this.isMounted = false;
}
then in your api request you would do this.
Api.UserList.get(this.state.request).then((response) => {
console.log('ajax-data');
if (response.isOk && this.isMounted) {
this.setState({ users: response.data, request: response.state });
}
});
I think is better to use componentWillMount() instead of componentDidMount() cause you want to load the list and then set the state, not after the component was mounted.
I recently started migrating things from jQ to a more structured framework being VueJS, and I love it!
Conceptually, Vuex has been a bit of a paradigm shift for me, but I'm confident I know what its all about now, and totally get it! But there exist a few little grey areas, mostly from an implementation standpoint.
This one I feel is good by design, but don't know if it contradicts the Vuex cycle of uni-directional data flow.
Basically, is it considered good practice to return a promise(-like) object from an action? I treat these as async wrappers, with states of failure and the like, so seems like a good fit to return a promise. Contrarily mutators just change things, and are the pure structures within a store/module.
actions in Vuex are asynchronous. The only way to let the calling function (initiator of action) to know that an action is complete - is by returning a Promise and resolving it later.
Here is an example: myAction returns a Promise, makes a http call and resolves or rejects the Promise later - all asynchronously
actions: {
myAction(context, data) {
return new Promise((resolve, reject) => {
// Do something here... lets say, a http call using vue-resource
this.$http("/api/something").then(response => {
// http success, call the mutator and change something in state
resolve(response); // Let the calling function know that http is done. You may send some data back
}, error => {
// http failed, let the calling function know that action did not work out
reject(error);
})
})
}
}
Now, when your Vue component initiates myAction, it will get this Promise object and can know whether it succeeded or not. Here is some sample code for the Vue component:
export default {
mounted: function() {
// This component just got created. Lets fetch some data here using an action
this.$store.dispatch("myAction").then(response => {
console.log("Got some data, now lets show something in this component")
}, error => {
console.error("Got nothing from server. Prompt user to check internet connection and try again")
})
}
}
As you can see above, it is highly beneficial for actions to return a Promise. Otherwise there is no way for the action initiator to know what is happening and when things are stable enough to show something on the user interface.
And a last note regarding mutators - as you rightly pointed out, they are synchronous. They change stuff in the state, and are usually called from actions. There is no need to mix Promises with mutators, as the actions handle that part.
Edit: My views on the Vuex cycle of uni-directional data flow:
If you access data like this.$store.state["your data key"] in your components, then the data flow is uni-directional.
The promise from action is only to let the component know that action is complete.
The component may either take data from promise resolve function in the above example (not uni-directional, therefore not recommended), or directly from $store.state["your data key"] which is unidirectional and follows the vuex data lifecycle.
The above paragraph assumes your mutator uses Vue.set(state, "your data key", http_data), once the http call is completed in your action.
Just for an information on a closed topic:
you don’t have to create a promise, axios returns one itself:
Ref: https://forum.vuejs.org/t/how-to-resolve-a-promise-object-in-a-vuex-action-and-redirect-to-another-route/18254/4
Example:
export const loginForm = ({ commit }, data) => {
return axios
.post('http://localhost:8000/api/login', data)
.then((response) => {
commit('logUserIn', response.data);
})
.catch((error) => {
commit('unAuthorisedUser', { error:error.response.data });
})
}
Another example:
addEmployee({ commit, state }) {
return insertEmployee(state.employee)
.then(result => {
commit('setEmployee', result.data);
return result.data; // resolve
})
.catch(err => {
throw err.response.data; // reject
})
}
Another example with async-await
async getUser({ commit }) {
try {
const currentUser = await axios.get('/user/current')
commit('setUser', currentUser)
return currentUser
} catch (err) {
commit('setUser', null)
throw 'Unable to fetch current user'
}
},
Actions
ADD_PRODUCT : (context,product) => {
return Axios.post(uri, product).then((response) => {
if (response.status === 'success') {
context.commit('SET_PRODUCT',response.data.data)
}
return response.data
});
});
Component
this.$store.dispatch('ADD_PRODUCT',data).then((res) => {
if (res.status === 'success') {
// write your success actions here....
} else {
// write your error actions here...
}
})
TL:DR; return promises from you actions only when necessary, but DRY chaining the same actions.
For a long time I also though that returning actions contradicts the Vuex cycle of uni-directional data flow.
But, there are EDGE CASES where returning a promise from your actions might be "necessary".
Imagine a situation where an action can be triggered from 2 different components, and each handles the failure case differently.
In that case, one would need to pass the caller component as a parameter to set different flags in the store.
Dumb example
Page where the user can edit the username in navbar and in /profile page (which contains the navbar). Both trigger an action "change username", which is asynchronous.
If the promise fails, the page should only display an error in the component the user was trying to change the username from.
Of course it is a dumb example, but I don't see a way to solve this issue without duplicating code and making the same call in 2 different actions.
actions.js
const axios = require('axios');
const types = require('./types');
export const actions = {
GET_CONTENT({commit}){
axios.get(`${URL}`)
.then(doc =>{
const content = doc.data;
commit(types.SET_CONTENT , content);
setTimeout(() =>{
commit(types.IS_LOADING , false);
} , 1000);
}).catch(err =>{
console.log(err);
});
},
}
home.vue
<script>
import {value , onCreated} from "vue-function-api";
import {useState, useStore} from "#u3u/vue-hooks";
export default {
name: 'home',
setup(){
const store = useStore();
const state = {
...useState(["content" , "isLoading"])
};
onCreated(() =>{
store.value.dispatch("GET_CONTENT" );
});
return{
...state,
}
}
};
</script>
when a request is made via setVariables is there a way to take account of the local state in-between async requests i.e. to implement loading indicator ?
an illustration making requests to https://www.graphqlHub.com/graphql
_onChange = (ev) => {
this.setState({
loading:true
})
let gifType = ev.target.value;
this.props.relay.setVariables({
gifType
});
this.setState({
loading:false
})
}
this won't track the loading state and loading will pass on to false immediately while the async change to the view will have lag.
if we move loading into setVariables is there any way to track the response ? in the root container there is the ability to track response via
renderLoading={function() {
return <div>Loading...</div>;
}}
is there any similar method for Relay.createContainer
is it bad practice to use setVariables to navigate through data sets ?
full code
class GiphItems extends React.Component {
constructor(){
super();
this.state = {
loading: false
}
}
render() {
const random = this.props.store.random
return <div>
<select onChange={this._onChange.bind(this)} value={this.props.relay.variables.gifType}>
<option value="sexy">Sexy</option>
<option value="cats">Cats</option>
<option value="goal">Goal</option>
<option value="lol">LOL</option>
</select>
{this.state.loading ? 'LOADING' : <a href={random.url}><img src={random.images.original.url} className="img-responsive"/></a>}
</div>;
}
_onChange = (ev) => {
this.setState({
loading:true
})
let gifType = ev.target.value;
this.props.relay.setVariables({
gifType
});
this.setState({
loading:false
})
}
}
GiphItems = Relay.createContainer(GiphItems, {
initialVariables: {
gifType: "sexy"
},
fragments: {
store: () => Relay.QL`
fragment on GiphyAPI {
random(tag: $gifType ) {
id
url
images {
original {
url
}
}
}
}
`,
},
});
setVariables method also accepts a callback as the 2nd argument which responds to the events involved with the data fulfillment and it receives a 'readyState' object that you can inspect:
this.props.relay.setVariables({
gifType,
}, (readyState)=> {
if (!readyState.done) {
this.setState({
loading: true
})
} else if(readyState.done) {
this.setState({
loading: false
})
}
})
Great question. What you have is a viable option in terms of dealing with loading screens for a particular component. But that can become a burden to implement for every loading scenario for each individual component.
Here's what you can do instead if to provide a more generic solution: You can set up a global event system in your React app that will broadcast a global state to each component based on whether or not a call is being made. And for each component that you need this for, you can subscribe to this global event from componentDidMount() and unsubscribe with componentWillUnmount(). As soon as your component sees a change in this global state, that component should call setState(), which will determine whether or not that component should display a loading scene or not.
This is a good resource to learn how to communicate between components to set up a global event system:
https://facebook.github.io/react/tips/communicate-between-components.html
You can also use Facebook's Flux to implement this as well:
https://facebook.github.io/flux/
Hope this helps!