getMeteorData race conditions with component lifecycle? - meteor

I'm getting some pretty undesirable behavior in my app, and I'm having a hard time replicating the issue and/or figuring out what I'm doing wrong or not understanding about React that's causing my components to act this way.
What I want to do is to get some data from Mongo on the App component, then have all of that data readily available for any child that I want.
<App> //get data here, pass to children through props
<ChildElement1 data={this.data.appData}/>
<ChildElement2 data={this.data.appData}/>
<ChildElement3 data={this.data.appData}/>
</App>
Here's how I've attempted to tackle this with React so far:
App = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
let _id = 'exampleId';
return {
appData: Collection.findOne({_id})
};
},
render() {
return (<ChildElement1 data={this.data.appData} />);
}
});
ChildElement1 = React.createClass({
getInitialState() {
return {
values: ['val1', 'val2', 'val3', 'val4'] //default values
};
},
componentWillMount() {
if(this.props.data.specificValues) {
this.setState({values: this.props.data.specificValues});
}
},
render() {
let values = this.state.values;
return (<span>{values[0]} {values[1]} {values[2]} {values[3]}</span>);
}
});
So here's where it gets weird. When I call componentWillMount(), sometimes this.props.data is defined and other times it's not, which leads me to believe there's some sort of race conditions going on where sometimes that data gets loaded correctly as a prop and other times it doesn't.
I then figured that, well okay, I can't depend on the data prop being there before the component is initially mounted, so I could instead use componentWillReceiveProps(nextProps) and check the updated props that way (and update the state, if necessary). HOWEVER! After using componentWillReceiveProps, now this.props.data is seemingly ALWAYS correctly attached to the props of ChildElement1 (which means componentWillReceiveProps doesn't run!).
My final solution was to use BOTH componentWillMount and componentWillReceiveProps to account for both situations and to do the exact same check in both locations. This fix works, but boy does it seem messy and probably indicates a lack of understanding of component lifecycles, how the meteor/react should properly interact, both, or something else completely.
I'd sure appreciate a bit of help here.
edit: I've come up with a small improvement - instead of using componentWillMount and componentWillReceiveProps to do the check to see if there are specific values defined in the Mongo Collection, I put that logic in render like so:
render() {
let data = this.props.data,
values = (data) ? data.specificValues : this.state.values;
return (<span>{values[0]} {values[1]} {values[2]} {values[3]}</span>);
}
There's definitely still some sort of underlying issue, however, as I still don't understand why this.props is so inconsistent when given data retrieved from getMeteorData. This version is a bit more succinct, however.

I found a better approach to this rather than passing the data returned from getMeteorData to each of the children as props. Using the methods described here: https://www.tildedave.com/2014/11/15/introduction-to-contexts-in-react-js.html, I explicitly listed the childContextTypes and getChildContext in <App /> and then contextTypes in <ChildElement1 />, which allows this.data.appData to be available by way of this.context in <ChildElement1 /> and presumably within any other children of <App />. Although I gotta admit, declaring every single proptype of the collection is a major PITA, seems like it'd be necessary to write a mixin (or rather, a bunch of mixins) to handle that stuff.

Related

Vue3 prop updates title attribute but not computed prop

I'm using an external library rendered using Vue3. It has the following component from a third part library [Edit: I realize the GitHub repo for that library is out of date, so updating with the actual code in my node_modules.]
<template>
<div class="socket" :class="className" :title="socket.name"></div>
</template>
<script>
import { defineComponent, computed } from "vue";
import { kebab } from "./utils";
export default defineComponent({
props: ["type", "socket"],
setup(props) {
const className = computed(() => {
return kebab([props.type, props.socket.name]);
});
return {
className
};
}
});
</script>
It renders based on a Socket object passed as a prop. When I updated the name property of the Socket, I see the title updated accordingly. However, the CSS/class does not update. I've tried $forceRefresh() on its parent, but this changes nothing.
Update: I was able to move the rendering code to my own repo, so I can now edit this component if needed.
Based on this updated code, it seems the issue is that the class is computed. Is there any way to force this to refresh?
The only time it does is when I reload the code (without refreshing the page) during vue-cli-service serve.
For reference, the | kebab filter is defined here:
Vue.filter('kebab', (str) => {
const replace = s => s.toLowerCase().replace(/ /g, '-');
return Array.isArray(str) ? str.map(replace) : replace(str);
});
Do filtered attributes update differently? I wouldn't think so.
I was also wondering if it could be a reactivity issue, and whether I needed to set the value using Vue.set, but as I understand it that's not necessary in Vue3, and it's also not consistent with the title properly updating.
Computed properties are reactive, however Vue does not expect you to mutate a prop object.
From the documentation:
Warning
Note that objects and arrays in JavaScript are passed by reference, so
if the prop is an array or object, mutating the object or array itself
inside the child component will affect the parent state and Vue is
unable to warn you against this. As a general rule, you should avoid
mutating any prop, including objects and arrays as doing so ignores
one-way data binding and may cause undesired results.
https://v3.vuejs.org/guide/component-props.html#one-way-data-flow
I know that this says, that you should not mutate it in the child, but the general rule is, that you should not mutate properties at all, but instead create new object with the modified data.
In your case the computed function will look for changes in the properties itself, but not the members of the properties, that is why it is not updating.

Redux container render a collection which parameters to pass?

We are using React+Redux and it's doing well. But there is one situation I never know which strategy to use.
When I need to loop over a collection, I could write:
Pass the element
Code:
render() {
collection.map(element => <ElementItem key={element.id} element={element} />)
}
Pass the spread element
Code:
render() {
collection.map(element => <ElementItem key={element.id} {...element} />)
}
Pass the ID
Code:
render() {
collection.map(element => <ElementItem key={element.id} id={element.id} />)
}
and in ElementItem.js:
connect((state, ownProps) => {
element: state.collection.find(_el => _el.id === ownProps.id)
})(ElementItem)
All in one file:
Code:
render() {
collection.map(element => <li key={element.id}><p>{element.name}</p></li>)
}
Solution #4 is not reusable so not much interesting.
I don't like solution #2 since attributes are drowned in others
I find #3 to be the cleanest since it is the one with lesser dependencies and forwarded props. The biggest trade off is that it feels lame to launch a .find for each ElementItem
So I guess it is the first one which wins. But I have the feeling this is not the redux-way of doing things, is it? If I pass the element parameter, why wouldn't I pass more? Then we are loosing all the benefits of isolating container from presentation components, don't we ?
Solutions #1 and #2 are perfectly fine, because in that case ElementItem is a presentational component and received its data from props.
Solution #3 makes no sense, because the component looping over the collection already got the collection part of the state (either because this component is connected to Redux, or because it got it from props).
In the redux documentation, there is an example where they render a collection of todos: They use 2 presentational components: Todo, a single todo item, and TodoList, a list showing todos. Then there is a container component, VisibleTodoList, which computes the list of visible todos from the state and display them using TodoList. You could use the same strategy when you want to loop over a collection.
Another point: if you don't want to use find to get the right item, you could normalize your state, so each 'collection table' stores the items in an object with the ids of the items as keys. This way, you could get the right item like this:
const element = state.collection[elementId];

Dispatch redux action after apollo-react query

I want to dispatch a redux action, right after a query finishes. – where would be the right place to do this?
here I am keeping a reference to the refetch function, so that I can easily update the view with the most recent data at a later point.
export default graphql(
allFilesQuery,
{
props: ({ ownProps, data }) => {
const { dispatch } = ownProps;
dispatch(
setRefetchAllFiles(data.refetch)
);
return {
data,
...ownProps,
};
}
}
)(FileListComponent);
while this works, I also get a warning, saying:
Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
The props function is supposed to be pure and return props to inject in component without performing any kind of side effects. Actually you might be able to dispatch in it, by wrapping your dispatch in a setTimeout, but this would be a very bad idea, because the props function is run everytime your component re-renders and will likely trigger many unwanted dispatches. It could even lead to infinite loops if your dispatch makes the component re-renders.
The correct place to do what you want is in your component. You can use componentWillReceiveProps (or other lifecycle), and compare previous props to next props trigger a dispatch when appropriate. You can use data.networkStatus or data.loading for that.

React-redux: should the render always happen in the same tick as dispatching an action?

In my react-redux application, I have a controlled text input. Every time the component changes value, it dispatches an action and in the end, the value comes back through the redux loop and is rendered.
In the example below this works well, but in practice, I've run into an issue where the render happens asynchronously from the action dispatch and the input loses cursor position. To demonstrate the issue, I've added another input with a delay explicitly put in. Adding a space in the middle of a word causes the cursor to skip in the async input.
I have two theories about this and would like to know which one is true:
This should work, but I have a bug somewhere in my production application that causes the delay
The fact that it works in the simple example is just luck and react-redux doesn't guarantee that render would happen synchronously
Which one is right?
Working example:
http://jsbin.com/doponibisi/edit?html,js,output
const INITIAL_STATE = {
value: ""
};
const reducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'SETVALUE':
return Object.assign({}, state, { value: action.payload.value });
default:
return state;
}
};
const View = ({
value,
onValueChange
}) => (
<div>
Sync: <input value={value} onChange={(e) => onValueChange(e.target.value)} /><br/>
Async: <input value={value} onChange={(e) => { const v = e.target.value; setTimeout(() => onValueChange(v), 0)}} />
</div>
);
const mapStateToProps = (state) => {
return {
value: state.value
};
}
const mapDispatchToProps = (dispatch) => {
return {
onValueChange: (value) => {
dispatch({
type: 'SETVALUE',
payload: {
value
}
})
}
};
};
const { connect } = ReactRedux;
const Component = connect(
mapStateToProps,
mapDispatchToProps
)(View);
const { createStore } = Redux;
const store = createStore(reducer);
ReactDOM.render(
<Component store={store} />,
document.getElementById('root')
);
EDIT: Clarifying question
Marco and Nathan have both correctly pointed out that this is a known issue in React that won't be fixed. If there is a setTimeout or other delay between onChange and setting the value, the cursor position will be lost.
However, the fact that setState just schedules an update is not enough to cause this bug to happen. In the Github issue that Marco linked, there is a comment:
Loosely speaking, setState is not deferring rendering, it's batching
updates and executing them immediately when the current React job has
finished, there will be no rendering frame in-between. So in a sense,
the operation is synchronous with respect to the current rendering
frame. setTimeout schedules it for another rendering frame.
This can be seen in JsBin example: the "sync" version also uses setState, but everything is working.
The open question still is: is there something inside of Redux that creates a delay that lets a rendering frame in-between, or could Redux be used in a way that avoids those delays?
Workarounds for the issue at hand are not needed, I found one that works in my case but I'm interested in finding out the answer to the more general question.
EDIT: issue solved
I was happy with Clarks answer and even awarded the bounty, but it turns out it was wrong when I really tested it by removing all middlewares. I also found the github issue that is related to this.
https://github.com/reactjs/react-redux/issues/525
The answer is:
this is an issue in react-redux that will be fixed with react-redux 5.1 and react v16
What middleware are you using in your Redux application? Perhaps one of them is wrapping a promise around your action dispatches. Using Redux without middleware does not exhibit this behaviour, so I think it's probably something specific to your setup.
The issue is not related to Redux, but to React. It is a known issue and won't be fixed in the React core as it is not considered a bug but an "unsupported feature".
This answer explains the scenario perfectly.
Some attempts to address this issue have been made, but as you might see, they all involve a wrapper component around the input, so it's a very nasty solution if you ask me.
Asynchronously updating without losing the position was never supported
--- Dan Abramov (gaearon)
The solution is to track the cursor position and use a ref inside componentDidUpdate() to place the cursor correctly.
Additional info:
When you set attributes in react, internally this happens:
node.setAttribute(attributeName, '' + value);
When you set value this way, the behavior is inconsistent:
Using setAttribute() to modify certain attributes, most notably value in XUL, works inconsistently, as the attribute specifies the default value.
--- https://developer.mozilla.org/en/docs/Web/API/Element/setAttribute
Regarding your question about whether rendering occurs synchronously, react's setState() is asynchronous and used internally by react-redux:
There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains
--- https://facebook.github.io/react/docs/react-component.html#setstate
There is an internal joke in the team that React should have been called "Schedule" because React does not want to be fully "reactive".
--- https://facebook.github.io/react/contributing/design-principles.html#scheduling
I think react-redux and redux are totally irrelevant to your case, this is pure React behavior. React-redux eventually calls setState on your component, there's no magic.
The problem that your async setState creates rendering frame between the react rendering and browser native event is because the batch update mechanism only happens within React synthetic events handler and lifecycle methods. Can check this post for detail.

what does createContainer in Meteor using React do?

I'm working on the Meteor using React tutorial and trying to understand createContainer(). From reading here:
http://guide.meteor.com/v1.3/react.html#using-createContainer
I think that its a function defined in meteor/react-meteor-data that is used for data loading. In this specific case, it retrieving data from the Mini Mongo Database (named Task here). My question is, what does the second argument to createContainer do? (named App here). Thank you!
class App extends Component {
//...class definition
}
export default createContainer(() => {
return {
//Tasks is a Mongo.Collection
//returns the matching rows (documents)
//here we define the value for tasks member
tasks: Tasks.find({}, { sort: { createdAt: -1} }).fetch(),
};
}, App);
A component created with createContainer is a simple wrapper around your actual component, but it's powerful in that it handles Meteor's reactivity for you so you don't have to think about how to keep your everything up to date when your data changes (e.g. a subscription loads, ReactiveVar / Session var changes)
A React component is basically just a JavaScript function, it is called with a bunch of arguments (props) and it produces an output. React doesn't know if your data has changed unless you tell it so. The component created with createContainer will re-render when your reactive data changes and send a new set of props to your actual component.
The options for createContainer are a function that returns the reactive data you want, and the component you want to wrap. It's really simple, and the render function for createContainer is literally one line:
return <Component {...this.props} {...this.data} />;
It passes through any props you pass to the wrapped component, plus it adds the reactive data source you set up.
You can see the code for yourself here: https://github.com/meteor/react-packages/blob/devel/packages/react-meteor-data/createContainer.jsx
The <Component {...this.props} syntax is known as a splat and basically turns:
{
prop1: 'some value',
prop2: 'another value'
}
into:
<Component prop1='some value' prop2='another value />
(See: https://facebook.github.io/react/docs/jsx-spread.html)
Asking a coworker, this is the answer that I got:
createContainer's second argument is the class name that you want the data to be encapsulated in. It will then have "reactive data" because every time the data in the DB is changed, the class's props will change to include the new data.
Also, the createContainer() function should be called outside of the class definition.
If anyone has anything to add please feel free to contribute.
createContainer's second argument is the name of the class in which you want to pass on the props to.
Lets say createContainer returns a prop called firstName
Now, whenever there is a new firstName entry or an updated firstName in the db, then createContainer is going to call the second argument which is our class name with the prop that it holds i.e firstName.
I hope that makes sense.

Resources