I'm creating a app that allows users to create assessments (that other users can complete). Right now I am working on an 'edit' page, where a users loads a form that is prefilled with the relevant data - but he is then able to change these values.
However, I am having trouble with two things (that I suspect are related).
First: the input fields wont display a default value that is derived from the component state.
Second: If I set the input fields directly from the props I am no longer able to change the values.
The components gets passed a prop that is called block_data which is a dict containing key/value pairs of strings.
I'm attempting to convert load it into the state like so
constructor(props) {
super(props);
this.state = {
block: this.props.block_data,
question_list: [],
question_options: ['BO', 'SC', 'SR', 'NO'],
block_list: [],
};
(..)
}
However, this does not work. When I check in the chrome react extension the state is not set.
The input fields are all very simular to the example I've included below. Here I've set its value from the props. In this case it does display the correct initial data. But I am unable to edit it.
<input
onChange={e => this.changeIsANaturalPartOfLife(e)}
value={this.props.block_data.title}
name="title"
/>
Below is the 'on change' function. When I check the chrome react tool, I can see that only the first letter of the state is updated when I start typing. The input field does not show any changes.
changeIsANaturalPartOfLife(e, optional) {
const target = e.target;
const name = target.name;
const value = target.value;
console.log(value);
this.setState({ block: {[name]: value }});
}
I am really at a loss here on what to do. What I am trying to do here seems simple enough, yet I'm unable to advance beyond this flawed stage. Does anyone have an idea what I am doing wrong?
As you mentioned in comment: "the data is loaded from a DjangoRestAPI".
Solution of first problem:
You need to use componentwillreceiveprops lifecycle method to update the state with new props values (after successfully fetched from server) otherwise state of child component will always have the initial data of parent component.
As per DOC:
componentWillReceiveProps() is invoked before a mounted component
receives new props. If you need to update the state in response to
prop changes (for example, to reset it), you may compare this.props
and nextProps and perform state transitions using this.setState() in
this method.
Use this method:
componentwillreceiveprops(nextProps) {
// compare nextProps.block_data and this.state.block if not equal
this.setState({
block: nextProps.block_data
})
}
Solution of second problem:
When you are directly using the props instead of storing into state, then you need to update the value in parent component during onChange, because you are using this.props.value and updating this.state.value, and hence props.value will always be same and it will not allow you to type anything.
Related
My code is as follows, and you can see how it works in https://codepen.io/rongeegee/pen/BaVJjGO:
const { useState } = React;
const Counter = () => {
const [data, setData] = useState({
displayData: "data_one",
data_one: {
text: ""
},
data_two:{
text:""
}
})
function handleOnChange(event){
event.preventDefault();
const new_data = {...data};
if (event.target.name == "displayData"){
new_data.displayData = event.target.value;
setData(new_data);
}
else{
new_data[event.target.name]["text"] = event.target.value;]
setData(new_data);
}
}
return (
<div>
<form onChange={handleOnChange}>
<select name="displayData" value={data.displayData}>
<option value="data_one">data_one</option>
<option value="data_two">data_two</option>
</select>
<br/>
{
data.displayData == "data_one"
?
<>data One: <input name="data_one" defaultValue={data.data_one.text} /></>
:
<>data two: <input name="data_two" defaultValue={data.data_two.text} /></>
}
</form>
</div>
)
}
ReactDOM.render(<Counter />, document.getElementById('app'))
If I type something in the input of data_one, toggle between the values between "data_one" and "data_two", the data_two input field will have the same value inside. If I change the value in data_one toggle the dropdown to "data_one", data_one will have the same value again.
This shouldn't happen since data_one input uses the value of the text field in data_one field in the data state while data_two input uses the one in data_two field. One should not take the value from another field in the state.
React has a way to determine if/which elements/components have changed and which haven't. This is neccesary, because DOM manipulation is expensive, so we should try to limit it as much as possible. That's why React has a way to determine what changed between rerenders and what didn't; and only changes what changed in the DOM.
So if we in your case swith from dataOne to dataTwo, React goes something like: "Oh nice input element stays input element. Nice I don't have to completely destroy that DOM node and render it rom scratch, I can just check what changed and change that. Let's see: The name has changed ... so let's change that, but apart from that everything stayed the same." (This means your input element won't get destroyed and the other one initialy rendered, but the one input element get's a name change and React calls it a day - and since default Value only works on initial creation of an element/DOM node, it won't be shown)
The way React rerenders stuff and compares/modifies the DOM is quite complicated. For futher information I can recomend the following video: https://youtu.be/i793Qm6kv3U (It really helped me understand the whole React Render process way better).
A possible fix to your problem, would be to give each input element a key. So your input elements could look something like:
<input key="1" name="data_one" defaultValue={data.data_one.text} />
<input key="2" name="data_two" defaultValue={data.data_two.text} />
So yeah, the fix is fairly easy; understanding the reason to why we need this fix however is everything but easy.
regarding your comment (my answer would be to long for a comment, and formatting is nicer in an answer)
nope you aren't changing state on input-field change ... you are changing/manipulating the state variable data ... but you are not updating your state. Hence no rerender gets triggered, and in case of a rerender triggered by something else the data will be lost. So you aren't actually changing state.
Changes to your state can only be made by calling the callback Funcnction provided by the useState-Hook. (In your case the callback provided by the useState-Hook is setData. In your else statement you are not calling the provided callback, just manipulating the data object (since you shallow clone and therefor manipulate the original data object)
When changing state ALWAYS use the provided callback const [stateVariable, thisIsTheCallback] = useState(initVal). If you don't use this callback and just manipulate the stateVariable you will sooner or later run into problems (and debugging that issue is particularly tedious, since it seems like you are changing state (because the stateVariable changes) but you aren't changing state since only the callback can do this)
In place of defaultValue attribute to your <input/> replace that with value attribute and everything will work.
And to know why defaultValue won't get updated after state change, read the first answer by #Lord-JulianXLII
I have a page with several components on it, one of the components reads redux state.
When there some changes occur on the redux object component it reloads, shows the spinner and all the data on the component gets updated.
But my task is to update one single value in the redux object state without having to rerender and refresh everything.
For instance here is the redux object, which holds an array of objects with some data.
I need to update one value state in a certain object let say the last one.
But I need to do it without having to update all-state.
state:{
0:{
name : 'some name.....',
surname: 'some surname',
age: '23',
phone: '+12345678'
state: 'ON'
},
1:{
name : 'some name.....',
surname: 'some surname',
age: '23',
phone: '+12345678'
state: 'ON'
},
2:{
name : 'some name.....',
surname: 'some surname',
age: '23',
phone: '+12345678'
state: 'OFF' <-- I need to update only this one
},
}
I have one function in Redux actions that load all these data.
But now I need something like a separate function updateData function which will be updating one value in an existing state object without having to refresh everything.
Is it possible to do so in Redux?
Your state is an object (not an array) of objects.
You can't update a single property of your state. You have to supply a new object with the updated values. Study immutability in redux: https://redux.js.org/faq/immutable-data
Try this in your reducer:
const changedVal=state[2];
changedVal.state='ON';
return {...state,changedVal};
You will need to familiarise yourself with how actions and reducers work in Redux but yes, this is most definitely possible.
You can make an UPDATE_DATA action creator that you would call inside your react component(s). And you would have a reducer that will control how the data is processed and state changed.
-edit-
As mentioned in the comments, I use "immer" in reducers with Redux. Here's an example of how I've used it for editing and existing item in an array:
case EDIT_ITEM:
return produce(state, (draft) => {
const index = findItemIndex(draft, action.payload._id)
draft[index] = action.payload
})
So you pass in state and a callback, with draft being the mutable equivalent of the state object.
In short, updating one property isn't possible practically. Of course it can't be done, but it's very difficult. I can explain why.
Let's start with updating an item. This is definitely possible. Suppose you have a redux selector listening to the data, when sending the data over to a component.
const DisplayOneItem = React.memo(( name, age ) => { ... })
You can see, first the name (and age) is sent to this component, also a React.memo is applied so that only when one of the property is changed, it can then update. But don't get mixed with the following line:
const DisplayOneItem = React.memo(( item ) => { ... })
The above line takes entire item over, and when you change one property, the item changes as well, therefore no matter what, you will get a new update.
You might wonder what happens if React.memo isn't used? Yes, render all the time. Practically if you change a property, either item or data gets changed entirely. Otherwise how do you notify React there's a change? :)
NOTE: React can't update according to a value change by default. The reason why it updates is because you ask it to, through setState({ ...data }). So now it's the question about how granularity YOU control the dispatch (or render). People might think React is magic in terms of rendering, but actually it only does it upon a request. So now your question is, I ask it to render, but it shouldn't render? Haha, you get the point.
How would you go about storing Ref elements in Redux and would you do it at all?
I have a component containing some form elements where I need to store the state of which field in the form the user had selected if they leave the page and come back.
I tried registering each input field in Redux like so (I'm using the <InputGroup> component from Blueprint.js):
<InputGroup
inputRef={(ref) => { dispatch(addRefToState(ref)) }}
...more props...
/>
That resulted in a circular JSON reference error since Redux is serializing the ref element to JSON in order to save it to localStorage. I then tried "safe" stringifying the object with a snippet I found here on Stackoverflow, removing any circular references before turning the object into JSON. That sort of works, but the Ref elements are still so huge that 3-5 refs stored in state turns into a 3MB localStorage and my browser starts being painfully slow. Further, I'm concerned whether I can actually use that Ref object to reference my components, know that I essentially modified the Ref object. I've not tried yet, because performance is so poor with the stringified objects.
I'm contemplating abandoning "the React way" and just adding unique IDs on each component, storing those in Redux and iterating over the DOM with document.querySelector to focus the right element when the page is loaded. But it feels like a hack. How would you go about doing this?
I am not sure if I would use them for that purpose but It would not be among the first ways to do that.
It is perfectly fine to have a React state to store a unique identifier of focused form element. Every form element, or any element in general, can have a unique identifier which can just be a string. You can keep them in your app's redux store in any persistence like web storage.
While you navigate away you can commit that state to your redux store or to persistence, by using a React effect.
const [lastFocusedElementId, setLastFocusedElementId] = useState();
useEffect(() => {
// here you can get last focused element id from props, or from directly storage etc, previously if any
if(props.lastFocusedElID) {
setLastFocusedElementId(props.lastFocusedElID);
}
// here in return you commit last focused id
return saveLastFocusedElementID(lastFocusedElementId) // an action creator that saves to the redux store before every rerender of component and before unmount
}, [props.lastFocusedElID]);
Alternatively
const [lastFocusedElementId, setLastFocusedElementId] = useState();
useEffect(() => {
const lastFocusedElID = window.localStorage.getItem(lastFocusedElementId);
if (lastFocusedElID) {
setLastFocusedElementId(lastFocusedElID);
}
return window.localStorage.setItem('lastFocusedElementId', lastFocusedElementId);
}, []);
Not to mention you need to use onFocus on form elements you want to set the last focused element ID. Id can be on an attribute of your choice, it can be id attribute, or you can employ data-* attributes. I showed id and data-id here.
<input onFocus={e => setLastFocusedElementId(e.target.id)} />
<input onFocus={e => setLastFocusedElementId(e.dataset.id)} />
Also needed a way to focus the last focused element with the data from your choice of source when you reopen the page with that form elements. You can add autoFocus attribute every element like
<input autoFocus={"elementID"===lastFocusedElementId} />
Lastly if your user leave the form page without focusing any element you might like to set the id to a base value like empty string or so. In that case you need to use blur events with onBlur handler(s).
I have a component AddMenu
constructor(props) {
super(props);
this.state = {
menu: '',
make: '',
newCar: '',
}
this.addConfirmFuction=this.addConfirmFuction.bind(this);
}
Make the menu, make, model, year updated by changing the input text, it works.
handleChangeEvent(e) {
this.setState({
[e.target.name]: e.target.value,
});
}
Then I want to combine everything in the state to "newcar" in the state
addConfirmFuction() {
let newEntered=[];
newEntered=newEntered.concat(this.state.menu);
newEntered=newEntered.concat(this.state.make);
this.setState({newCar:newEntered,});
}
After run the last line of code: this.setState (newCar: newEnteredCar), it is not updated, it is still ''. I looked at lots of documents and couldn't figure out...I have bind the function in the state constructor, I have used setState function, but couldn't update the state.thank you in advance.
Did you check if the state values "make" and "menu" are really not empty inside "addConfirmFunction()"?
Also, from your code, it looks like "make" and "menu" are just strings. Any reason for "newCar" being set with a value of type array? If it need not to be array, then this.state.make + this.state.menu would suffice I guess!
Edit:
I think I got the problem. If you are checking the state value immediately after the line is called, then you might not see the new value there since setState is an async function in React. (For performance gains). Please read this stack overflow link. There are lots of article which talks about it.
If you want to see the new value, since this setState calls for a render, keep a breakpoint at render method and check if the value of newCar is available there.
I'm new to react an got a question about components.
I made a component like this:
class HeaderLabel extends React.Component {
constructor() {
super();
}
componentWillMount() {
this.setState({ value: String(this.props.value) });
}
render() {
return (
<div>
{this.props.value && this.props.value != "" ? <label className="control-label">{this.props.value}</label> : <label className="text-muted">({Translation.getPhrase("EMPTY")})</label>}
</div>
);
}
}
HeaderLabel.propTypes = {
value: PropTypes.string
}
export default HeaderLabel;
As you can see I'm using this.props.value to display the value. Is this the right way or is it better to use this.state.value?
Short answer: Use this.props.value.
Since you are taking the value directly from the props without mutating it somehow, there is no benefit at all in first storing it in the state and then using this.state.value.
Basically this boils down to the fundamental idea of React, which is that you should only ever have "one source of truth". By having value both in your props and in the state you have two sources of truth. As a developer, you should try to have a "master state" which passes the data down to its components.
Sometimes, however rarely, it can be a good idea to store the prop value directly to your state. For example, you might want to have a parent component which holds a string which is displayed in your app. This string can be modified in a modal which is opened when clicking the appropriate button. The "temporary under-edit" string would be in the modal-state, whereas the previously saved one still in the master component.
You might find this official blog article helpful: You Probably Don't Need Derived State
In this example, you are using the props value to display the data which you have received, then using this.props.value makes sense. Props are read-only, means the child is not the owner of that particular data and hence it cannot change anything on that data.
when to use this.state.value, let say a scenario where you get data from parent and you have to do some manipulations on that data before displaying, then, in that case, take props into a state and do the manipulations.
you can use both this.props.value and this.state.value. But first you must answer the following question. Depending on the answer you choose appropriate way of setting the value.
Which component handles the subsequent change of value received from this.props.value:
Change of value is coming from Parent component.Then you should use this.props.value.There is no need for you to assign this.props.value to this.state.value. Because the state is maintained inside parent component.
2.Change is handled by HeaderLabel component itself. Then you should set this.state.value.