Set state not being executed corretly. - javascript

I have two components, where a user can attach an image or gif to a post. Whenever the image is clicked, a modal pop up, and I want to pass the data, back to modal, so the clicked image shows.
I have made a lifecycle hook so that I can check if props are being sent, and then set the state in my child component like such:
componentWillReceiveProps(){
if(this.props.modalImageData){
this.setState({imageData: this.props.modalImageData})
}
}
this works fine, but I'm having issues passing the state correctly.
I made a handler, where I'm passing the image data, of the clicked image into a handler, that is setting the state, for a property called imageModalData, which I'm passing to the CreateGif component:
showModalSpeceficHandler = (event, image) =>{
//console.log(event.target.value);
console.log('image data is ' + image)
this.setState({imageModalData: image})
console.log('modal image data is ' +
this.state.imageModalData)
this.showModalHandler()
}
Here I'm experiencing issues when I first console log the image, that is being passed into the function, it is correct, but afterward, when I console log the state, of the imageModalData It's displaying a random picture.
afterward, the random picture is passed back down to the child component, which is not desired, what am I doing wrong since the state is not sat correctly
Modal show={this.state.showModal} modalClosed={this.showModalHandler}>
<CreateGif onSelectUrl={this.addedImageHandler} onSelectFile={this.addFileDataHandler} modalImageData={this.state.imageModalData}/* this is where the data is passed back down */ />

Take a look at the docs for setState:
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
If you need to ensure that the update has been applied before some action is fired (like rendering your modal), you can use the setState(updater, callback) form:
this.setState(
// updater function, returning a state update
state => ({imageModalData: image}),
// callback function, executed after update is applied
() => this.showModalHandler()
);

Related

React Native Button component not being disabled immediately

I'm working on a RN app for my company and I've been experiencing this weird issue that isn't RN specific - more to do with useState component rerendering.
const [checkoutButtonEnabled, setCheckoutButtonEnabled] = useState<boolean>(
true);
const handleCheckout = useCallback(async () => {
setCheckoutButtonEnabled(false);
// bunch of async code here making network requests etc
// reenable checkout button if some requests fail (so the user can try ordering again with another payment method, for instance)
}
return (
// JSX
<Button enabled={checkoutButtonEnabled} onPress={handleCheckout} />
// JSX
);
Clicking on this button once would have the expected outcome - the button would be disabled after the checkoutButtonEnabled state value changes and the component gets rerendered. However, during testing, I found that if I click on the button multiple times consecutively, the event handler will still fire, even though the checkoutButtonEnabled value has changed. Theoretically this should mean that the button should also be disabled.
I found an SO answer (ReactJs: Prevent multiple times button press) with an attached github issue link in which Dan Abramov gave an example of how to overcome this issue by passing in the callback function in the onPress event handler conditionally. Here is the github link - https://github.com/facebook/react/issues/11171#issuecomment-357945371
Following his example, once I changed my Button component to be like this -
<Button enabled={checkoutButtonEnabled} onPress={checkoutButtonEnabled ? handleCheckout : undefined} />
it's working as expected. I can't really find why the button doesn't get disabled after the first time I click it, but once I conditionally pass in the event handler function, it works as expected (the event handler callback is only fired once).
If the conditional check is reading the latest value of checkoutButtonEnabled, doesn't it mean that the component has already rerendered? If that's the case, then I don't really understand why the ternary operator is getting the correct updated value after just one click (expected behavior) but the button takes some time to be rerendered with the correct value of the enabled prop.
In the attached github link, this user also asked the same question that I'm asking - https://github.com/facebook/react/issues/11171#issuecomment-410135165 but he didn't get any replies.
I'd love to know why this is happening so I can prevent similar issues in the future. Thanks!
Edit:
I found something that I thought was not related but apparently it is -
const handleCheckout = useCallback(async () => {
setCheckoutButtonEnabled(false);
dispatch(setShowLoadingTransparent(true));
// bunch of async code here making network requests etc
// reenable checkout button if some requests fail (so the user can try ordering again with another payment method, for instance)
}
After calling setState, we are also dispatching a redux action, which in turns updates the App.tsx component, which then causes a rerender of all child components - thus explaining why the Button in question can still be clicked. However, I'm still wondering why the ternary operator check in the onPress event handler works immediately. If React batches event handler calls, then my assumption is that both the setState and dispatch calls would be batched together - thus triggering only one render. If this were the case, wouldn't the event handler still pass in the handleCheckout callback instead of undefined until the component in question rerenders (as a child component of App.tsx, which is subscribed to the Redux store, thus rerendering after dispatching the aforementioned action)?

Calling setState without triggering re-render

I am storing a UI state in the React component's state, say this.state.receivedElements which is an array. I want re-renders whenever an element is pushed to receivedElements. My question is, can I not trigger rendering when the array becomes empty ?
Or in general, can I call setState() just one time without re-render while re-rendering all other times ? ( are there any options, work-arounds ? )
I've read through this thread: https://github.com/facebook/react/issues/8598 but didn't find anything.
I want re-renders whenever an element is pushed to receivedElements.
Note that you won't get a re-render if you use:
this.state.receivedElements.push(newElement); // WRONG
That violates the restriction that you must not directly modify state. You'd need:
this.setState(function(state) {
return {receivedElements: state.receivedElements.concat([newElement])};
});
(It needs to be the callback version because it relies on the current state to set the new state.)
My question is, can I not trigger rendering when the array becomes empty ?
Yes — by not calling setState in that case.
It sounds as though receivedElements shouldn't be part of your state, but instead information you manage separately and reflect in state as appropriate. For instance, you might have receivedElements on the component itself, and displayedElements on state. Then:
this.receivedElements.push(newElement);
this.setState({displayedElements: this.receivedElements.slice()});
...and
// (...some operation that removes from `receivedElements`...), then:
if (this.receivedElements.length) {
this.setState({displayedElements: this.receivedElements.slice()});
}
Note how we don't call setState if this.receivedElements is empty.
What about useRef?
Documentation says:
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
So if you change ref value inside useEffect it won’t rerender component.
const someValue = useRef(0)
useEffect(() => {
someValue.current++
},[])

Callbacks using redux-thunk / redux-observable with redux

I am learning how redux works but its a lot of code to do simple things. For example, I want to load some data from the server before displaying. For editing reasons, I can't simply just use incoming props but I have to copy props data into the local state.
As far as I've learned, I have to send a Fetch_request action. If successful, a fetch_success action will update the store with new item. Then updated item will cause my component's render function to update.
In component
componentWillMount() {
this.props.FETCH_REQUEST(this.props.match.params.id);
}
...
In actions
export function FETCH_REQUEST(id) {
api.get(...)
.then(d => FETCH_SUCCESS(d))
.catch(e => FETCH_FAILURE(e));
}
...
In reducer
export function FETCH_REDUCER(state = {}, action ={}) {
switch (action.type) {
case 'FETCH_SUCCESS':
return { ...state, [action.payload.id]: ...action.payload }
...
}
Back in component
this.props.FETCH_REDUCER
// extra code for state, getting desired item from...
Instead, can I call a react-thunk function and pass some callback functions? The react-thunk can update the store and callbacks can change the component's local state.
In component
componentWillMount() {
this.props.FETCH_REQUEST(this.props.match.params.id, this.cbSuccess, this.cbFailure);
}
cbSuccess(data) {
// do something
}
cbFailure(error) {
// do something
}
...
In action
export function FETCH_REQUEST(id, cbSuccess, cbFailure) {
api.get(...)
.then(d => {
cbSuccess(d);
FETCH_SUCCESS(d);
}).catch(e => {
cbFailure(d);
FETCH_FAILURE(e);
});
}
...
Is this improper? Can I do the same thing with redux-observable?
UPDATE 1
I moved nearly everything to the redux store, even for edits (ie replaced this.setState with this.props.setState). It eases state management. However, every time any input's onChange fires, a new state is popping up. Can someone confirm whether this is okay? I'm worried about the app's memory management due to redux saving a ref to each state.
First of all, you should call your API in componentDidMount instead of componentWillMount. More on this at : what is right way to do API call in react js?
When you use a redux store, your components subscribe to state changes using the mapStateToProps function and they change state using the actions added a props through the mapDispatchToProps function (assuming you are using these functions in your connect call).
So you already are subscribing to state changes using your props. Using a callback would be similar to having the callback tell you of a change which your component already knows about because of a change in its props. And the change in props would trigger a re-render of the component to show the new state.
UPDATE:
The case you refer to, of an input field firing an onChange event at the change of every character, can cause a lot of updates to the store. As mentioned in my comments, you can use an api like _.debounce to throttle the updates to the store to reduce the number of state changes in such cases. More on handling this at Perform debounce in React.js.
The issue of memory management is a real issue in real world applications when using Redux. The way to reduce the effect of repeated updates to the state is to
Normalize the shape of state : http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
Create memoized selectors using Reselect (https://github.com/reactjs/reselect)
Follow the advice provided in the articles regarding performance in Redux github pages (https://github.com/reactjs/redux/blob/master/docs/faq/Performance.md)
Also remember that although the whole state should be copied to prevent mutating, only the slice of state that changes needs to be updated. For example, if your state holds 10 objects and only one of them changes, you need to update the reference of the new object in the state, but the remaining 9 unchanged objects still point to the old references and the total number of objects in your memory is 11 and not 20 (excluding the encompassing state object.)

What are typical use cases for React lifecycle methods like componentWillReceiveProps

componentWillReceiveProps and other lifecycle methods seems like deceptive temptation to bring unnecessary complexity and noise to the code in the hands of inexperienced React coder. Why do they exist? What are their most typical use cases? In the moment of uncertainty, how would I know if the answer lies in the lifecycle methods?
I have been using react for couple of months now, and most of my work is creating a large application from scratch. So the same questions have presented themselves in the start.
The following information is based on learning while development and going through multiple docs out there to get it right.
As asked in the question here are couple of uses cases for the lifecycle methods in react
componentWillMount()
This is called once on the server side, if server side rendering is present, and once the client side.
I personally have used it just to do api calls which do not have direct effect on the components, for example getting oAuth tokens
componentDidMount()
This function is mostly used for calling API's (here is why to call it in componentDidMount and not in componentWillMount)
Components state initialisations which are based on the props passed by parents.
componentWillReceiveProps(nextProps,nextState)
This function is called every time props are received except the first render
Most common use I have encountered is to update the state of my current component which i can not do it in componentWillUpdate.
shouldComponentUpdate(nextProps, nextState)
This method is invoked before the render happens when new props or states are received. Here we can return false if the re-render is not required.
I see this as a performance optimisation tool. In case of frequent re-rendering of parent component this method should be used to avoid unnecessary update to current component
componentWillUpdate(nextProps,nextState)
this function is called every time a component is updated, it is not called when component mounts
Carry out any data processing here. For example, when a api fetch returns data, modelling the raw data into props to be passed to children
this.setState() is not allowed in this function , it is to be done in componentWillReceiveProps or componentDidUpdate
componentDidUpdate(prevProps,prevState)
Invoked right after the changes are pushed to the DOM
I have used it whenever the required data is not at the first render (waiting for api call to come through) and DOM requires to be changed based on the data received
Example, based on the age received show the user if he is eligible for application for an event
componentWillUnmount()
As the official docs mentions, any event listeners or timers used in the component to be cleaned here
In the moment of uncertainty, how would I know if the answer lies in
the lifecycle methods?
What analogy i suggest
Change is triggered in the component itself
Example, Enable editing of fields on click of an edit button
A function in the same component changes the state no involvement of lifecycle functions
Change is triggered outside of the component
Example, api call finished , need to display the received data
Lifecycle methods for the win.
Here are some more scenarios -
Does the change in state/props requires the DOM to be modified?
Example, if the current email is already present , give the input class an error class.
componentDidUpdate
Does the change in state/props requires to data to be updated?
Example, parent container which formats data received after api call and passes the formatted data to children.
componentWillUpdate
Props being passed to a child are changed , child needs to update
Example,
shouldComponentUpdate
Adding an event listener
Example, add a listener to monitor the DOM, based on window size.
componentDidMount
'componentWillMount' , to destroy the listner
Call api
'componentDidMount'
Sources -
Docs - https://facebook.github.io/react/docs/component-specs.html
this scotch.io article which cleared the lifecycle concepts
Event Listener - https://facebook.github.io/react/tips/dom-event-listeners.html
Some typical use cases for the most commonly used lifecycle methods:
componentWillMount: Invoked before initial rendering. Useful for making AJAX calls. For instance, if you need to grab the user information to populate the view, this is a good place to do it. If you do have an AJAX call, it would be good to render an indeterminate loading bar until the AJAX call finishes. I've also used componentWillMount to call setInterval and to disable Chrome's drag and drop functionality before the page renders.
componentDidMount: Invoked immediately after the component renders. Useful if you need to have access to a DOM element. For instance I've used it to disable copy and pasting into a password input field. Great for debugging if you need want to know the state of the component.
componentWillReceiveProps: Invoked when component receives new props. Useful for setting the state with the new props without re-rendering.
componentWillReceiveProps is part of Update lifce cycle methods and is called before rendering begins. The most obvious example is when new props are passed to a Component. For example, we have a Form Component and a Person Component. The Form Component has a single that allows the user to change the name by typing into the input. The input is bound to the onChange event and sets the state on the Form. The state value is then passed to the Person component as a prop.
import React from 'react';
import Person from './Person';
export default class Form extends React.Component {
constructor(props) {
super(props);
this.state = { name: '' } ;
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({ name: event.currentTarget.value });
}
render() {
return (
<div>
<input type="text" onChange={ this.handleChange } />
<Person name={ this.state.name } />
</div>
);
}
}
Any time the user types into the this begins an Update for the Person component. The first method called on the Component is componentWillReceiveProps(nextProps) passing in the new prop value. This allows us to compare the incoming props against our current props and make logical decisions based on the value. We can get our current props by calling this.props and the new value is the nextProps argument passed to the method.

ReactJs: change state in response to state change

I've got a React component with an input, and an optional "advanced input":
[ basic ]
Hide Advanced...
[ advanced ]
The advanced on the bottom goes away if you click "Hide Advanced", which changes to "Show Advanced". That's straightforward and working fine, there's a showAdvanced key in the state that controls the text and whether the advanced input is rendered.
External JS code, however, might change the value of advanced, in which case I want to show the [advanced] input if it's currently hidden and the value is different than the default. The user should be able to click "Hide Advanced" to close it again, however.
So, someone external calls cmp.setState({advanced: "20"}), and I want to then show advanced; The most straightforward thing to do would just be to update showAdvanced in my state. However, there doesn't seem to be a way to update some state in response to other state changes in React. I can think of a number of workarounds with slightly different behavior, but I really want to have this specific behavior.
Should I move showAdvanced to props, would that make sense? Can you change props in response to state changes? Thanks.
Okay first up, you mention that a third party outside of your component might call cmp.setState()? This is a huge react no-no. A component should only ever call it's own setState function - nothing outside should access it.
Also another thing to remember is that if you're trying change state again in response to a state change - that means you're doing something wrong.
When you build things in this way it makes your problem much harder than it needs to be. The reason being that if you accept that nothing external can set the state of your component - then basically the only option you have is to allow external things to update your component's props - and then react to them inside your component. This simplifies the problem.
So for example you should look at having whatever external things that used to be calling cmp.setState() instead call React.renderComponent on your component again, giving a new prop or prop value, such as showAdvanced set to true. Your component can then react to this in componentWillReceiveProps and set it's state accordingly. Here's an example bit of code:
var MyComponent = React.createClass({
getInitialState: function() {
return {
showAdvanced: this.props.showAdvanced || false
}
},
componentWillReceiveProps: function(nextProps) {
if (typeof nextProps.showAdvanced === 'boolean') {
this.setState({
showAdvanced: nextProps.showAdvanced
})
}
},
toggleAdvancedClickHandler: function(e) {
this.setState({
showAdvanced: !this.state.showAdvanced
})
},
render: function() {
return (
<div>
<div>Basic stuff</div>
<div>
<button onClick={this.toggleAdvancedClickHandler}>
{(this.state.showAdvanced ? 'Hide' : 'Show') + ' Advanced'}
</button>
</div>
<div style={{display: this.state.showAdvanced ? 'block' : 'none'}}>
Advanced Stuff
</div>
</div>
);
}
});
So the first time you call React.renderComponent(MyComponent({}), elem) the component will mount and the advanced div will be hidden. If you click on the button inside the component, it will toggle and show. If you need to force the component to show the advanced div from outside the component simply call render again like so: React.renderComponent(MyComponent({showAdvanced: true}), elem) and it will show it, regardless of internal state. Likewise if you wanted to hide it from outside, simply call it with showAdvanced: false.
Added bonus to the above code example is that calling setState inside of componentWillReceiveProps does not cause another render cycle, as it catches and changes the state BEFORE render is called. Have a look at the docs here for more info: http://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops
Don't forget that calling renderComponent again on an already mounted component doesn't mount it again, it just tells react to update the component's props and react will then make the changes, run the lifecycle and render functions of the component and do it's dom diffing magic.
Revised answer in comment below.
My initial wrong answer:
The lifecycle function componentWillUpdate will be ran when new state or props are received. You can find documentation on it here: http://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate
If, when the external setState is called, you then set showAdvanced to true in componentWillUpdate, you should get the desired result.
EDIT: Another option would be to have the external call to setState include showAdvanced: true in its new state.

Categories

Resources