I'm starting to play with reactjs, and for this project, I have ->
an input field
a button that toggles a modal
the modal has another input field.
How do we make both input fields stay in sync?
What I though I would need to do use to use the LinkedState mixin.
So, I do something like this ->
MainFoo = React.createClass
mixins: [React.addons.LinkedStateMixin]
getInitialState: ->
searchTerm: ''
render: ->
input valueLink: #linkState('searchTerm') # this works
CustomReactChild
searchTermLink: #linkState('searchTerm') #passing into child.
CustomReactChild = React.createClass
renderModal:
unless #modal
$anchor = $('<div>').appendTo('body');
comp = (Modal
body: (CustomReactChildchild
searchTermLink: #props.searchTermLink)
) #custom react modal class
#modal = React.renderComponent comp, anchor
#modal.show()
render: ->
label
onClick: #renderModal
# Deep inside CustomReactChild
CustomReactChildsChild = React.createClass
render: ->
input valueLink: #props.searchTermLink # Am unable to change the value via this input
Am I using this incorrectly? How can I get the second input to change the value of the parent's input and vice versa?
#Douglas popped in an awesome answer in the comments -->
Looking at the logs here, jsfiddle.net/kb3gN/3602 - I think part of the problem is that the modal popup is re-rendering (or at least running the "Controlled Input" handler) before MainFoo has updated its state. I'd suggest merging the models together, so that there is only one React.renderComponent call, or factor out the state into a shared Store which both inputs update and get change events from.
```
Related
I have a simple component that has a render like this:
render: function(){
return (
<textarea className="wmd-input" id="wmd-input" ref={this.initiatePagedown}></textarea>
)
}
initiatePagedown: function(input){
//code that initiates markdown editor.
attr = $(input).attr('id').split('wmd-input')[1];
converter = new Markdown.Converter();
Markdown.Extra.init(converter, {highlighter: "highlight"});
editor = new Markdown.Editor(converter, attr);
return editor.run();
},
The component mounts n different times, hence creating multiple textareas.
The problem is the ref callback is running with the input of the first component, so it's always the first component that's manipulated, not the one that I select. So let's say this component was mounted twice, then the ref callback will be called on the first component instance twice, not once of each component instance. How do I solve this issue?
You're using the Pagedown editor to point to a specific element ID on the page, because you're passing in the second argument, attr:
new Markdown.Editor(converter, attr);
Check out the documentation for the second argument of the Markdown.Editor constructor:
If given, this argument is a string, appended to the HTML element ids of the three elements used by the editor...so you may create the second input box as <textarea id="wmd-input-2"> and pass the string "-2" as the second argument to the constructor.
Right now you're always creating an editor with the same ID:
id="wmd-input"
So the editor constructor will always match every existing instance of that ID on the page.
This is a very poor API forcing you to point to element IDs. As a workaround I would probably make the id a prop you pass in, so that the wrapping component/page can decide how many editors should be there, and you can build the ID like this
return (
<textarea id={ `wmd-input-${ this.props.id }` } ... />
)
Then you can instantiate the unfortunate API with something like
editor = new Markdown.Editor(converter, `-${ this.props.id }`);
I'm running into a weird case that only seems to happen upon first loading a component on a heavily based component page (loading 30+ components).
#Component{
selector: <parent-component>
template: `<child-component [myObject]=myObject>
}
export class ParentComponent {
private myObject:DTOValue;
constructor(service:MyService){
service.getDTOValue().subscribe((dtoValue:DTOValue) => {
this.myObject = dtoValue;
});
}
}
#Component{
selector: <child-component>
template: `<div></div>
}
export class ChildComponent {
#Input set myObject(value:DTOValue) => {//do something};
constructor(){
}
}
In this code, the Parent is going to get a value to a child as an input. This value comes from a request at a later time, so when the child is first initialized, the input could be undefined. When the value does get returned from the request and is set on the variable myObject, I'd expect that the child component would receive an input event being triggered. However, due to the timing, it seems like this is not always the case, especially when I first load a page that contains a lot of files being loaded.
In the case that the child component doesn't receive the input, if I click else where on my page, it seems to now trigger the change detection and will get the input value.
The 2 possible solutions I can think of that would require some large code changes so I want to make sure I choose the right now before implement them.
Change the input to be an Subject, so that I push the input value which should ensure that a correct event is triggered(this seems like overkill).
Use the dynamic loader to load the component when the request as return with the correct value (also seems like overkill).
UPDATE:
Adding a plnker: http://plnkr.co/edit/1bUelmPFjwPDjUBDC4vb, you can see in here that the title seems to never get its data bindings applied.
Any suggestions would be appreciated.
Thanks!
If you can identify where the problem is and appropriate lifecycle hook where you could solve it, you can let Angular know using ChangeDetectorRef.
constructor(private _ref: ChangeDetectorRef)
method_where_changes_are_overlooked() {
do_something();
// tell angular to force change detection
this._ref.markForCheck();
}
I had a similar issue, only with router - it needed to do redirect when/if API server goes offline. I solved it by marking routerOnActivate() for check...
When you trigger change detection this way a "branch" of a component tree is marked for change detection, from this component to the app root. You can watch this talk by Victor Savkin about this subject...
Apologize, the issue ended up being my interaction with jQuery. When I triggered an event for a component to be loaded, inside of the jQuery code, it wouldn't trigger the life cycle. The fix was after the code was loaded to then call for a change detection.
I initialize CKEditor in a React component like this:
render: () ->
<div ref="editable" dangerouslySetInnerHTML={{__html: #props.html}} />
componentDidMount: () ->
#editor = AlloyEditor.editable(#refs.editable, {extraPlugins: AlloyEditor.Core.ATTRS.extraPlugins.value + 'plugins go here'})
#editor.get('nativeEditor').on('blur', (event) =>
#props.handleChange())
//At this point the #props.html changes and the component is re-rendered
I define widgets following this tutorial. The appropriate html elements are properly recognized as widgets and everything works fine until the 'blur' event is fired. After the component is re-rendered, the widgets' upcast functions don't get invoked again and the html elements don't become widgets any longer.
I thought that I could use methods such as checkWidgets and initOnAll in the componentDidUpdate method, but they only work for widget candidates with .cke_widget_new class. As far as I understand, the upcasting happens at the data (html) processing stage, and it looks like the editor doesn't process html again after DOM was re-rendered. What can I do about this? Thanks!
Update: Eventually oleq's solution worked for me. I added:
componentDidUpdate: () ->
data = #editor.get('nativeEditor').getData()
#editor.get('nativeEditor').setData(data)
The only problem is that this (and probably any other) solution doesn't work with the blur event, which was a bad event choice, because it is also triggered right before the user adds a widget by pressing a button. I replaced this event with another one that seems to work for me at least for now.
You can use editor.widgets.checkWidgets() as documented here:
https://docs.ckeditor.com/ckeditor4/latest/api/CKEDITOR_plugins_widget_repository.html
The general idea is that it checks all the widgets in the document and refreshes ones that have not been made widgets yet.
OR if you only want to init one widget that you have a reference to, do it with initOn() documented on the same page.
So I made a component for including content-editable components in my app. I copied it from some gist I believe, then edited to what i needed.
The code is below. When I edit it, it triggers updates on the parent just fine, but when I attempt to set props.html in the parent, it doesn't reflect in the UI.
FURTHER, the console.log shows that this.props.html is equal to '' a blank string, yet the UI doesn't update, and maintains the text that was originally in there.
I don't understand how this is possible... dangerouslySetInnerHtml = {__html: ''} should make it so the UI reflects an empty string... it feels like it should be impossible for it to show the old text.
var React = require('react');
var ContentEditable = React.createClass({
render: function(){
//TODO: find where html=undefined and fix it! So I can remove this? Maybe I should keep this safety.
var html = this.props.html || '';
console.log('content editable render, html: ', this.props.html);
return <div id="contenteditable"
onInput={this.emitChange}
onBlur={this.emitChange}
contentEditable
dangerouslySetInnerHTML={{__html: html}}></div>;
},
shouldComponentUpdate: function(nextProps){
return nextProps.html !== this.getDOMNode().innerHTML;
},
emitChange: function(){
var html = this.getDOMNode().innerHTML;
if (this.props.onChange && html !== this.lastHtml) {
this.props.onChange({
target: {
value: html
}
});
}
this.lastHtml = html;
}
});
module.exports = ContentEditable;
(A little background, I'm trying to clear my input after submitting it to be saved. The clearing isn't working, hence this question.)
I got a very similar problem using contentEditable and shouldComponentUpdate, it looks like there is a bug when resetting the innerHTML to the same previous value using dangerouslySetInnerHTML function (or prop) (I think it does not work even if you insert the code without using it) ... I suspect (this is just an idea) that React compares the last value set through dangerouslySetInnerHTML with the new one you are trying to send and decides not to update the component because it is the same (even if the real innerHtml has changed due to user interactions, because those interactions does not trigger any state or props update on React).
Solution: The easiest solution I found was to use a different key each time I needed it to re-render. for example using key={Date()}.
Example: Here you can find your code (I changed some of it to make it work), when you type '?' into the div, the text inside the ContentEditable component should become an empty string (i.e. ''), it works only once, the second time you type '?' won't work because the innerHTML for react will be the same to the one you're setting (i.e. an empty string so it won't update the component).
And here, I added the key={Date()} (this is the easiest way to show you that this work, but it is not the best way to set a unique key each time it re-render) to the editable component, now you can type any number of '?' and it will work.
I found another solution that is probably better than generating random keys. Putting a key specifically on the div that calls #dangerouslySetInnerHtml, and not just on the component itself
<div class='wrapper'>
<div key={this.props.thing.id} dangerouslySetInnerHtml={this.props.thing.content} />
</div>
This is not the case here but make sure you always dangerouslyset html on div tag and never on span, p ... because if span element child is div, there will be a problem.
Solved rerender bug to me
My (very simple) React (version 16) app: It has a contentEditable <div>.
It successfully re-renders this <div> upon a progression of submit button clicks. Instead of dangerouslySetInnerHtml, I used ref={el => this.myRefElem = el} with componentWillUpdate(nextProps) { this.myRefElem.innerHTML = nextProps.myInputText; } For me, nextProps was important for the proper value to re-render. See my app's project files, to see the rest of the required code.
CLICK-HERE to see my React app. It has a button to download its (development mode) project files. It (basically) only has an index.js file. - - - This app was initiated by mlbrgl, who asked me for an alternative technique.
I ran into the same issue (React 16) and used an approach suggested by MLR which consists in dropping dangerouslySetInnerHTML and using componentDidMount() instead for the initial render and componentDidUpdate() for any subsequent renders.
Solution here, adapted to React 16: https://codepen.io/mlbrgl/pen/PQdLgb
These hooks would perform the same update, directly updating innerHTML from props:
componentDidMount() {
this.updateInnerHTMLFromProps();
}
componentDidUpdate() {
this.updateInnerHTMLFromProps();
}
updateInnerHTMLFromProps() {
this.refEl.innerHTML = this.props.html;
}
This makes it clearer (for me at least) to see what is really going on, without having the false expectation that dangerouslySetInnerHTML would keep the DOM in sync in all circumstances, as suggested by Mike Woodcock here https://stackoverflow.com/a/38548616/9408759.
For a complete view of the problem and both solutions outlined here, please check https://codepen.io/mlbrgl/pen/QQVMRP.
Adding key property for an element with dangerouslySetInnerHTML did resolve my issue.
As a key I used
key={new Date().getTime()} // timestamp
I have a form with several input fields. When changing values in inputs, view with form is re-rendering. Here is some piece of Backbone.View code:
initialize: ->
#model.on('change', #render, #)
events:
'change input': 'change'
change: (event) ->
ctrl = #$(event.currentTarget)
#model.set(ctrl.data('name'), ctrl.val())
render: ->
#$el.html(#template(#model.toJSON()))
But re-rendering occurs also when I press tab key to set focus in next input and focus is lost. Is it possible to solve this problem with a little portion of code?
you could make the model update silent to prevent re-rendering:
change: (event) ->
ctrl = #$(event.currentTarget)
#model.set(ctrl.data('name'), ctrl.val(), {silent:'true'})
Currently you're (in essence) re-rendering the whole form every time anything changes. Ideally a better way to go would be to have different views for the different parts of the form, and only re-render the parts that actually need to be render-ed, based on the specifics of what changed.
But that doesn't really help if you're looking for:
is it possible to solve this problem with a little portion of code?
So here's a different approach you can try: modify your render to do something like:
render: ->
focusedElementId = $(':focus').attr('id');
#$el.html(#template(#model.toJSON()))
$('#' + focusedElementId).focus();
In other words, on every render store which element is focused (or rather, its ID, since that element will go away as part of your rendering), and then restore that focus after the render.
Of course, this assumes that all of the elements involved have IDs. If they don't, you'll either need to add IDs to them or find some other way to keep track of which element was focused. For example, you could do something like var focusedIndex = $('input').index();, but don't use that literally without testing first to ensure that the index stays consistent, because it might not be (if say the number or order of inputs on the page changes).