React onChange method not working in react_on_rails - ruby-on-rails

I am trying to create a sample app based on react_on_rails gem. In my react code react inbuild function like onChange or onSubmit are not working.
My HelloWorldWidget Component looks like this.
...
constructor(props, context) {
super(props, context);
_.bindAll(this, 'handleChange');
}
handleChange(e) {
const name = e.target.value;
console.log(name);
//this.props.updateName(name);
}
render() {
const { name } = this.props;
return (
<div className="container">
<h3>
Hello, {name}!
</h3>
<input className="form-control input-sm col-sm-4" type="text" onChange={this.handleChange}/>
</div>
);
}
Also if I disable server side pre-render of my component in my views/hello_world/index.html.erb file then the component is not rendering on UI.
<%= react_component("HelloWorldApp", props: #hello_world_props , prerender: false) %>
Github Repo: react-on-rails-sample-app

I'm not sure where _.bindAll method came from but the orthodox way of binding handlers is with this syntax:
this.handleChange = this.handleChange.bind(this);
If you use arrow function you don't need to bind it to the class;
handleChange = (e) => {
const name = e.target.value;
console.log(name);
//this.props.updateName(name);
}

Try to use arrow functions like this:
onChange={(e) => this.handleChange(e)}

Related

Migration from v6 to v7

I have upgraded to v7 of react-hook-form today. And all went fine until i came across some legacy code using ref attribute.
In version 6 this worked perfectly
<ToggleSwitch toggleName='ifMonitoring'
ref={(e) => {
monitoring.current = e;
register(e);
}}
/>
But in version 7 ref is not used anymore, instead its {...register('ifMonitoring')}. This works fine accross the application but the above example is only one which doesnt work.
I have tried to search for similar issues but to no avail.
Anyone can help?
EDIT:
Adding more code to better understand this
function Edit() => {
const monitoring = useRef(null);
return <Controller name='monitoring' control={control} render={({ field: { ref }, fieldState }) => <ToggleSwitch ref={ref} checked={portInfo.isMonitored} />} />
ToggleSwitch is component with its own state. It does have onChange but to maintain its state
const ToggleSwitch = forwardRef((props, ref) => {
const [toggleCheck, setToggleCheck] = useState(props.checked);
const handleOnChange = (e) => {
setToggleCheck((prevState) => !prevState);
if (props.onChange) {
props.onChange(props.entry._id);
}
};
return (
<div className={`toggle btn btn-sm`}>
<input type='checkbox' defaultChecked={toggleCheck} onChange={handleOnChange} ref={ref} name={`toggle`} />
<div className='toggle-group'>
<label htmlFor={`toggle`} className={`btn btn-success`}>
In Use
</label>
<label htmlFor={`toggle`} className={`btn btn-danger`}>
Not in Use
</label>
<span className={`toggle-handle btn btn-light btn-xs`}></span>
</div>
</div>
);
EDIT 2&3:
Not working v7 Codesandbox
Working v6 Codesandbox
The ref is actually still there, it's returned by register among other things that we spread.
There is an example on the React Hook Form documentation to share ref usage.
You can do like this:
const { ref, ...rest } = register("ifMonitoring");
<ToggleSwitch
{...rest}
ref={(e) => {
monitoring.current = e;
ref(e);
}}
/>;
Edit
In your specific case, you are passing the ...rest to your ToggleSwitch component, but the component does not forward these props to the inner input (except the name that you pass yourself in a prop).
The problem here comes especially from onChange that is part of the elements inside your rest variable. As you have also your custom onChange, you can combine both yours and the one of React Hook Form.
So, in your main component you can pass the rest props like this:
<ToggleSwitch
inputProps={rest}
ref={(e) => {
ref(e);
inUseRef.current = e;
}}
// [...]
/>
And in your ToggleSwitch component, you can call the onChange of RHF in your own function, pass the input props to the input, and pass your onChange function after.
const handleOnChange = (e) => {
setToggleCheck((prevState) => !prevState);
if (inputProps.onChange) {
inputProps.onChange(e);
}
};
// [...]
<input
// [...]
{...inputProps}
onChange={handleOnChange}
ref={ref}
// [...]
/>
Here is the codesandbox.

Svelte: Reference to self for use in modules

I have a simple popup component in Popup.svelte:
<script>
let hidden = true;
</script>
<button on:click={() => hidden = !hidden}> Pop </button>
<div class:hidden> Extra Content </div>
<style>
.hidden { display: none; }
</style>
and I have multiple of these shown in my app:
<script>
import Popup from './Popup.svelte';
</script>
<div> <Popup /> </div>
<div> <Popup /> </div>
<div> <Popup /> </div>
I would like to hide other popups at the component level when clicked, meaning only one popup can be visible at any time. I thought svelte module contexts would suit, but I am not able to add a reference to self using which I can call say setHidden function for others.
<script>
import {onMount} from 'svelte';
let hidden = true;
export const setHidden = value => hidden = value;
const toggleHidden = () => {
if (hidden === true) { // transition to false
hideOthers();
}
hidden = !hidden
}
onMount(() => elements.add(self);
</script>
<script context="module">
const elements = new Set();
const hideOthers = () => elements.forEach(e => e.setHidden(true))
</script>
Playground here
The context approach is a good one, but instead of adding self to the elements, you can add the component's setHidden function:
<script context="module">
const elements = new Set();
const hideOthers = () => elements.forEach(hide => hide())
</script>
<script>
onMount(() => elements.add(setHidden))
</script>
For simply hiding things, #Stephane's answer works just fine. But if there are multiple functions, you could add them inside an object and add that to the module context.
You can also check if you are with the current instance or not while looping through all the elements by using a Map instead of a set. I'm using object keys to get unique references to each instance.
<script context="module">
const elements = new Map();
const hideOthers = () => Array.from(elements.entries()).forEach(([k, funcs]) => {
funcs.setHidden(true)
// if (k === key)
// true for current instance
// do something in the current instance
});
</script>
<script>
let key = {};
let hidden = true; ​
​ const funcs = {
setHidden: value => hidden = value,
setSomething: value => something = value,
setElse: value => else = value
}
​onMount(() => elements.set(key, funcs))
</script>

How to get the url to insert as a data with Redux Form?

I was able to get the url in the attachment field but for the redux form it was empty, how is possible to pass the value of the url to the redux form? Below is the code and the screenshot:
<div className="FileUpload">
<Dropzone
onDrop={this.onImageDrop.bind(this)}
multiple={false}
accept="image/*">
<div>Drop an image or click to select a file to upload.</div>
</Dropzone>
</div>
<div className="form-group">
<label htmlFor="attachment">Attachment:</label><br />
<input className="form-control" focus placeholder="attachment" type="text" name="attachment" ref="attachment" value={this.state.uploadedFileCloudinaryUrl} />
{this.state.uploadedFileCloudinaryUrl === '' ? null :
<div>
<p>{this.state.uploadedFile.name}</p>
<img src={this.state.uploadedFileCloudinaryUrl} alt="" />
</div>}
</div>
<div className="ui small image">
<img src={this.props.workrequest.attachment} alt="" />
</div>
the url in the attachemnt field
The first one is using the React Dropzone to get the url but for the Redux Form it was empty. May I know how to do that to get the url inserts at Redux Form here? Thank you
import React from 'React';
import { connect } from 'react-redux';
import { change, reduxForm } from 'redux-form';
import Dropzone from 'react-dropzone';
class UploadForm extends React.Component {
onDrop = (accepted) => {
if (!accepted.length) return;
// start uploading
this.setState({ isUploading: true });
const formData = new FormData();
formData.append('file', accepted[0]);
axios.post('upload', formData).then(
(res) => {
this.props.dispatch(change('uploadForm', 'url', res.url));
this.setState({ isUploading: false });
},
() => {
this.setState({ isUploading: false });
}
);
};
render() {
return (
<form>
<Dropzone onDrop={this.onDrop} />
</form>
);
}
}
export default connect()(
reduxForm({
form: 'uploadForm',
initialValues: {
url: ''
}
})(UploadForm)
);
Please use this.
Actually, you must use Field component from Redux-form.
Other wise, you can change form values using dispatch and change action creator.
import { Field, change } from 'redux-form';
this.props.dispatch(change('uploadForm', 'url', this.state.url));

ReactJS not binding via map to state values

I'm working with an MVC5 project and running into an issue with React not binding an array. I had this working in an MVC Core project, but had to "regress" back to the old structure. Biggest change seemed to be in the controller, changing from JsonResult (Core MVC) to Json (MVC5) for the return type on the ajax call.
Here's the output from Chrome Developer Tools:
(removed due to lack of reputation points)
And, my code for my .jsx file:
var LineItem = React.createClass({
render: function () {
return (
<div className="gridItem">
<div className="lessLineHeight smallFont">
<div className='section group'>
<div className="col span_1_of_2" id={this.props.ordHeaderId}>
<text>{this.props.code}</text>
</div>
<div className='col span_1_of_2 text-right'>
<i className={this.props.apptIconString} aria-hidden='true'></i>
<i className={this.props.highValueIconString}></i>
<i className={this.props.hazmatIconString}></i>
</div>
</div>
<div className='section group'>
<div className='col span_6_of_10'>
<text title='Trading Partner - Client'>{this.props.tradingPartnerName}</text>
</div>
<div className='col span_4_of_10 text-right'>
<text className='overflowElip' title='Account Manager'>{this.props.accountManager}</text>
</div>
</div>
<div className='section group'>
<div className='col span_1_of_2'>
<text title={"Origin: " + this.props.originAddress + "; " + this.props.origContact}>{this.props.originAddress}</text>
</div>
<div className='col span_1_of_2 text-right'>
<text title={"Destination:" + this.props.destinationAddress + "; " + this.props.destContact}>{this.props.destinationCity}</text>
</div>
</div>
<div className='section group'>
<div className='col span_1_of_3'>${this.props.freightValue}</div>
<div className='col span_1_of_3 text-center'>
<a title='Promote Order to Load'>To Load</a>
</div>
<div className='col span_1_of_3 text-right' id={'datePlanned' + this.props.ordHeaderId}>
<text title='Pickup Date'>{this.props.dateCreated}</text>
</div>
</div>
</div>
</div>
);
}
});
var ItemList = React.createClass({
getInitialState: function () {
return { items: [] };
},
loadData: function () {
$.ajax({
url: this.props.url,
dataType: 'json',
success: function (data) {
console.log(data);
this.setState({ items: data });
console.log(this.state.items);
$("#column1").find(".gridItem:odd").css({ "background-color": "#ddd" }).end().find(".gridItem:even").css({ "background-color": "#fff" });
}.bind(this),
error: function (xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
componentDidMount: function () {
this.loadData();
/*window.setInterval(this.loadData, this.props.pollInterval);*/
},
render: function () {
if (this.state.items) {
console.log("State has items.");
var itemNodes = this.state.items.map(function (foo) {
return (
<LineItem key={foo.ordHeaderId}
accountManager={foo.accountManager}
apptIconString={foo.apptIconString}
commodityDescription={foo.commodityDescription}
commodityId={foo.commodityId}
dateCreated={foo.dateCreated}
deliveryAppt={foo.deliveryAppt}
destContact={foo.destContact}
destinationAddress={foo.destinationAddress}
destinationAddressName={foo.destinationAddressName}
destinationCity={foo.destinationCity}
earlyDeliveryTime={foo.earlyDeliveryTime}
earlyPickupTime={foo.earlyPickupTime}
equipmentName={foo.equipmentName}
freightValue={foo.freightValue}
handlingUnits={foo.handlingUnits}
hazmatIconString={foo.hazmatIconString}
highValueIconString={foo.highValueIconString}
isHazmat={foo.isHazmat}
isHighValue={foo.isHighValue}
lateDeliveryTime={foo.lateDeliveryTime}
latePickupTime={foo.latePickupTime}
loadId={foo.loadId}
loadNum={foo.loadNum}
loadTmsStatus={foo.loadTmsStatus}
ordHeaderId={foo.ordHeaderId}
ordNum={foo.ordNum}
orderType={foo.orderType}
origContact={foo.originContact}
originAddress={foo.originAddress}
originAddressName={foo.originAddressName}
originationCity={foo.originationCity}
pickupAppt={foo.pickupAppt}
pieces={foo.pieces}
plannedEnd={foo.plannedEnd}
plannedStart={foo.plannedStart}
requiredTemp={foo.requiredTemp}
specialInstructions={foo.specialInstructions}
targetCost={foo.targetCost}
teamId={foo.teamId}
tempControlled={foo.tempControlled}
tradingPartnerNameCNum={foo.tradingPartnerNameCNum}
tradingPartnerName={foo.tradingPartnerNameClient}
transportMode={foo.transportMode}
user3gIdBookedBy={foo.user3gIdBookedBy}
user3gIdCreatedBy={foo.user3gIdCreatedBy}
weight={foo.weight} />
);
});
return (
<div className="itemList">
{itemNodes}
</div>
);
} else {
return null;
}
}
});
ReactDOM.render(
<ItemList url="/DispatchBoard/getColumn1Data" pollInterval={2000} />,
document.getElementById('column1')
);
As you can see from the image, the render: in the loadData function sees the items coming back from the ajax call, and then sets them to state, but when it comes time to map them, it does nothing.
Any ideas on what I'm not seeing?
EDIT
Here's a screen show showing the 'undefined' value(s) in one of the LineItems after failing to map properly. undefined values
EDIT #2
Here's a screenshot showing that the objects are hydrated and not being parsed. object present, not parsed
After seeing the screenshot you posted in EDIT #2
The issue is you're using different property name when accessing the data from foo while setting the properties on your component
So changing it from
<LineItem key={foo.ordHeaderId}
accountManager={foo.accountManager}
apptIconString={foo.apptIconString}
to
<LineItem key={foo.ordHeaderId}
accountManager={foo.AccountManager}
...
should do the trick
That is use the exact property name from your foo object instead of using camel cased or some other version of it.
The if condition in <ItemList> render is wrong. It should be like
if(this.state.items.length > 0)
Everything else looks fine. But, you forgot to add the key to the <LineItem> component
<LineItem key={foo.ordHeaderId}
accountManager={foo.accountManager}
... />
Here, you are passing key as a prop to the <LineItem> component but you forgot to set that key from the prop to the parent element.
var LineItem = React.createClass({
render: function () {
return (
<div className="gridItem" key={this.props.key}>
<div className="lessLineHeight smallFont">
....
)
}
})
This should remove the error/warning
From what I have experienced you can't pass key as a prop element. Remove this from you LineItem and see if it works. Let the warning persist. You can figure out a way to remove the warning later if this works.
<LineItem
accountManager={foo.accountManager}
apptIconString={foo.apptIconString}
commodityDescription={foo.commodityDescription}
commodityId={foo.commodityId}
dateCreated={foo.dateCreated}
deliveryAppt={foo.deliveryAppt}
destContact={foo.destContact}
destinationAddress={foo.destinationAddress}
destinationAddressName={foo.destinationAddressName}
destinationCity={foo.destinationCity}
earlyDeliveryTime={foo.earlyDeliveryTime}
earlyPickupTime={foo.earlyPickupTime}
equipmentName={foo.equipmentName}
freightValue={foo.freightValue}
handlingUnits={foo.handlingUnits}
hazmatIconString={foo.hazmatIconString}
highValueIconString={foo.highValueIconString}
isHazmat={foo.isHazmat}
isHighValue={foo.isHighValue}
lateDeliveryTime={foo.lateDeliveryTime}
latePickupTime={foo.latePickupTime}
loadId={foo.loadId}
loadNum={foo.loadNum}
loadTmsStatus={foo.loadTmsStatus}
ordHeaderId={foo.ordHeaderId}
ordNum={foo.ordNum}
orderType={foo.orderType}
origContact={foo.originContact}
originAddress={foo.originAddress}
originAddressName={foo.originAddressName}
originationCity={foo.originationCity}
pickupAppt={foo.pickupAppt}
pieces={foo.pieces}
plannedEnd={foo.plannedEnd}
plannedStart={foo.plannedStart}
requiredTemp={foo.requiredTemp}
specialInstructions={foo.specialInstructions}
targetCost={foo.targetCost}
teamId={foo.teamId}
tempControlled={foo.tempControlled}
tradingPartnerNameCNum={foo.tradingPartnerNameCNum}
tradingPartnerName={foo.tradingPartnerNameClient}
transportMode={foo.transportMode}
user3gIdBookedBy={foo.user3gIdBookedBy}
user3gIdCreatedBy={foo.user3gIdCreatedBy}
weight={foo.weight} />
Random User found the answer and it's contained in his comment.
The "key" to the problem was not capitalizing the properties that were to be mapped. Not sure why it worked the way it was in Core MVC, but, obviously, it doesn't work the same in MVC 4.

Expressions are not evaluated in $watch of custom directive of angularjs

I have a below custom directive in angularjs which uses model thats gets updated from server,
I have added a watch listener to watch the changes of that model,
var linkFn;
linkFn = function(scope, element, attrs) {
scope.$watch('$parent.photogallery', function(newValue, oldValue) {
if(angular.isUndefined(newValue)) {
return;
}
var $container = element;
alert($container.element);
$container.imagesLoaded(function() {
$container.masonry({
itemSelector : '.box'
});
});
});
};
return {
templateUrl:'templates/Photos_Masonry.htm',
replace: false,
transclude:true,
scope: {
photogallery: '=photoGallery',
},
restrict: 'A',
link: linkFn
However, when i debug in my watch directive, i still see that expressions in templates are still unresolved.i.e. photo.label, ng-src all are still unresolved. AFIK, $digest would be called only after $eval. Is this intended behavior?
My jQuery calls are not working due to this? Is there any other event where i get the result element with evaluated expressions?
Here is my template, which has ng-repeat in it,
<div id="container" class="clearfix">
<div class="box col2" ng-repeat="photo in photogallery">
<a ng-href="#/viewphotos?id={{photo.uniqueid}}&&galleryid={{galleryid}}"
title="{{photo.label}}"><img
ng-src="{{photo.thumbnail_url}}" alt="Stanley" class="fade_spot" /></a>
<h3>
<span style="border-bottom: 1px solid black;font-weight:normal;font-size:14px;">{{galleryname}}</span>
</h3>
<h3>
<span style="color:#20ACB8;font-weight:normal;font-size:17px;">{{photo.seasonname}}</span>
</h3>
</div>
</div>
photogallery is initialized in parent controller,
function MyCtrlCampaign($scope, srvgallery, mygallery) {
$scope.updatedata = function() {
$scope.photogallery = srvgallery.getphotos($routeParams);
};
$scope.getphotos = function() {
srvgallery.photos().success(function(data) {
$scope.updatedata();
}).error(function(data) {
});
};
Directive is used in below way,
<div masonry photo-gallery="photogallery" >
</div>
Kindly let me know your views on this.
Looks like this has been resolved in your Github issue (posted for the convenience of others).

Resources