I am working on form layout for multiple children. Each child will have its own data about their name, surname,phone and email.
My data structure for each child looks like this.
[id]:{
name:"",
surname:"",
phone:""
email:""
}
I am changing between child using unregister with keepValue:true. Where selectedChildId is the active child with dark gray bg.
useEffect(() => {
unregister(selectedChildId, { keepValue: true });
}, [selectedChildId]);
This way I can jump between children without needing to save their state anywhere else.
Everything works fine, but if I delete a child, the react hook form still keeps the child object with empty string values and I dont want that. Is there any way of removing a specific value (object) from react hook form state?
Related
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 want to pass array from one component to the other in vueJs, which i am able to do with
<add-new-admin-modal
:permissions = "permissions"
</add-new-admin-modal>
In my other component which is a modal actually,
I am receiving the props as,
props: {
permissions: {
type: Array,
default: () => []
}
}
Here when i try to change the permissions array, it reflects the parent data, As mentioned in the documentation.
https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
so i tried with spread operator
data () {
return {
statePermissions: [...this.permissions]
}
}
The statePermissions array is still empty when i try above method,
I even tried with Object.assign
data () {
return {
statePermissions: Object.assign([], this.permissions)
}
}
Still it doesn't work.
In my Modal, I am accessing it as
<div v-for = "permission in statePermissions" :key = "permission.id">
...someHtml here.
</div>
The main idea is, I have a component which gets the data through an api, then i have a modal which takes this data and updates it accordingly and submit it to an api.
when the modal is closed, the parent component should need to have the unedited data, so that if modal is reopened it should get unedited data.
In the process of using Modal, My parent component remains in the same state (It neither gets mounted nor changed), so their is no point in making the request for default data again from parent.
Your problem is probably that the default value for you prop is an empty array and you're assigning it to a local variable before the property is properly populated by the parent (or it might even be a lifecycle issue).
Try moving your assignment to the local variable to the mounted() hook or even better if you wan't it to be reactive watch your property:
watch: {
permissions(newValue) {
this.statePermissions = newValue
}
}
You also don't need to ...spread an array to assign it to an array.
Since permissions is an array of objects, when you make a copy of it, the resulting array is a shallow copy meaning it will contain the same references to objects. That's why modifying the new array's values update the old array's values as well. You need to create copies of the objects inside permissions.
If permissions only contains primitives, you can do something like this:
this.statePermissions = JSON.parse(JSON.stringify(this.permissions));
If permissions is of a certain type (i.e. new Permission()), you can map it (I think this is cleaner):
this.statePermissions = this.permissions.map(x => new Permission(x.propA, x.propB, etc.));
This way, each cloned object in statePermissions will have the same properties as the object it's copied from in permissions, but it's independent so modifications won't affect the parent it was created from.
There's a few other ways in this post.
I am creating a profile screen for user in my apps . I am using lightbox from React-Native-Navigation by wix to perform an edit profile . So , the user will click the touchableopacity and a lightbox will pop up and the user will enter the new information and save it . So, im wonder is it possible if i want to pass the textinput value from lightbox to the parent(profile.js) so that i can setstate in the profile.js ?
Yes this is possible. You will need to send the data as props to the parent. If you haven't done it before it might feel a bit tricky but you'll get there.
From the parent:
<LightboxComponent
userData={this.handleUserData(data)}
/>
handleUserData(data) {
/* Do something with the data here */
}
From the child:
To send the data you need to set an onChange event or similar on the input you want to capture, like this:
<input name="user-name" onChange={ (e) => this.props.userData(e.target.value) }
This will make the input data from the child get sent to the parent. Every change will trigger a re-render of the affected components.
If your app complains about not being able to setState correctly, then you need to bind this in the parents constructor like this:
this.handleUserData = this.handleUserData.bind(this);
I would also say pass the parent's function pointer to the child as props (as seen on the React site). Although some people opt for using an event emitter. I'm actually really curious about more developers' opinions on that.
Is derailing a thread on StackOverflow grounds for epic down voting?
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.
The standard way for a React component to include child components is to create them in the render method and set to the children property. In my use case, children may be created before the parent is rendered and passed in through the parent's properties.
Events in the child are bubbled up to the parent as expected, but changes to the parent container do not re-render children created this way. The docs indicate that there is a difference between parent and owner relationships, the latter being established only for components created in render, so my guess is this relationship is missing and important for cascading re-renders.
Here is a simple example (fiddle)
/** #jsx React.DOM */
globalState = 'initial state';
var Child = React.createClass({
render: function() {
return React.DOM.input({
value:globalState
});
}
});
var Parent = React.createClass({
handleChange: function(e) {
globalState = e.target.value;
this.forceUpdate();
},
render: function() {
return React.DOM.div({
children: [
Child(),
React.DOM.br(),
this.props.passedChild
],
onChange: this.handleChange
});
}
});
c = Child();
p = Parent({passedChild:c});
React.renderComponent(p, document.body);
In this example, both child inputs can be edited, the onChange event is caught by the parent and a forceUpdate() is called. This does cascade down to the first child which is created in the render method, but not to the second child which is created elsewhere and passed in.
How can I update the owner of a child component so it will update as desired?
My backup plan is to wire up an event listener on child components. In my application, there is quite a bit of logic around when components are created that would make it impractical to do everything in render().
This is slightly complex to explain here, but your code will now work on the master build of React (post 0.10.0).
I'm not too sure what you're trying to accomplish here, but if you change your this.props.passedChild to this.props.passedChild() and c = Child(); to c = Child;, it'll work. Call it this.props.passedChildClass or something. You can also try this.props.passedChildFn with c = function() { return Child(); }. Whatever suits your need.
Don't create an instance of a component and pass it around (it won't be a big problem anymore soon; the return value of Child() won't be an instance anymore). In general it's bad practice because this encourages mutation. Create your children on the fly as you need them and let React handle the rest.
In my application, there is quite a bit of logic around when components are created that would make it impractical to do everything in render().
Break them into helper functions! You really don't have to stuff everything into a single render.
Also, global state is a bad idea. The fact that your child updates correctly by reading from globalState is very fragile. You must have gotten a warning in your console (providing you're using the dev build) to add an onChange handler. Go read it =).