How to find div from one component from different component - vuejs - javascript

Consider i have multiple Vue.js components.
<loginFields></loginFields>
<submitButton></submitButton>
Now, after clicking submitButton (which in fact is a div with unique id) i want a method to be started to check values of loginFields.
Component loginFields has v-for directive inside which creates multiple input fields like username or password.
What i want to achieve is to get value of user loginField and password loginField and pass it to method from submitField.
With jQuery it would be easy - call $('#userLoginField').val() and it is done.
I believe there are two ways to do it in vue, however i guess they are both not best approaches:
create Bus and on clicking submitButton emit an event which starts cascade of events which finally comes back to submitButton with value of all input fields. I think it is quite messy.
Call this.$parent.$refs of submitField and then get refs of inputFields component, search it for all other refs, grab value. It is messy too.
Is there a short and clean solution like jQuery version to easily get data from other children of same parent or siblings? Each sibling may have unique id, be a part of same/different class so it can be easily identified.
Kalreg.

I think the simplest way (i.e., least amount of "Vue wiring") is using the native form's submit-event, which includes the form's elements (including children of <login-fields>) in event.target.elements. The submit-event handler could validate those elements' values as needed.
// in <login-form>
methods: {
submit(e) {
// Get elements that have a "name" attr
const elems = Array.from(e.target.elements).filter(el => el.name);
// Create dictionary of those elements' values
const values = elems.reduce((p,c) => {
p[c.name] = c.value;
return p;
}, {});
if (this.validate(values)) {
// submit form
} else {
// don't submit...show error
e.preventDefault();
}
}
}
demo

Related

How to design a complicated form in React

I need to create a form in React made of multiple components, such as <TextField>, <DropDown>, <DatePicker> where each one has a different props, some of them are common such as Id, Label
Question is how should I program this, I have come with a few ideas ->
It would be easier to split it into separate arrays: dropdowns, textFields, datePickers and then render them via .map(), but it wouldn't work as they are not in order in the form
Put all of components into array and render them via .map() with if/switch/function where I would decide which component is which
const App = (props) => {
const forms = props.forms
const getItem(type) {
if (type === Type.DropDown) {
return <Dropdown dropDownProps>
}
....
}
return (
forms.map(item => {
return getItem(item.type)
})
)
}
Next question is how/where to store all props - there is about 10 of these fields in the form, so should I do some file like InitData.ts with
const FirstField = {id: 0, label: "FirstField"}
...
but then how would I pass props such as onChange etc?
Also how should I store all those inputs using hooks? Array of strings, or some object?
Always base your components on abstraction and try to make them reusable, so assume I want to create a login form.
I would create a folder called common having common components, like text field and dropdowns as a way of reusing them.
and then another component for wrapping up most of the functionalities: handling submit events, handling form validation, render input, render dropdown. Most of these functionalities are core in my form, so abstract them away into a separate component called form component.
finally, I create another component called loginForm, in which I call these functionalities to render them.
I store the initial field values of type empty strings in a state hook inside loginform and pass it as an argument to the props of the form component.

How to update state for all inputs using functional components

I'm having trouble wording my question, so hopefully I can explain it well enough here!
I'm making a generic form component, which handles form validation on the top level. The end result would look like this:
<Form action='/api/endpoint'>
... form inputs go here
</Form>
Everything is working really well. If I go through and fill out the form, I can submit it just fine and the validation works.
The form has state which looks like this:
{
values: { name: 'Johnny', age: 18, email: 'email#example.com' },
used: { name: true, age: true, email: true },
validators: { name: (Yup Validator), age: (Yup Validator), email: (Yup Validator) }
}
The state is populated using custom onChange and onBlur functions in each of the components (they are passed using react context). For example, onBlur the used part of the state is updated to true for the current element, onChange the values part of the state is updated to the elements value, etc.
As aforementioned, this works well when I go through and use every element. What I want to happen is that when the user clicks the Submit button, it checks EVERY field to ensure it's valid before sending the POST request.
An easy solution I thought to do would be to just programmatically blur every element which calls the onBlur function. However, because setState is asynchronous, it only adds the last element to the state (because it's being overridden, if that makes sense).
I'm wondering if I can either modify how I'm setting my form state or if there's a way I can wait before setting the form state again.
I'm using functional components, and I know that useEffect exists for this purpose, but I'm not sure how to use it in my situation.
Here's how I'm setting the state onChange and onBlur:
const handleBlur = (e) => {
e.persist();
setFormState({...formState, used: {...formState.used, [e.target.name]: true}});
}
(hopefully you can infer onChange from that, it's the same but with values instead of used)
My idea of blurring everything was as follows:
const blurAll = async () => {
let inputs = [];
// Get all inputs on the current page
const inputTags = document.getElementsByTagName("input");
// Get all text areas (because they are <textarea> instead of <input>)
const textAreas = document.getElementsByTagName("textarea");
// Concat the arrays
inputs.push(...inputTags, ...textAreas);
// Trigger a blur event for each (to initialize them in formstate)
inputs.map(input => { input.focus(); input.blur(); });
}
However because setFormState uses the existing form state, it all happens too fast and so only the last element is added to the state.
I understand that this blurAll is probably not a great solution, but I was just trying to get something working (and then I was going to get only the inputs in the form itself).
This leads me to my question, how do I wait for the previous setState to complete before setting the state again (in the case on my onChange and onBlur)?
OR is there a way in JavaScript to simply update one key in my state object instead of replacing the entire object?
If you need any more information, comment and I'll provide as much as I can. I'd prefer not to use a form library, I'm trying to learn React, and this seemed like a good thing to further my knowledge! Thank you!

React different state properties getting bound

I am trying to implement a table in react where the user can edit individual rows by clicking the edit button on a row and then submit once he has made his change. I have say two components App.js and its child Table.js to implement this.
The way I thought of doing this initially was letting each of this component have their own state for rows and then the Table component reads from the props send to it by parent initially and only change the parent rows when users submits the change as oppose to onChange event. But I've read that reading props into state is an anti-pattern.
So decided to have everything in the parent by having two values for row (oldrows,newrows). And using them to maintain state instead, This is the design I came up with :
But what happens is whenever I click cancel the oldRows get bound to the newRows, here is a codePen example I put up:
https://codepen.io/snedden-gonsalves/pen/zYOVMWz
handleChangeRowInput = (event, keyValue) => {
let keyVals = [...this.state.newValuesArray];
keyVals[this.state.editIndex][keyValue] = event.currentTarget.value;
this.setState({
newValuesArray: keyVals
})
}
handleCancelRowInput = () => {
this.setState({
newValuesArray: [...this.state.oldValuesArray],
editIndex: -1
})
console.log('array', this.state.newValuesArray)
}
handleSubmitRowInput = () => {
this.setState({
oldValuesArray: [...this.state.newValuesArray],
editIndex: -1
})
}
In the codePen example if you enter a new value then cancel and then try adding a new value again the the old values and new values get bound.
I tried using lodash deepClone but it didn't work out, not sure why this is happening.
Also if you could comment on what is the best way to design this in react that would be awesome as I am very new to react and just trying to learn ..
I didn't find any issue after the cancel function. For me, the issue was coming up after I called the save function.
After clicking on the save button and then editing again, the old values and new values were get bound.
The handleSubmitRowInput function should create a new array for the oldValuesArray using the cloneDeep function
handleSubmitRowInput = () => {
this.setState({
oldValuesArray: _.cloneDeep(this.state.newValuesArray),
editIndex: -1
})
}

How to update a state object in the DOM from componentDidMount() (not componentDidUpdate())?

I'm trying to conditionally show or not show per say a button based on data that I receive from clicking on a point. I realized that regular jquery functions to add a class don't really work in React. So I figured I could store strings in the state like
this.state: {
hidden_components: {
add_comment: "hide"
}
}
This way I can conditionally show or hide a button by
<button className={this.state.hidden_components.add_comment}> Add Comment </button>
After the render() I have more or less:
componentDidMount() {
this.state.g = new Dygraph
this.state.modal = new Modal
this.state.modal.setContent(use some ID here to reference a div that is hidden but will show up in the modal)
const set_hidden_container = () => {
// I'm just going to use this = notation instead of setState()
// this is supposed to reset the
this.state.hidden_components = "hide"
if (check_comment(this.state.points[at some index].value)) {
this.state.hidden_components = "show"
}
}
this.state.g.updateOptions( {
pointClickCallback: (event, p) => {
console.log("i clicked a point on the graph")
this.setState({
currentPoint: p
})
set_hidden_containers()
// force update
this.setState({
currentPoint: p
})
// I want the modal to open a div of things that only show jsx based on logic in set_hidden_container()
this.state.modal.open()
}
}
componentDidUpdate() {
// logic goes here for like event listeners and anything that queries the DOM after initialization
}
Then in componentDidMount() I have a function that depending on the data received from clicking on a point I do the following:
1) reset all the classes stored in the state to "hide"
2) based on conditions set some of them to "show"
3) concatenate all the classes stored in the state with various styling classes
UPDATE:
I've long since found an easier solution to this problem, however, I'm guessing some people might have similar issues. Therefore, I'll update this question with more psuedocode and a workaround: maybe someone down the line can solve this. This component is particularly frustrating to work with because I haven't been able to make it as modular as I want because of the particular library I'm working with. There are actually about a 1000 lines in this component (I know I know not good).
WORKAROUND:
For those of you who are having trouble with a component's lifecycle in dynamically setting parts of the DOM but don't want to use global variables to set classNames, jquery functions, or use react syntax to show components containing the content I recommend you do the following.
You can still have a set_hidden_container() set content dynamically, you just have to set things based on an id with innerHTML instead of setting a state object to be a string "show". The important thing is, however, that for every time you need to dynamically change content you reset these references to be empty as well as force an update. You can simply change the state of anything and then in componentDidUpdate() you can insert 1) a conditional to check if the innerHTML was actually set or not (since you're not always going to be displaying everything) and 2) within that conditional you can set whatever logic you want associated with the content showing on the page.
componentDidMount is invoked immediately after a component is mounted. If you want to set classNames based on clicks, I would put that logic in componentDidUpdate, which is invoked after updating occurs.

How to 'reset' a ReactJS element?

I'm trying to 'reset' a ReactJS element.
In this case, the element is 90%+ of the contents of the page.
I'm using replaceState to replace the state of the element with with its initial state.
Unfortunately, sub-elements which have their own 'state' do not reset. In particular, form fields keep their contents.
Is there a way of forcing a re-render of an element, which will also cause sub-elements to re-render, as if the page had just loaded?
Adding a key to the element forces the element (and all its children) to be re-rendered when that key changes.
(I set the value of 'key' to simply the timestamp of when the initial data was sent.)
render: function() {
return (
<div key={this.state.timestamp} className="Commissioning">
...
The this.replaceState(this.getInitialState()) method doesn't actually reset children that are inputs, if that's what you're looking for. For anyone looking to just reset their form fields, there is a standard DOM reset() function that will clear all the inputs in a given element.
So with React, it'd be something like this:
this.refs.someForm.getDOMNode().reset();
Doumentation:
https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset
If it is a form you want to reset, you simply can use this
// assuming you've given {ref: 'form'} to your form element
React.findDOMNode(this.refs.form).reset();
While I don't personally think you should store local, interim component state (like in-progress input boxes) in a centralized location (like a flux store) in most cases, here it may make sense, depending on how many you have, especially since it sounds like the inputs already have some server interaction/validation around them. Pushing that state up the component hierarchy or into some other central location may help a lot in this case.
One alternative idea off the top of my head is to use a mixin in components that might need to reset local state, and do some kind of event triggering, etc. to make it happen. For example, you could use Node's EventEmitter or a library like EventEmitter3 with a mixin like this (warning: not tested, maybe best this as pseudocode :)
var myEmitter = new EventEmitter(); // or whatever
var ResetStateMixin = {
componentWillMount: function() {
myEmitter.on("reset", this._resetState);
},
componentWillUnmount: function() {
myEmitter.off("reset", this._resetState);
},
_resetState: function() {
this.replaceState(this.getInitialState());
},
triggerReset: function() {
myEmitter.emit("reset");
}
};
Then you could use it in components like so:
React.createClass({
mixins: [ResetStateMixin],
getInitialState: function() {
return { ... };
},
onResetEverything: function() {
// Call this to reset every "resettable" component
this.triggerReset();
}
});
This is very basic and pretty heavy handed (you can only reset all components, every component calls replaceState(this.getInitialState()), etc.) but those problems could be solved by extending the mixin a bit (e.g. having multiple event emitters, allowing component-specific resetState implementations, and so forth).
It's worth noting that you do have to use controlled inputs for this to work; while you won't need to push your state all the way up the component hierarchy, you'll still want all your inputs to have value and onChange (etc.) handlers.
You could also use document.forms[0].reset()

Categories