Vue.js mutating input data property - javascript

I have a component called Input.vue, which basically prints label and and input of some type. It looks like this:
<template>
<div class="form-element">
<label :for="inputId" :class="['form-element-title', { 'js-focused': focused }]">{{ label }}</label>
<input :disabled="!canEdit" :type="inputType" :id="inputId" :name="inputName" v-model="inputData" :placeholder="placeholder" #focus="focused = true" #blur="focused = false">
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class Input extends Vue {
#Prop({ default: () => {} }) inputId!: string
#Prop({ default: () => {} }) inputType!: string
#Prop({ default: () => {} }) inputName!: string
#Prop({ default: () => {} }) label!: string
#Prop({ default: () => {} }) placeholder!: string
#Prop({ default: () => {} }) data!: string
#Prop({ default: () => true }) canEdit!: boolean
private inputData = this.data;
private focused = false
}
</script>
It's being called from another component like this:
<input-component inputId="formDiscountName"
inputType="text"
inputName="discountName"
:label="translations['name']"
:placeholder="translations['name']"
:data="discount.name"
/>
Now the problem is that if I enter any data in this input, discount.name doesn't get any value set, because inside input-component it's instantly assigned to inputData variable and data property doesn't get any update. Now I can't mutate data property itself because of the Vue warning.
What should I do? I tried to use computed properties but I am not sure what get and set functions should look like.
One thing I can think of is to use computed property and emit an update on set function:
get inputData() {
return this.data;
}
set inputData(value: string) {
this.$root.$emit('inputUpdate', {'inputName' : this.inputName, 'inputValue' : value});
}
But emit doesn't look very clean method. Is there anything better?

You can add a watcher on the inputData. Post that there are couple of ways that discount.name can be updated.
Either emit the inputData value from the watcher and add a handler in parent component
Or
Not sure what is your usecase here but instead of passing discount.name, you can pass the discount object and then modify the name property of the object - there won't be any warning since you are modifying a property of an object.

Related

Many boolean properties in react state - how to refactor?

I have a React component that is a report with many buttons. You can open some subreports on table row click or by button click. I keep in state boolean values for each report. Basically my State interface for types looks like this:
interface State {
isSubreportVisible: boolean;
isLogsVisible: boolean;
isSelectStatusVisible: boolean;
isMoneyVisible: boolean;
isTargetStoreWarningVisible: boolean;
isMultiVisible: boolean;
isCancelVisible: boolean;
isPrintVisible: boolean;
}
Then my initial state is like this:
public state: State = {
isSubreportVisible: false,
isLogsVisible: false,
isSelectStatusVisible: false,
isMoneyVisible: false,
isMultiVisible: false,
isTargetStoreWarningVisible: false,
isCancelVisible: false,
isPrintVisible: false,
};
And inside component I have:
{isSubreportVisible && (
<Subreport
...
...
...
/>
)}
File of the component is really long - almost 600 lines of code. Is there anything could be done here? All subreports in modals are connected to this component?
Is it a good pattern to create a file called for example "types.tsx" and move all interfaces there (State, StateToProps, Props etc.)? Any ideas? Maybe it could be better to have just one string value in state - currentModalVisible and to keep there its names from enum?
Maybe it could be better to have just one string value in state - currentModalVisible and to keep there its names from enum?
This depends -- is only one modal visible at a time? If so then I would store a single value which is either null/undefined or the name of the visible modal. If the modals toggle independently then you will want to store a boolean for each one.
Either way, I think a good goal here is to have generalized functions that take a string key for the modal as an argument. You want to be able to call things like isModalVisible('print'), toggleModalVisible('print'), setModalVisible('print', false), etc.
I would define a type for the props that are available to the modals/subreports. You want to require that the components cannot require any additional props. That way you can call a component by its variable and pass it all the available props some will be ignored).
For maximum flexibility, you could even pass the available modals as a prop.
interface ModalProps {
someKey: string;
}
interface FlexibleSettings {
// what to display in your tab/toggle
// optional because can default to a cased version of the `key`
buttonText?: React.ReactNode;
// the component to render when selected
modal: React.ComponentType<ModalProps>;
}
interface VeryFlexibleComponentProps<Keys extends string> {
// pass the modals as an object keyed by the modal key
modals: Record<Keys, FlexibleSettings>;
// can start with one visible
initialVisible?: Keys;
// need to access the ModalProps somewhere
// maybe some come from this component and can be omitted
modalProps: ModalProps;
}
const VeryFlexibleComponent = <Keys extends string>({
modals,
initialVisible,
modalProps
}: VeryFlexibleComponentProps<Keys>) => {
const [visible, setVisible] = React.useState(initialVisible); // type Keys | undefined
// need an uppercase variable to call a component with JSX
const CurrentModal = visible ? modals[visible].modal : undefined;
return (
<div>
<h3>Select Modal</h3>
{(Object.keys(modals) as Keys[]).map((key) => (
<div key={key}>
<input
type="radio"
name="visible"
value={key}
id={key}
checked={visible === key}
onChange={(e) => {
if (e.target.checked) setVisible(key);
}}
/>
<label htmlFor={key}>
{modals[key].buttonText ?? _startCase(key)}
</label>
</div>
))}
{CurrentModal !== undefined && (
<div>
<h3>Viewing Modal</h3>
<CurrentModal {...modalProps} />
</div>
)}
</div>
);
};
Code Sandbox

What is the correct to create a interface for action object with react hooks and typescript

I am working with react hooks and typescript. I used useReducer() for global state. The action of the reducer function contains two properties name and data. name means the name of event or change and data will be particular data required for that particular name.
There are four value for name till now. If name "setUserData" then data should IUserData(interface). If name is setDialog then data should DialogNames(type containing two strings). And if its something else then data is not required.
//different names of dialog.
export type DialogNames = "RegisterFormDialog" | "LoginFormDialog" | "";
//type for name property in action object
type GlobalStateActionNames =
| "startLoading"
| "stopLoading"
| "setUserData"
| "setDialog";
//interface for main global state object.
export interface IGlobalState {
loading: boolean;
userData: IUserData;
dialog: DialogNames;
}
interface IUserData {
loggedIn: boolean;
name: string;
}
//The initial global state
export const initialGlobalState: IGlobalState = {
loading: false,
userData: { loggedIn: false, name: "" },
dialog: ""
};
//The reducer function which is used in `App` component.
export const GlobalStateReducer = (
state: IGlobalState,
{ name, data }: IGlobalStateAction
): IGlobalState => {
switch (name) {
case "startLoading":
return { ...state, loading: true };
case "stopLoading":
return { ...state, loading: false };
case "setUserData":
return { ...state, userData: { ...state.userData, ...data } };
case "setDialog":
return { ...state, dialog: data };
default:
return state;
}
};
//The interface object which is passed from GlobalContext.Provider as "value"
export interface GlobalContextState {
globalState: IGlobalState;
dispatchGlobal: React.Dispatch<IGlobalStateAction<GlobalStateActionNames>>;
}
//intital state which is passed to `createContext`
export const initialGlobalContextState: GlobalContextState = {
globalState: initialGlobalState,
dispatchGlobal: function(){}
};
//The main function which set the type of data based on the generic type passed.
export interface IGlobalStateAction<
N extends GlobalStateActionNames = GlobalStateActionNames
> {
data?: N extends "setUserData"
? IUserData
: N extends "setDialog"
? DialogNames
: any;
name: N;
}
export const GlobalContext = React.createContext(initialGlobalContextState);
My <App> component looks like.
const App: React.SFC = () => {
const [globalState, dispatch] = React.useReducer(
GlobalStateReducer,
initialGlobalState
);
return (
<GlobalContext.Provider
value={{
globalState,
dispatchGlobal: dispatch
}}
>
<Child></Child>
</GlobalContext.Provider>
);
};
The above approach is fine. I have to use it like below in <Child>
dispatchGlobal({
name: "setUserData",
data: { loggedIn: false }
} as IGlobalStateAction<"setUserData">);
The problem is above approach is that it makes code a little longer. And second problem is I have to import IGlobalStateAction for not reason where ever I have to use dispatchGlobal
Is there a way that I could only tell name and data is automatically assigned to correct type or any other better way. Kindly guide to to the correct path.
Using useReducer with typescript is a bit tricky, because as you've mentioned the parameters for reducer vary depending on which action you take.
I came up with a pattern where you use classes to implement your actions. This allows you to pass typesafe parameters into the class' constructor and still use the class' superclass as the type for the reducer's parameter. Sounds probably more complicated than it is, here's an example:
interface Action<StateType> {
execute(state: StateType): StateType;
}
// Your global state
type MyState = {
loading: boolean;
message: string;
};
class SetLoadingAction implements Action<MyState> {
// this is where you define the parameter types of the action
constructor(private loading: boolean) {}
execute(currentState: MyState) {
return {
...currentState,
// this is how you use the parameters
loading: this.loading
};
}
}
Because the state update logic is now encapsulated into the class' execute method, the reducer is now only this small:
const myStateReducer = (state: MyState, action: Action<MyState>) => action.execute(state);
A component using this reducer might look like this:
const Test: FunctionComponent = () => {
const [state, dispatch] = useReducer(myStateReducer, initialState);
return (
<div>
Loading: {state.loading}
<button onClick={() => dispatch(new SetLoadingAction(true))}>Set Loading to true</button>
<button onClick={() => dispatch(new SetLoadingAction(false))}>Set Loading to false</button>
</div>
);
}
If you use this pattern your actions encapsulate the state update logic in their execute method, which (in my opinion) scales better, as you don't get a reducer with a huge switch-case. You are also completely typesafe as the input parameter's types are defined by the action's constructor and the reducer can simply take any implementation of the Action interface.

Pass value from class method to a react component

Situation: I have a class with its own methods. This class is instantiated in a React component.
What I'm needing: During one of the methods in the class, it changes the value of an input (this.$el) using .val(), but I'm listening to changes to this input in the React.component via onChange. I need to pass the value I'm using to set the value of the input (via this.$el.val(value)) to the React component to change its state.
What I've tried: I've tried chaining .change() and trigger('change') to the val(value), but it doesn't have any affect.
So, I need to be able to access the value I'm using in .val(value) in my React component WHEN it is set in the class method. I thought about using a method and calling that method on componentWillUpdate, but the component doesn't update since setting the input value via val() doesn't trigger a change.
Any ideas?
Component code:
// Create a ref to manage blur/focus state on the search input
this.inputRef = React.createRef()
// Setup initial state
this.state = {
supersearchResults: [],
value: this.props.initialValue || ''
}
this.onInputChange = this.onInputChange.bind(this)
tag('input', {
autoComplete: 'off',
className: blockElement('search-input'),
onChange: this.onInputChange,
placeholder: 'Find people, collections and pages',
ref: this.inputRef,
type: 'text',
value: this.state.value
})
Class code:
this = class
this.$el = input
// What is happening:
// A user types in an input, suggestions display in a list, when you
// select a suggestion, it calls the below to change the input value
this.$el.val(complete)
this.$el.blur()
this.hide()
If I understand correctly, you want to be able to access the value of a html field. Then please take into consideration the following considerations. Use controlled inputs such that the
class ReactComponent extends...
constuctor (props) {
super();
this.state = { fieldValue: props.fieldValue || '' };
}
onFieldChange = (event) => {
this.setState('fieldValue': event.currentTarget.value)
}
render () {
return (
<div>
<input type="text"
value={this.state.fieldValue}
onChange={this.onFieldChange}
>
<div>
)
}
}
Now having this code, in case you need to use some external class to call some code, simply put it correctly in the lifecycle. But in order to reference the value use the components state. And in case you want to programmatically want to change the value, do the same update the value in the state. If I missed something let me know in the comments.
You need to keep the state in your class component. consider the following
class TextExample extends Component{
constructor(){
super(props);
this.state ={
username: null
}
this._handleChange = this._handleChange.bind(this);
}
_handleChange(e){
const { name, value } = e.target;
this.setState({ username: value}) // for single TextField
// if you want to reuse this _handleChange function for all the TextFields then you need to use the below commented code which updates state of current TextField
//this.setState({ [name]: value }) // it is similar like the JSON bracket notation
}
render(){
return(
<div>
<TextField
id="username"
label="Username"
name="username"
value={this.state.username}
onChange={this._handleChange} // it will call the _handleChange function on every keypress inside the TextField.
/>
</div>
)
}
}

Updating from an input element in ReactJS

I'm attempting to create a page using React, whereby I can update a single element of the state; here is how the state is defined:
interface MyState {
data?: MyData;
loading: boolean;
}
interface MyData {
id: number;
description: string;
test: string;
}
I have the following inside my render function:
return <div>
<h1>Description: {myData.description}</h1>
<br/><br/>
<input type="text" value={emailType!.test} onChange={this.handleChange} />
</div>;
And the handleChange (which is the heart of my issue):
handleChange(event: React.FormEvent<HTMLInputElement>) {
this.setState({ emailType!.test: event.currentTarget.value });
}
As I'm using tsx, the function above won't even compile. However, it does illustrate what I'm trying to do. Typically, when I've called this.setState, I've done so with a full state (that is, I know the entire new state). In this case, I only want to change the contents of a single field: is that possible?
setState allows changing only top-level key in the state like
handleChange = (event: React.FormEvent<HTMLInputElement>) => {
const emailType = { ...emailType, test: event.currentTarget.value }
this.setState({ emailType })
}
Don't forget to bind your function to the proper context.
Another option is to use function as a parameter in setState:
this.setState((oldState) => {
return {
...oldState,
emailType: {
...oldState.emailType,
test: event.currentTarget.value
}
}
})

ReactJS: How to get state value into container?

I need to get data from DB depending on a search string value. Therefore I'm using an input field. The search string is stored as a state value.
The data for the component comes from a container (using npm meteor/react-meteor-data).
Now my problem is, how do I get the search string into the container to set the parameter for the publication?
container/example.js
export default createContainer((prop) => {
Meteor.subscribe('images', searchString) // How to get searchString?
return { files: Images.find({}).fetch() }
}, Example)
component/example.jsx
class Example extends Component {
constructor(props) {
super(props)
this.state = {
searchString: ''
}
}
searchImage(event) {
const searchString = event.target.value
this.setState({ searchString })
}
render() {
return (<Input onChange={ this.searchImage.bind(this) }/>)
}
}
export default Example
publication
Meteor.publish('images', function(search) {
return Images.find({ title: search }).cursor
})
Maybe you can create two different components: a parent and a child, and you can wrap child component with createContainer HOC like the following
childComponent.js
const Example = (props) => {
return <Input onChange={props.searchImage}/>
}
export default createContainer(({searchString}) => {
Meteor.subscribe('images', searchString)
return { files: Images.find({}).fetch() }
}, Example)
parentComponent.js
class ExampleWrapper extends Component {
constructor(props) {
super(props)
this.state = {
searchString: ''
}
}
searchImage = (event) => {
const searchString = event.target.value
this.setState({ searchString })
} // instead of binding this, you can also use arrow function that
// takes care of binding
render() {
return (<Example searchImage={this.searchImage} searchString={this.state.searchString} {...this.props} />)
}
}
export default ExampleWrapper
The idea is, since createContainer is a higher order component, it doesn't have access to the props of any component wrapped by it.
What we need to do is, passing the value of searchString from a parent component.
The way to do is the following:
ExampleWrapper has a state called searchString and Example component has a prop called searchString. We can set the value of searchString prop to state.searchString.
Since the default export corresponds to createContainer({..some logic…}, Example}), createContainer can make use of prop called searchString.
In order to change the value of state.searchString we also passed searchImage function as a prop to Example component. Whenever there is a change event, onChange triggers searchImage function that updates the value of state.searchString. And eventually, the minute the value of state.searchString changes searchString prop’s value changes thus your subscription result also changes
onChange={ (e)=> {this.setState({ searchString: $(e.target).val() }) } }
This is how we assign values to our internal state properties :)
EDIT: I appear to have misunderstood the question...

Categories

Resources