React.js: react on changes in external object - javascript

After reading official react.js documentation I understand how it should work in a good way, like
I have list of items in initial component state
adding new item through setState will update state and trigger update of UI
What should I do if I use external object as model like some global array which should be available for some not react.js parts of code OR could be modified with web sockets somewhere in future? Is calling ReactDOM.render after each action a good way? AFAIK it should work ok from performance point of view.

You still use setState:
let React = require('React');
let externalThing = require('tools/vendor/whoever/external-lib');
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = this.getInitialState();
}
getInitialState() {
// This assumes your external thing is written by someone who was
// smart enough to not allow direct manipulation (because JS has
// no way to monitor primitives for changes), and made sure
// to offer API functions that allow for event handling etc.
externalThing.registerChangeListener(() => this.updateBasedOnChanges(externalThing));
return { data: externalThing.data }
}
updateBasedOnChanges(externalThing) {
// note that setState does NOT automatically trigger render(),
// because React is smarter than that. It will only trigger
// render() if it sees that this new 'data' is different
// (either by being a different thing entirely, or having
// different content)
this.setState({
data: externalThing.data
});
}
render() {
// ...
}
}
If the external thing you're using is terribly written and you have to manipulate its data directly, your first step is to write an API for it so you don't directly manipulate that data.
let externalData = require('externaldata') // example: this is a shared array
let ExternalDataAPI = new ExternalDataAPI(externalData);
...
And then you make sure that API has all the update and event hooks:
class ExternalDataAPI {
constructor(data) {
this.data = data;
this.listeners = [];
}
addListener(fn) {
this.listeners.push(fn);
}
update(...) {
// do something with data
this.listeners.forEach(fn => fn());
}
...
}
Alternatively, there are frameworks that already do this for you (flux, etc) but they also somewhat dictate how many more things "should be done" so that might be overkill for your need.

Since your question is about organizing your code in a manageable way, I would first of all suggest pairing ReactJS with a Flux-type framework, like Redux or Relay.
If you want to skip that for now, then you can organize your project using some react components at the top of the hierarchy for storing and retrieving data. For example, in such a component, in its componentWillMount method, you can start a setTimeout that periodically checks your global array and calls setState when appropriate. The render method should then contain child components that receive this state as props.
Below is an example. Obviously, the timers can be replaced by whichever method you use to subscribe to your data changes.
// your global object
var globalState = {name: "Sherlock Holmes"}
function onData(callback) {
setInterval(function(){
callback(globalState)
}, 1500)
}
var Child = React.createClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
var Root = React.createClass({
getInitialState: function() {
return {}
},
componentWillMount: function() {
var that = this;
this.props.onData(function(data){
that.setState({external: data})
})
},
render: function() {
if (this.state.external)
return <Child name={this.state.external.name}/>
else
return <div>loading...</div>;
}
});
ReactDOM
.render(<Root onData={onData} />, document.getElementById('container'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>

Related

Is it ok to modify Vuex state using only the payload argument of a mutation?

For example, could I iterate over Vuex data in a Vue file and choose the data needing updating, then pass the found data to an action, which commits it and then the mutation only makes the update?
The reason I'm unsure about it is because the typical format of a Vuex mutation contains the parameter for 'state', so I assume it needs to be used, and the only way to do that is either by doing all the looping inside the mutation, or to pass indexes to it to more quickly find the exact fields needing changing.
For who asked, a code example:
someVueFile.vue
computed: {
...mapState({
arrayOfObjects: (state) => state.someVuexStore.arrayOfObjects
}),
},
methods: {
myUpdateMethod() {
let toBePassedForUpdate = null;
let newFieldState = "oneValue";
this.arrayOfObjects.forEach((myObject) => {
if (myObject.someDataField !== "oneValue") {
toBePassedForUpdate = myObject.someDataField;
}
})
if (toBePassedForUpdate) {
let passObject = {
updateThis: toBePassedForUpdate,
newFieldState: newFieldState
}
this.$store.dispatch("updateMyObjectField", passObject)
}
}
}
someVuexStore.js
const state = {
arrayOfObjects: [],
/* contains some object such as:
myCoolObject: {
someDataField: "otherValue"
}
*/
}
const mutations = {
updateMyObjectField(state, data) {
data.updateThis = data.newFieldState;
}
}
const actions = {
updateMyObjectField(state, data) {
state.commit("updateMyObjectField", data);
}
}
Yes, it's alright to mutate state passed in through the payload argument rather than state. Vuex doesn't bother to distinguish between the two. In either case, it's the same state, and neither option detracts from the purposes of using mutations.
To feel more sure of that, you can ask what are the purposes of mutations and of enforcing their use. The answer is to keep a centralized, trackable location for concretely defined changes to state.
To illustrate this is a good thing, imagine an app with 1000 components, each one changing state locally, outside of a mutation, and in different ways. This could be a nightmare to debug or comprehend as a 3rd party, because you don't know how or where state changes.
So mutations enforce how and a centralized where. Neither of these are damaged by only using the payload argument in a mutation.
I would do all of the logic from one action, you can desctructured the context object in the action signature like so :
actions: {
myAction ({ state, commit, getters, dispacth } ,anyOtherParameter) {
let myVar = getters.myGetter//use a getter to get your data
//execute logic
commit('myCommit', myVar)//commit the change
}
}
If you need to do the logic in your component you can easily extract the getter and the logic from the action.

React setState for nested object while preserving class type

Extending from this question React Set State For Nested Object
The way to update nested state is to decompose the object and re-construct it like the following:
this.setState({ someProperty: { ...this.state.someProperty, flag: false} });
However, this is a problem for me, as it will not preserve the component class (More detail below). I am aiming for my state to be structured like the following:
this.state = {
addressComponent: {
street: '',
number: '',
country: ''
},
weather: {
/* some other state*/
}
}
To make my life easier, I created a simple address class that builds the object for me and have some other utility function such as validation.
class AddressComponent {
constructor(street, number, country) {
this.street = street;
this.number = number;
this.country = country;
}
validate() {
if (this.street && this.number, this.country) {
return true;
}
return false;
}
}
This allows me to do is change the initialization of state to be:
this.state = {
addressComponent : new AddressComponent(),
weather: new WeatherComponent();
}
this will allow my view component to perform nice things like
if (this.state.addressComponent.validate()) {
// send this state to parent container
}
The problem with this approach is that if I want to mutate one single piece of information like country and use the above Stack Overflow approach such as:
this.setState({addressComponent: {...addressComponent, country: 'bluh'}})
Doing this will mean that the resolting addressComponent is no longer part of AddressComponent class hence no validate() function
To get around it I cound recreate a new AddressComponent class every time like:
this.setState({addressComponent: new AddressComponent(this.state.street, this.state.number, 'bluh');
But this seems weird.
Am I doing this wrong? Is there a better approach? Is it acceptable to use classes like this with react?
It's undesirable to use anything but plain objects for React state to avoid situations like this one. Using class instances will also make serialization and deserialization of the state much more complicated.
The existence of AddressComponent isn't justified, it doesn't benefit from being a class.
The same code could be rewritten as functional with plain objects:
const validateAddress = address => !!(street && address.number && address.country);
...
if (validateAddress(this.state.address)) {
// send this state to parent container
}
I think what you said about reinstantiating your class each time you update it in your state is the cleanest way to ensure your validate() method is called at that time. If it were me, I would probably write it as:
const { addressComponent } = this.state;
this.setState({ addressComponent: new AddressComponent(addressComponent.street, addressComponent.number, 'bluh') });
You can create a reusable function to update a complex state like this.
updateState = (option, value) => {
this.setState(prevState => ({
addressComponent: {
...prevState.addressComponent,
[option]: value
}
})
}

Can I do dispatch from getters in Vuex

Fiddle : here
I am creating a webapp with Vue 2 with Vuex. I have a store, where I want to fetch state data from a getter, What I want is if getter finds out data is not yet populated, it calls dispatch and fetches the data.
Following is my Vuex store:
const state = {
pets: []
};
const mutations = {
SET_PETS (state, response) {
state.pets = response;
}
};
const actions = {
FETCH_PETS: (state) => {
setTimeout(function() {
state.commit('SET_PETS', ['t7m12qbvb/apple_9', '6pat9znxz/1448127928_kiwi'])
}, 1000)
}
}
const getters = {
pets(state){
if(!state.pets.length){
state.dispatch("FETCH_PETS")
}
return state.pets
}
}
const store = new Vuex.Store({
state,
mutations,
actions,
getters
});
But I am getting following error:
Uncaught TypeError: state.dispatch is not a function(…)
I know I can do this, from beforeMount of Vue component, but I have multiple components which uses same Vuex store, so I have to do it in one of the components, which one should that be and how will it impact other components.
Getters can not call dispatch as they are passed the state not context of the store
Actions can call state, dispatch, commit as they are passed the context.
Getters are used to manage a 'derived state'.
If you instead set up the pets state on the components that require it then you would just call FETCH_PETS from the root of your app and remove the need for the getter
I know this is an older post and I'm not sure if this is good practice, but I did the following to dispatch from a getter in my store module:
import store from "../index"
And used the store inside my getter like this:
store.dispatch("moduleName/actionName")
I did this to make sure data was made available if it was not already present.
*edit:
I want you to be aware of this: Vue form - getters and side effects
This is related to #storsoc note.
If you need to dispatch from your getter you probably are already implementing your state wrong. Maybe a component higher up should already have fetched the data before (state lifting). Also please be aware that getters should only be used when you need to derive other data from the current state before serving it to your template otherwise you could call state directly: this.$store.state.variable to use in methods/computed properties.
Also thing about your lifecycle methods.. you could for example in your mounted or created methods check if state is set and otherwise dispatch from there. If your getter / "direct state" is inside a computed property it should be able to detect changes.
had the same Problem.. also wanted all Vue-Instances to automaticly load something, and wrote a mixin:
store.registerModule('session', {
namespaced: true,
state: {
session: {hasPermission:{}},
sessionLoaded:false
},
mutations: {
changeSession: function (state, value)
{
state.session = value;
},
changeSessionLoaded: function (state)
{
state.sessionLoaded = true;
}
},
actions: {
loadSession(context)
{
// your Ajax-request, that will set context.state.session=something
}
}
});
Vue.mixin({
computed: {
$session: function () { return this.$store.state.session.session; },
},
mounted:function()
{
if(this.$parent==undefined && !this.$store.state.session.sessionLoaded)
{
this.$store.dispatch("session/loadSession");
this.$store.commit("changeSessionLoaded");
}
},
});
because it loads only one per vue-instance and store and it it inlcuded automaticly in every vue-instance, there is no need to define it in every main-app
I use a getter to configure a dynamic page. Essentially, something like this:
getter: {
configuration: function () {
return {
fields: [
{
component: 'PlainText',
props: {},
setPropsFromPageState: function (props, pageState, store) {
// custom logic
}
}
]
};
}
}
Then in the page component, when I am dynamically setting the props on a dynamic component, I can call the setPropsFromPageState(field.props, this.details, this.$store) method for that component, allowing logic to be set at the config level to modify the value of the props being passed in, or to commit/dispatch if needed.
Basically this is just a callback function stored in the getter that is executed in the component context with access to the $store via it.

Use React state to manage list of already instanced objects?

I want to check each collection I instance to see if another instance of the same type already exists so I can use the stored version instead of creating a new one, how can I use the react state to keep a list to check against?
Sample code:
export default React.createBackboneClass({
getInitialState() {
return {
data: [],
groups: {}
}
},
componentWillMount() {
this.setState({
data: this.props.collection
});
},
render() {
const gridGroups = this.state.data.map((model) => {
let gridGroupsCollection = null;
if(this.state.groups[model.get('id')]) {
gridGroupsCollection = this.state.groups[model.get('id')];
} else {
gridGroupsCollection = this.state.groups[model.get('id')] = new GridGroupCollection([], {
groupId: model.get('id')
});
this.setState((previous) => {
groups: _.extend({}, previous, gridGroupsCollection)
});
}
return <GridGroupComponent
key={model.get('id')}
name={model.get('n')}
collection={gridGroupsCollection} />
});
return (
<div>
{gridGroups}
</div>
);
}
});
Few points. First of all, I would use the componentWillReviewProps to do the heavy work. Doing so on the render method can be more costly. I have yet to save instances on the state object. Is it needed for caching performance-wise or to solve a sorting issue?
Moreover, as noted already, calling setState from the render method will generate an infinite loop. as render is called when the state changes.
On a side note I would consider Redux for this, which offers several holistic ways to separate components from data manipulation.
update
This TodoMVC example might point you to the direction of how to combine react and backbone:
componentDidMount: function () {
// Whenever there may be a change in the Backbone data, trigger a
// reconcile.
this.getBackboneCollections().forEach(function (collection) {
// explicitly bind `null` to `forceUpdate`, as it demands a callback and
// React validates that it's a function. `collection` events passes
// additional arguments that are not functions
collection.on('add remove change', this.forceUpdate.bind(this, null));
}, this);
},
componentWillUnmount: function () {
// Ensure that we clean up any dangling references when the component is
// destroyed.
this.getBackboneCollections().forEach(function (collection) {
collection.off(null, null, this);
}, this);
}

ParseReact.Mixin how do I listen for changes to this.data?

I have a React component that observes data on a Parse server.
mixins: [ParseReact.Mixin],
observe: function() {
var query = new Parse.Query('Item');
return {
items: query
};
}
In the render method I do receive my items and that work well. But, I want to be able to listen for when this.data.items will change it's value.
I'm aware of the regular lifecycle methods, but in them I have no way of checking if this.data.items is the same as before the update.
componentWillUpdate: function(nextProps, nextState) {},
componentDidUpdate: function(prevProps, prevState) {},
How do I do that?
You mean you need a "receiving data" callback?
Now there are no such a callback.
See this issue.
You can override "_receiveData" function in class extended ParseComponent by yourself though I'm not sure it's the right way for your situation.
_receiveData(name, value) {
this.data[name] = value;
delete this._pendingQueries[name];
delete this._queryErrors[name];
this.forceUpdate();
}

Categories

Resources