How to remove background when click on material-table action buttons - material-ui

I want to remove this background grey color when switching on or off in Material-Table.
Here is my table code.
Here is the code for material-table.When I inspect the code I see a button as a parent element of this Switch element. I am pretty sure this is due to that button element. I added a styling to remove the hover color and It's working fine but for this click event I am unable to change it.
here is the code for removing hover styling:
rootTable: {
"& .MuiIconButton-root": {
backgroundColor:'transparent',
},
},
<div className={classes.rootTable}>
<MaterialTable
options={{
paging: false,
addRowPosition: 'first',
search: true,
showTitle: false,
doubleHorizontalScroll: true,
minBodyHeight: '65vh',
maxBodyHeight: '68vh',
actionsColumnIndex: columns.length
}}
style={{ width: '100%' }}
actions={[
//rowData => ({
// icon: (rowData.companyAddress && rowData.companyAddress != null) ? "location_on" : "location_off",
// tooltip: "Update Address",
// onClick: (event, rowData) => dispatch(Actions.setSelectedCompanyData(rowData))
// })
rowData => ({
icon:()=>RenderSwitch(rowData),
tooltip: "Supprimer cet accès Net",
}),
]}
components={{
Toolbar: props => {
const propsCopy = { ...props };
propsCopy.showTitle = false;
return (
<Grid container direction="row" alignItems={"center"}>
<Grid container item xs={3} justify={'flex-end'}>
{t('LBL.NUMBER_OF_LOGINS')+": "+ data.length}
</Grid>
<Grid container item xs={3} justify={'flex-end'}>
{renderPasswordVisibleToggle()}
</Grid>
<Grid item xs={6}>
<MTableToolbar {...propsCopy} />
</Grid>
</Grid>
);
}
}}
isLoading={loading}
localization={{
pagination: {
labelDisplayedRows: `{from}-{to} ${t('TBL.PAGGING.TO')} {count}`,
labelRowsPerPage: t('TBL.PAGGING.LBL')
},
header: {
actions: ['Actions']
},
body: {
emptyDataSourceMessage: t('TBL.EMPTY_RECORDS_MESSAGE'),
addTooltip: t('TBL.ADD'),
editTooltip: t('TBL.EDIT'),
editRow: {
deleteText: 'Voulez-vous supprimer cette ligne?',
cancelTooltip: t('TBL.CANCEL'),
saveTooltip: t('TBL.SAVE')
}
},
toolbar: {
searchTooltip: t('TBL.SEARCH'),
searchPlaceholder: t('TBL.SEARCH')
}
}}
columns={columns.map(column => {
column.title = t(column.translate);
return column;
})}
data={data}
editable={{
onRowAdd: !loggedUser.role.includes("ROLE_SUB_USER") ? (newData) =>
new Promise((resolve, reject) => {
newData.submitted = true;
if (newData.siret.length!==14) {
setNameError({
error: true,
label: "required",
helperText: t('LBL.INVALID_COMPANY_ID') ,
validateInput: true,
});
reject();
newData.submitted = undefined;
return;
}
newData.submitted = undefined;
if (isDevelopmentEnvironment || newData?.societe?.includes('test-qa')){
dispatch(Actions.saveNewLogin(newData,resolve));
}else {
const url = apiConfig.baseUrl + '/api/company-login/check-login-status';
axios
.post(url, newData, { validateStatus: () => true })
.then(response => {
if (response?.data?.validLogin) {
setCreateData(newData);
setTimeout(() => {
resolve();
}, 600);
} else {
dispatch(MessageActions.showMessage({
message: t("LBL.ERROR_MESSAGE"),
variant: 'error'
}));
reject();
}
})
.catch(error => {
console.error(error);
reject();
});
}
}) : null,
onRowUpdate: newData => {
return new Promise(resolve => {
dispatch(Actions.updateCompanyLogin(newData,resolve));
})
}
}}
icons={{
Add: forwardRef((props, ref) => (
<Fab ref={ref} {...props} className={classes.iconButton} aria-label="add">
<AddIcon />
</Fab>
))
}}
/>
</div>

Related

Why are my submitError's rendering on the second submit instead of the initial submit

I wanted to create a reusable component that could render server side errors if there are validation errors in the form. On the initial submit even if there are errors coming from the server, they will not be displayed until the "submit" button is pressed again. After the initial submit the meta: {touched} is not changing to true.
Tools Utilized:
"react-final-form": "^6.5.3",
"#material-ui/core": "^4.12.4",
This is my first time asking a question so if more information is needed let me know and I will try and provide you with the necessary information.
I have tried to add an onBlur to the TextField which does change the touched to true but still does not submit the error on the initial submit. I have also tried to switch the && and || to see if that would change anything but it doesn't.
Reusable Form Component (input/index.js)
/* eslint-disable implicit-arrow-linebreak */
import React, { useEffect } from 'react';
import { Field } from 'react-final-form';
import {
MaterialTextField,
} from '#/shared/components/form/MaterialFormElements';
const TextField = ({
input, label, meta: { touched, error, submitError }, children, select, multiline, name, maxLength, value,
}) =>
(
<MaterialTextField
label={label}
type={input.type}
error={touched && (error || submitError)}
helperText={touched && (error || submitError)}
value={input.value || value}
select={select}
multiline={multiline}
defaultValue={input.defaultValue}
name={name}
onChange={(e) => {
e.preventDefault();
input.onChange(e.target.value);
}}
style={{ width: '100%' }}
inputProps={{ maxLength }}
>
{children}
</MaterialTextField>
);
TextField.defaultProps = {
meta: null,
select: false,
children: [],
multiline: false,
};
const validate = value => (value ? undefined : 'Required');
export function Input({
name, label, required, select, children, maxLength,
}) {
return (
<div style={{ width: '100%' }}>
<Field
name={name}
label={label}
select={!!select}
validate={required && validate}
maxLength={maxLength}
>
{props => (
<div>
<TextField {...props}>
{children}
</TextField>
</div>
)}
</Field>
</div>
);
}
CreateCustomerForm.jsx
import React from 'react';
import { Form } from 'react-final-form';
import {
MaterialFormContainer,
ButtonWrap,
} from '#/shared/components/form/MaterialFormElements';
import { Button } from '#/shared/components/Button';
import PropTypes from 'prop-types';
import { Input } from '../../Input';
const CreateCustomerForm = ({ onSubmit, toggle, setError }) => {
const handleClick = (event) => {
event.preventDefault();
setError(false);
toggle(prevState => !prevState);
};
const fields = [
{
name: 'customerName', label: 'Customer Name', required: true, maxLength: 45,
},
{
name: 'address', label: 'Address', required: true, maxLength: 150,
},
{
name: 'city', label: 'City', required: true, maxLength: 45,
},
{
name: 'state', label: 'State', required: true, maxLength: 45,
},
{
name: 'zip', label: 'Zip Code', required: true, maxLength: 10,
},
{
name: 'phone', label: 'Phone', required: true, maxLength: 20,
},
{
name: 'primaryContactName', label: 'Primary Contact Name', required: true, maxLength: 90,
},
{
name: 'primaryContactEmail', label: 'Primary Contact Email', required: true, maxLength: 255,
},
{
name: 'customerKey', label: 'Customer Key', required: true, maxLength: 255,
},
];
return (
<Form onSubmit={onSubmit}>
{({ handleSubmit }) => (
<MaterialFormContainer onSubmit={handleSubmit}>
{fields?.map(field => <Input {...field} />)}
<ButtonWrap>
<Button variant="primary" type="submit">Submit</Button>
<Button variant="secondary" type="button" onClick={handleClick}>
Cancel
</Button>
</ButtonWrap>
</MaterialFormContainer>
)}
</Form>
);
};
CreateCustomerForm.propTypes = {
onSubmit: PropTypes.func.isRequired,
toggle: PropTypes.func.isRequired,
setError: PropTypes.func.isRequired,
};
export default CreateCustomerForm;
createCustomerModal.jsx
import React, { useEffect, useState } from 'react';
import {
StyledModal, ModalHeader, ModalTitle, ModalCloseButton,
} from '#/shared/components/Modal';
import { useSelector, useDispatch } from 'react-redux';
import { Button } from '#/shared/components/Button';
import { createCustomer, toggleCustomerModal } from '../../../redux/actions/customer/customerActions';
import CreateCustomerForm from './CreateCustomerForm';
const CreateCustomer = () => {
const { toggleModal } = useSelector(state => state.customer);
const dispatch = useDispatch();
const [modal, setModal] = useState(toggleModal);
const [error, setError] = useState([]);
useEffect(() => {
setModal(toggleModal);
}, [toggleModal]);
const toggle = () => {
dispatch(toggleCustomerModal());
};
const handleSubmit = ({
customerName,
address,
city,
state,
zip,
phone,
primaryContactName,
primaryContactEmail,
customerKey,
}) => {
setError([]);
dispatch(createCustomer(
customerName,
address,
city,
state,
zip,
phone,
primaryContactName,
primaryContactEmail,
customerKey,
setError,
toggle,
));
if (error?.length > 0) {
let serverErr = {};
error?.forEach((err) => {
serverErr = {
...serverErr,
[err?.loc[1]]: err?.msg,
};
});
return serverErr;
}
return false;
};
return (
<div>
<Button variant="primary" onClick={toggle}>Create Customer</Button>
<StyledModal
show={modal}
onHide={toggle}
color="primary"
>
<ModalHeader className="mb-1">
<ModalCloseButton
className="lnr lnr-cross"
aria-label="close-btn"
type="button"
onClick={toggle}
/>
<ModalTitle>Create Customer</ModalTitle>
</ModalHeader>
<CreateCustomerForm onSubmit={handleSubmit} toggle={toggle} setError={setError} />
</StyledModal>
</div>
);
};
export default CreateCustomer;
CustomerPage.jsx (customer/index.jsx)
import React, { useEffect, useState } from 'react';
import {
StyledModal, ModalHeader, ModalTitle, ModalCloseButton,
} from '#/shared/components/Modal';
import { useSelector, useDispatch } from 'react-redux';
import { Button } from '#/shared/components/Button';
import { createCustomer, toggleCustomerModal } from '../../../redux/actions/customer/customerActions';
import CreateCustomerForm from './CreateCustomerForm';
const CreateCustomer = () => {
const { toggleModal } = useSelector(state => state.customer);
const dispatch = useDispatch();
const [modal, setModal] = useState(toggleModal);
const [error, setError] = useState([]);
useEffect(() => {
setModal(toggleModal);
}, [toggleModal]);
const toggle = () => {
dispatch(toggleCustomerModal());
};
const handleSubmit = ({
customerName,
address,
city,
state,
zip,
phone,
primaryContactName,
primaryContactEmail,
customerKey,
}) => {
setError([]);
dispatch(createCustomer(
customerName,
address,
city,
state,
zip,
phone,
primaryContactName,
primaryContactEmail,
customerKey,
setError,
toggle,
));
if (error?.length > 0) {
let serverErr = {};
error?.forEach((err) => {
serverErr = {
...serverErr,
[err?.loc[1]]: err?.msg,
};
});
return serverErr;
}
return false;
};
return (
<div>
<Button variant="primary" onClick={toggle}>Create Customer</Button>
<StyledModal
show={modal}
onHide={toggle}
color="primary"
>
<ModalHeader className="mb-1">
<ModalCloseButton
className="lnr lnr-cross"
aria-label="close-btn"
type="button"
onClick={toggle}
/>
<ModalTitle>Create Customer</ModalTitle>
</ModalHeader>
<CreateCustomerForm onSubmit={handleSubmit} toggle={toggle} setError={setError} />
</StyledModal>
</div>
);
};
export default CreateCustomer;
package.json
{
"name": "stnportal",
"version": "2.5.0",
"private": true,
"scripts": {
"git-info": "node src/gitInfo.js",
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"lint:css": "stylelint './src/**/*.js'",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
"dependencies": {
"#material-ui/core": "^4.12.4",
"#material-ui/icons": "^4.11.3",
"#material-ui/lab": "^4.0.0-alpha.61",
"#mdi/js": "^7.0.96",
"#mdi/react": "^1.6.1",
"#tanstack/react-query": "^4.22.0",
"#tanstack/react-query-devtools": "^4.22.0",
"axios": "^0.21.4",
"bootstrap": "^5.1.1",
"clsx": "^1.2.1",
"customize-cra": "^1.0.0",
"dotenv": "^16.0.1",
"draft-js": "^0.11.7",
"draftjs-to-html": "^0.9.1",
"final-form": "^4.20.2",
"mdi-react": "^7.4.0",
"moment": "^2.29.4",
"polished": "^4.2.2",
"prop-types": "^15.7.2",
"rc-notification": "^4.5.4",
"rc-time-picker": "^3.7.3",
"react": "^17.0.1",
"react-avatar-editor": "^13.0.0",
"react-beautiful-dnd": "^13.1.0",
"react-big-calendar": "^0.35.0",
"react-bootstrap": "^2.2.1",
"react-datepicker": "^3.7.0",
"react-device-detect": "^1.15.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^17.0.1",
"react-draft-wysiwyg": "^1.14.7",
"react-dropzone": "^11.2.4",
"react-final-form": "^6.5.3",
"react-final-form-arrays": "^3.1.4",
"react-folder-tree": "^5.0.3",
"react-highlight-words": "^0.18.0",
"react-hook-form": "^7.9.0",
"react-html-parser": "^2.0.2",
"react-i18next": "^11.8.4",
"react-icons": "^4.4.0",
"react-qr-code": "^2.0.7",
"react-redux": "^8.0.2",
"react-router-dom": "^5.2.0",
"react-scripts": "^4.0.1",
"react-select": "^5.4.0",
"react-smooth-scrollbar": "^8.0.6",
"react-table": "^7.6.3",
"react-text-mask": "^5.4.3",
"react-toastify": "^8.2.0",
"recharts": "^2.1.2",
"redux": "^4.0.5",
"redux-actions": "^2.6.5",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0",
"smooth-scrollbar": "^8.5.3",
"styled-components": "^5.3.5",
"styled-theming": "^2.2.0"
},
"resolutions": {
"styled-components": "^5",
"#mui/styled-engine": "npm:#mui/styled-engine-sc#latest",
"react-error-overlay": "6.0.9"
},
"devDependencies": {
"#testing-library/jest-dom": "^4.2.4",
"#testing-library/react": "^9.3.2",
"#testing-library/user-event": "^7.1.2",
"eslint-config-airbnb": "^18.2.1",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-node": "^0.3.4",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-react-hooks": "^5.0.0-next-d1bb1c586-20220922",
"react-app-rewired": "^2.1.8",
"react-error-overlay": "6.0.9",
"vitest": "^0.25.2"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Preset input values of the form with Vuex

In my project (laravel/vue spa) I have a form opened in a modal window, the form shall display the initial values from the object kept in the vuex store. The problem is the input doesn't see objet property that I set through value, so with my code now when the modal is opened, no data is displayed inside the input, although if i output the section object in the markup it generally sees it.
How can I make the input work with the preset values?
Here's my code:
ModalForm.vue markup
<template>
<app-modal>
<Form #submit="submitSectionForm">
<div class="form-group">
<label for="title" class="control-label">Title</label>
//<p>{{ section }}</p>
<Field
:value="sectionTitle" #input="handleUpdateTitle"
:rules="isRequired"
type="text"
name="section_title"
class="form-control"
/>
<ErrorMessage
name="section_title"
class="pr-1 display-block color-red"
/>
</div>
<button class="btn btn-default" #click="closeModal">Cancel</button>
<button class="btn btn-primary ml-1">Ok</button>
</Form>
</app-modal>
</template>
ModalForm.vue functionality
<script>
import AppModal from "../../components/Modals/Modal.vue";
import { Field, ErrorMessage } from "vee-validate";
import { mapGetters, mapActions, mapState, mapMutations } from "vuex";
export default {
props: {
menu_type_id: Number,
menu_id: Number,
},
components: {
AppModal,
Field,
ErrorMessage,
},
methods: {
...mapActions("menu_sections", ["saveSection"]),
...mapMutations("menu_sections", ['updateTitle']),
isRequired(value) {
if (value && value.trim()) {
return true;
}
handleUpdateTitle(e) {
this.updateTitle(e.target.value);
}
submitSectionForm(value) {
console.log(value);
this.saveSection(value);
this.closeModal();
},
computed: {
...mapGetters("menu_sections", { section: "getSection" } ),
...mapGetters("menu_sections", { sectionTitle: "getSectionTitle" }),
},
};
</script>
section_store.js
import axios from "axios";
export default {
namespaced: true,
state: {
section: {},
message: "",
},
getters: {
indexById: (state) => (id) =>
state.sections.findIndex((item) => item.id == id),
sectionById: (state) => (id) =>
state.sections.filter((item) => item.id == id),
getSection: (state) => state.section,
getSectionTitle: (state )=>state.section.title
},
mutations: {
setSection(state, section) {
state.section = { ...section };
},
saveSection(state, data) {
state.message = data.message;
},
updateTitle(state, title) {
state.section.title = title;
console.log(state.section.title);
},
},
actions: {
saveSection({ commit }, section_object) {
axios
.post("/api/menu-sections/" + section_object)
.then((response) => {
commit("saveSection", response.data);
})
.catch((e) => {
console.log("Section save error");
});
}
},
};

Search bar in React Native not updating or filtering

I'm trying to set up data filtering by SearchBar from react-native-elements. I'm returning some data from the server in a JSON format so it arrives in this form of an Array of Objects.
This is what I have so far:
export default function AktList() {
const [akt, setAkt] = useState([]);
const [temp, setTemp] = useState([]);
async function request() {
fetch("http://192.168.5.12:5000/aktprikaz", {
method: "get"
})
.then(res => res.json())
.then(res => setAkt(res))
.then(temp => setTemp(akt));
}
useEffect(() => {
request();
}, []);
function Item({ title, selected }) {
return (
<TouchableOpacity
onPress={() => console.log(temp)}
style={[
styles.item,
{ backgroundColor: selected ? "#6e3b6e" : "#f9c2ff" }
]}
>
<Text style={styles.title}>{title}</Text>
</TouchableOpacity>
);
}
function contains(text) {
const newData = temp.filter(item => {
const itemData = {title};
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setAkt(newData);
}
return (
<SafeAreaView style={styles.container}>
<Header />
<SearchBar onChangeText={text => contains(text)} />
<FlatList
data={akt}
renderItem={({ item }) => <Item title={item.title} />}
keyExtractor={item => item.id}
/>
</SafeAreaView>
);
}
Currently, nor is the text updating nor is it filter anything. I've tried following this tutorial online (tho it is written using classes not functions). What am I doing wrong here?
Check below example which i created using flatlist and TextInput. Items are displayed in the form of a dropdown list when you search items. i think this will help you.
import React, { Component } from 'react';
import { View, Text, FlatList, TextInput, ListItem } from 'react-native';
class FlatListDropDown extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
value: '',
};
this.arrayNew = [
{ name: 'Robert' },
{ name: 'Bryan' },
{ name: 'Vicente' },
{ name: 'Tristan' },
{ name: 'Marie' },
{ name: 'Onni' },
{ name: 'sophie' },
{ name: 'Brad' },
{ name: 'Samual' },
{ name: 'Omur' },
{ name: 'Ower' },
{ name: 'Awery' },
{ name: 'Ann' },
{ name: 'Jhone' },
{ name: 'z' },
{ name: 'bb' },
{ name: 'cc' },
{ name: 'd' },
{ name: 'e' },
{ name: 'f' },
{ name: 'g' },
{ name: 'h' },
{ name: 'i' },
{ name: 'j' },
{ name: 'k' },
{ name: 'l' },
];
}
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: '100%',
backgroundColor: '#CED0CE',
}}
/>
);
};
searchItems = text => {
const newData = this.arrayNew.filter(item => {
const itemData = `${item.name.toUpperCase()}`;
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
this.setState({
data: newData,
value: text,
});
};
renderHeader = () => {
return (
<TextInput
style={{ height: 60, borderColor: '#000', borderWidth: 1 }}
placeholder=" Type Here...Key word"
onChangeText={text => this.searchItems(text)}
value={this.state.value}
/>
);
};
render() {
return (
<View
style={{
flex: 1,
padding: 25,
width: '98%',
alignSelf: 'center',
justifyContent: 'center',
}}>
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<Text style={{ padding: 10 }}>{item.name} </Text>
)}
keyExtractor={item => item.name}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
/>
</View>
);
}
}
export default FlatListDropDown;
Feel free for doubts.

vue managing form editing state, boilerplate code

I have a decent number of forms in my app, in which the user can choose to edit, revert, or save changes to the object, which are eventually saved to the backend.
Very similar to this: (code found in another question)
https://jsfiddle.net/k5j6zj9t/22/
var app = new Vue({
el: '#app',
data: {
isEditing: false,
user: {
firstName: 'John',
lastName: 'Smith',
}
},
mounted() {
this.cachedUser = Object.assign({}, this.user);
},
methods: {
save() {
this.cachedUser = Object.assign({}, this.user);
this.isEditing = false;
},
cancel() {
this.user = Object.assign({}, this.cachedUser);
this.isEditing = false;
}
}
})
Since v-model binding immediately changes the underlying object, I have to first create a clone of the object. Also I need to save a data member whether the object is in editing state.
Multiply this code for more forms and fields, and I end up with too much data members and a lot of boilerplate code.
In server frameworks like django, a model is in 'temporary state' until it is saved, so I can edit like this
user.first_name = 'aa' # temporary object in memory
user.save() # saved to the db
My question, is there a model component/pattern for vue to handle this task better?
Something that will hold the model state - i.e isEditing, automatically clone the object for form editing, revert the changes, etc.
So I won't have to write such code for so many objects?
Uses Scoped Slots may meet your requirements.
My solution:
Create one component with one slot
Then this slot will bind values with clonedValues (if closeMode is false, clondedValues = values)
finally, in parent component, generate your template with the properties of scoped slot, then pass it to the slot.
Like below demo:
Vue.component('child', {
template: `
<div>
<div>
<slot v-bind:values="clonedValues"></slot>
</div>
<p>
<button #click="saveAction(clonedValues)">Save</button>
<button #click="resetAction()">Reset</button>
</p>
</div>`,
props: {
'cloneMode': {
type: Boolean,
default: true
},
'values': {
type: Object,
default: () => { return new Object() }
},
'saveAction': {
type: Function,
default: function (newValues) {
this.$emit('save', newValues)
}
},
'resetAction': {
type: Function,
default: function () {
this.syncValues(this.values)
}
}
},
data() {
return {
clonedValues: {}
}
},
created: function () {
this.syncValues(this.values)
},
watch: {
values: {
handler: function (newVal) {
this.syncValues(newVal)
},
deep: true
},
cloneMode: function () {
this.syncValues(this.values)
}
},
methods: {
syncValues: function (newVal) {
this.clonedValues = this.cloneMode ? Object.assign({}, newVal) : newVal // if you'd like to support nested object, you have to deep clone
}
}
})
Vue.config.productionTip = false
app = new Vue({
el: "#app",
data: {
mode: true,
labels: ['id', 'name'],
childForm: {
'id': 1,
'name': 'test'
}
},
methods: {
saveForm: function (ev) {
Object.keys(this.childForm).forEach((item) => {
this.childForm[item] = ev[item]
})
// call backend to update the data
},
changeCurrentValue: function () {
this.childForm.id += '#'
this.childForm.name += '#'
}
}
})
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<p><button #click="mode=!mode">Mode: {{mode}}</button></p>
<p>Current: {{childForm}} --<button #click="changeCurrentValue()">Change Current</button></p>
<child :values="childForm" #save="saveForm($event)" :clone-mode="mode">
<template slot-scope="slotProps">
<p>ID: <input v-model="slotProps.values['id']"/></p>
<p>Name: <input v-model="slotProps.values['name']"/></p>
</template>
</child>
</div>
Edit for OP requested:
change default slot to named slot=edit, then create one slot=view
added data property=editing, if true, show 'Edit' slot, if false, show 'View' slot.
in parent component, design the template for slot=view.
Like below demo:
Vue.component('child', {
template: `
<div>
<div v-show="editing">
<slot name="edit" v-bind:values="clonedValues"></slot>
<button #click="saveForm(clonedValues)">Save</button>
<button #click="resetAction()">Reset</button>
</div>
<div v-show="!editing">
<slot name="view"></slot>
<button #click="editing = true">Edit</button>
</div>
</div>`,
props: {
'values': {
type: Object,
default: () => { return new Object() }
},
'saveAction': {
type: Function,
default: function (newValues) {
this.$emit('save', newValues)
}
},
'resetAction': {
type: Function,
default: function () {
this.syncValues(this.values)
}
}
},
data() {
return {
editing: false,
clonedValues: {}
}
},
created: function () {
this.syncValues(this.values)
},
watch: {
editing: function (newVal) {
if(newVal) this.syncValues(this.values)
},
values: {
handler: function (newVal) {
if(this.editing) this.syncValues(newVal) //comment out this if don't want to sync latest props=values
},
deep:true
}
},
methods: {
syncValues: function (newVal) {
this.clonedValues = Object.assign({}, newVal) // if you'd like to support nested object, you have to deep clone
},
saveForm: function (values) {
this.saveAction(values)
this.editing = false
}
}
})
Vue.config.productionTip = false
app = new Vue({
el: "#app",
data: {
childForm: {
'id': 1,
'name': 'test'
}
},
methods: {
saveForm: function (ev) {
Object.keys(this.childForm).forEach((item) => {
this.childForm[item] = ev[item]
})
// call backend to update the data
},
changeCurrentValue: function () {
this.childForm.id += '#'
this.childForm.name += '#'
}
}
})
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<p>Current: {{childForm}} --<button #click="changeCurrentValue()">Change Current</button></p>
<child :values="childForm" #save="saveForm($event)">
<template slot-scope="slotProps" slot="edit">
<h3>---Edit---</h3>
<p>ID: <input v-model="slotProps.values['id']"/></p>
<p>Name: <input v-model="slotProps.values['name']"/></p>
</template>
<template slot="view">
<h3>---View---</h3>
<p>ID: <span>{{childForm['id']}}</span></p>
<p>Name: <span>{{childForm['name']}}</span></p>
</template>
</child>
</div>

How to pass input value to form onSubmit without using state in component that renders multiple forms?

This is a bit of a longwinded problem and giving me a ton of headache to solve.
I'm making a voting app. On the page there will be a list of polls on which you can vote. Each poll is a form consisting of input radio buttons representing the different options available for that poll.
What I was doing previously was saving the option you choose to component state in this.state.value and then passing it as an argument to an action creator when the form is submitted.
Problem with this approach is that if I click an option of one poll, and then click submit on another poll, I've actually submitted the wrong option to the wrong poll.
Is there a way to pass input value to form onSubmit without storing it in component state?
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import Loading from '../Loading';
class MyPolls extends Component {
constructor(props) {
super(props);
this.state = {
skip: 0,
isLoading: true,
isLoadingMore: false,
value: ''
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
this.props.fetchMyPolls(this.state.skip)
.then(() => {
setTimeout(() => {
this.setState({
skip: this.state.skip + 4,
isLoading: false
});
}, 1000);
});
}
sumVotes(acc, cur) {
return acc.votes + cur.votes
}
loadMore(skip) {
this.setState({ isLoadingMore: true });
setTimeout(() => {
this.props.fetchMyPolls(skip)
.then(() => {
const nextSkip = this.state.skip + 4;
this.setState({
skip: nextSkip,
isLoadingMore: false
});
});
}, 1000);
}
handleSubmit(title, e) {
// console.log(e.target);
e.preventDefault();
const vote = {
title,
option: this.state.value
};
console.log(vote)
}
handleChange(event) {
this.setState({ value: event.target.value });
}
renderPolls() {
return this.props.polls.map(poll => {
return (
<div
className='card'
key={poll._id}
style={{ width: '350px', height: '400px' }}>
<div className='card-content'>
<span className='card-title'>{poll.title}</span>
<p>
Total votes: {poll.options.reduce((acc, cur) => { return acc + cur.votes }, 0)}
</p>
<form onSubmit={e => this.handleSubmit(poll.title, e)}>
{poll.options.map(option => {
return (
<p key={option._id}>
<input
name={poll.title}
className='with-gap'
type='radio'
id={option._id}
value={option.option}
onChange={this.handleChange}
/>
<label htmlFor={option._id}>
{option.option}
</label>
</p>
)
})}
<button
type='text'
className='activator teal btn waves-effect waves-light'
style={{
position: 'absolute',
bottom: '10%',
transform: 'translateX(-50%)'
}}
>
Submit
<i className='material-icons right'>
send
</i>
</button>
</form>
</div>
<div className='card-reveal'>
<span className='card-title'>{poll.title}
<i className='material-icons right'>close</i>
</span>
<p>
dsfasfasdf
</p>
</div>
</div>
)
})
}
render() {
return (
<div className='center-align container'>
<h2>My Polls</h2>
{this.state.isLoading ? <Loading size='big' /> :
<div
style={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-evenly',
alignItems: 'center',
alignContent: 'center'
}}>
{this.renderPolls()}
</div>}
<div className='row'>
{this.state.isLoadingMore ? <Loading size='small' /> :
<button
className='btn red lighten-2 wave-effect waves-light' onClick={() => this.loadMore(this.state.skip)}>
Load More
</button>}
</div>
</div>
);
}
}
function mapStateToProps({ polls }) {
return { polls }
}
export default connect(mapStateToProps, actions)(MyPolls);
App demo: https://voting-app-drhectapus.herokuapp.com/
(use riverfish#gmail.com and password 123 to login)
Github repo: https://github.com/drhectapus/voting-app
I'm open to any suggestions. Thanks!
The more "React'ish" pattern would be to break it down to more components.
a Poll is a component, a PollOption could be a component as well.
Where each can handle the state internally.
This will allow you to keep global state in your App or some other state manager like redux that will hold all of your polls and each can reference to the selected option (id).
Another thing worth pointing, is that you tend to pass a new function reference on each render call.
For example:
onSubmit={e => this.handleSubmit(poll.title, e)}
This is considered as bad practice because you can interfere with the Reconciliation and The Diffing Algorithm of react.
When you break it down to components that each can fire back a callback with its
props, then you don't need to pass the handler this way.
Here is a small example with your data:
const pollsFromServer = [
{
_id: "5a0d308a70f4b10014994490",
title: "Cat or Dog",
_user: "59f21388843e737de3738a3a",
__v: 0,
dateCreated: "2017-11-16T06:30:34.855Z",
options: [
{ option: "Cat", _id: "5a0d308a70f4b10014994492", votes: 0 },
{ option: "Dog", _id: "5a0d308a70f4b10014994491", votes: 0 }
]
},
{
_id: "5a0c7941e655c22b8cce43d7",
title: "Blonde or Brunette?",
_user: "59f21388843e737de3738a3a",
__v: 0,
dateCreated: "2017-11-15T17:28:33.909Z",
options: [
{ option: "Blonde", _id: "5a0c7941e655c22b8cce43d9", votes: 0 },
{ option: "Brunette", _id: "5a0c7941e655c22b8cce43d8", votes: 0 }
]
},
{
_id: "5a0c7924e655c22b8cce43d4",
title: "Coke or Pepsi",
_user: "59f21388843e737de3738a3a",
__v: 0,
dateCreated: "2017-11-15T17:28:04.119Z",
options: [
{ option: "Coke", _id: "5a0c7924e655c22b8cce43d6", votes: 0 },
{ option: "Pepsi", _id: "5a0c7924e655c22b8cce43d5", votes: 0 }
]
},
{
_id: "5a0c78c2e655c22b8cce43d0",
title: "Favourite german car?",
_user: "59f21388843e737de3738a3a",
__v: 0,
dateCreated: "2017-11-15T17:26:26.724Z",
options: [
{ option: "BMW", _id: "5a0c78c2e655c22b8cce43d3", votes: 0 },
{ option: "Mercedes", _id: "5a0c78c2e655c22b8cce43d2", votes: 0 },
{ option: "Audi", _id: "5a0c78c2e655c22b8cce43d1", votes: 0 }
]
}
];
class Poll extends React.Component {
onSubmit = optionId => {
const { pollId, onSubmit } = this.props;
onSubmit(pollId, optionId);
};
render() {
const { title, options, selectedOption } = this.props;
return (
<div>
<h3>{title}</h3>
<ul>
{options.map((o, i) => {
return (
<PollOption
isSelected={selectedOption === o._id}
onClick={this.onSubmit}
name={o.option}
optionId={o._id}
/>
);
})}
</ul>
</div>
);
}
}
class PollOption extends React.Component {
onClick = () => {
const { optionId, onClick } = this.props;
onClick(optionId);
};
render() {
const { name, isSelected } = this.props;
const selectedClass = isSelected ? "selected" : '';
return (
<li
className={`poll-option ${selectedClass}`}
onClick={this.onClick}
>
{name}
</li>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
polls: pollsFromServer,
submittedPolls: []
};
}
onPollSubmit = (pollId, optionId) => {
this.setState({
submittedPolls: {
...this.state.submittedPolls,
[pollId]: optionId
}
});
};
render() {
const { polls, submittedPolls } = this.state;
return (
<div>
{polls.map((p, i) => {
const selectedPoll = submittedPolls[p._id];
return (
<Poll
selectedOption={selectedPoll}
pollId={p._id}
onSubmit={this.onPollSubmit}
title={p.title}
options={p.options}
/>
);
})}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
.poll-option{
cursor: pointer;
display: inline-block;
box-shadow: 0 0 1px 1px #333;
padding: 15px;
}
.selected{
background-color: green;
color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>