I made some app to get asynchronously data from remote server.
The app just parent component - to get data, and two child components.
One child component for display asynchronous data.
Other child for filter functionality. Just input string where user typing and data in first component display appropriate items.
There are a lot code with console.log everywhere, but in simple scheme it:
class App extends Component {
state = {isLoading:true, query:''}
getData = (location) => {
axios.get(endPoint).then(response=>{ response.map((item) => { places.push(item)})
// ***** first setState
this.setState({isLoading:false})
})
}
updateQuery = (e) => {
// ***** second setState
this.setState({query:e.target.value.trim()})
}
componentDidMount(){
this.getData(location)
}
render() {
if (!this.state.isLoading){
if (this.state.query){
const match = new RegExp(escapeRegExp(this.state.query),'i')
searchTitles = places.filter(function(item){return match.test(item.name)})
}else{
searchTitles = places.slice();
}
}
return (
<div className="App">
<input type='text' onChange={this.updateQuery} value={this.state.query}/>
<List places = {searchTitles}/>
</div>
);
}
}
export default App;
When state change in case of using everything is OK - content refreshed in next child component.
But child component that display data - some items not full of content... no photos and some text information. So probably its rendered before getting remote data.
But why its not re-render it after state.isLoad toggled to 'false' (in code - after got response) ?
I put among code console.log to track processes ... and weird things: state.isLoad switched to false before some part of data came from server. (((
I dont use ShouldComponentUpdate() inside child component.
Per React's documentation for setState
setState() will always lead to a re-render unless
shouldComponentUpdate() returns false.
As mentioned, one way to avoid a re-render is shouldComponentUpdate returning false (shouldComponentUpdate takes in nextProps and nextState) but it's not clear why someone would trigger a state change with setState and then nullify that state change with shouldComponentUpdate.
Related
I am working on the issue regarding parent component render twice and child component also render twice. the existing code for the parent is
<childform
value ="name"
serverapi={this.valueservice.api}
update={this.update}
/>
the child component start render using
componentDidMount()
{
this.callservice(serverapi)
}
since its componentDidMount function called twice, the API also render twice which has to be avoided, each time the parent render the child's state is been refreshed so I cannot compare with the state, is it possible anyway to check or solution to this issue that I can refer? that resolves how many time I call the parent it call the API once
It seems that the update prop you are passing to childform is a boolean indicating whether or not the component should rerender. If that is the case you can use the shouldComponentUpdate() lifecycle method the to check if there is an update and only rerender when true:
shouldComponentUpdate(nextProps, nextState) {
//You can perform more logic here such as
//return this.props.update === nextProps.update
return nextProps.update;
}
You can find more information here:
https://reactjs.org/docs/react-component.html#shouldcomponentupdate
You can have a global variable called cache.
What this cache do is to check if your_awesome_api is called before.
This code below is a common approach:
// cache.js
let cache = {};
export default cache;
// someComponent.js
import cache from './cache';
// ...ignore some react component code...
componentDidMount() {
if (cache['your_awesome_api']) {
doSomething(cache['your_awesome_api'])
} else {
this.callservice(your_awesome_api).then(result => {
cache['your_awesome_api'] = result
})
}
}
This way, you don't need to worry about re-rendering problem ever!
Re-rendering in react is really common!
In the ReactJS, I am changing the route to some new one with an "id", then based on this "id", I must call an API and fetch data.
I have used the API call in componentDidMount and componentWillMount and tried setState() to have my data in the state. but they didn't work in my case.
The problem is in the render() part when I want to use my data (from state), the data is not there because the API call takes some time to update the state.
Here is my code:
componentDidMount() {
api.get(id).then((response) => {
this.setState({response,});
});
With this approach, I don't have the data when I want it (render method), it will eventually be in the state but too late!
How can I change my approach to fix the loading problem?
The way you are doing it, you could validate the response state object in your render method, and if it is null/empty, then return <div>Loading...</div>.
When your state is then updated based on the response, the component will automagically re-render. This time, the object will be populated and you can return your component HTML.
e.g.
render(){
if (!this.state.response){
return <div>Loading...</div>
}
return (
<div>Proper Stuff</div>
);
}
Edit On a site note, if you set the initial state in the constructor, you can make sure the object isn't empty, but just has null/empty values. This could also help depending what your render method is doing, e.g.
constructor(props){
super(props);
this.state = {
response: {...}
};
}
Your render code should be
render(){
return({this.state.response?
<div> Render your component here</div>:
<div> Loading ... </div>});
}
This way your component would not render till it has the data and show Loading ... text instead.
I have multiple modals for signing up, and having trouble with it. I have three actions in total. First the register button which then activates the first register modal to either sign up with google or facebook, then after that completes the modal for additional information which could not be gathered by the provider will appear with pre-filled inputs gathered form provider. I render the two modals when I render the application and only show it when the register button is clicked. I need the componentDidMount to be called after I complete the facebook or google login, but it was called when I rendered the modals when the app fist started. The buttons hit action reducers and reducers changing the state of type bool on wether to show the modal or not.
class HeaderRegisterButton extends Component {
render() {
return(
<div>
<Register1/>
<Register2/>
</div>
);
}
}
Register Modal 1
class Register1 extends Component {
render() {
return(
<div>
<button onClick={() => this.props.showRegister2()} /> //This would hit the action reducer to get initial info and change the bool to show register 1 to false and register 2 to true.
</div>
);
}
}
Register Modal 2
import { reduxForm, Field, initialize } from 'redux-form';
class Register2 extends Component {
componentDidMount() {
hInitalize() //only called when the app fires, not when the state changes in the action reducer to change bool to show this modal.
}
hInitalize() {
var initData = {};
const initData = {
"name" = this.props.name//name stored inside redux store
};
this.props.initialize(initData)
}
render() {
return(
<div>
//Code to display the modal which works.
</div>
);
}
}
componentDidMount is only called once in the lifecycle of any component, re-render will not reinitialize the component. componentDidUpdate will be called where you can manage your logic.
componentDidMount will execute only once, when React component mounted and it doesn't execut when state or props changed.
therefore you need to use componentDidUpdate and don't forget to compare props or state ( you can also use it when props changed in parent's component )
componentDidUpdate(prevProps,prevState) {
if (this.state.userID !== prevState.userID) {
console.log('userId changed');
}
}
important : DON'T update state in componentDidUpdate without wrappe it in a condition because that triggers re-render and you'll cause an infinite loop
IMO you don't need componentDidMount to be called, simply provide some callbacks to a parent component that holds some temporary state of all the information needed/gathered during the process. Each of your modals will display information given to them by this parent component.
The process would be something like this.
Register Button Clicked
Display choice between Google and Facebook
User Clicks Google or Facebook
Perform Authentication via Social Media Account
Data from authentication sent back to parent component to be stored temporarily
Secondary Registration form displayed with data given to it from parent component
User fills out secondary form
Submit button clicked
Use data from temporary state in parent component for actions
Hope that makes sense :)
UPDATE:
Well I don't know how your implementation is... but you can pass functions into child components (via props) and use them in the child to communicate with the parent, so in the component that does the Facebook authentication have the parent pass someCallback that accepts the parameters shown below and adds them to the parents state (remember a change in state will cause any components who use that state to update). I have done this by passing fbLoginCallback as the callback for my facebook login process which calls Facebook's FB.getLoginStatus(....)
export class FbLoginButton extends Component {
constructor(props) {
super(props);
}
fbLoginCallback = response => {
const { authResponse, status } = response;
if (status === 'connected') {
// Logged into your app and Facebook.
const userId = authResponse.userID;
const accessToken = authResponse.accessToken;
FB.api('/me', user => {
const displayName = user.name;
this.props.someCallback(userId, accessToken, displayName);
});
}
}
}
I've removed the rendering of the of the actual button and few other small things, but that's more or less how the callback should go.
componentDidMount is a lifecycle method that is called only when the component is mounted. It is called only once after the first render.
I have several React containers and components running perfectly, but I'm still studying and learning React. When my function componentDidMount runs in a new component I'm working on, it appears that all the data my component needs is available to the component, but this.props.loading is set to true;
componentDidMount() {
const a = 100; //breakpoint here
if (this.props.loading === false){
const {myDataID} = this.props;
this.fromID = Meteor.userId();
this.toID = myDataID;
this.subscribe(fromID, toID);
}
}
What controls the value in this.props.loading? Will componentDidMount run again if this.props.loading is set to false?
The prop, this.props.loading is set by the parent component. If you feel your component has all the necessary data to perform a render you don't need to check if this.props.loading is true or false. It's a good practice to have this check because some times due to network errors you might not get the data you needed. This will break your code.
componentDidMount will only be called once after the render function is done
Invoked once, both on the client and server, immediately before the initial rendering occurs. If you call setState within this method, render() will see the updated state and will be executed only once despite the state change.
But, when you change the loading prop in the parent, componentWillReceiveProps will be called with the nexProp, which is your change.
For more info, check here
What controls the value in this.props.loading?
The component that is rendering that component. I.e. if this componentDidMount method is in a component Bar, and in Foo's render method:
render() {
return <Bar loading={computeLoadingState()} />
}
Every time Foo is rerendered, Bar is potentially passed new value for loading.
Will componentDidMount run again if this.props.loading is set to false?
No. The documentation says:
Invoked once, only on the client (not on the server), immediately after the initial rendering occurs.
Other methods however are invoked whenever props change.
I have a React component that dispatches a redux state change in its componentWillMount function. The reason is that when the component is loaded, it needs to get the id from the url (powered by react-router), and trigger an action that sets up the state with that id's data.
Here is the component:
class Editor extends React.Component {
componentWillMount() {
const { dispatch, params } = this.props
dispatch(editItem(params.id))
}
render() {
const item = this.props.item
console.log("Editing", item)
}
}
export default connect(state => ({item: state.item}))(Editor)
Here's the catch: render is getting called twice. item is undefined on the first call, and valid on the second. Ideally, it should only be called once this.props.item actually exists (after the editItem action has been dispatched and run).
According to the React docs: "If you call setState within this method, render() will see the updated state and will be executed only once despite the state change."
In redux, dispatch is the equivalent of calling setState, as it results in a state change. However, I'm guessing something in the way connect works is still causing render to be called twice.
Is there a way around this besides adding a line like if (!item) return; ?
One thing you might do is create a higher order component that handles the basic pattern of loading a different component (or no component) before the required props are loaded.
export const LoaderWrapper = function(hasLoaded, Component, LoaderComponent, onLoad) {
return props => {
if (hasLoaded(props)) {
return <Component {...props} />
}
else {
if (onLoad) onLoad(props)
return { LoaderComponent ? <LoaderComponent /> : null }
}
}
}
Then you can wrap your component before connecting it to get the desired behaviour.
export default connect(state => ({item: state.item}))(LoaderWrapper(
((props) => !!props.item),
Editor,
null,
(props) => props.dispatch(editItem(props.params.id))
))
You might want to add some currying magic to make sure you can compose these kinds of wrapper functions more nicely. Take a look at recompose for more info.
It looks like there's already an issue in the react-redux library.
https://github.com/rackt/react-redux/issues/210
What does editItem do? Does it add item to the redux state or is it there already?
If it is adding I imagine what is happening is that a render cycle happens with the current props, ie item being blank.
Then it gets rendered again when the props have changed, via setting the item.
One approach to fixing this sort of thing is to create a higher order component that wraps Editor and calls the dispatch action the rendering though is set either to a loading screen or and empty div until item is set. That way you can be assured that Editor will have an item.
But without knowing what editItem does it's sort of hard to know. Maybe you could paste the code for that?