I'm using a lightweight ORM to connect my react app with an external service... This package returns objects of your model and allows you to perform operations directly against them. While this is really awesome, I'm struggle to figure out how I can include these objects in state and still follow the "never modify state directly" tenant of react.
If I had a component that updated the account name, would it be acceptable to do something like this?
interface IAppState {
account: Account
}
class App extends React.Component<{}, IAppState> {
constructor(props) {
super(props);
this.state = {
account: new Account()
}
}
//set the new name and update the external service
public updateAccount = (newName: string)=>{
account.name = newName; //REDFLAG!!!
acc.update().then(()=>{
this.setState({ account: this.state.account })
})
}
//retrieve our account object from external service
public componentDidMount() {
const qParams = queryString.parse(window.location.search);
Account.get(qParams.externalId).then((acc)=>{
this.setState({account: acc})
})
}
render() {
return <NameEditor handleClick={this.updateAccount} account={a} />
}
}
I guess I could avoid mutating state by initiating a blank ORM object, copying the properties over, sending the update and then setting state, but this seems like a major pain.. Especially since these ORM objects can contain child ORM objects that I'd like to be able to modify as well.
Is the way I'm mutating state above "dangerous" or "bad form"???
Update
Did some reading and it definitely seems like this is probably bad form and might be navigated elegantly using the react/addons... However, what if the ORM call has a side effect on the object? For example, calling insert sets the objects external id field.
public updateAccount = (newName: string)=>{
//account.name = newName; //REDFLAG!!!
// You can use the below code to update name after it is updated
// on server.
// This will work because the object being passed here
// will be merged with Component state.
acc.update().then(()=>{
this.setState({account: {name : newName}})
})
}
Directly modifying state is not recommended as react will not come to know of the change and it will not cause a rerender.
All the diffing happens on Virtual DOM and react only updates the change attributes to Browser DOM. You can read more about the react diffing algorithm here.
React recommends to use immutable objects for setting state. you can either use Object.assign or immutable.js for this purpose which will make our life easier.
If you mutate your state object it will affect the the performance of your react component.
You can refer the below link for more information.
https://facebook.github.io/react/docs/optimizing-performance.html#examples
Related
I am just starting working with react and I am trying to use a web sdk. With client side code, the library is very easy to use: Add its script to the page header and, when the document is ready, it provides you with an object that has properties, methods and events. My goal would be to have access to that object methods and properties inside react. Is it even possible?
In the code below AddScript will create an object called Stuff on the client side and using the browser dev tools I can confirm that the object exists and it not null. How do I use Staff in React?
export class MyStuff extends Component {
constructor(props) {
super(props);
this.state = {
};
}
async componentDidMount() {
try {
await AddScript();
...
Sorry if I wasted anyone's time with this question:
window.Staff
has the required object.
I have a React App that gets asynchronously its info with the method getInitialInfo. The MessageView is the uppermost component, so, the Data should live in its state, even if it will not change. In a perfect world, the IMessageInfo would be passed through props.
We need to export the type IMessageInfo because there is code that depends on this interface.
OPTION 1 - (Flat solution with no private State)
import * as React from 'react';
import Message from './Message';
// IMessageInfo needs to be exported because there is code that depends on it
export interface IMessageInfo {
message: string;
icon: string;
}
export interface IMessageViewProps {
getInitialInfo(): Promise<IMessageInfo>;
}
// MessagesView is the upper most Component
class MessageView extends React.Component<IMessageViewProps, IMessageInfo> {
constructor(props) {
super(props);
this.state = {
message: '',
icon: ''
};
this.getInitialInfo();
}
private async getInitialInfo(): void{
let info = await this.props.getInitialInfo();
this.setState(info);
}
render(): JSX.Element {
// Message is reusable component that receives the info through `props`
return <Message {...this.state} />);
}
}
From the React's design perspective the State must be private to the component. I agree with that. But here, all the state info is Public. what can throw this concept way. (If the DATA is always managed in a presenter for example, why should it be private in this case?)
OPTION 2 - (Flat solution, having a private replicated interface for State)
Talking with some colleagues, they argue that we should always keep the state private. I immediately thought about creating a IMessageViewState that would be exactly the same as IMessageInfo. This way, it gets conceptually right but we would get a maintenance issue (IMessageInfo and IMessageViewState to update whenever some of its members change).
OPTION 3 - (Compose IMessageInfo into IMessageViewState. Has a private state)
So, my colleagues suggested to define IMessageViewState as:
interface IMessageViewState {
messageInfo: IMessageInfo;
}
This way we are favoring composition (they say). But I don't see any advantage to have composition here. Do you see any? For example, if any member of the IMessageInfo changes (message or icon), we would need to pass all the object messageInfo to the this.setState(...), instead of only updating the icon for example. Basically, it would be a more error-prone implementation.
OPTION 4 - (Extend IMessageInfo. Has a private state)
I also thought about having IMessageViewState extending IMessageInfo. It seems the best solution to accomplish a state that is not exported.
But my colleagues said that it's not a good solution because we are giving priority inheritance over composition.
I think that inheritance doesn't bring any throwback in here.
CONCLUSION
In my opinion, the Option 1 is the one that best fits the problem. Since all members of the State are public, I think there's no need to have a private State. The Option 1 keeps the code cleaner.
Although, if I were to choose a solution with a private State, the Option 4 would fit better.
QUESTION: What solution would be more correct?
I think here is some misunderstanding about interface and state. Interface is only type of some object. So you may have separate file which exports all required interfaces for you solution. IMessageInfo possibly can be used in several places.
State is object of some type. It can be of type IMessageInfo or some other. state is private to one component. And this is be design of React.
You may consider additional option of using Redux to make state central to whole solution. In this case IMessageInfo can be exported from part of you store responsible for MessageInfo.
Example
// store/MessageInfo.cs
export interface IMessageInfo { /*...*/ }
const initalMessageInfo: IMessageInfo = { /*...*/ }
export const actionCreators = {
requestMessageInfo () //...
}
export const reducer: Reducer<IMessageInfo> //...
In this case you'll (1) keep IMessageView sigle thru your solution, (2) make state based on IMessageInfo available to several components of your solution.
This way we are favoring composition (they say). But I don't see any advantage to have composition here. Do you see any? For example, if any member of the IMessageInfo changes (message or icon), we would need to pass all the object messageInfo to the this.setState(...),
Consider the case when you need to add more data to the MessageView internal state. This is the case the Option 3 is optimized for:
interface IMessageViewState {
messageInfo: IMessageInfo;
}
When it's separated in this way from the start, it's obvious where you can add whatever data you need to add to the private state: it's added to non-exported IMessageViewState, without affecting any users of exported IMessageInfo interface.
If you are 100% sure that IMessageInfo has everything the MessageView class will ever need, you can go with Option 1.
I'll start by saying I'm very new to React and am just playing around with having components interact with each other... trying to get a sense for where state belongs and the most efficient way(s) to render changes on screen.
I have 2 sibling components, Bro and Sis that are direct children of Dad. Bro makes an HTTP request in componentWillMount to get initial values for its state. It then passes one of the pieces of data from the response (uid) back up to Dad (via a method defined in Dad) which is then passed down to Sis via props. Sis then uses this value in making ITS initial HTTP request (in componentDidUpdate) to populate ITS state.
Dad
class Dad extends Component {
state = {
uid: null
}
updateUID = id => {
this.setState({uid: id});
}
}
render() {
return (
<>
<Bro />
<Sis update={this.updateUID} />
</>
);
}
Sis
class Sis extends Component {
state = {
uid: null,
something: null,
another: null
}
componentDidUpdate() {
axios.get('example.com/endpoint2.json')
.then(res => {
/*
transform as needed and put the vales from
res.data into this.state accordingly...
*/
});
}
render () {
return <section>Component: Sis</section>;
}
}
Bro
class Bro extends Component {
state = {
uid: null,
blah: null,
blah-blah: null
}
componentWillUpdate() {
axios.get('example.com/endpoint1.json')
.then(res => {
/*
...
transform as needed and put the vales from
res.data into this.state accordingly...
*/
// pass uid back up to Dad to be passed down to Sis
this.props.update(res.data.uid);
});
}
render () {
return <section>Component: Bro</section>;
}
}
Is this Bro --> Dad --> Sis passing of data the right way to do this? This seems a bit slow and perhaps unnecessarily complicated to me... I think. The alternate ways i can think of doing it are:
have Sis make its initial HTTP request in componentWillMount and fetch the value of uid on its own. This would eliminate the need to pass it from one child to the parent to the other child, but it would involve a partially redundant query on the backend which is why I chose not to go this route.
have Dad make an HTTP request that performs 1 combined query to return the data needed by both Bro and Sis and pass it down to each accordingly. As it stands right now, Dad does not always display Bro and Sis (depending on the route). In those cases, it would be a useless HTTP request and thus definitely not right, but I'm thinking a bit of restructuring may make this viable...
perhaps nesting Dad in something like Grandpa and letting Grandpa take care of the routing while Dad fetches the data for Bro and Sis.
So I guess ultimately my question is: should I be passing data between child/adjacent/sibling components via their parent component or should the parent component be the source of the data for both children and pass it down to each accordingly?
First of all, you shouldn't be calling an HTTP request in componentWillMount(). Instead do so in componentDidMount() as stated in React docs
Your method is complete fine. However based on the container/presentational (smart/dump) components strategy you'd better do all your data fetching in <Dad /> component, then pass down the required data to the children. This way it would be so much easier to keep track of your requests and your data won't be scattered about.
An alternative is to use 3rd-party libraries such as Redux or Mobx State Tree. I'm not sure about Mobx, but what Redux does is it keeps the state outside of the components and make it available to the whole application by React context. You should be thinking about using this as it's extremely powerful and easy to learn
Last but no least, I will include a couple of posts here about container/presentational components pattern:
From Dan Abramov - The creator of Redux
Another medium post
I have been struggling with this issue for quite some time and have failed to find any answers.
I use react-meteor-data to manage my data with react in my meteor application. It is working fine when dealing with data for mongo but I can't make it reactive with props.
Here in App.js, I call my container which I want to be reactive and rerender when the state of App change.
<MyContainer someState={this.state.MyState} />
In MyContainer.js I have a createContainer from react-meteor-data
export default createContainer(params => {
Meteor.subscribe('someCollection');
return {
someCollection: SomeCollection.find({}).fetch(),
stateFromParent: params.someState
};
}, MyContainer);
This worked fine when rendering the component for the first time, MyContainer correctly get MyState.
The thing is, when the MyState from App change, I can see in Chrome Dev React tool that it is indeed updated for the createContainer( ReactMeteorDataComponent has a prop with the right updated state) but the createContainer function is not run, thus the props do not update for MyContainer.
So the props are updated from ReactMeteorDataComponent but not for MyContainer who keeps indefinitely the data. It's like createContainer doesn't consider the update of its prop has a change and thus doesn't run its function.
I really think I'm missing something since that seems pretty basic stuff, thank you for your help.
The OP did not mention how the state was changed, so the original example is incomplete. Therefore, I will try to explain the gist of how the container creation works, in hope that understanding it will be useful.
How does it work?
It uses meteor's Tracker to auto-update the wrapped component when its computation is invalidated (i.e, when one of the reactive data sources, such as reactive variables, subscription handles or fetched MiniMongo cursors, has a new value). To learn more about Tracker, consult the Tracker manual. This is an in-depth resource, and is not necessary to understand how the basics work.
It does so in a way that is different from the way you normally approach reactivity tracking in Meteor, since it also needs to re-run the computation whenever the container's props are changed.
The source code is not very long or complex and can be found on GitHub (currently here).
Tracker.autorun((c) => {
if (c.firstRun) {
//...
data = component.getMeteorData();
} else {
// Stop this computation instead of using the re-run.
// We use a brand-new autorun for each call to getMeteorData
// to capture dependencies on any reactive data sources that
// are accessed. The reason we can't use a single autorun
// for the lifetime of the component is that Tracker only
// re-runs autoruns at flush time, while we need to be able to
// re-call getMeteorData synchronously whenever we want, e.g.
// from componentWillUpdate.
c.stop();
// Calling forceUpdate() triggers componentWillUpdate which
// recalculates getMeteorData() and re-renders the component.
component.forceUpdate();
}
})
Whenever the computation is invalidated (and therefore rerun), it stops the computation and forces a re-render of the container, which will re-create a new computation and have the updated data.
The high-level container functionality is here (some parts were removed for brevity):
export const ReactMeteorData = {
componentWillMount() {
this.data = {};
this._meteorDataManager = new MeteorDataManager(this); // (1)
const newData = this._meteorDataManager.calculateData(); // (2)
this._meteorDataManager.updateData(newData); // (3)
},
componentWillUpdate(nextProps, nextState) {
// backup current state and props, assign next ones to components
let newData = this._meteorDataManager.calculateData(); // (2)
this._meteorDataManager.updateData(newData); // (3)
// restore backed up data
},
componentWillUnmount() {
this._meteorDataManager.dispose(); // (4)
},
};
The main points are:
- Before being mounted, a new data manager is created (1). It is in charge of running the computation and populating this.data according to data changes.
- At first and whenever the component should update, the computation is run (2) and the data is updated (3). The update happens whenever the component receives new state or props (in this type of container, it should only be props), and, as we saw earlier, also when the Tracker computation is invalidated, due to the call to component.forceUpdate().
The wrapped component receives the parent's props, as well as the Tracker computation's data as props:
return <WrappedComponent {...this.props} {...this.data} />;
Any more points as to how it should be used?
The react-meteor-data has a short section in the meteor guide.
Generally, the simple example in the guide (as well as the OP's example) should work just fine, as long as the state is set appropriately, using setState() (see the "how does it work?" section above).
Also, there is no need to re-map the container state to props sent to the child, as they are passed along (unless there is a very good reason for doing so).
Do consider the point in the preventing re-renders section if you encounter any performance issues.
From the guide:
export default ListPageContainer = withTracker(({ id }) => {
const handle = Meteor.subscribe('todos.inList', id);
const loading = !handle.ready();
const list = Lists.findOne(id);
const listExists = !loading && !!list;
return {
loading,
list,
listExists,
todos: listExists ? list.todos().fetch() : [],
};
})(ListPage);
in this example, note that the container expects an id prop, and it will also be made available to the wrapped component, as well as loading, list, etc (which come from the container's computation in the example).
From redux doc ([http://redux.js.org/docs/api/Store.html][1]):
A store is not a class. It's just an object with a few methods on it.
and the methods are:
getState()
dispatch(action)
subscribe(listener)
replaceReducer(nextReducer)
(In flux it is is similar, with a difference that there is also an ActionDispatcher (and possibly an EventEmitter), but Store(s) are registered to the ActionDispatcher, so they are coupled.)
The question is why? Why store is not just a decoupled data type without logic, preferably an immutable one.
The following pseudo code is an example to show what I am trying to say. I used "appState" word instead of "store", as it is more natural to me:
const initialAppState = require("./initial-app-state.json");
function main() {
var actionDispatcher = new ActionDispatcher();
var appState;
actionDispatcher.register(function onAction(action) {
var newAppState = appState = reducers_combined(appState: ?AppState, action); //apply the action to appState, and create a new app state; as state is immutable
var newAppProps = createAppProps(newAppState); //we can write the createAppProps function, which takes an app state and create all the props to be passed down to the root component
ReactDom.render(React.createElement(App, newAppProps), document.getElementById("root"));
});
actionDispatcher.dispatch({
type: "LOAD_APP_REQUESTED",
appState: recordify(initialAppState); //we can write the recordify function that turns initialAppState JSONValue to an Immutable Record
})
}
As above we can create our ActionDispatcher single instance in our main function, and register an onAction callback to it, which has access to the current app state via closure, and will in turn create the new app state, update the current app state reference to the newly created one, create new app props with respect to the new app state and render it. The views will dispatch actions to the actionDispatcher directly (the actionDispatcher instance can be passed down the component tree via context) or indirectly.
What is the rationale making the Store work, instead of just hold? Are there any advantages?
Afaik because in redux when creating a store a reducer is required.
The store is usually created with createStore(reducer, [preloadedState], [enhancer]).
The functions mentioned in your question are utility function useful when working with the store.