I made an application in which the user passes coordinates. The function makes a request to the server according to the given coordinates and looks for the nearest available establishments. Further, the data is transferred to the formatter and finally to the state. This is what App.tsx looks like
//App.tsx
import React, { useEffect, useState } from "react";
import "./App.css";
import { useAppSelector } from "./hook";
import { useRequestPlaces } from "./hooks/index";
import { useAppDispatch } from "./hook";
const cities = [
{ name: "New York", latlong: "40.760898,-73.961219" },
{ name: "London", latlong: "51.522479,-0.104528" },
{ name: "London Suburb", latlong: "51.353340,-0.032366" },
{ name: "Desert", latlong: "22.941602,25.529665" },
];
const defaultLatlong = "40.760898,-73.961219";
function App() {
const dispatch = useAppDispatch();
const fetchPlaces = useRequestPlaces();
const { ids, entities } = useAppSelector((state) => state.places);
const [latlong, setLatlong] = useState(defaultLatlong);
const minRadius = 50;
useEffect(() => {
fetchPlaces(minRadius, latlong, dispatch);
console.log(entities);
}, [fetchPlaces, latlong, entities, ids]);
return (
<div className="App">
<header className="App-header">
{cities.map((city) => {
return (
<button
type="button"
className="btn btn-outline-light"
onClick={() => {
setLatlong(city.latlong);
console.log(latlong);
}}
>
{city.name}
</button>
);
})}
</header>
<main>
{ids.map((id, index) => {
const place = entities[id];
return (
<div
className="card mx-auto mt-2"
key={index}
style={{ width: "18rem" }}
>
<div className="card-body">
<h5 className="card-title">{place?.name}</h5>
<h6 className="card-subtitle mb-2 text-muted">
<ul>
{place?.categories.map((category) => {
return <li key={category.id}>{category.name}</li>;
})}
</ul>
</h6>
<p className="card-text">
Distance: {place?.distance} meters
<br />
Adress: {place?.location}
</p>
</div>
</div>
);
})}
</main>
</div>
);
}
export default App;
At this stage, the user transmits the coordinates by clicking on the buttons with cities. Next, the coordinates are passed to the API handler functions.
//fetch.ts
import { Dispatch } from "react";
import { getClosestPlaces } from "./getClosestPlaces";
import { placesActions } from "../../slices";
import { Action } from "redux";
import client from "./client";
const fetch = async (
radius: number,
latlong: string,
dispatch: Dispatch<Action>
) => {
const { fetchPlaces } = client();
const params = {
client_id: `${process.env.REACT_APP_CLIENT_ID}`,
client_secret: `${process.env.REACT_APP_CLIENT_SECRET}`,
ll: latlong,
radius: radius.toString(),
limit: "50",
};
const response = await fetchPlaces(new URLSearchParams(params).toString());
const { results } = response.data;
if (results.length !== 0) {
const closestPlaces = getClosestPlaces(results);
// AND HERE IS THE MAIN ISSUE! At this point all reqired data is ok it's an array of objects so I pass it to Action addPlaces which is addMany method.
dispatch(placesActions.addPlaces(closestPlaces));
} else if (results.length === 0 && radius < 1600) {
fetch(radius + 50, latlong, dispatch);
}
return [];
};
export { fetch };
And finally I want to show you Slice, where the method is stored. All the payloads are OK, but it doesn't work with updateMany ???
import {
createSlice,
EntityState,
createEntityAdapter,
} from "#reduxjs/toolkit";
import { FormattedPlace } from "./index";
import { RootState } from "./index";
import { Slice } from "#reduxjs/toolkit/src/createSlice";
import { SliceActions } from "#reduxjs/toolkit/dist/query/core/buildSlice";
const placesAdapter = createEntityAdapter<FormattedPlace>();
const initialState = placesAdapter.getInitialState();
type PlacesReducerActions = {
addPlaces(state: any, { payload }: { payload: any }): void;
};
export type PlacesSliceType = Slice<
EntityState<FormattedPlace>,
PlacesReducerActions,
"places"
>;
const placesSlice: PlacesSliceType = createSlice({
name: "places",
initialState,
reducers: {
addPlaces(state, { payload }) {
// HERE
placesAdapter.updateMany(state, payload);
},
},
});
export const selectors = placesAdapter.getSelectors<RootState>(
(state) => state.places
);
export const { actions } = placesSlice;
export default placesSlice.reducer;
Problem was solved with method setAll. I’m stupid, cause didn’t realise that method updateMany updates only those entities which had been added to state before. So if you want to rewrite your state totally use setAll()
I'm facing an issue with react-quill lib where the component calls itself or I don't know what it calls but I see it on the console, over 500 times and freezes my screen until the loops are done.
Here is the component:
//#ts-nocheck
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
import './Editor.local.css';
const Editor = ({ method, ...props }: any): any => {
return (
<pre style={{ width: '100%', marginBottom: '5em' }}>
<label className="llabel">{props.label}</label>
<ReactQuill
style={{ height: '420px', borderRadius: '0px 0px 8px 8px' }}
value={props.value}
onChange={method}
modules={Editor.modules}
formats={Editor.formats}
bounds={'#root'}
placeholder={props.placeholder}
/>
</pre>
);
};
Editor.modules = {
toolbar: [
[{ header: [1, 2, 3, 4, 5, 6, false] }],
['bold', 'italic', 'underline'],
[{ list: 'ordered' }, { list: 'bullet' }],
['link'],
['clean']
],
clipboard: {
matchVisual: false,
},
};
export default Editor;
And here Is how i map it and call it multiple times based on the list length:
//#ts-nocheck
import { Item } from '../..';
import { WorkItemValidation } from '../../../../utils/Validation';
import { Date, Input } from '../../../Shared';
import { Editor } from '../../..';
import { Items } from '../Components';
import { Container, Liner, Linerier } from './Components'
import { useState } from 'react';
import { Employment as Work } from '../../../../operations/Resume';
const Employment: any = (props: any) => {
const [isDatePickerOpen, setIsDatePickerOpen] = useState(false);
const handleDescription = (e: any, index: number) => Work.Description(props, index, e);
return (
<Container>
<Items>
{
props.resume.employmentHistory.positions.map(
(position: any, index: number) => {
const title = Work.Show(
position.jobTitle || '',
position.employerName || '',
position.startDate.date || '',
position.endDate.date || '',
position.isCurrent || false
);
const isBoxError = WorkItemValidation(
position.employerName,
position.jobTitle,
position.startDate.date,
position.endDate.date,
position.isCurrent
);
return (
<Item>
<Editor
label="Description"
index={index}
value={position.description && position.description || ''}
placeholder="Brief description of the role..."
method={(e) => handleDescription(e, index)}
/>
</Item>
)
}
)
}
</Items>
</Container >
)
}
export default Employment;
So i don't know what or why, but when i console log inside the editor or the loop i see that this is called many times, over 500 or more or less based on the length of the array and the description.
I used an editable data Table of prime-react , and it worked mouse click. But Problem is tab key not work.I want to enable editable mode one cell to another cell using tab key. I use Prime react version 1.4.0
A full code are given below:-
index.js
where my editable table code contains
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import { createStructuredSelector } from 'reselect';
import { compose } from 'redux';
import injectSaga from 'utils/injectSaga';
import injectReducer from 'utils/injectReducer';
import reducer from './reducer';
import saga from './saga';
import messages from './messages';
import { Messages } from 'primereact/components/messages/Messages';
import { Growl } from 'primereact/components/growl/Growl';
import { Panel } from 'primereact/components/panel/Panel';
import { Button } from 'primereact/components/button/Button';
import { DataTable } from 'primereact/components/datatable/DataTable';
import { Column } from 'primereact/components/column/Column';
import { InputText } from 'primereact/components/inputtext/InputText';
import { Dropdown } from 'primereact/components/dropdown/Dropdown';
import CustomDataTable from 'components/CustomDataTable';
import { makeSelectSelectedStudent, makeSelectSectionName, makeSelectStudentUpdateBasicInformation, makeSelectSectionList, makeSelectStdBasicInfo, makeSelectEditorStudent, makeSelectSetMessage, makeSelectSetErrMessage, makeSelectLoaderOff, makeSelectLoaderOn } from './selectors';
import { selectStudent, setEditorData, submitUpdate, getMessage, getErrMessage, submitForm, changeSectionName, getLoaderOn, getLoaderOff } from './actions';
import AppPrivateLayout from '../AppPrivateLayout';
export class StudentUpdateBasicInformation extends React.Component {
componentDidUpdate() {
this.props.changeMessage('');
}
rollBody(rowData) {
return <InputText type="text" value={rowData.studentRoll} />;
}
nameBody(rowData) {
return <InputText type="text" value={rowData.studentName} />;
}
fatherNameBody(rowData) {
return <InputText type="text" value={rowData.fatherName} />;
}
motherNameBody(rowData) {
return <InputText type="text" value={rowData.motherName} />;
}
contactBody(rowData) {
return <InputText type="text" value={rowData.guardianMobile} />;
}
genderBody(rowData) {
let gender = [
{ label: 'Male', value: 'Male' },
{ label: 'Female', value: 'Female' },
{ label: 'Others', value: 'Others' }
];
return <Dropdown value={rowData.studentGender} options={gender} style={{ width: '100%' }} placeholder="Select" />
}
religionBody(rowData) {
let religion = [
{ label: 'Islam', value: 'Islam' },
{ label: 'Hindu', value: 'Hindu' },
{ label: 'Buddhist', value: 'Buddhist' },
{ label: 'Christian', value: 'Christian' },
{ label: 'Others', value: 'Others' }
];
return <Dropdown value={rowData.studentReligion} options={religion} style={{ width: '100%' }} placeholder="Select" />
}
bloodBody(rowData) {
let blood = [
{ label: 'A+', value: 'A+' },
{ label: 'A-', value: 'A-' },
{ label: 'B+', value: 'B+' },
{ label: 'B-', value: 'B-' },
{ label: 'O+', value: 'O+' },
{ label: 'O-', value: 'O-' }
];
return <Dropdown value={rowData.bloodGroup} options={blood} style={{ width: '100%' }} placeholder="Select" />
}
render() {
let rollEditor = (row) => {
return <InputText onChange={(evt) => { this.props.onEditorValueChange(row, evt.target.value); }} placeholder="Roll" value={row.rowData.studentRoll} />
}
let nameEditor = (row) => {
return <InputText onChange={(evt) => { this.props.onEditorValueChange(row, evt.target.value); }} placeholder="Student name" value={row.rowData.studentName} />
}
let fatherNameEditor = (row) => {
return <InputText onChange={(evt) => { this.props.onEditorValueChange(row, evt.target.value); }} placeholder="Father name" value={row.rowData.fatherName} />
}
let motherNameEditor = (row) => {
return <InputText onChange={(evt) => { this.props.onEditorValueChange(row, evt.target.value); }} placeholder="Mother name" value={row.rowData.motherName} />
}
let contactEditor = (row) => {
return <InputText onChange={(evt) => { this.props.onEditorValueChange(row, evt.target.value); }} placeholder="Contact Number" value={row.rowData.guardianMobile} />
}
let genderEditor = (row) => {
let gender = [
{ label: 'Male', value: 'Male' },
{ label: 'Female', value: 'Female' },
{ label: 'Others', value: 'Others' }
];
return <Dropdown value={row.rowData.gender} options={gender} onChange={(evt) => this.props.onEditorValueChange(row, evt.value)} style={{ width: '100%' }} placeholder="Select" />
}
let religionEditor = (row) => {
let religion = [
{ label: 'Islam', value: 'Islam' },
{ label: 'Hindu', value: 'Hindu' },
{ label: 'Buddhist', value: 'Buddhist' },
{ label: 'Christian', value: 'Christian' },
{ label: 'Others', value: 'Others' }
];
return <Dropdown value={row.rowData.religion} options={religion} onChange={(evt) => { this.props.onEditorValueChange(row, evt.value); }} style={{ width: '100%' }} placeholder="Select" />
}
let bloodEditor = (row) => {
let blood = [
{ label: 'A+', value: 'A+' },
{ label: 'A-', value: 'A-' },
{ label: 'B+', value: 'B+' },
{ label: 'B-', value: 'B-' },
{ label: 'O+', value: 'O+' },
{ label: 'O-', value: 'O-' }
];
return <Dropdown value={row.rowData.blood} options={blood} onChange={(evt) => { this.props.onEditorValueChange(row, evt.value); }} style={{ width: '100%' }} placeholder="Select" />
}
let msg = "";
if (this.props.setMessage) {
msg = { severity: 'success', detail: this.props.setMessage.message };
this.growl.show(msg);
}
else if (this.props.setErrMessage) {
msg = { severity: 'error', summary: 'Failed', detail: this.props.setErrMessage };
this.growl.show(msg);
}
if(this.props.loaderOn){
if(this.props.loaderOn === 'On') {
$('.loaderDiv').show();
} else if(this.props.loaderOn === 'Off'){
$('.loaderDiv').hide();
}
}
let content = '';
if (this.props.stdBasicInfo && this.props.stdBasicInfo.length) {
$('#UpdateBtnID').show();
let selectedStudentArr = [];
if (this.props.selectedStudent.length) {
Array.prototype.push.apply(selectedStudentArr, this.props.selectedStudent);
}
let columnData = [
<Column selectionMode="multiple" header="Mark" style={{ width: '3em' }} />,
<Column field="studentRoll" header="Roll No." editor={rollEditor} body={this.rollBody} style={{ width: '55px' }} />,
<Column field="studentName" header="Name" editor={nameEditor} body={this.nameBody} style={{ width: '170px' }} />,
<Column field="fatherName" header="Father Name" editor={fatherNameEditor} body={this.fatherNameBody} style={{ width: '145px' }} />,
<Column field="motherName" header="Mother Name" editor={motherNameEditor} body={this.motherNameBody} style={{ width: '145px' }} />,
<Column field="guardianMobile" header="Contact No." editor={contactEditor} style={{ width: '100px' }} body={this.contactBody} />,
<Column field="studentGender" header="Gender" editor={genderEditor} body={this.genderBody} style={{ width: '85px' }} />,
<Column field="studentReligion" header="Religion" editor={religionEditor} body={this.religionBody} style={{ width: '85px' }} />,
<Column field="bloodGroup" header="Blood Group" editor={bloodEditor} style={{ width: '80px' }} body={this.bloodBody} />
];
content = <CustomDataTable
info={this.props.stdBasicInfo}
onSelectionChange={this.props.onSelectionChange}
selectedData={selectedStudentArr}
columnData={columnData}
isSelectionOn={true}
editable={true}
header={'Student List'}
rows={10}
/>
}
//FOR SECTION LIST
let sectionListOptions = [];
if (this.props.sectionList && this.props.sectionList.length) {
sectionListOptions = this.props.sectionList.map((item) => ({
value: item.classConfigId,
label: item.classShiftSection,
}))
}
return (
<div>
<AppPrivateLayout>
<Panel header="Student Information Update">
<form method="post" onSubmit={this.props.onSubmitForm} >
<div className='ui-g form-group'>
<div className='ui-g-2 ui-lg-2 ui-md-2'></div>
<div className='ui-g-2 ui-lg-2 ui-md-2 ui-sm-12 netiLabel'>
<label> Section <span className="required"> * </span></label>
</div>
<div className='ui-g-3 ui-lg-3 ui-md-4 ui-sm-12 ui-fluid'>
<Dropdown value={this.props.sectionname} onChange={this.props.onChangeSectionList} options={sectionListOptions} placeholder="Select Section" autoWidth={false} />
</div>
<div className='ui-g-2 ui-lg-2 ui-md-3 ui-sm-12 ui-fluid'>
<Button icon="ui-icon-search" title="Search" label='Search'></Button>
</div>
<div className='ui-g-2 ui-lg-2 ui-fluid'></div>
</div>
</form>
{content}
<div className='ui-g'>
<Growl ref={(el) => this.growl = el} />
<div className='ui-g-4 ui-lg-4 ui-md-4 ui-sm-12 ui-fluid'>
</div>
<div className='ui-g-6 ui-lg-6 ui-md-5 ui-sm-12 ui-fluid'></div>
<div className='ui-g-2 ui-lg-2 ui-md-3 ui-sm-12 ui-fluid'>
<Button id="UpdateBtnID" style={{ display: 'none' }} onClick={this.props.onUpdate} icon='ui-icon-autorenew' label='Update'></Button>
</div>
</div>
</Panel>
<div class="loaderDiv" style={{display: 'none'}}>
<img className="sticky" src="https://loading.io/spinners/harmony/lg.harmony-taiji-spinner.gif" />
</div>
</AppPrivateLayout>
</div>
);
}
}
StudentUpdateBasicInformation.propTypes = {
stdBasicInfo: PropTypes.any,
onSubmitForm: PropTypes.func,
onEditorValueChange: PropTypes.func,
value: PropTypes.any,
onUpdate: PropTypes.func,
setMessage: PropTypes.any,
setErrMessage: PropTypes.any,
changeMessage: PropTypes.func,
sectionList: PropTypes.any,
onChangeSectionList: PropTypes.func,
sectionname: PropTypes.any,
loaderOn: PropTypes.any,
};
const mapStateToProps = createStructuredSelector({
stdBasicInfo: makeSelectStdBasicInfo(),
selectedStudent: makeSelectSelectedStudent(),
value: makeSelectEditorStudent(),
setMessage: makeSelectSetMessage(),
setErrMessage: makeSelectSetErrMessage(),
sectionList: makeSelectSectionList(),
sectionname: makeSelectSectionName(),
loaderOn: makeSelectLoaderOn(),
});
function mapDispatchToProps(dispatch) {
return {
changeMessage: (evt) => {
dispatch(getMessage());
dispatch(getErrMessage());
dispatch(getLoaderOn(evt));
},
onSubmitForm: (evt) => {
if (evt !== undefined && evt.preventDefault)
evt.preventDefault();
dispatch(submitForm());
},
onSelectionChange: (evt) => dispatch(selectStudent(evt.data)),
onEditorValueChange: (row, value) => {
dispatch(setEditorData(row, value));
},
onUpdate: (evt) => dispatch(submitUpdate()),
onChangeSectionList: (evt) => dispatch(changeSectionName(evt.value)),
};
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);
const withReducer = injectReducer({ key: 'studentUpdateBasicInformation', reducer });
const withSaga = injectSaga({ key: 'studentUpdateBasicInformation', saga });
export default compose(
withReducer,
withSaga,
withConnect,
)(StudentUpdateBasicInformation);
constants.js
export const DEFAULT_ACTION = 'app/StudentUpdateBasicInformation/DEFAULT_ACTION';
export const SUBMIT_FORM = 'app/StudentUpdateBasicInformation/SUBMIT_FORM';
export const SET_STD_BASIC_INFO = 'app/StudentUpdateBasicInformation/SET_STD_BASIC_INFO';
export const SELECT_STUDENT = 'app/StudentUpdateBasicInformation/SELECT_STUDENT';
export const SET_EDITOR_DATA = 'app/StudentUpdateBasicInformation/SET_EDITOR_DATA';
export const GET_MESSAGE = 'app/StudentUpdateStudentId/GET_MESSAGE';
export const GET_ERR_MESSAGE = 'app/StudentUpdateStudentId/GET_ERR_MESSAGE';
export const SUBMIT_UPDATE = 'app/StudentUpdateBasicInformation/SUBMIT_UPDATE';
export const SET_SECTION_LIST = 'app/StudentUpdateBasicInformation/SET_SECTION_LIST';
export const CHANGE_SECTIONNAME = 'app/StudentUpdateBasicInformation/CHANGE_SECTIONNAME';
export const GET_LOADER_ON = 'app/StudentUpdateBasicInformation/GET_LOADER_ON';
actions.js
import {
DEFAULT_ACTION, SUBMIT_FORM, SET_STD_BASIC_INFO, SELECT_STUDENT, SET_EDITOR_DATA, SUBMIT_UPDATE, GET_MESSAGE, GET_ERR_MESSAGE, CHANGE_SECTIONNAME, SET_SECTION_LIST, GET_LOADER_OFF, GET_LOADER_ON
} from './constants';
export function defaultAction() {
return {
type: DEFAULT_ACTION,
};
}
export function setStdBasicInfo(item) {
return {
type: SET_STD_BASIC_INFO,
item,
}
}
export function selectStudent(data) {
return {
type: SELECT_STUDENT,
data,
};
}
export function setEditorData(row, value) {
return {
type: SET_EDITOR_DATA,
row,
value,
};
}
export function submitForm() {
return {
type: SUBMIT_FORM,
};
}
export function submitUpdate() {
return {
type: SUBMIT_UPDATE,
};
}
export function getMessage(message) {
return {
type: GET_MESSAGE,
message,
}
}
export function getErrMessage(errmessage) {
return {
type: GET_ERR_MESSAGE,
errmessage,
}
}
export function setSectionList(sectionList) {
return {
type: SET_SECTION_LIST,
sectionList,
};
}
export function changeSectionName(sectionname) {
return {
type: CHANGE_SECTIONNAME,
sectionname,
};
}
export function getLoaderOn(loaderOn){
return{
type: GET_LOADER_ON,
loaderOn,
}
}
reducer.js
import { fromJS } from 'immutable';
import {
DEFAULT_ACTION, SET_STD_BASIC_INFO, SELECT_STUDENT, SET_EDITOR_DATA, GET_MESSAGE, GET_ERR_MESSAGE, SET_SECTION_LIST, CHANGE_SECTIONNAME, GET_LOADER_ON, GET_LOADER_OFF
} from './constants';
const initialState = fromJS({
selectedStudent: [],
value: [],
sectionList: {},
sectionname: '',
});
function studentUpdateBasicInformationReducer(state = initialState, action) {
switch (action.type) {
case DEFAULT_ACTION:
return state;
case SET_STD_BASIC_INFO:
return state.set('stdBasicInfo', action.item);
case SELECT_STUDENT:
return state.set('selectedStudent', action.data);
case SET_EDITOR_DATA:
let updatedInfost = [...action.row.value];
updatedInfost = [...action.row.value];
updatedInfost[action.row.rowIndex][action.row.field] = action.value;
return state.set('stdBasicInfo', updatedInfost);
case GET_MESSAGE:
return state.set('setMessage', action.message);
case GET_ERR_MESSAGE:
return state.set('setErrMessage', action.errmessage);
case SET_SECTION_LIST:
return state.set('sectionList', action.sectionList);
case CHANGE_SECTIONNAME:
return state.set('sectionname', action.sectionname);
case GET_LOADER_ON:
return state.set('loaderOn', action.loaderOn);
// case GET_LOADER_OFF:
// return state.set('loaderOff', action.loaderOff);
default:
return state;
}
}
export default studentUpdateBasicInformationReducer;
selectors.js
import { createSelector } from 'reselect';
const selectStudentUpdateBasicInformationDomain = (state) => state.get('studentUpdateBasicInformation');
const makeSelectStudentUpdateBasicInformation = () => createSelector(
selectStudentUpdateBasicInformationDomain,
(substate) => substate.toJS()
);
const makeSelectStdBasicInfo = () => createSelector(selectStudentUpdateBasicInformationDomain, (abc) => abc.get('stdBasicInfo'));
const makeSelectSelectedStudent = () => createSelector(selectStudentUpdateBasicInformationDomain, (substate) => substate.get('selectedStudent'));
const makeSelectEditorStudent = () => createSelector(selectStudentUpdateBasicInformationDomain, (substate) => substate.get('value'));
const makeSelectSetMessage = () => createSelector(selectStudentUpdateBasicInformationDomain, (abc) => abc.get('setMessage'));
const makeSelectSetErrMessage = () => createSelector(selectStudentUpdateBasicInformationDomain, (abc) => abc.get('setErrMessage'));
const makeSelectSectionName = () => createSelector(selectStudentUpdateBasicInformationDomain, (abc) => abc.get('sectionname'));
const makeSelectSectionList = () => createSelector(selectStudentUpdateBasicInformationDomain,(substate) => substate.get('sectionList'));
const makeSelectLoaderOn = () => createSelector(selectStudentUpdateBasicInformationDomain, (substate) => substate.get('loaderOn'));
export {
selectStudentUpdateBasicInformationDomain,
makeSelectStdBasicInfo,
makeSelectSelectedStudent,
makeSelectEditorStudent,
makeSelectSetMessage,
makeSelectSetErrMessage, makeSelectSectionName, makeSelectSectionList, makeSelectLoaderOn,
//makeSelectLoaderOff
};
saga.js
import { take, call, put, select, takeLatest } from 'redux-saga/effects';
import { BASE_URL, FETCH_BASIC_INFO_LIST, UPDATE_ID, GET_CLASS_CONFIGURATION_URL, STUDENT_CONFIG_LIST } from '../../utils/serviceUrl';
import { setStdBasicInfo, getMessage, getErrMessage, selectStudent, setSectionList, changeSectionName, getLoaderOn } from './actions';
import request from '../../utils/request';
import { SUBMIT_FORM, SUBMIT_UPDATE } from './constants';
import { makeSelectEditorStudent, makeSelectSelectedStudent, makeSelectSectionList, makeSelectSectionName } from './selectors';
import { getTokenData } from '../../utils/authHelper';
//FOR SECTION LIST
export function* fetchSectionList() {
const tokenData = JSON.parse(getTokenData());
const requestUrl = BASE_URL.concat(GET_CLASS_CONFIGURATION_URL);
const options = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': tokenData.token_type+" "+tokenData.access_token,
},
};
try {
const response = yield call(request, requestUrl, options);
if (response.item) {
yield put(setSectionList(response.item));
}
} catch (err) {
console.dir(err);
}
}
//FOR STUDENT BASIC INFO LIST
export function* fetchStudentBasicInfoList() {
const tokenData = JSON.parse(getTokenData());
const classConfigId = yield select(makeSelectSectionName());
let msgg;
if (classConfigId == '') {
let msg = "An error has occured. Please fill up all required fields";
yield put(getErrMessage(msg));
}
else {
msgg = 'On';
yield put(getLoaderOn(msgg));
const requestURL = BASE_URL.concat(STUDENT_CONFIG_LIST).concat('?classConfigId=').concat(classConfigId);
const options = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': tokenData.token_type+" "+tokenData.access_token,
},
};
const info = yield call(request, requestURL,options);
yield put(setStdBasicInfo(info.item));
msgg = 'Off';
yield put(getLoaderOn(msgg));
}
}
//FOR UPDATE STUDENT INFORMATION
export function* updateStdBasicInfo() {
const tokenData = JSON.parse(getTokenData());
const selectedCheckData = yield select(makeSelectSelectedStudent());
let selectedData = [];
if (selectedCheckData.length === undefined || selectedCheckData.length === 0) {
const errresult = "An error has occured. Please fill up all required fields";
yield put(getErrMessage(errresult));
} else {
for (const i in selectedCheckData) {
const DataList = selectedCheckData[i];
selectedData.push(DataList);
}
const requestURL = BASE_URL.concat(UPDATE_ID);
const options = {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': tokenData.token_type+" "+tokenData.access_token,
},
body: JSON.stringify(selectedData),
}
try {
const result = yield call(request, requestURL, options);
yield put(selectStudent([]));
yield fetchStudentBasicInfoList();
yield put(getMessage(result));
} catch (err) {
const errresult = "Something went wrong. Please try again.";
yield put(setStdBasicInfo(info.item));
yield put(getErrMessage(errresult));
}
}
}
export default function* defaultSaga() {
yield fetchSectionList();
yield takeLatest(SUBMIT_FORM, fetchStudentBasicInfoList);
yield takeLatest(SUBMIT_UPDATE, updateStdBasicInfo);
}
Dušan's answer worked for me but for 2 exceptions:
In onEditorKeyDown instead of
var table = this.dt.container.childNodes[1].childNodes[0].childNodes[1];
use
let table = this.dt.container.childNodes[0].childNodes[0].childNodes[1];
In addition, (event.which === 9) should be used instead of (event.keyCode == 9) and all the functions must be bound in the constructor:
this.inputTextEditor = this.inputTextEditor.bind(this)
this.xxxEditor = this.xxxEditor.bind(this)
this.onEditorKeyDown = this.onEditorKeyDown.bind(this)
I have an idea how you can do this:
while you are in editable cell, you need to intercept TAB key and then to simulate click on next cell to the right or, if current cell is the last one, on first cell in next row.
Steps in following example are based on PrimeReact's show case editable page. You can adopt them for your particular case.
FUNCTIONAL EXAMPLE
Step 1:
define onRowClickhandler and add it to DataTable component to be able to catch currently clicked rowIndex (we need to know what is the row index of the cell currently edited)
onRowClick(event) {
this.rowIndex = event.index;
}
...
<DataTable ref={(el) => this.dt = el} editable={true} onRowClick={(e) => this.onRowClick(e)} ...>
Step 2:
Define column index (ordinal number) to each of column editors: 1st column has index 0, 2nd column index 1, etc. For example
vinEditor(props) {
return this.inputTextEditor(props, 'vin', 0);
}
yearEditor(props) {
return this.inputTextEditor(props, 'year', 1);
}
brandEditor(props) {
return this.inputTextEditor(props, 'brand', 2);
}
where inputTextEditor now look like this
inputTextEditor(props, field, columnIndex) {
return <InputText type="text" value={props.rowData.year}
onKeyDown={(e) => this.onEditorKeyDown(e, columnIndex)} onChange={(e) => this.onEditorValueChange(props, e.target.value)} />;
}
Note that I've added onKeyDown handler and passed columnIndex arg to it so that we can recognize the column where some key is typed.
Step 3:
Finally we can define onEditorKeyDown to do the magic (check out code comments for additional explanations)
onEditorKeyDown(event, columnIndex) {
console.log("onKeyDown", event);
console.log("key code", event.keyCode);
console.log("columnIndex", columnIndex);
console.log("rowIndex", this.rowIndex);
//change following 2 constants to to fit your case
const columnCount = 4;
const rowCount = 10;
//check if TAB (key code is 9) is pressed on InputText
if (event.keyCode == 9) {
//prevent default behaviour on TAB press
event.preventDefault();
//this.dt is reference to DataTable element
//get reference to `table` node only
var table = this.dt.container.childNodes[1].childNodes[0].childNodes[1];
//check if we are not in last column
if (columnIndex < columnCount - 1) {
//simulate click on next column
table.childNodes[this.rowIndex].childNodes[columnIndex + 1].click();
} else {
//we are in the last column, check if we are not in last row
if (this.rowIndex < rowCount - 1) {
//we are not in the last row
// select next row
this.rowIndex += 1;
//simulate click on first column in next row
table.childNodes[this.rowIndex].childNodes[0].click();
}
}
}
}
How to enable the circular progress when user clicks on submit on the login page? I can able to see the loader symbol in the app bar on other pages but I'm not able to activate it on the login page.
We need to add Custom reducer for login page. I did it in the following way.
1.1. Create a new login page. Just copy and paste the admin-on-rest login page code.
1.2. Update the propTypes like below
Login.propTypes = {
...propTypes,
authClient: PropTypes.func,
previousRoute: PropTypes.string,
theme: PropTypes.object.isRequired,
translate: PropTypes.func.isRequired,
userLogin: PropTypes.func.isRequired,
isLogging: PropTypes.bool.isRequired,
};
1.3. Add the below line
function mapStateToProps(state, props) {
return {
isLogging: state.loginReducer > 0
};
}
1.4. Update the login page with below code.
const enhance = compose(
translate,
reduxForm({
form: 'signIn',
validate: (values, props) => {
const errors = {};
const { translate } = props;
if (!values.username) errors.username = translate('aor.validation.required');
if (!values.password) errors.password = translate('aor.validation.required');
return errors;
},
}),
connect(mapStateToProps, { userLogin: userLoginAction }),
);
export default enhance(Login);
1.5. Replace the submit button code
<CardActions>
<RaisedButton type="submit" primary disabled={isLogging} icon={isLogging && <CircularProgress size={25} thickness={2} />} label={translate('aor.auth.sign_in')} fullWidth />
</CardActions>
1.6 The complete code for the login page is
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { propTypes, reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import { Card, CardActions } from 'material-ui/Card';
import Avatar from 'material-ui/Avatar';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
import CircularProgress from 'material-ui/CircularProgress';
import { cyan500, pinkA200, white } from 'material-ui/styles/colors';
import defaultTheme, {translate, Notification, userLogin as userLoginAction } from 'admin-on-rest';
const styles = {
main: {
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
alignItems: 'center',
justifyContent: 'center',
},
card: {
minWidth: 300,
},
avatar: {
margin: '1em',
textAlign: 'center ',
},
avatarText:{
verticalAlign:'middle',
fontSize:20,
},
form: {
padding: '0 1em 1em 1em',
},
input: {
display: 'flex',
},
};
function getColorsFromTheme(theme) {
if (!theme) return { primary1Color: cyan500, accent1Color: pinkA200 };
const {
palette: {
primary1Color,
accent1Color,
},
} = theme;
return { primary1Color, accent1Color };
}
// see http://redux-form.com/6.4.3/examples/material-ui/
const renderInput = ({ meta: { touched, error } = {}, input: { ...inputProps }, ...props }) =>
<TextField
errorText={touched && error}
{...inputProps}
{...props}
fullWidth
/>;
class Login extends Component {
login = (auth) => this.props.userLogin(auth, this.props.location.state ? this.props.location.state.nextPathname : '/');
render() {
const { handleSubmit, submitting, theme, translate, isLogging } = this.props;
const muiTheme = getMuiTheme(theme);
const { primary1Color } = getColorsFromTheme(muiTheme);
return (
<MuiThemeProvider muiTheme={muiTheme}>
<div style={{ ...styles.main, backgroundColor: primary1Color }}>
<Card style={styles.card}>
<div style={styles.avatar}>
<div>
<Avatar backgroundColor={white} src="EnsembleGreenLogo.png" size={45} />
</div>
<div>
<span style={styles.avatarText}>Ensemble SmartWAN Manager</span>
</div>
</div>
<form onSubmit={handleSubmit(this.login)}>
<div style={styles.form}>
<div style={styles.input} >
<Field
name="username"
component={renderInput}
floatingLabelText={translate('aor.auth.username')}
disabled={submitting}
/>
</div>
<div style={styles.input}>
<Field
name="password"
component={renderInput}
floatingLabelText={translate('aor.auth.password')}
type="password"
disabled={submitting}
/>
</div>
</div>
<CardActions>
<RaisedButton
type="submit"
primary
disabled={isLogging}
icon={isLogging && <CircularProgress size={25} thickness={2} />}
label={translate('aor.auth.sign_in')}
fullWidth
/>
</CardActions>
</form>
</Card>
<Notification />
</div>
</MuiThemeProvider>
);
}
}
Login.propTypes = {
...propTypes,
authClient: PropTypes.func,
previousRoute: PropTypes.string,
theme: PropTypes.object.isRequired,
translate: PropTypes.func.isRequired,
userLogin: PropTypes.func.isRequired,
isLogging: PropTypes.bool.isRequired,
};
Login.defaultProps = {
theme: defaultTheme,
};
function mapStateToProps(state, props) {
return {
isLogging: state.loginReducer > 0
};
}
const enhance = compose(
translate,
reduxForm({
form: 'signIn',
validate: (values, props) => {
const errors = {};
const { translate } = props;
if (!values.username) errors.username = translate('aor.validation.required');
if (!values.password) errors.password = translate('aor.validation.required');
return errors;
},
}),
connect(mapStateToProps, { userLogin: userLoginAction }),
);
export default enhance(Login);
2.1. Add a new file (src/loginReducer.js) in src folder with the below content
import { USER_LOGIN_LOADING, USER_LOGIN_SUCCESS, USER_LOGIN_FAILURE, USER_CHECK } from 'admin-on-rest';
export default (previousState = 0, { type }) => {
switch (type) {
case USER_LOGIN_LOADING:
return previousState + 1;
case USER_LOGIN_SUCCESS:
case USER_LOGIN_FAILURE:
case USER_CHECK:
return Math.max(previousState - 1, 0);
default:
return previousState;
}
};
3.1 Update the app.js admin tag.
<Admin
menu={createMenus}
loginPage={Login}
dashboard={Dashboard}
appLayout={Layout}
customReducers={{ loginReducer }}
>
3.2 import the login page and login reducers in app.js
import loginReducer from './loginReducer';
import Login from "./Login";