Vue Form Components and Conditional Rendering Overhead - javascript

I've seen and read a lot of posts on how best to handle forms. I know there's a lot of different opinions and that's not the point of this question. I'm still fairly new to Vue and have a pointed question regarding the framework in general rather that how to implement forms.
Due to numerous factors, we've decided the best way to go in our case is to create a generic FormField component with a prop of inputType. So we may have something like this:
<form-field input-type="text" :value="someValue"></form-field>
In the form component, we'll use <v-if="isText"> or similar. Obviously, there won't be a ton of if statements in this particular component (i.e. text, password, checkbox, etc) but I can't find any information on the overhead of conditional rendering.
Is there a significant amount of overhead in using conditional rendering over separating this out into a <my-checkbox-input>, <my-text-input>, <my-password-input>?

Well, as you said, there are many options to do it, I like to use the "Component" component (weird right?), This approach is kind of advanced, but I like it.
By the way, using AI (a lot of ifs) is not bad, because you are rendering what you need, just be sure to use v-if, v-else-if and v-else statements, ie:
<template>
<div>
<my-checkbox-input v-if="inputType === 'checkbox' />
<my-text-input v-else-if="inputType === 'text' />
<my-password-input v-else-if="inputType=== 'password' />
<input v-else />
<div>
</template>
<script>
export default {
props: {
inputType: {
type: String,
required: true
}
}
}
</script>
Above you can see a basic example, you should pass the required props or attributes to each input.
Now, this is what I use to have a group of components using the vue component "component":
<component
v-for="setting in additionalSettings"
:key="setting.feature"
:is="setting.component"
v-model="setting.enabled"
:class="setting.class"
:label="setting.label"
:description="setting.description"
:input-description="getEnableOrDisableWord(setting.enabled)"
:disabled="onRequest"
/>
To import the component(s):
export default {
name: "AdditionalSettingsContentLayout",
components: {
SelectInput: () =>
import(/* webpackChunkName: "SelectInput" */ "../components/SelectInput"),
},
}
I'm using this import syntax to add code splitting and lazy load (I think that is for that)
In this case, I'm using a json file as settings for the components:
settings: [
{
component: "SelectInput",
enabled: false,
label: "Toggle Personal Agenda feature on/off by event in ControlPanel",
feature: "ENABLE_MY_AGENDA",
class: "tw-mt-2 tw-mb-10",
},
{
component: "SelectInput",
enabled: false,
label: "Enable/disable Meeting scheduling by event",
feature: "ENABLE_MEETING_SCHEDULING",
class: "tw-mt-2 tw-mb-0 tw-mb-10",
},
{
component: "SelectInput",
enabled: false,
label: "Enable/disable Matchmaking by event",
feature: "ENABLE_MATCHMAKING",
class: "tw-mt-2 tw-mb-0",
},
],
I'm using Vuex to handle the change/update of the "enabled" state. The example above only uses the SelectInput component, but it could be any other component, only be sure to pass the required information for each field/input.
Be sure to do step by step changes or updates in your forms.
Read more about it here

Related

Define and reuse inline component in Vue3

Is it possible to define a "sub component" inline in Vue3 which can be reused in the same .vue file/component, but without actually defining a new .vue file (i.e. without defining this sub component as a new component).
The use case is that I have a very specific way of formatting <select> options which is used for multiple <select>s within the same component (.vue file), but which will not be used anywhere else (it's also small, so I am inclined to define this options formatting part inline). I don't necessarily want to copy and paste the formatting (and it would be good to keep it within the same .vue file because it's small).
I realise that this is only syntactic sugar which may or may not be relevant in specific cases (I'm also not seeking advice on whether or not this is a good idea). I'm just looking for a way this can be done (if not, that's also an answer ;-))
You can define h() function to create vnodes inside your setup script in vue 3.
for example:
<template>
<MyComponent />
</template>
<script setup>
const MyComponent = h('div', { class: 'bar', innerHTML: 'hello' })
</script>
vue document will help you completely. document
You could do something like this in Vue2
Vue.component('my-checkbox', {
template: '<div class="checkbox-wrapper" #click="check"><div :class="{ checkbox: true, checked: checked }"></div><div class="title">{{ title }}</div></div>',
data() {
return { checked: false, title: 'Check me' }
},
methods: {
check() { this.checked = !this.checked; }
}
});
Probably still works

Injecting a node into an external Vue component

I am a junior developer who lacks experience, so I apologize if my question showcases signs of sheer ignorance. My title may not be expressive of the problem I face, so I shall do my best to be descriptive.
In my project, I am making use of a 3rd party component (a dropdown menu), which I would like to modify in my application. I don't want to fork and edit their code since I would like to pull the latest styling changes since my modification is only slight, being that I would like to add some text next to the dropdown icon.
Here is a (simplified) version of the code.
<template>
<overflow-menu
ref="overflow_menu"
>
<overflow-menu-item
v-for="item in overflowMenuItems"
:id="item.id"
:key="item.name"
>
{{ item.tabName }}
</overflow-menu-item>
</overflow-menu>
</template>
<script>
import { OverflowMenu, OverflowMenuItem } from '#some-library/vue'; //Don't have control of the implementation of these components.
export default {
name: 'CustomOverflowMenu',
components: {
OverflowMenu,
OverflowMenuItem,
},
props: {
overflowMenuItems: Array,
label: String,
},
mounted() {
this.injectOverflowMenuLabel();
},
methods: {
injectOverflowMenuLabel() {
const overflowMenuElement = this.$refs.overflow_menu.$el.firstChild;
const span = document.createElement("span");
const node = document.createTextNode(this.$props.label);
span.appendChild(node);
overflowMenuElement.insertBefore(
span,
overflowMenuElement.firstChild,
);
}
}
};
</script>
It functionally works ok, however, it doesn't seem a particularly elegant solution, and I feel as if I could be doing it in a more "Vuey" way. I also am greeted with a Vue warning of: Error in created hook: "TypeError: Cannot read property 'insertBefore' of undefined. This ultimately means I am not able to mount my component and unit test my custom overflow menu.
Is there a way to get this functionality, but in a more maintainable manner. I would either like to simplify the logic of the injectOverflowMenuLabel function, or perhaps there is a completely alternative approach that I haven't considered.
Would appreciate any help, you lovely people.
Thanks,
Will.

How to make reusable proptype validations?

Assume that I have a parent component called Logo which contains two child components respectively Image and Text. Below code shows how I validate the parent component's props.
Logo.propTypes = {
type: PropTypes.string,
children: PropTypes.oneOfType([
PropTypes.shape({
type: PropTypes.oneOf([Image, Text])
}),
PropTypes.arrayOf(
PropTypes.shape({
type: PropTypes.oneOf([Image, Text])
})
)
])
};
This is working fine and does what I really want.
But I have so many other components which I do the same children validation nut only the change is PropTypes.oneOf([Image, Text]) array. So, in that case, I think there should be a common way of using this in javascript/react making this code snippet reusable across the components.
You could think in a fancier solution, but propTypes is really just an object, what if you turn it into a function which receives two parameters image and text and return an object, so you could just import it in your components:
export const myPropTypesCreator = (image, text) =>({/*return the object*/})
And inside your components:
import { myPropTypesCreator } from './mypath'
Logo.propTypes = myPropTypesCreator(image,text)

preactjs seeing [Object object] instead of link

I am using preactjs to create my application. On top of this, I am using kendo grid. Inside the grid, I want to show a hyperlink. if the user clicks on the link, it should change the route. To render the link, i am using preact-router.
Here is the working fiddle.
let { h, render, Component } = preact;
// import { ... } from 'preact';
let { route, Router, Link } = preactRouter;
/** #jsx h */
class App extends Component {
componentDidMount() {
console.log('did mount !');
$("#grid").kendoGrid({
selectable: "multiple cell",
allowCopy: true,
columns: [
{ field: "productName",
template: function(e) {
return <link href="/">Home</link>
} },
{ field: "category" }
],
dataSource: [
{ productName: "Tea", category: "Beverages" },
{ productName: "Coffee", category: "Beverages" },
{ productName: "Ham", category: "Food" },
{ productName: "Bread", category: "Food" }
]
});
};
render({}, { }) {
return (
<div>
<h1>
Preact Kickstart
<sub>powered by preact</sub>
</h1>
<div id="grid"></div>
</div>
);
}
}
// Start 'er up:
render(<App />, document.body);
This is not even related to preact.
What you're doing is rendering a kendo grid using via jquery inside a preact component and using a preact component as a template.
One way to fix this is to return a html string:
template: function(e) {
const linkEl = $('<a>')
.attr('href', '#') // keep the a el but do not redirect to a different page on click
.text('Home')
.click((e) => {
e.preventDefault(); // prevent the original a tag behavior
route('/'); // this is using the `route` from preactRouter, which is already included at the top of the original file
});
return linkEl[0].outerHTML; // access the JS DOM element from jQuery element and get it's full html
}
I've also replaced the link (which is a typo, probably should've been Link) with a tag since there is no link element in basic html. Even though the name is wrong, this still works because the JSX transformer will interpret all the lowercase components as string name instead of using the component as a function (see how Babel compiles it). The transformer will generate h("link", { href: "/" }, "Home") and the h function returns an object which is then rendered as [Object object] because this is what happens when you try to convert to a string via .toString function. If preact would work in this case, it would render the actual <link href="/">Home</link> to the user, which would not have the desired behavior (except if a custom link component is defined somewhere else).
You can't return a preact component here because the kendo grid template is expecting a string template. One way would be to convert the preact component to a string, but I'm not sure that's even possible, I have never seen it done and you shouldn't have to.
Note: As said, you shouldn't have to be converting little parts of React or React-like to html. I would strongly advise against mixing preact code with jQuery which is rendering the kendo grid in your case. These two libraries are doing the rendering very differently. While jQuery is using the old approach of directly modifying the DOM and replacing the whole subtree, React (and all the implementation, such as preact) are rendering to virtual DOM and then have internal logic which figures out the difference with the actual DOM the user is seeing and display only the minimum difference so it makes the least updates required. With my quick google search, I have found react-kendo, but it doesn't seem very popular. There are also some blog posts from the kendo team themselves, but I haven't found any official support. If you want to use preact, try to find the (p)react way of doing it, for your example you could be using something like react-table (official demo). If on the other hand, you want to use powerful UI tools provided by kendo, you would be better off not adding the preact to the mix, it will make things more complex without much benefit or it could even make the whole thing worse.

_React Js one page application

I am looking to create a one page application with ReactJS.
Is it advisable to combine it with angular or it is suitable just on its own? I would like to populate the one page site with sections - adding various features like carousels, sliders, isotope filters ...
<!DOCTYPE html>
<html>
<head>
<title>React Js one page</title>
<script src="https://fb.me/react-with-addons-0.14.7.min.js"></script>
<script src="https://fb.me/react-dom-0.14.7.min.js"></script>
</head>
<body>
<section>
One
<script>
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
ReactDOM.render(<HelloMessage name="Colonel Mustard" />, mountNode);
</script>
</section>
<section>
Two
<script>
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
Hello, world! I am a CommentBox.
</div>
);
}
});
ReactDOM.render(<CommentBox />, mountNode);
</script>
</section>
<section>
Three
<script>
"use strict";
var MarkdownEditor = React.createClass({
displayName: "MarkdownEditor",
getInitialState: function getInitialState() {
return { value: 'Type some *markdown* here!' };
},
handleChange: function handleChange() {
this.setState({ value: this.refs.textarea.value });
},
rawMarkup: function rawMarkup() {
return { __html: marked(this.state.value, { sanitize: true }) };
},
render: function render() {
return React.createElement(
"div",
{ className: "MarkdownEditor" },
React.createElement(
"h3",
null,
"Input"
),
React.createElement("textarea", {
onChange: this.handleChange,
ref: "textarea",
defaultValue: this.state.value }),
React.createElement(
"h3",
null,
"Output"
),
React.createElement("div", {
className: "content",
dangerouslySetInnerHTML: this.rawMarkup()
})
);
}
});
ReactDOM.render(React.createElement(MarkdownEditor, null), mountNode);
</script>
</section>
</body>
</html>
If you're just starting out with React, I'd highly recommend following Pete Hunt's advice:
You’ll know when you need Flux. If you aren’t sure if you need it, you don’t need it.
The best thing to do is get started with React by itself and manage application state using the local state that comes with each of your components.
When you find that you start having to pass data back up to parent components, then add Flux into the mix and rewrite your stateful components to instead use Flux stores.
We'll look at how to tackle writing a simplified version of the StackOverflow answer component as a React application from the bottom up.
Notice, I said React application, not React component. This is because there's no technical difference. A React application is a big React component made up of lots of smaller ones.
Identify Components
Once you have an interface for your application (anything from wireframes to html/css) you can visually subdivide them to work out how they'll fit together as React components.
There are no hard and fast rules about how exactly you decide what should or should not be it's own component, but you'll get a feeling for it the more times you do it.
is <Answer />
is <Votes />
is <AnswerText />
is <AnswerActions />
Because we're building from the bottom up, we'd start by implementing each of the child components and testing that they work alone.
At this point in the development lifecycle we'd just write static markup for each component. There's no need to think about props or state yet.
We can use the stateless component syntax to get started on the components we've identified. Here's an example of how we might write the <Votes /> component.
function Votes() {
return (
<div>
<a>▲</a>
<strong>0</strong>
<a>▼</a>
</div>
);
}
Of course this doesn't do anything, but it allows us to start composing our components to get a feel for the structure of the application.
We can render this into the DOM to check that it works at any time.
ReactDOM.render(<Votes />, document.getElementById('app'));
Once you'd finished implementing static versions of the other components, you could put them together to create the parent <Answer /> component.
function Answer() {
return (
<div>
<Votes />
<AnswerText />
<AnswerActions />
</div>
);
}
Design Data Flow
The next thing to do is to figure out how data flows through your application.
At this point we can create some dummy data in the form of an answer object that looks something like this:
{
"id": 0,
"votes": 0,
"text": "This is an answer"
}
Initially we can render the <Answer /> component by passing this answer object to it as a prop.
<Answer answer={answer} />
Now it's that components job to pass down the appropriate data to its children.
Obviously not each child needs all of the data though, so we'll have to decide what data goes where. Let's update our <Answer /> component.
function Answer(props) {
var answer = props.answer;
return (
<div>
<Votes id={answer.id} count={answer.votes} />
<AnswerText text={answer.text} />
<AnswerActions id={answer.id} />
</div>
);
}
The <Votes /> component needs know the current number of votes and it also needs to know the id of the answer so that it can communicate change to the server.
The <AnswerText /> component just renders a block of text, so that's all we need to pass it.
Finally, the <AnswerActions /> component renders a list of links that allow the user to perform some action (share, edit, flag) on the answer. This component also needs the answer's id so that it can communicate with the server.
Now we have to update these child components in turn to use these new dynamic values, instead of the static values we used at first. We'll revisit the <Votes /> component to see this happen.
function Votes(props) {
var urls = {
upvote: '/api/answers/' + props.id + '/upvote',
downvote: '/api/answers/' + props.id + '/downvote'
};
return (
<div>
<a href={urls.upvote}>▲</a>
<strong>{props.votes}</strong>
<a href={urls.downvote}>▼</a>
</div>
);
}
Now our vote component will make a HTTP request to the appropriate endpoint when we click on the vote buttons, however, we'd rather make this update without reloading and re-rendering the entire application.
Identify Stateful Components
The final piece of the component development process is to identify stateful components. These components have moving parts and data that will change during the lifetime of the application.
Each time the state inside a component changes, the entire component re-renders. We can revisit the wireframes to see which of our components needs to manage changing data.
This application only has one stateful component () and that's `. When we click on one of the arrows, we need to update the number to reflect the new count.
It's the only one of our components that ever needs to re-render.
This means we'll need to upgrade the component to use React's createClass syntax. This allows it to start managing it's own state.
var Votes = React.createClass({
getInitialState: function() {
return { votes: this.props.votes };
},
upvote: function() {
var newVotes = this.state.votes + 1;
this.setState({
votes: newVotes
});
},
downvote: function() {
var newVotes = this.state.votes - 1;
this.setState({
votes: newVotes
});
},
render: function() {
return (
<div>
<a onClick={this.upvote}>▲</a>
<strong>{this.state.votes}</strong>
<a onClick={this.downvote}>▼</a>
</div>
);
}
});
I've jumped the gun a bit and implemented the full component, but hopefully you'll get the idea.
First we use getInitialState to set up some state to represent the initial number of votes in the component.
Next we implement upvote and downvote component methods that update the component state.
Finally we re-implement the render method from before, but have the arrows trigger the new component methods, not page requests.
Each time we make a call to setState, React will re-render the component. Hopefully you can see why we put the state in the <Votes /> component and not the <Answer /> component. It would be crazy to re-render the answer text and actions, just because the votes had changed.
Flux It Up
Once we've identified and implemented all of our stateful components, we can start to move their state out into Flux stores.
It's much more likely that a real application would have an <AnswerStore /> than a <VoteStore />, so that's what we'll implement. For now we'll just keep mocking our data.
var AnswerStore = {
_listeners: [],
_answers: {
"0": {
"id": 0,
"votes": 0,
"text": "This is an answer"
}
},
get: function(id) {
return this._answers[id];
},
update: function(id, update) {
var answer = this.get(id);
var updatedAnswer = Object.assign({}, answer, update);
this._answers[id] = updatedAnswer;
this.emit();
},
listen: function(f) {
this._listeners.push(f);
},
emit: function() {
this._listeners.forEach(function(f) {
f();
});
}
};
In this example, I've written a fairly generic store that contains data, provides simple handlers for listening to model changes and finally exposes methods for mutating the data in the store.
It's important that our update method treats the individual answers as immutable in this application, otherwise we risk mutating an object that other parts of the application have a reference to, causing the object to change underneath them. We use Object.assign to create a new object each time, based on the old one.
The next thing we need to do is set up some action handlers for this store.
dispatcher.register(function(action) {
switch(action.type) {
case 'UPVOTE':
var votes = ActionStore.get(action.id);
ActionStore.update(action.id, { votes: votes + 1 });
break;
case 'DOWNVOTE':
var votes = ActionStore.get(action.id);
ActionStore.update(action.id, { votes: votes - 1 });
break;
}
});
This simply wires the update method to two actions called 'UPVOTE' and 'DOWNVOTE'
Now we connect Flux to our <AnswerComponent /> which needs to be re-written in the long form.
var Answer = React.createClass({
getInitialState: function() {
return { answer: {} };
},
componentWillMount: function() {
this.update();
AnswerStore.listen(this.update);
},
update: function() {
var id = this.props.id;
this.setState({ answer: AnswerStore.get(id) });
},
render: function() {
var answer = this.state.answer;
return (
<div>
<Votes id={answer.id} count={answer.votes} />
<AnswerText text={answer.text} />
<AnswerActions id={answer.id} />
</div>
);
}
});
In our componentWillMount method we fetch our initial data for the store, then set up a listener on the store that fetches and updates the component state, whenever the store changes.
Finally, we need a way to dispatch the appropriate actions from our <Votes /> component.
The most popular way to do this is with action creators. An action creator is a function which takes some data as parameters, then packages it up and dispatches it as an action.
var Actions = {
upvote: function(id) {
dispatcher.dispatch({
type: 'UPVOTE',
id: id
});
},
downvote: function(id) {
dispatcher.dispatch({
type: 'DOWNVOTE',
id: id
});
}
};
Then we call these actions from inside our <Votes /> component (which can become stateless again).
function Votes(props) {
var id = props.id;
return (
<div>
<a onClick={Actions.upvote.bind(null, id)}>▲</a>
<strong>{props.votes}</strong>
<a onClick={Actions.downvote.bind(null, id)}>▼</a>
</div>
);
}
This component now uses the action creators to dispatch actions for our Flux store(s) to handle.
If we look at the flow of data through our application, we can see that we now have a unidirectional cycle, rather than a tree.
The <Answer /> component passes the id down to the <Votes /> component.
The <Votes /> component dispatches an action using that id.
The AnswerStore processes the action and emits a change.
The <Answer /> component hears the update and updates its state, re-rendering its children.
Here's a jsfiddle of this demo application.
Scale Up
This is a very simple component that only handles a tiny amount of data flow and even less application state, however, it's enough to show you how to compose a React component and that's all you need to build a React app.
Let's imagine we were implementing a StackOverflow question as a React application.
function App() {
return (
<Page>
<Navigation />
<SideBar>
<MetaDetails />
<Ads />
<RelatedQuestions />
</SideBar>
<Question />
<AnswerList />
<AnswerEditor />
<Footer />
</Page>
);
}
It might seem like a complex application, but you can break it down and express it as distinct components, then you can implement and test the components individually, just like we did here and bring them altogether to form a complex application.
Don't Over Complicate
For most simple React applications like this one, Flux is not actually necessary. It's worth remembering that React was released over a year before Flux and yet it was adopted by a lot of developers regardless.
Of course, I've only really covered structuring and implementing components here. Taking an application from wireframes to deployment is a much more complicated process and there's no way it could be covered in detail in one answer. In time, you'll probably also want to learn about:
Package management and dependencies
Module bundlers
Routing
Hot Reloading
ES6/Babel
Redux
Server Side Rendering
Immutable Data
Inline Styles
Relay/Falcor/GraphQL
It takes some time to get through this list of things, the trick is not to rush. Don't overcomplicate your existing project until you find the reasons that these solutions exist, naturally.
I think react-howto is the best guide out there. Although it's not heavy on detail, it links to a lot of good resources and most importantly it provides an opinionated guide to the order in which you should learn these technologies on your way to becoming a competent ReactJS developer.
The choice of framework (Angular/React) does not prevent building any of the functionality you described, and your site could be built with neither, either, or both of those frameworks.
While you certainly could combine Angular and React, I'm not sure why you would want to, and it's not going to be the easiest of tasks. It's doable, of course, but it's going to provide a lot of difficulty for very little ultimate gain.
If you want to build a SPA with React, I would focus more on finding a Flux implementation that you like, and learning how to integrate that. Flux is designed specifically with React in mind to handle large SPAs which might have complicated data flow, and it's certainly a tool that is easier to incorporate early on.
The only other library I would consider out of the gate would be Immutable.js, which pairs very well with both React and Flux.
But otherwise, until you find a need to bring in additional frameworks/libraries, attempt to hold off. With all of the exciting JS frameworks out there, it's tempting to want to use them all, but in reality you're better off picking one to focus on, and then maybe bringing in some tools from another later on when they are necessitated.

Categories