How to make reusable proptype validations? - javascript

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)

Related

Vue Form Components and Conditional Rendering Overhead

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

Is there value in propTyping repeated required properties at every level of nested components?

Say one component always calls another, but adds some properties, do you propType for required properties in this component even if they are checked in the component it calls? Or only at the higher level?
Simple Example:
const Input = props => {
let finalProps = {
...props,
...{onChange: (e) => props.onChange(props.id, e.target.value)}
};
return <input {...finalProps}/>
};
Input.propTypes = {
id: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
value: PropTypes.string,
tag: PropTypes.string
};
Input.defaultProps = {
value: ''
};
const Checkbox = props => <Input {...props} type="checkbox"/>;
Checkbox.propTypes = {
id: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
};
In this example, would you propType "Checkbox" as well as "Input" or just for "Input"?
My way of looking at it is, that if you use some props in a component, you have to propType it and define relevant defaultProps. But if you are not using any of the props, and you are just passing them down without even accessing them, i would not use propTypes or defaultProps.
In your case <Checkbox/> does not use any of the props passed to it, but just passes them down to <Input/> component. Hence you don't need to propType, id or onChange in <Checkbox/>, but in <Input/> you are using onChange and since you are rendering a <input/> tag, there are props you should supply to it, so you need to check for propTypes
It depends on all components which use the "Input" component. If all those higher-level components need to use the "type" prop, and I assume that they do, then I would put that property in the "Input" component, since it always appears and is always used.
Even if you have the situation when there are, for instance, 5 components which use the "Input" component, and 2 of those components work with a specific, but the same prop, I would put that prop, but without the "isRequired" attribute.

React.js - what is the good approach to show/hide loading image

I have a loading image in my page and a number of react components. In several components I need to implement the functionality of show/hide loading image.
So there are three options I can think of:
In each component, use a state variable and a loadingImage component to show hide the image. Like below code:
{this.state.showLoaidngImage ? <LoadingImage/> : null}
I can choose only to have this component at top-level component and let sub-components to call the parent display loading image method.
I can also use pure jquery here in each component and directly use the id to show/hide
The first approach seem to duplicate the component tags in each component and I am thinking of whether it is a good approach or not.
The second one is a bit complicated to implement.
The third approach seems dirty to me.
So which one should I use in the react world?
You should be going with the second approach because in that case you will not have to rewrite your loadingImage component again and according to the react good practices we should create components for everything, and use them wherever possible.
I think I would favor having your LoadingImage inside a component itself that handles hiding and showing via a prop. Something like this:
import React from 'react';
import PropTypes from 'prop-types';
import LoadingImage from 'where/this/exists';
const Loader = ({
show
}) => {
return (
{ show && <LoadingImage /> }
);
};
Loader.propTypes = {
show : PropTypes.bool.isRequired
};
export default Loader;
Then in your template:
<Loader show={ this.state.showLoader } />
(this result might be combined with #awildeep's response)
Assuming that you have React components that fetches to APIs, and those componends needs a "Loading image" separately, the first thing that comes into my mind is using redux and redux-promise-middleware.
Why?
You could have a global state, say for example
API_1: {
isFullfilled: false,
isRejected: false,
...
},
API_2: {
isFullfilled: false,
isRejected: false,
...
}
So, for instance, let's say that you have two React components that connects with those APIs. You will have two states!
{!this.state.API_1.isFullfilled && !this.state.API_1.isRejected : <LoadingImage /> : null }
Yes, this is too much code, but there's a way to simplyfy it, in mapStateToProps:
const mapStateToProps = state => {
const { API_1, API_2 } = state
return {
isFullfilled_API_1: API_1.isFullfilled,
isRejected_API_1: API_1.isRejected,
isFullfilled_API_2: API_2.isFullfilled,
isRejected_API_2: API_2.isRejected,
}
}
// Let's get those variables!
const { isFullfilled_API_1, isRejected_API_1 } = this.props
{ !isFullfilled_API_1 && !isRejected_API_1 ? <LoadingPage> : null}
You can track status for each component without a headache
You will accomplish your goal!
Hope it helps, and let me know if you have any concern!

How to define propTypes for a component wrapped with withRouter?

I'm wondering what the best practices are for defining propTypes on a component that will be wrapped with a 3rd-party HOC, in this case, withRouter() from React-Router.
It is my understanding that the point of propTypes is so you (and other developers) know what props a component should expect, and React will give warnings if this is violated.
Therefore, since the props about location are already passed by withRouter() with no human intervention, is it necessary to worry about them here?
Here is the component I'm working with:
const Menu = ({ userId, ...routerProps}) => {
const { pathname } = routerProps.location
return (
// Something using userID
// Something using pathname
)
}
Menu.propTypes = {
userId: PropTypes.number.isRequired,
// routerProps: PropTypes.object.isRequired,
// ^ this is undefined, bc withRouter passes it in later?
}
export default withRouter(Menu)
//.... in parent:
<Menu userId={id} />
What would be the convention in this case?
It is my understanding that the point of propTypes is so you (and other developers) know what props a component should expect, and React will give warnings if this is violated.
This is correct.
What would be the convention in this case?
I don't think you will find a definitive answer to this. Some will argue that if you define one propType you should define all expected prop types. Others will say, as you did, that it wont be provided by the parent component (excluding the HOC) so why bother. There's another category of people that will tell you not to worry with propTypes at all...
Personally, I fall into either the first or last category:
If the component is for consumption by others, such as a common ui component (e.g. TextField, Button, etc.) or the interface for a library, then propTypes are a helpful, and you should define them all.
If the component is only used for a specific purpose, in a single app, then it's usually fine to not worry about them at all as you'll spend more time maintaining them than debugging when the wrong props are passed (especially if you're writing small, easy to consume functional components).
The argument for including the routerProps would be to protect you against changes to the props provided by withRouter should they ever change in the future.
So assuming you want to include the propTypes for withRouter then we need to breakdown what they should actually be:
const Menu = ({ userId, ...routerProps}) => {
const { pathname } = routerProps.location
return (
// Something using userID
// Something using pathname
)
}
Looking at the above snippet, you may think the propTypes should be
Menu.propTypes = {
userId: PropTypes.number.isRequired,
routerProps: PropTypes.object.isRequired
}
But you'd be mistaken... Those first 2 lines pack in a lot of props transformation. In fact, it should be
Menu.propTypes = {
userId: PropTypes.number.isRequired,
location: PropTypes.shape({
pathname: PropTypes.string.isRequired
}).isRequired
}
Why? The snippet is equivalent to:
const Menu = (props) => {
const userId = props.userId
const routerProps = Object.assign({}, props, { userId: undefined }
const pathname = routerProps.location.pathname
return (
// Something using userID
// Something using pathname
)
}
As you can see, routerProps doesn't actually exist in the props at all.
...routerProps is a rest parameter so it gets all the other values of the props, in this case, location (and maybe other things you don't care about).
Hope that helps.

Should a child component have access to the store?

Disclaimer: sorry this is going to be a bit long
So I'm working on an intermediately complex app using Vue JS and Vuex.
This is my first Vue SPA project so I'm facing some difficulties with architectural choices, particularity with the question "who should know about the store?"
I'll describe my problem with a dummy example:
Say we have a Post component that has 3 states to get switched with a button:
Read: the component shows the title and text.
Update: the component shows title input and text input
Create: the same component is used for creating a new post, so it's just like update but with empty values.
First Approach: the component handles the store data:
Now in order to show and update a post, the component gets an id prop and selects the post object from a list of posts within the store, and dispatches actions when necessary. It has an update internal attribute to switch between show and update states.
As for the create state, a null id is passed to the component, so it won't fetch anything from the store and shows inputs, it dispatches insert actions.
Example Code
const KnowledgebalePost = {
name: 'Post',
props: ['id'],
data() {
return {
post: {
title: '',
text: '',
},
state: 'show'
}
},
methods: {
updateClicked() {
this.state = 'update';
},
saveClicked() {
this.state = 'show';
const postObject = { id: this.id, ...this.post };
const action = this.id ? 'updatePost' : 'addPost';
this.$store.dispatch(action, postObject);
},
},
created() {
if(this.id) {
// just to simplify
this.post = this.$store.state.posts[this.id];
}
}
};
Comments
The benefits I see in this is mainly the encapsulation of everything related to the component. It knows how to get its data and it is stand alone, all I need is to pass it an id.
On the other hand, knowing too much is problematic, a lot of things outside of the scope of the component could break it.
Second Approach: the component knows nothing about the store:
In this approach the component would get everything as a property: id, title, text, and state to tell it if it should render inputs or just text fields.
And instead of dispatching actions it would maybe emit events.
Example Code
const IgnorantPost = {
name: 'Post',
props: ['id', 'title', 'text', 'state'],
data() {
return {
post: {
title: '',
text: '',
},
internalState: 'show'
}
},
methods: {
updateClicked() {
this.internalState = 'update';
},
saveClicked() {
this.internalState = 'show';
this.$emit('saving', { id: this.id, ...this.post });
},
},
created() {
this.post.title = this.title;
this.post.text = this.text;
this.internalState = this.state;
}
};
Comments
While this solves the dependencies problem, it just pushes some of the logic to the parent component like handling if states of the Post component.
Also if this parent has many children other than Post, it'd become a very fat logic container.
The End
Note that my component is a lot more complex, any ideas on how to approach this particular problem?
Thanks in advance, and I appreciate you reading so far.
You're pretty much in the area of "primarily opinion-based," but I'll put in my two cents: The store is a global; lean toward having things manipulate it directly, rather than putting layers between it and components.
However, if the components you're using to implement your program have much promise of reusability, you might want to implement them in the usual encapsulated way, pushing the store interaction up.
But mostly I think manipulate the store directly.

Categories