-
-
Notifications
You must be signed in to change notification settings - Fork 116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Recommended way to implement forms #145
Comments
Great question - I'm just running into this now and will share what I find. Curious to hear other people's solutions. @christianalfoni? @Yomguithereal? |
@marbemac, I see a few possible directions:
Quoting @Yomguithereal:
So:
@christianalfoni wrote a good article about forms, but there are no mention of Baobab (article is old enough and focus differs a bit). He desribed approach 3) |
There are two types of forms: with immediate save and with save on submit. First type of forms is popular in OS settings (immediate reaction, not required to scroll long list of options...) but is not really applicable for creation. immediate forms have no clue when they should submit data for new model in general case. So CRUD should be either implemented with a mix of the two (complex) or with second type only (easy). That's why I propose to ignore the possibility of immediate forms for a while in this issue. |
The other thing here is to be able to define whether your Baobab tree, i.e. your app's state, really needs to hold the state of your form, or at least to be in full sync when the inputs change. In most cases, you don't and state component is way simpler and can handle the form alone. In other, however, for whatever reasons, you'll need to store the form's state into the app's one. Only one tricky thing here with React: you'll have to commit the state changes synchronously if you don't want to observe nasty cursor hopping. this.tree.commit(); |
@Yomguithereal I agree with what you say about Baobab aspect of this. But the most complex thing here is how to request external data from component. Every example in the Internet just shows data already preloaded, lol. There is no way to declare external data dependency in component, React has nothing to help your: hooks doesn't allow to return promises, etc. I'm getting very complex and error prone code when trying to emulate that in imperative way.
So we'll end up with at least few rerender sessions for form component. React is shockingly helpless with data loading questions. |
Well, I've just decided to accept reality, and simply preload all data from the start. |
@ivan-kleshnin, what about cycleJS? |
@Yomguithereal unfortunately, I don't reach form question there. I continue to research both React and Cycle ecosystem, I try to elaborate proof-of-concept apps in both. CycleJS is a long-distance goal for me as it will be compatible with Web Components and have a lot of other benefits (at least in theory) but the lack of matherial about RxJS, the lack of examples slows everything. One of the big problems with React (as I've already said a number of times) is it's hooks. React just merges rendering, control and state aspects in Component. Seals it and expose hooks. As long as I will have some form examples on Cycle, I'll drop them here. |
I've got working edit form prototype but with huge hacks. Please review and advice how to improve. // app.js
RobotActions.loadMany(); // trigger inital preload of data from global space // state.js
export default new Baobab({
robots: {
models: {}, // format is {[id]: {id: ..., name: ...}}
loaded: false,
loadError: undefined,
}
}); // EditRobotComponent.js
export default React.createClass({
mixins: [State.mixin],
cursors() {
return {
robots: ["robots"], // required to track load status
loadModel: ["robots", "models", this.getParams().id], // "reference" model (see reset)
}
},
getInitialState() {
return {
model: undefined, // mutable model, reflects state of fields
loaded: false,
loadError: undefined,
loadModel: { // "immutable" reference model
name: undefined,
assemblyDate: undefined,
manufacturer: undefined,
},
}
},
componentWillMount() {
// `componentWillUpdate` is not called at first render, and data is not "changed"
// so we need to hack this manually. How to handle this case without hack???
RobotActions.askData();
},
componentWillUpdate(nextProps, nextState) {
// clone "immutable" referential instance into working instance
if (!this.state.model && nextState.cursors.loadModel) {
nextState.model = Object.assign({}, nextState.cursors.loadModel);
}
},
render() {
let {models, loaded, loadError} = this.state.cursors.robots;
let loadModel = this.state.cursors.loadModel
let model = this.state.model;
if (!loaded) {
return <Loading/>;
} else if (loadError) {
return <NotFound/>;
} else {
return (
<DocumentTitle title={"Edit " + model.name}>
... form ...
</DocumentTitle>
);
}
},
} // RobotActions.js
export function askData() {
State.select("robots").set("timestamp", new Date()); // dirty hack to trigger data sharing
} Playground: https://github.com/Paqmind/react-starter#baobab |
React mixins are completely broken. No wonder they deprecate it. Follow me: Baobab I have second implementation of the same So the only way to solve this case is to split component into "data-source component" and "working component". Everyone seems to hate mixins now and for a reason. But the root of the problem lies in React's inability to provide decent compositional blocks. Components are extremely overcomplicated. They complect state, rendering, actions, deps from external data and DOM side-effects. Happy hipsters recommend just what I said. Extract data-source aspects from components and put them... into different components. Flummox does this and Reflux follows. But then we combine them... in HTML topology. This is nothing better than Angular attempts to stretch HTML language to have programming language constructs. It's obviously broken for multi-step dependencies. Every dependency step will require additional wrapper. Or it's better to surrender and put data in All described is required for Edit forms only. For Add forms there is no Baobab reference model. Working with React every business-case feels like an edge-case.
|
Using my query mixin, this became fairly trivial. https://gist.github.com/marbemac/a18582da8d2912285950#file-mixins-js var Post = React.createClass({
mixins: [ StateMixins.query ],
queries: {
post: {
cursors: {
entries: ['models', 'posts'],
},
local: function(_this, data) {
// using react-router
var id = _this.context.router.getCurrentParams().postId;
// if we're editing a post, fetch it
if (id) {
return StateQuery.findOne(data.posts.get(), {id: id});
} else { // not editing a post? return a new one right away
return new PostModel();
}
},
remote: function(_this, data) {
return StateActions.findOne(
StateActions.models.POST,
_this.context.router.getCurrentParams().postId,
false
);
}
}
}
}) In the local function, if there is a postId in the url, we look for the post locally (if not found, it will call the remote function and fetch it via the api). If no postId, then we just immediately return a new Post. Then, in render and all the functions that are called on form input updates, I use/edit this.state.post.result directly. |
@marbemac I think that's too much for one component. Tha'ts exactly what is called God-object. |
@ivan-kleshnin Why is it too much for one component? I understand the concept of the god-object, and don't believe this concept applies to components built in the way described above. From the wikipedia god-object definition:
The react component described in my post knows nothing about the rest of the program. It knows enough to render itself, and that's it. It is not a god-object. I actually modeled this after Relay. In relay, each component contains a definition defining the data needs for that component. This is useful, because now a component is self sufficient, and depends only on itself. You can drop it in anywhere and it will just work. It knows nothing about the rest of the system, or other components - it just knows how to render itself. I'm curious about how you are fetching/managing data for your components? I'm totally open to other solutions! For me, so far, the solution above has been working quite well. |
I'm updating Baobab tree on
I would put it on Back to the code, instead of calling That way we would have what could be called an |
@lucasmreis, you probably have cursor issues because baoab's updates are asynchronous and because react cannot handle controlled input with asynchronous state updates. You should try |
When I did Reflux-based app I just used
this.state.model
as both initial data for formsand write target at form changes. It was just a pointer to the same data for both component and store.
so it worked and was more convenient than piping every field through actions (omg).
With Baobab such trick is impossible(?), as data is "immutable". I wonder how people implement their forms and what are recommended approaches. 2-way data flow is very handy with forms so I'd like to have it in some kind.
The text was updated successfully, but these errors were encountered: