Angular with Redux triggering change detection - javascript

I'm new to Redux so I'm sure this is something I'm missing, but I have a component that is subscribing to a Redux store, which is an observable of an array of objects. The problem is that even though a single property is changing on one of the objects, I'm returning a copy of the object, so Angular thinks everything changed and triggers a redraw. This would be fine except that my component has CSS transitions that don't get fired correctly because they should only react to the property change and now be removed and added again. Here's a simple example (pseudo code)
const state = {
items: [
{
open: false,
},
{
open: true,
}
]
}
Then a component is something like:
<div *ngFor='for let item of items'>
<div #openClose='item.open'>Some stuff</div>
</div>
So when my store sets the open property from true to false, instead of closing my component through a triggered animation, it simply redraws each div.
This must be a common problem, so is there an accepted way to set this up so the component is only created once?

Related

VueJS XHR inside reusable component

Asking for best practice or suggestion how to do it better:
I have 1 global reusable component <MainMenu> inside that component I'm doing XHR request to get menu items.
So if I place <MainMenu> in header and footer XHR will be sent 2 times.
I can also go with props to get menu items in main parent component and pass menu items to <MainMenu> like:
<MainMenu :items="items">
Bet that means I cant quickly reuse it in another project, I will need pass props to it.
And another way is to use state, thats basically same as props.
What will be best option for such use case?
If you don't want to instantiate a new component, but have your main menu in many places you can use ref="menu" which will allow you to access it's innerHTML or outerHTML. I've created an example here to which you can refer.
<div id="app">
<main-menu ref="menu" />
<div v-html="menuHTML"></div>
</div>
refs aren't reactive so if you used v-html="$refs.menu.$el.outerHTML" it wouldn't work since refs are still undefined when the component is created. In order to display it properly you would have to create a property that keeps main menu's HTML and set it in mounted hook:
data() {
return {
menuHTML: ''
}
},
mounted() {
this.menuHTML = this.$refs.menu.$el.outerHTML;
}
This lets you display the menu multiple times without creating new components but it still doesn't change the fact that it's not reactive.
In the example, menu elements are kept in items array. If the objects in items array were to be changed, those changes would be reflected in the main component, but it's clones would remain unchanged. In the example I add class "red" to items after two seconds pass.
To make it work so that changes are reflected in cloned elements you need to add a watcher that observes the changes in items array and updates menuHTML when any change is noticed:
mounted() {
this.menuHTML = this.$refs.menu.$el.outerHTML;
this.$watch(
() => {
return this.$refs.menu.items
},
(val) => {
this.menuHTML = this.$refs.menu.$el.outerHTML;
}, {
deep: true
}
)
}
You can also watch for changes in any data property with:
this.$refs.menu._data
With this you don't need to pass props to your main menu component nor implement any changes to it, but this solution still requires some additional logic to be implemented in it's parent component.

Child prop not updating, even though the parent does

I have a pretty weird situation where a change in a child component's prop is not triggering a re-render.
Here is my setup. In the parent I have:
<child :problemProp="proplemPropValue""></child>
In the child I have defined the prop:
{
name: "Child",
props: {
problemProp: {
type: [File], //yes it is a file
required: true
}
}
and then I try to render it (still in the child component)
<template>
<div id="dropzone-preview" class="file-row">
{{problemProp}} <--This should just show the prop as a JSON string-->
</div>
</template>
This is rendered correctly initially. But problemProp has a property upload.progress, and when I change it in the parent (I can confirm that it does change on the parent) it does NOT change in the child.
If I now add a second prop, dummyProp to the child:
{
name: "Child",
props: {
problemProp: {
type: [File], //yes it is a file
required: true
},
dummyProp: {
type: Number
}
}
Now, when dummyProp changes, propblemProp also changes. What is going on here? Why does the change in dummyProp force a re-render but a change in propblemProp does not?
The root of the problem appears to be that when you add a File object to the array, Vue fails to convert it into an observed value. This is because Vue ignores browser API objects:
The object must be plain: native objects such as browser API objects
and prototype properties are ignored.
That being the case, any changes to that object will not be reflected in the Vue automatically (as you would typically expect) because Vue doesn't know they changed. That is also why it appears to work when you update dummyProp, because changes to that property are observed and trigger a re-render.
So, what to do. I've been able to get your bin to work by making a small change to the addedFile method.
addedfile(file){
console.log(file);
self.problemPropValues.push(Object.assign({}, file));
},
First, you don't need (or want) to use $data, just reference the property directly. Second, by making a copy using Object.assign, Vue properly observes the object and updates are now reflected in the child. It may be the case that you will need to use a deep copy method instead of Object.assign at some point, depending on your use case, but for now this appears to be working.
Here is the code I ended up with getting the bin to work (converted to codepen because I find working with codepen easier).

React Component Flickers on Rerender

I'm pretty new to react and have been working on this new page for work. Basically, there's a panel with filter options which lets you filter objects by color. Everything works but I'm noticing the entire filter panel flickers when you select a filter.
Here are the areas functions in the filter component I think bear directly on the filter and then the parent component they're inserted into. When I had originally written this, the filter component was also calling re render but I've since refactored so that the parent handles all of that - it was causing other problems with the page's pagination functionality. naturally. and I think that's kind of my problem. the whole thing is getting passed in then rerendered. but I have no idea how to fix it. or what's best.
checks whether previous props are different from props coming in from parent and if so, creates copy of new state, calls a render method with those options. at this point, still in child component.
componentDidUpdate(prevProps, prevState) {
if (prevState.selectedColorKeys.length !== this.state.selectedColorKeys.length ||
prevState.hasMatchingInvitation !== this.state.hasMatchingInvitation) {
const options = Object.assign({}, {
hasMatchingInvitation: this.state.hasMatchingInvitation,
selectedColorKeys: this.state.selectedColorKeys
});
this.props.onFilterChange(options);
}
}
handles active classes and blocks user from selecting same filter twice
isColorSelected(color) {
return this.state.selectedColorKeys.indexOf(color) > -1;
}
calls to remove filter with color name so users can deselect with same filter button or if its a new filter, sets state by adding the color to the array of selected color keys
filterByColor(color) {
if (this.isColorSelected(color.color_name)) {
this.removeFilter(color.color_name);
return;
}
this.setState({
selectedColorKeys:
this.state.selectedColorKeys.concat([color.color_name])
});
}
creating the color panel itself
// color panel
colorOptions.map(color => (
colorPicker.push(
(<li className={['color-filter', this.isColorSelected(color.color_name) ? 'active' : null].join(' ')} key={color.key} ><span className={color.key} onClick={() => { this.filterByColor(color); }} /></li>)
)
));
parent component
callback referencing the filter child with the onFilterChange function
<ThemesFilter onFilterChange={this.onFilterChange} />
onFilterChange(filters) {
const { filterThemes, loadThemes, unloadThemes } = this.props;
unloadThemes();
this.setState({
filterOptions: filters,
offset: 0
}, () => {
filterThemes(this.state.filterOptions.selectedColorKeys, this.state.filterOptions.hasMatchingInvitation);
loadThemes(this.state.offset);
});
}
when I place break points, the general flow seems to be :
filterByColor is triggered in event handler passing in that color
active classes are added to the color, a filter tag for that color is generated and appended
componentDidMount takes in the previous props/state and compares it to the new props/state. if they don't match, i.e state has changed, it creates a copy of that object, assigning the new states of what's changed. passes that as props to onFilterChange, a function in the parent, with those options.
onFilterChange takes those options, calls the action method for getting new themes (the filtering actually happens in the backend, all I really ever need to do is update the page) and passes those forward. its also setting offset to 0, that's for the page's pagination functionality.
It looks like the problem might be around the componentDidUpdate function which, after setting breakpoints and watching it go through the steps from filterByColor to componentDidMount, that componentDidMount loops through twice, checking again if the colorIsSelected, and throughout all that the color panel pauses to re-render and you get a flicker.
Is it possible creating the copy is causing it? since it's being treated, essentially, as a new object that isColorSelected feels necessary to double check? any help you guys have would be much appreciated, this shit is so far over my head I can't see the sun.
Can you change
componentDidUpdate(prevProps, prevState)
with
componentWillUpdate(nextProps, nextState)

ReactJs: change state in response to state change

I've got a React component with an input, and an optional "advanced input":
[ basic ]
Hide Advanced...
[ advanced ]
The advanced on the bottom goes away if you click "Hide Advanced", which changes to "Show Advanced". That's straightforward and working fine, there's a showAdvanced key in the state that controls the text and whether the advanced input is rendered.
External JS code, however, might change the value of advanced, in which case I want to show the [advanced] input if it's currently hidden and the value is different than the default. The user should be able to click "Hide Advanced" to close it again, however.
So, someone external calls cmp.setState({advanced: "20"}), and I want to then show advanced; The most straightforward thing to do would just be to update showAdvanced in my state. However, there doesn't seem to be a way to update some state in response to other state changes in React. I can think of a number of workarounds with slightly different behavior, but I really want to have this specific behavior.
Should I move showAdvanced to props, would that make sense? Can you change props in response to state changes? Thanks.
Okay first up, you mention that a third party outside of your component might call cmp.setState()? This is a huge react no-no. A component should only ever call it's own setState function - nothing outside should access it.
Also another thing to remember is that if you're trying change state again in response to a state change - that means you're doing something wrong.
When you build things in this way it makes your problem much harder than it needs to be. The reason being that if you accept that nothing external can set the state of your component - then basically the only option you have is to allow external things to update your component's props - and then react to them inside your component. This simplifies the problem.
So for example you should look at having whatever external things that used to be calling cmp.setState() instead call React.renderComponent on your component again, giving a new prop or prop value, such as showAdvanced set to true. Your component can then react to this in componentWillReceiveProps and set it's state accordingly. Here's an example bit of code:
var MyComponent = React.createClass({
getInitialState: function() {
return {
showAdvanced: this.props.showAdvanced || false
}
},
componentWillReceiveProps: function(nextProps) {
if (typeof nextProps.showAdvanced === 'boolean') {
this.setState({
showAdvanced: nextProps.showAdvanced
})
}
},
toggleAdvancedClickHandler: function(e) {
this.setState({
showAdvanced: !this.state.showAdvanced
})
},
render: function() {
return (
<div>
<div>Basic stuff</div>
<div>
<button onClick={this.toggleAdvancedClickHandler}>
{(this.state.showAdvanced ? 'Hide' : 'Show') + ' Advanced'}
</button>
</div>
<div style={{display: this.state.showAdvanced ? 'block' : 'none'}}>
Advanced Stuff
</div>
</div>
);
}
});
So the first time you call React.renderComponent(MyComponent({}), elem) the component will mount and the advanced div will be hidden. If you click on the button inside the component, it will toggle and show. If you need to force the component to show the advanced div from outside the component simply call render again like so: React.renderComponent(MyComponent({showAdvanced: true}), elem) and it will show it, regardless of internal state. Likewise if you wanted to hide it from outside, simply call it with showAdvanced: false.
Added bonus to the above code example is that calling setState inside of componentWillReceiveProps does not cause another render cycle, as it catches and changes the state BEFORE render is called. Have a look at the docs here for more info: http://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops
Don't forget that calling renderComponent again on an already mounted component doesn't mount it again, it just tells react to update the component's props and react will then make the changes, run the lifecycle and render functions of the component and do it's dom diffing magic.
Revised answer in comment below.
My initial wrong answer:
The lifecycle function componentWillUpdate will be ran when new state or props are received. You can find documentation on it here: http://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate
If, when the external setState is called, you then set showAdvanced to true in componentWillUpdate, you should get the desired result.
EDIT: Another option would be to have the external call to setState include showAdvanced: true in its new state.

Reset Ember Component on Load

I have an Ember.Component that adds items to an empty array and returns the array on submission. The problem is, if I navigate away from the Route that contains the Component (both after submitting and without submitting), and then go back to it later, the information that was last in the array is still there. I would like to to be reset every time I navigate to the route with the component.
If this were a route, I'd simply write a willTransition or deactivate method to reset my attributes. But since it's a component, it doesn't have those methods, and I can't (that I know of) access the attribute I wish to reset from the parent route. So, how can I reset this array to be empty (or reset the the entire component) every time I load this route? Thanks!
More likely than not, you're not setting the value you're using properly. Take these examples:
Ember.Component.extend({
items: []
});
Ember.Component.extend({
items: null,
init: function() {
this._super();
this.set('items', []);
}
});
In the first component, the same items array is shared by every instance of the component. So if you add an item, then create a new component, the new component still has the item (which I think is your problem).
In the second component, you can see that I set the items property in the init function. And when I set the property, I set it to a different array every time. Now, each component has their own items property.
It's hard to say without your code, but this seems like your issue.

Categories

Resources