Passing props through in Render function - properties

Any idea why I can log out the 'conditions' variable but cannot pass it through as a property in the render method? It is always undefined - "cannot read property 'conditions' of undefined".
Code below.
var React = require('react');
var ReactDOM = require('react-dom');
import Square from '../components/square.jsx';
import Circle from '../components/circle.jsx';
import Weather from '../components/weather.jsx';
var conditions = [];
$.ajax({
url : "http://api.wunderground.com/api/xxxxxxxxxxxxxxxx/forecast10day/q/England/London.json",
dataType : "jsonp",
success : function(parsed_json) {
conditions = parsed_json.forecast.simpleforecast.forecastday;
console.log(conditions[1].conditions);
}
});
ReactDOM.render(
<div>
<h1>Hello React!!!</h1>
<SquareComp />
<Square />
<Circle width={100} height={100}/>
<Weather cityName={'London'} weatherConditions={conditions[1].conditions} />
</div>,
document.getElementById('rootNode')
);
If I pre-fill the var all is well, so I'm guessing it's not receiving data before render is happening?
Thanks.

$.ajax is asynchronous. Immediately after calling it, it proceeds to calling ReactDOM.render, and, at this point conditions is still an empty array. You should render your components only after the Ajax call is finished loading the results sent from the server.
Try the following:
function renderConditions (conditions, domElement){
ReactDOM.render(
<div>
<h1>Hello React!!!</h1>
<SquareComp />
<Square />
<Circle width={100} height={100}/>
<Weather cityName={'London'} weatherConditions={conditions[1].conditions} />
</div>,
domElement
);
}
Define a function that render the data given as parameters.
$.ajax({
url : "http://api.wunderground.com/api/xxxxxxxxxxxxxxxx/forecast10day/q/England/London.json",
dataType : "jsonp",
success : function(parsed_json) {
conditions = parsed_json.forecast.simpleforecast.forecastday;
renderConditions(conditions, document.getElementById('rootNode'));
}
});
Call the function renderConditions passing the appropriate parameters in the ajax success callback function.

$.ajax is always asynchronous, so there's just no chance React will render with a meaningful 'conditions' array. Keeping your current architecture (which is fine), just move the render after the 'conditions' assignement in the ajax callback, or if you need to render the initial, empty state too move the render code inside a function and call it in both places.

Related

StencilJs: nested stencil component doesn't work with events, such as 'click'

I have a stenciljs component that has a nested stenciljs component:
<c-button-group>
<c-button></c-button>
</c-button-group>
In c-button-group template, I'm not using a slot and instead I'm using #Element() private element: HTMLElement to get all the nested elements after I render them in a loop.
#Component({
tag: 'c-button-group'
})
export class CButtonGroup {
#Element() private element: HTMLElement
render() {
return (
Array.from(this.element.children)
.map((child) => {
const el = child as ButtonInterface
return <c-button variant="grouped">{el.textContent}</c-button>
})
)
}
}
The reason why i use loop because i have to add attribute variant="grouped" for each nested element and i want to add it here, in the template. So this works but i noticed that if i assign a click event handler it doesn't work.
<body>
<c-button-group>
<c-button id="cBtn" #click="btnClickHandler">Years</c-button>
</c-button-group>
<script>
document.querySelector('#cBtn').addEventListener('click', () => {
console.log('Years')
})
</script>
</body>
Click event above doesn't work.
And seems this is obvious because in the render function i create a new 'c-button' based on a nested c-button.
My question is how do I pass all events that were assigned from the nested component to the new component that was created in the render function?
PS:
I noticed that if i use on-click attribute without a value to new c-button element, click event works:
Array.from(this.element.children)
.map((child) => {
const el = child as ButtonInterface
return <c-button
variant="grouped"
on-click
>{el.textContent}</c-button>
})
I just added on-click and it started working but in console i started getting errors:
TypeError: Failed to execute 'addEventListener' on 'EventTarget': parameter 2 is not of type 'Object'.
So at first this is not an option because i am getting that error and at second what if i need not only 'click' event, maybe there will be 5 or 10 events, so in that case i will have to add them all manually, not very comfortable to say the least.
Thank you so much in advance!
You can try using outerHTML and innerHTML
render() {
return (
Array.from(this.element.children)
.map((child) => {
child.setAttribute('variant', 'grouped')
return <div innerHTML={child.outerHTML}></div>
})
)
}
However this will only be able to pass events which are attached by attribute instead of addEventListener, as outerHTML is a string
<c-button-group>
<c-button id="cBtn" onclick="btnClickHandler()">Years</c-button>
</c-button-group>
Here btnClickHandler will work for rendered children elements
So the only way i found myself is to use <slot /> and #Element() private element: HTMLElement together.
#Element() private element: HTMLElement
componentWillLoad() {
Array.from(this.element.children).forEach(el => {
el.setAttribute('variant', 'grouped')
})
}
render() {
return <slot />
}
Using <slot /> allows as to use all events and using this.element.children gives the ability to edit DOM elements before their rendering, we can remove, add any attribute, class, and so on...

vuejs payload only emitting first element in an array

im using the dropzone plugin with vuejs and the response from dropzone is an array. The payload has an array of arguments, a response and the file object. Every time i call #vdropzone-success="$emit('processFunction',$event) it will correctly send the request to the right function but only the first element of the payload array is returned. Why? I have attached a screenshot of the vue debugger to help illustrate the problem. How can i access the payload in processFunction to get to array element 0 and 1?
to add more context. This is the function in the library that is emitting to my child component
this.dropzone.on("success", function(file, response) {
vm.$emit("vdropzone-success", file, response);
});
after this in my vue code i am emitting this result to my index component:
<vue-dropzone
id="dropzone"
ref="myVueDropzone"
#vdropzone-drop="$emit('loading')"
#vdropzone-success="$emit('loaded',...$event)"
:use-custom-slot="true"
:options="dropzoneOptions"
>
I am binding to the element on my index.vue here:
<Upload #loading="loading" #loaded="loaded" />
to then call the function loaded
loaded(e) {
console.log(e);
this.notLoading = true;
this.isLoading = false;
},
https://github.com/vuejs/vue/issues/5527 this looks like A similar problem. Whenever i call $event it only returns 0 which is the file. I want it to return 1 which is the response from the serve (Object).
ok.. I feel like there should be a better way to do this, but... it looks like i can't pass the array variable through the vue tags from the library->child->parent directly. Instead I need to pull the array out in js in the child, and re-emit it in the js to the parent. the below solution worked.
library:
this.dropzone.on("success", function(file, response) {
vm.$emit("vdropzone-success", file, response);
});
child (helper.vue):
<!-- file upload -->
<vue-dropzone
id="dropzone"
ref="myVueDropzone"
#vdropzone-drop="$emit('loading')"
#vdropzone-success="processdata"
:use-custom-slot="true"
:options="dropzoneOptions"
>
and the js part of that
methods: {
processdata(...ev){
this.$emit("loaded",ev);
},
and finally the parent (index.vue)
<Upload #loading="loading" #loaded="loaded" />
and the js for that
loaded(...e) {
this.storedValue = e;
not the most elegant solution, but it is working!
You can try this:
Child
#vdropzone-success="$emit('loaded')"
Parent
<Upload #loading="loading" #loaded="loaded" />
loaded(payload) {
this.storedValue = payload;
}

Vuex dispatch running infinity

Please, can anybody tell me why this function is running in an infinite loop?
<a:href="authenticationChoice(currentView['name'])" > **(Here. Keeps executing)**
<img
class="w-12 inline-block align-middle"
:src="showAuthenticationIcon(currentView['gitType'])"
/>
</a>
This is the direct function being call to dispatch the vuex action.
authenticationChoice(recieved) {
this.$store.state.gitSourceAuthenticationChoice = recieved;
this.$store.dispatch("gitSourceAuthenticationURL").then((response) => {
this.navigationURL = response["oauth2_redirect"];
console.log(this.navigationURL)
});
return this.navigationURL;
},
This is the action function in the vuex file
async gitSourceAuthenticationURL({ state }) {
return await axios
.get(`${Config.ApiUrl}/auth/login/${state.gitSourceAuthenticationChoice}`)
.then(response => {
return response.data
}).catch((error) => {
//console.log(error.data)
});
},
It is a property binding, so property bound to a value in VueJS is reactive, so every change detection, it ran and execute that.
That is why your method is getting called everything, when change detection is happening in VueJS.
<a:href="authenticationChoice(currentView['name'])" >
Kindly use click event or button to avoid this.
<button #click="authenticationChoice(currentView['name'])" >Text</button>
Or you can bind the click event on <a> tag, but you need to use <a> as button, that's not very recommendable.

Using FlatList#onViewableItemsChanged to call a Component function

I'm currently attempting to implement a form of LazyLoading using the FlatList component, which introduces a neat little feature called onViewableItemsChanged which gives you a list of all of the components that are no longer on the screen as well as items that are now on the screen.
This is a custom LazyLoad implementation and as such is more complicated than most LazyLoad open-sourced libraries that are available, which is why I'm working on my own implementation. I'm already looked into react-native-lazy-load and others.
Basically, I need to be able to call a function that's part of the component being rendered in the FlatList, I've tried creating a reference to the item rendered in the FlatList and calling it as such, but it doesn't seem to work.
For example:
<FlatList data={...}
renderItem={(item) => <Example ref={(ref) => this[`swiperRef_${item.key}`] = ref}}
onViewableItemsChanged={this.onViewableItemsChanged}
/>
onViewableItemsChanged = ({viewableItems}) => {
viewableItems.forEach((item) => {
const { isViewable, key } = item;
if(isViewable && !this.cachedKeys.includes(key)) {
const ref = this[`swiperRef_${key}`];
if(!ref) return console.error('Ref not found');
ref.startLoading();
this.cachedKeys.push(key);
}
});
}
Now in the <Example /> component I would have a function called startLoading which should be called when a new visible item is brought onto the screen, however the ref never exists.
I was actually doing everything correctly, but I accidently forgot to deconstruct the parameter returned from the renderItem function, so (item) should have been ({ item })
That's all there was to it.

Spying on React components using Enzyme (and sinon?) to check arguments

I'm wanting to assert that a component gets called from within another component with the correct arguments.
So within the component that I am testing there is a Title component that gets called with properties title & url. I'm trying to assert that it gets called with the correct arguments.
I'm pretty sure I want to use a sinon spy and do something like this
const titleSpy = sinon.spy(Title, render)
expect(titleSpy).to.be.calledWith( '< some title >' )
but with regards to React and Enzyme, I'm not really sure what I should be spying on. (Because apparently it's not render!)
In my spec file I am importing Title and console.loging it's value to find a function to spy on and I get:
function _class() {
_classCallCheck(this, _class);
return _possibleConstructorReturn(this, Object.getPrototypeOf(_class).apply(this, arguments));
}
Any ideas on how I can do this? Is it a case of going through and finding the element and checking it's attributes? If so that seems a bit...messy and seems like it goes against the principle of the Shallow render ("Shallow rendering is useful to constrain yourself to testing a component as a unit").
If you're just checking the value of properties passed to the component, you don't need sinon. For example, given the following component:
export default class MyComponent extends React.Component {
render() {
return (
<MyComponent myProp={this.props.myProp} />)
}
}
Your test might look like this:
describe('MyComponent ->', () => {
const props = {
myProp: 'myProp'
}
it('should set myProp from props', () => {
const component = shallow(<MyComponent {...props} />)
expect(component.props().myProp).to.equal(props.myProp)
})
})
You can achieve it with the help of .contains() method, without messing up with spies.
If you have a component:
<Foo>
<Title title="A title" url="http://google.com" />
</Foo>
You can make such an assertion:
const wrapper = shallow(<Foo />);
expect(wrapper.contains(<Title title="A title" url="http://google.com" />)).to.equal(true);
Such will fail:
const wrapper = shallow(<Foo />);
expect(wrapper.contains(<Title title="A wrong title" url="http://youtube.com" />)).to.equal(true);
This is an older question, but my approach is a little different than the existing answers:
So within the component that I am testing there is a Title component that gets called with properties title & url. I'm trying to assert that it gets called with the correct arguments.
ie. You're wanting to check that the component being tested renders another component, and passes the correct prop(s) to it.
So if the component being tested looks something like:
const MyComp = ({ title, url }) => (
<div>
<Title title={title} url={url} />
</div>
)
Then the test could look something like:
import Title from 'path/to/Title';, u
it('renders Title correctly', () => {
const testTitle = 'Test title';
const testUrl = 'http://example.com';
const sut = enzyme.shallow(<MyComp title={testTitle} url={testUrl} />);
// Check tested component rendered
expect(sut.exists).toBeTruthy();
// Find the Title component in the subtree
const titleComp = sut.find(Title); // or use a css-style selector string instead of the Title import
// Check that we found exactly one Title component
expect(titleComp).toHaveLength(1);
// Check that the props that were passed were our test values
expect(titleComp.prop('title')).toBe(testTitle);
expect(titleComp.prop('url')).toBe(testUrl);
});
I generally find Enzyme's functions to be very useful for all kinds of checks about components, without needing other libraries. Creating Sinon mocks can be useful to pass as props to components, to (for example) test that a callback prop is called when a button is clicked.