MDC Components won't update UI when new values are added/ reset - javascript

I'm trying to reinitialize/ redraw all MDC components for form elements when their values change via javascript, but so far my attempts have fallen short. Is there an easy way to achieve this with a built in MDC method that I'm unaware of?
I created a custom way to reload the MDC components with a data-mdc-reload html attribute that fires on click but this isn't quite doing the job.
Here's a codepen showing the issue: https://codepen.io/oneezy/pen/XvMavP
click the UPDATE FORM VALUES button to add data
the VALUE output in red means the component is broke/ blue means it works
click the RESET button to reset data to initial state (this is broke too)
Javascript
// MDC Reload Component
function mdcReload(time = 1) {
var components = mdc.autoInit();
let reloadComponents = document.querySelectorAll('[data-mdc-reload]');
for (const reloadItem of reloadComponents) {
reloadItem.addEventListener("click", async () => {
setTimeout(function() {
components.forEach((c) => c.layout && c.layout());
}, time);
});
}
}
// Initialize MDC Components
mdcReload();

You can use the Foundations and Adapters provided by MDC.
I would suggest you make a MDC instance of every component and use the built in methods in mdc.COMPONENT.foundation_.adapter_.
Here is the modified pen
The issue was that you need to call floatLabel(true) to let the labels float after you set their values.
if (c.foundation_.adapter_ && c.foundation_.adapter_.floatLabel) {
c.foundation_.adapter_.floatLabel(true);
}
Furthermore I changed the select component to
// $(MDCselect).val('Option 3');
// Instantiate the MDC select component.
const mdcSelect = mdc.select.MDCSelect.attachTo(document.querySelector('.mdc-select'));
mdcSelect.foundation_.adapter_.setValue('Option 3');
Hope it helps !

I found that the easiest way to refresh the labels for the MDC components after setting a custom value via javascript is to send a Blur event to the input like this:
yourMDCInput.dispatchEvent(new Event('blur'))
This will leave the MDC component to decide which action it has to take, so it floats the label if there is a value set or resets the label if there is no value set.
It is quite annoying, but without digging into the code of the MDC foundation to find a better solution, I couldn't spot any better solution in the docs (which are incomplete and partly wrong anyways).
If you can, try using the MDC class instance to set your values - in that case, the MDC component is informed about the change and will behave as intended. When using autoInit, please note that the docs say the MDC class instance is attached to the root <div> while it is actually attached to the <label> where the data-mdc-auto-init attribute is set.
Assuming you wrap an MDCTextField in a <div id='my-text-field'>, you could do something like:
document.querySelector('#my-text-field label').MDCTextField.value = 'hello world'
and the field will update as expected.

Related

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

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

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.

Building an autocomplete textbox with ReactJS

My idea is to mantain the list of filtered users (suggestions) as state on the component, when the input changes, the state is updated.
How can I display the filtered list below the text box?
One option is 'datalist' tag (HTML5), but the list is already filtered, and part of the functionality of this tag is filtering.
I can't use any library or framework.
English is not my native language, sorry if you find some mistake.
Thanks.
Try a component from a design library, like the Material-UI autocomplete component http://www.material-ui.com/#/components/auto-complete
The dataSource attribute represents the array of autocomplete options.
How I did it was to pass in the dataList array as a prop and filterByField prop so that you can change what to filter, then add an event listener to the input (onChange) that passes the value to a function that filters the dataList.
onChangeInput(e) {
const { dataList, filterByField } = this.props;
const filteredDataList = dataList.filter(items => items[filterByField].toLowerCase().startsWith(e.target.value.toLowerCase()) );
// update internal component state to trigger render of dropdown list
this.setState({filteredList: filteredDataList});
}
I also added a check for no matches found so I can show a message:
if (filteredDataList.length === 0) {
this.setState({noMatchFound: true});
}
Then in my render() I simply check if filteredList isn't null and show an unordered list that I use css to display below the input.
{this.state.filteredList !== null
<ul className="autocomplete-list">
{this.filteredListMarkup()}
</ul>
}
filteredListMarkup() then uses map to return an <li> for each item with the necessary event handlers to update the selected item into the input and close the autocomplete-list by this.setState({filteredList: null});
You might also find this one useful:
https://github.com/reactjs/react-autocomplete
Even if you could use dependencies, I tried a bunch of the top current ones and personally wasn't happy with any of them (added dependencies like jQuery, not lightweight to use/understand/customize, css challenges, etc).
In then end, I found this lightweight vanilla React typeahead tutorial (no, I didn't write the tutorial). It's quick, simple, and three's no added dependency tree weight (eg: jQuery) or dependency maintenance. This solution also easily adjusted to the newer React patterns & the libraries I was using, and I'm guessing the same would be true of the patterns/libraries you may be using. Maybe this will help you or someone else like it did me.

Setting a field directly on the class definition in ReactJS

Is it a good practice to do this in ReactJS?
var Component = React.createClass({
render: function () {
return (<div></div>);
},
field: 'value', // is this safe?
method: function () {
// do something with field
}
})
Before starting to suggest that I should use this.props or this.state, for me it's not the case, because those are fields that do not affect rendering in any way directly, they just work together to control the rendering.
I would like to use the React class as I do with regular javascript 'classes'.
My main concern here is how those fields and methods are handled inside React, and if the fields are set on the instance itself or directly on the prototype, which would not be suitable at all for what I need.
I ran a quick test and it seems that the fields are set on the instance, and the methods on the prototype, which is ideal. But is this the expected and documented behavior? And is this safe for future versions?
I think it can work the way you are doing and that it's safe. However if I understand well you are proceeding data calculation/transformation directly in the view.
So I would advise that you remove this logic from the view and treat it in the model part of a mvc or mv*, in your backbone models, or in your flux store for example.
This way you won't be mixing data transformation logic and pure rendering.
I would say so, I have been using things like this for a while and have not seen any issues. For example, let's say you want a handler of some sort that you want to pass to nested components, you would create the function in this component and pass it as a prop to a child. I believe they have examples that use similar concept in the ReactJS Facebook site.
Under the hood React is just looping through the properties of the object you pass to createClass and copying them to the prototype of the Component. Primitive values like strings or numbers obviously cannot be copied by reference, so don't get shared across all instances, whereas objects, functions, arrays and so on will.
If you want to work with values that are just local to the component instance you need to use the state API. I'm not sure what you mean by "[state and props] do not affect rendering in any way directly, they just work together to control the rendering". The whole point of props and state is that they work together to generate values to be used when (re)rendering.
https://facebook.github.io/react/docs/component-api.html
A React component should only render in response to either changing props or changing state. You cannot/shouldn't trigger a re-render by mutating other fields directly.
You need to think of your component as something closer to a pure function. State and props go in at the top, and static VDOM/HTML comes out.
I would re-write your example as,
var Component = React.createClass({
getInitialState: function () {
return {field: 'value'};
},
render: function () {
var field = this.state.field;
return (<div>{field}</div>);
},
method: function () {
var field = this.state.field;
// do something with field
this.setState({field: 'anotherValue'});
}
})

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