How to avoid making a double call to the service in react - javascript

The code makes a call to the API that will get me the races of a track through an event that captures its TrackID and gets the respective races of that track, but I need that data to persist, since when I click on the track more than once calls again in the service which is not optimal.
**Method with API call API**
raceTrackRequest(data){
this.getConfiguration();
const url = this.baseURL + 'BusRace/GetRacesTrack';
const requestJSON = {
DGSReq: {
InParams: {
LogonSessionID: 1,
ProfileID: 1,
TrackID: data.id,
RaceDate: this.actualDate
}
}
};
axios.post(url, requestJSON)
.then((response) => {
AppDispatcher.dispatch({
type: ActionTypes.GET_RACE_TRACK,
data: {...response.data.DGSResp, ...{TrackID: data.id}}
});
})
.catch((error) => {
console.log(error);
});
}
**Store **
getInitialState() {
return {
racesTrack: {
Races: []
}
};
}
reduce(state, action) {
let newState = Object.assign({}, state);
switch (action.type) {
case ActionTypes.GET_RACE_TRACK:
newState.selectedOptions.track = action.data.TrackID;
newState.racesTrack = action.data;
break;
default:
newState = state;
}
return newState;
}
**Component**
static getStores() {
return [ConfigStore, PlayerStore];
};
static calculateState() {
const data = ConfigStore.getState();
return {
data: data
};
};
componentDidMount() {
let params = {...this.state, ...{actionType: ActionTypes.LOAD_CONFIG}};
ActionCreators.actionTrigger(params);
}
handleClick(actionType, id, name, post, distance, purse, surface, trackcond, WagerTypes, entry) {
const params = {
actionType: actionType,
id: id,
name,
post: post,
distance: distance,
purse: purse,
surface: surface,
trackcond: trackcond,
WagerTypes: WagerTypes,
entry: entry
};
ActionCreators.actionTrigger(params);
this.setState ({
raceID: id,
name,
post: post,
distance: distance,
purse: purse,
surface: surface,
trackcond: trackcond,
WagerTypes: WagerTypes,
render: entry
});
};
render() {
const races = this.state.data.racesTrack.Races;
const racesList = [];
if(races){
races.forEach((race) => {
racesList.push(
<span className="badge" style={{cursor: 'pointer'}} key={race['race-id']} onClick={() =>
this.handleClick(ActionTypes.GET_RACE_ENTRIES,
race['race-id'],
race['race-desc'],
race['post-time'],
race['distance'],
race['purse'],
race['surface'],
race['trackCond'],
race['WagerTypes']
)}>{race['race-number']}
</span>
);
});
}
return (
<div className={styles.container}>
</div>
);
}
}
**Component tracks who calls racesTrack**
class TracksBar extends Component {
static getStores() {
return [ConfigStore];
};
static calculateState() {
const configData = ConfigStore.getState();
return {
configInfo: configData
};
};
handleClick(actionType, id) {
const params = {actionType: actionType, id: id};
ActionCreators.actionTrigger(params);
};
render() {
const showTracks = this.state.configInfo.tracks.TrackList;
const tracklist = [];
const harnessList = [];
const greyList = [];
if(showTracks){
showTracks.forEach((track) => {
if(track['track-type'] === 0) {
tracklist.push(
<tr key={track['track-id']} onClick={() => this.handleClick(ActionTypes.GET_RACE_TRACK,track['track-id'])}>
<td className="col-sm-6"><Link to="/races">{track['track-name']}</Link></td>
</tr>
)
} else if(track['track-type'] === 1) {
harnessList.push(
<tr key={track['track-id']} onClick={() => this.handleClick(ActionTypes.GET_RACE_TRACK,track['track-id'])}>
<td className="col-sm-6"><Link to="/races">{track['track-name']}</Link></td>
</tr>
)
} else if(track['track-type'] === 2) {
greyList.push(
<tr key={track['track-id']} onClick={() => this.handleClick(ActionTypes.GET_RACE_TRACK,track['track-id'])}>
<td className="col-sm-6"><Link to="/races">{track['track-name']}</Link></td>
</tr>
)
}
});
}
else{
console.log('no hay tracks')
}
return (
<div className={styles.main}>
</div>
);
}
}
Is there any way to put that object that returns the service in an array so that when you click it again go to that array and check if that data already exists so that it does not call the service again?

Related

Redux+Reactjs+NodeJS How to get a specific value from the database and display it to the screen by redux?

i am new, and am getting acquainted with i reactjs and nodejs, i am writing a website in which i want to write a function to get the current login information of the user through redux, and then after then assign the obtained user value to react-select, then from react-select, we select that user to perform the assignment of new data to the database from reactjs. However, I have not been able to get the logged in user information through redux. This is my code, anyone have any ideas? Thanks very much
here is my FE(Reactjs code):
here is DoctorManageSchedule.js:
import React, { Component } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import './ManageSchedule.scss';
import Select from 'react-select';
import * as actions from "../../../store/actions";
import { CRUD_ACTIONS, LANGUAGES, dateFormat } from '../../../utils';
import DatePicker from '../../../components/Input/DatePicker';
import moment from 'moment';
import { toast } from "react-toastify";
import _ from 'lodash';
import { saveBulkScheduleDoctor, getScheduleDoctorById } from '../../../services/userService';
import DetailDoctor from './DetailDoctor';
class DoctorManageSchedule extends Component {
constructor(props) {
super(props);
this.state = {
arrDoctor: [],
selectedDoctor: {},
currentDate: '',
rangeTime: [],
minDate: moment().calendar(),
}
}
async componentDidMount() {
this.props.fetchDoctor(this.props.match.params.id);
this.props.fetchAllScheduleTimes();
}
async componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.doctor !== this.props.doctor) {
let dataSelect = this.buildDataInputSelect(this.props.doctor)
this.setState({
arrDoctor: dataSelect
})
}
if (prevProps.allScheduleTime !== this.props.allScheduleTime) {
let data = this.props.allScheduleTime;
if (data && data.length > 0) {
data = data.map(item => ({ ...item, isSelected: false }))
}
this.setState({
rangeTime: data
})
}
}
buildDataInputSelect = (inputData) => {
let result = [];
let { language } = this.props;
if (inputData && inputData.length > 0) {
inputData.map((item, index) => {
let object = {};
let labelEn = `${item.lastName} ${item.firstName}`;
let labelVi = `${item.firstName} ${item.lastName}`;
object.label = language === LANGUAGES.VI ? labelVi : labelEn;
object.value = item.id;
result.push(object)
})
}
return result;
}
handleChangeSelect = async (selectedOption) => {
this.setState({ selectedDoctor: selectedOption });
}
handleOnChangeDatePicker = (date) => {
this.setState({
currentDate: date[0]
})
}
handleClickBtnTime = (time) => {
let { rangeTime } = this.state;
if (rangeTime && rangeTime.length > 0) {
rangeTime = rangeTime.map(item => {
if (item.id === time.id) item.isSelected = !item.isSelected;
return item;
})
this.setState({
rangeTime: rangeTime
})
}
}
handleSaveSchedule = async () => {
let { rangeTime, selectedDoctor, currentDate } = this.state;
let result = [];
if (!currentDate) {
toast.error("Invalid date!");
}
if (selectedDoctor && _.isEmpty(selectedDoctor)) {
toast.error("Invalid selected doctor! ");
console.log('check doctor: ', this.state)
return;
}
let formatedDate = new Date(currentDate).getTime();
if (rangeTime && rangeTime.length > 0) {
let selectedTime = rangeTime.filter(item => item.isSelected === true);
if (selectedTime && selectedTime.length > 0) {
selectedTime.map((schedule, index) => {
let object = {};
object.doctorId = selectedDoctor.value;
object.date = formatedDate;
object.timeType = schedule.keyMap;
result.push(object);
})
} else {
toast.error("Invalid selected time! ");
return;
}
}
let res = await saveBulkScheduleDoctor({
arrSchedule: result,
doctorId: selectedDoctor.value,
formatedDate: formatedDate
})
if (res && res.errCode === 0) {
toast.success("Save Infor succeed!");
} else {
toast.error("error saveBulkScheduleDoctor ");
console.log('error saveBulkScheduleDoctor >>> res: ', res)
}
console.log('bao phuc check result: ', result);
console.log('check res: saveBulkScheduleDoctor : ', res);
}
render() {
let { rangeTime, arrDoctor } = this.state;
console.log("check doctor:", arrDoctor)
let { language } = this.props;
let today = new Date(new Date().setDate(new Date().getDate()));
return (
<div className="manage-schedule-container">
<div className="m-s-title">
<FormattedMessage id="manage-schedule.title"></FormattedMessage>
</div>
<div className="container">
<div className="row">
<div className="col-6 form-group">
<label>
<FormattedMessage id="manage-schedule.choose-doctor" /> </label>
<Select
value={this.state.selectedDoctor}
onChange={this.handleChangeSelect}
options={this.state.listDoctors}
/>
</div>
<div className="col-6 form-group">
<label>
<FormattedMessage id="manage-schedule.choose-date" /> </label>
<DatePicker
value={this.state.currentDate}
className="form-control"
onChange={this.handleOnChangeDatePicker}
minDate={today}
/>
</div>
<div className="col-12 pick-hour-container">
{rangeTime && rangeTime.length > 0 &&
rangeTime.map((item, index) => {
return (
<button className={item.isSelected === true ?
"btn btn-schedule active" : "btn btn-schedule"}
key={index} onClick={() => this.handleClickBtnTime(item)}>
{language === LANGUAGES.VI ? item.valueVi : item.valueEn}
</button>
)
})}
</div>
<div className="col-12">
<button className="btn btn-primary btn-save-schedule"
onClick={() => this.handleSaveSchedule()}>
<FormattedMessage id="manage-schedule.save" />
</button>
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
language: state.app.language,
isLoggedIn: state.user.isLoggedIn,
doctor: state.admin.doctor,
allScheduleTime: state.admin.allScheduleTime,
};
};
const mapDispatchToProps = dispatch => {
return {
fetchDoctor: () => dispatch(actions.fetchDoctorStart()),
fetchAllScheduleTimes: () => dispatch(actions.fetchAllScheduleTimes())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(DoctorManageSchedule);
and here is my actionTypes.js:
const actionTypes = Object.freeze({
FETCH_DOCTOR_SUCCESS: 'FETCH_DOCTOR_SUCCESS',
FETCH_DOCTOR_FAILED: 'FETCH_DOCTOR_FAILED',
})
export default actionTypes;
and here is my adminActions.js:
export const fetchDoctorStart = id => () => {
return async (dispatch, getState) => {
try {
let res = await getScheduleDoctorById(id);
if (res && res.errCode === 0) {
dispatch({
type: actionTypes.FETCH_DOCTOR_SUCCESS,
dataDoctor: res.data
})
} else {
toast.error("Failed to fetch doctor");
dispatch(fetchDoctorFailed());
}
} catch (e) {
toast.error("Failed to fetch doctor");
dispatch(fetchDoctorFailed());
console.log("check fetch doctor failed: ", e);
}
}
};
export const fetchDoctorSuccess = (data) => ({
type: actionTypes.FETCH_DOCTOR_SUCCESS,
doctor: data
})
export const fetchDoctorFailed = () => ({
type: actionTypes.FETCH_DOCTOR_FAILED,
})
here is my adminReducer.js:
case actionTypes.FETCH_DOCTOR_SUCCESS:
state.doctor = action.dataDoctor;
return {
...state,
}
case actionTypes.FETCH_DOCTOR_FAILED:
state.doctor = [];
return {
...state,
}
here is my userService.js:
const getScheduleDoctorById = (inputId) => {
return axios.get(`/api/get-schedule-doctor-by-id?id=${inputId}`)
}
here is my BE(Nodejs code):
here is web.js:
router.get('/api/get-schedule-doctor-by-id', doctorController.getScheduleById);
here is doctorController.js:
let getScheduleById= async (req, res) => {
try {
let infor = await doctorService.getScheduleById(req.query.id);
return res.status(200).json(infor);
} catch (e) {
console.log(e);
return res.status(200).json({
errCode: -1,
errMessage: 'Error from the server'
})
}
}
here is doctorService.js:
let getScheduleById = (inputId) => {
return new Promise(async (resolve, reject) => {
try {
if (!inputId) {
resolve({
errCode: 1,
errMessage: 'Missing required parameter!'
})
} else {
let data = await db.User.findOne({
where: {
id: inputId
},
attributes: {
exclude: ['password']
},
include: [
{ model: db.Allcode, as: 'positionData', attributes: ['valueEn', 'valueVi'] },
{
model: db.Doctor_Infor,
attributes: {
exclude: ['id', 'doctorId']
}
},
],
raw: false,
nest: true
})
if (data && data.image) {
data.image = new Buffer(data.image, 'base64').toString('binary');
}
if (!data) data = {};
resolve({
errCode: 0,
data: data
})
}
} catch (e) {
reject(e);
}
})
}
when i run the app i only get the available time slots from the database, but no info about who is logged in, i checked the network tab, but it seems the api gets the user info via redux do not run. Or does anyone have a way to do it other than using redux, that the user (doctor) can set his own schedule and save it to the database? However, I don't know why, please comment, thanks a lot

Cannot read property 'data' of undefined in ReactJs and SwaggerUI

I am working with ReactJs and Fetch API from Swagger UI. I code for Room management, this is my code for that
In actions.js file
// Room type action
export const actFetchRoomTypesRequest = () => {
return (dispatch) => {
return callApi('admin/host-room-types', 'GET', null).then((res) => {
dispatch(actFetchRoomTypes(res.data));
});
};
};
export const actFetchRoomTypes = (roomTypes) => {
return {
type: Types.FETCH_ROOMTYPES,
roomTypes,
};
};
export const actDeleteRoomTypeRequest = (id) => {
return (dispatch) => {
return callApi(`admin/host-room-types/${id}`, 'DELETE', null).then((res) => {
dispatch(actFetchRoomTypesRequest());
});
};
};
export const actAddRoomTypeRequest = (roomType) => {
return (dispatch) => {
return callApi(`admin/host-room-types`, 'POST', roomType).then((res) => {
return callApi(`admin/host-room-types`).then((ress) => {
dispatch(actFetchRoomTypes(ress.data));
});
});
};
};
export const actAddRoomType = (roomType) => {
return {
type: Types.ADD_ROOMTYPE,
roomType,
};
};
export const actGetRoomTypeRequest = (id) => {
return (dispatch) => {
return callApi(`admin/host-room-types/${id}`, 'GET', null).then((res) => {
dispatch(actGetRoomType(res.data));
});
};
};
export const actGetRoomType = (roomType) => {
return {
type: Types.EDIT_ROOMTYPE,
roomType,
};
};
export const actUpdateRoomTypeRequest = (roomType) => {
return (dispatch) => {
return callApi(`admin/host-room-types/${roomType.id}`, 'PUT', roomType).then((res) => {
return callApi(`admin/host-room-types`).then((ress) => {
dispatch(actFetchRoomTypes(ress.data));
});
});
};
};
export const actUpdateRoomType = (roomType) => {
return {
type: Types.UPDATE_ROOMTYPE,
roomType,
};
};
In RoomTypeItem.js which the place I show all action for that
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
class RoomTypeItem extends Component {
constructor(props) {
super(props);
console.log('constructor');
}
onDelete = (id) => {
console.log(id);
if (window.confirm('ban muon xoa?')) {
this.props.onDelete(id);
}
};
onSelectRoomType = (id) => {
console.log('onSelect' + id);
this.props.onSelect(id);
};
render() {
var { roomType, index } = this.props;
console.log('render' + roomType.id + this.props.checked);
return (
<tr>
<td>
<input
type="checkbox"
className="delid[]"
checked={this.props.checked}
onChange={() => this.onSelectRoomType(roomType.id)}
/>
</td>
<td>{index + 1}</td>
<td>{roomType.name}</td>
<td>{roomType.description}</td>
<td>
<Link to={`/roomType/${roomType.id}/edit`} className="btn btn-warning mr-10">
Update
</Link>
</td>
</tr>
);
}
}
export default RoomTypeItem;
And in RoomTypeActionPage have code like this
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { actAddRoomTypeRequest, actGetRoomTypeRequest, actUpdateRoomTypeRequest } from './../../actions/index';
import { connect } from 'react-redux';
class RoomTypeActionPage extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
txtName: '',
txtDescription: '',
};
}
componentDidMount() {
var { match } = this.props;
if (match) {
var id = match.params.id;
this.props.onEditRoomType(id);
}
}
componentWillReceiveProps(nextProps) {
if (nextProps && nextProps.itemEditing) {
var { itemEditing } = nextProps;
this.setState({
id: itemEditing.id,
txtName: itemEditing.name,
txtDescription: itemEditing.description,
});
}
}
onChange = (e) => {
var target = e.target;
var name = target.name;
var value = target.type === 'checkbox' ? target.checked : target.value;
this.setState({
[name]: value,
});
};
onSave = (e) => {
e.preventDefault();
var { id, txtName, txtDescription } = this.state;
var { history } = this.props;
var roomType = {
id: id,
name: txtName,
description: txtDescription,
};
if (id) {
//update
this.props.onUpdateRoomType(roomType);
} else {
this.props.onAddRoomType(roomType);
}
history.goBack();
};
render() {
var { txtName, txtDescription } = this.state;
return (
<div className="col-xs-6 col-sm-6 col-md-6 col-lg-6">
<form onSubmit={this.onSave}>
<div className="form-group">
<label>Name:</label>
<input
type="text"
className="form-control"
name="txtName"
value={txtName}
onChange={this.onChange}
/>
</div>
<div className="form-group">
<label>Description:</label>
<input
type="text"
className="form-control"
name="txtDescription"
value={txtDescription}
onChange={this.onChange}
/>
</div>
<Link to="/roomType-list" className="btn btn-danger mr-10">
Cancel
</Link>
<button type="submit" className="btn btn-primary">
Save
</button>
</form>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
itemEditing: state.itemEditing,
};
};
const mapDispatchToProps = (dispatch, props) => {
return {
onAddRoomType: (roomType) => {
dispatch(actAddRoomTypeRequest(roomType));
},
onEditRoomType: (id) => {
dispatch(actGetRoomTypeRequest(id));
},
onUpdateRoomType: (roomType) => {
dispatch(actUpdateRoomTypeRequest(roomType));
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(RoomTypeActionPage);
And this is my Reducer
import * as Types from './../constants/ActionTypes';
var initialState = {
loading: false,
data: [],
};
var findIndex = (roomTypes, id) => {
var result = -1;
roomTypes.data.forEach((roomType, index) => {
if (roomType.id === id) {
result = index;
}
});
return result;
};
const roomTypes = (state = initialState, action) => {
var index = -1;
var { id, roomType } = action;
switch (action.type) {
case Types.FETCH_ROOMTYPES:
state = action.roomTypes;
return { ...state };
case Types.DELETE_ROOMTYPE:
index = findIndex(state, id);
state.splice(index, 1);
return { ...state };
case Types.ADD_ROOMTYPE:
state.data.push(action.roomType);
return { ...state };
case Types.UPDATE_ROOMTYPE:
index = findIndex(state, roomType.id);
state[index] = roomType;
return { ...state };
default:
return { ...state };
}
};
export default roomTypes;
I do not know when I run my code it shows me an error like this
I already to check all but I can realize where I code wrong for that. PLease help me, I need you, Thank thank so much
This is my link for swagger UI : here
This is my apiCaller
import axios from 'axios';
import * as Config from './../constants/Config';
export default function callApi(endpoint, method = 'GET', body) {
return axios({
method: method,
url: `${Config.API_URL}/${endpoint}`,
data: body,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization:
'Bearer ' +
'eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImlhdCI6MTU5Njg4MTYxMSwiZXhwIjoxNjEyOTQzNjEwfQ.eXkXWuuIMk94uOtVZQSNysbfJyIQuP5dIFqS06Kx-KsYVCkVEAbU01IhwJpkR4YtgXt8idkN4MM0cT76Xre7Sg',
},
}).catch((err) => {
console.log(err);
});
}

React.js : Updating State of Nested Object

Front End - Front End
Upon clicking the star, I want to update the state of nested object, with the new rating value of star.
I tried many things but it didnt work as states are immutable.
Nested State
Can some upon please suggest how can I update the value in nested object
onStarClicked = (kTypName, subItemId1, newRating) => {
//console.log(subItemId.split("_"));
let evaluation = subItemId1.split("_")[0];
let subItemId = subItemId1.split("_")[1];
console.log(subItemId);
const r = { ...this.state.ratings };
let kT = r.knowledgeTypes;
let sub = '', kTN = '', kIN = '';
kT.map(knowledgeType => {
//console.log(knowledgeType.knowledgeTypeId);
knowledgeType.knowledgeItems.map(knowledgeItem => {
//console.log(knowledgeItem.knowledgeItemId);
knowledgeItem.subItems.map(knowledgeSubItem => {
//console.log(knowledgeSubItem.subItemId);
if (subItemId === knowledgeSubItem.subItemId) {
kTN = knowledgeType.knowledgeTypeName;
kIN = knowledgeItem.knowledgeItemName;
sub = knowledgeSubItem;
if (evaluation === "self") {
sub.evaluation.self.rating = newRating;
}
else if (evaluation === "evaluator") {
sub.evaluation.evaluator.rating = newRating;
}
//alert(evaluation + subItemId + ' ' + newRating);
//return;
}
})
})
});
this.setState({
...this.state,
ratings: {
...this.state.ratings,
knowledgeTypes: [
...this.state.ratings.knowledgeTypes,
this.state.ratings.knowledgeTypes.filter(kt => kt.knowledgeTypeName !== kTN),
{
...this.state.ratings.knowledgeTypes.knowledgeItems.
filter(ki => ki.knowledgeItemName !== kIN),
knowledgeItems: {
...this.state.ratings.knowledgeTypes.knowledgeItems.subItems.
filter(si => si.subItemId !== subItemId),
sub
}
}]
}
});
}
You basically have to create a new empty array of knowledgeTypes and use the current state to find which item of the state you need to change using Object.keys/map/filter functions.
You'd use the current state in a variable and modify that variable only. You'd likely not mess with the actual state object in any way.
After you have done that, simply append it to the empty array. Then you can setState() the new array to the actual state property.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
financialYear: "2019-20",
quarter: "Q1",
isCurrentQuarter: true,
knowledgeTypes: [
{
knowledgeTypeName: "Technology",
knowledgeItems: [
{
knowledgeItemName: "Java",
subItems: [
{
subItemId: "2",
subItemName: "Collections",
evaluation: {
self: {
ntnet: "Joe",
rating: 1,
isEditable: true
}
}
}
]
}
]
}
]
};
}
handleClick = e => {
const { knowledgeTypes } = this.state;
// transformation
const itemToChange = knowledgeTypes.map(item => {
if (item.knowledgeTypeName === "Technology") {
return item;
}
});
const currItems = itemToChange[0].knowledgeItems[0].subItems;
const subItem = currItems.map(item => {
if (item.subItemId === "2") {
return item;
}
});
const person = subItem[0].evaluation;
person.self.rating = 55; //change
const newKnowledgeTypes = [];
knowledgeTypes.map(item => {
if (item.knowledgeTypeName === "Technology") {
newKnowledgeTypes.push(itemToChange);
}
newKnowledgeTypes.push(item);
});
this.setState({
knowledgeTypes: newKnowledgeTypes
});
console.log(this.state);
};
render() {
return (
<div>
MyComponent
<button onClick={this.handleClick}>Hello</button>
</div>
);
}
}
The sandbox can be found on https://codesandbox.io/s/musing-dew-8r2vk.
Note: It is advisable you do not use nested state objects because state objects are something more lightweight so that they do not have performance considerations.
import React, { Component } from 'react';
import Auxilary from '../../../hoc/Auxilary/auxilary';
import KnowledgeItems from '../KnowledgeItems/KnowledgeItems';
import Tabs from 'react-bootstrap/Tabs';
import Tab from 'react-bootstrap/Tab';
import knowledge from '../../../assests/staticdata.json';
import './QuarterLog.css';
class QuarterLog extends Component {
constructor() {
super();
this.state = {
"financialYear": "",
"quarter": "",
"isCurrentQuarter": "",
"knowledgeTypes": []
}
}
onStarClicked = (kTypName, kItemName, subItemIdName, newRating) => {
let evaluation = subItemIdName.split("_")[0];
let subItemId = subItemIdName.split("_")[1];
const { knowledgeTypes } = this.state;
// transformation
let knowledgeTypeToChange = knowledgeTypes.map(kType => {
if (kType.knowledgeTypeName === kTypName) {
return kType;
}
});
knowledgeTypeToChange = knowledgeTypeToChange.filter(function (element) {
return element !== undefined;
});
console.log(knowledgeTypeToChange[0]);
let knowledgeItemToChange = knowledgeTypeToChange[0].knowledgeItems.map(item => {
if (item.knowledgeItemName === kItemName) {
return item;
}
});
knowledgeItemToChange = knowledgeItemToChange.filter(function (element) {
return element !== undefined;
});
let knowledgeSubItem = knowledgeItemToChange[0].subItems.map(subItem => {
if (subItem.subItemId === subItemId) {
return subItem;
}
});
knowledgeSubItem = knowledgeSubItem.filter(function (element) {
return element !== undefined;
});
console.log(knowledgeSubItem);
let personEvaluations = knowledgeSubItem[0].evaluation;
if (evaluation === "self") {
personEvaluations.self.rating = newRating.toString(); //change
}
else if (evaluation === "evaluator") {
personEvaluations.evaluator.rating = newRating.toString(); //change
}
const newKnowledgeTypes = [];
knowledgeTypes.map(item => {
if (item.knowledgeTypeName === kTypName) {
newKnowledgeTypes.push(knowledgeTypeToChange[0]);
}
else
newKnowledgeTypes.push(item);
});
this.setState({
knowledgeTypes: newKnowledgeTypes
});
console.log(this.state);
}
componentDidMount() {
// TODO: remove staticdata.js and call REST API and set the response in state
this.setState({
...this.state,
"financialYear": knowledge.financialYear,
"quarter": knowledge.quarter,
"isCurrentQuarter": knowledge.isCurrentQuarter,
"knowledgeTypes": knowledge.knowledgeTypes
})
}
onSubmitRatings = () => {
console.log(this.state);
}
render() {
let data = knowledge; //remove this code, once REST API is implemented
const posts = this.state.knowledgeTypes.map(knowledgeType => {
return (
<Tab key={knowledgeType.knowledgeTypeName} eventKey={knowledgeType.knowledgeTypeName}
title={knowledgeType.knowledgeTypeName}>
<KnowledgeItems
kTypeName={knowledgeType.knowledgeTypeName}
kItems={knowledgeType.knowledgeItems}
ratings={this.state.ratings}
onstarclicked={this.onStarClicked}
/>
</Tab>)
});
return (
<Auxilary>
<div className="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<div><h1>Financial Year : {data.financialYear}</h1></div>
<div><h2>Quarter : {data.quarter}</h2></div>
</div>
<div>
<Tabs defaultActiveKey="Domain" id="uncontrolled-tab-example">
{posts}
</Tabs>
</div>
<button onClick={this.onSubmitRatings}> Submit </button>
</Auxilary>
);
}
}
export default QuarterLog;

Component did update returning always the same props and state

I found a lot of solutions about this problem but none of them work.
I have a view which renders dynamically components depending on the backend response
/**
* Module dependencies
*/
const React = require('react');
const Head = require('react-declarative-head');
const MY_COMPONENTS = {
text: require('../components/fields/Description'),
initiatives: require('../components/fields/Dropdown'),
vuln: require('../components/fields/Dropdown'),
severities: require('../components/fields/Dropdown'),
};
const request = restclient({
timeout: 5000,
baseURL: '/api',
});
const { DropdownItem } = Dropdown;
class CreateView extends React.Component {
constructor(props) {
super(props);
this.state = {
modal: false,
states: props.states,
error: props.error,
spinner: true,
state: props.state,
prevState: '',
components: [],
};
this.handleChange = this.handleChange.bind(this);
this.getRequiredFields = this.getRequiredFields.bind(this);
this.onChangeHandler = this.onChangeHandler.bind(this);
this.changeState = this.changeState.bind(this);
this.loadComponents = this.loadComponents.bind(this);
}
componentDidMount() {
this.loadComponents();
}
onChangeHandler(event, value) {
this.setState((prevState) => {
prevState.prevState = prevState.state;
prevState.state = value;
prevState.spinner = true;
return prevState;
}, () => {
this.getRequiredFields();
});
}
getRequiredFields() {
request.get('/transitions/fields', {
params: {
to: this.state.state,
from: this.state.prevState,
},
})
.then((response) => {
const pComponents = this.state.components.map(c => Object.assign({}, c));
pComponents.forEach((c) => {
c.field.required = 0;
c.field.show = false;
});
response.data.forEach((r) => {
const ob = pComponents.find(c => c.field.name === r.name);
if (ob) {
ob.field.required = r.required;
ob.field.show = true;
}
});
this.setState({
components: pComponents,
fields: response.data,
spinner: false,
});
})
.catch(err => err);
}
loadComponents() {
this.setState((prevState) => {
prevState.components = Object.keys(MY_COMPONENTS).map((k) => {
const field = {
name: k,
required: 0,
show: true,
};
return {
field, component: MY_COMPONENTS[k],
};
});
return prevState;
});
}
handleChange(field, value) {
this.setState((prevState) => {
prevState[field] = value;
return prevState;
});
}
changeState(field, value) {
this.setState((prevState) => {
prevState[`${field}`] = value;
return prevState;
});
}
render() {
const Components = this.state.components;
return (
<Page name="CI" state={this.props} Components={Components}>
<Script src="vendor.js" />
<Card className="">
<div className="">
<div className="">
<Spinner
show={this.state.spinner}
/>
{Components.map((component, i) => {
const Comp = component.component;
return (<Comp
key={i}
value={this.state[component.field.name]}
field={component.field}
handleChange={this.handleChange}
modal={this.state.modal}
changeState={this.changeState}
/>);
})
}
</div>
</div>
</div>
</Card>
</Page>
);
}
}
module.exports = CreateView;
and the dropdown component
const React = require('react');
const request = restclient({
timeout: 5000,
baseURL: '/api',
});
const { DropdownItem } = Dropdown;
class DrpDwn extends React.Component {
constructor(props) {
super(props);
this.state = {
field: props.field,
values: [],
};
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('state', this.state.field);
console.log('prevState', prevState.field);
console.log('prevProps', prevProps.field);
console.log('props', this.props.field);
}
render() {
const { show } = this.props.field;
return (show && (
<div className="">
<Dropdown
className=""
onChange={(e, v) => this.props.handleChange(this.props.field.name, v)}
label={this.state.field.name.replace(/^./,
str => str.toUpperCase())}
name={this.state.field.name}
type="form"
value={this.props.value}
width={100}
position
>
{this.state.values.map(value => (<DropdownItem
key={value.id}
value={value.name}
primary={value.name.replace(/^./, str => str.toUpperCase())}
/>))
}
</Dropdown>
</div>
));
}
module.exports = DrpDwn;
The code actually works, it hide or show the components correctly but the thing is that i can't do anything inside componentdidupdate because the prevProps prevState and props are always the same.
I think the problem is that I'm mutating always the same object, but I could not find the way to do it.
What I have to do there is to fill the dropdown item.
Ps: The "real" code works, i adapt it in order to post it here.
React state is supposed to be immutable. Since you're mutating state, you break the ability to tell whether the state has changed. In particular, i think this is the main spot causing your problem:
this.setState((prevState) => {
prevState.components = Object.keys(MY_COMPONENTS).map((k) => {
const field = {
name: k,
required: 0,
show: true,
}; return {
field, component: MY_COMPONENTS[k],
};
});
return prevState;
});
You mutate the previous states to changes its components property. Instead, create a new state:
this.setState(prevState => {
const components = Object.keys(MY_COMPONENTS).map((k) => {
const field = {
name: k,
required: 0,
show: true,
};
return {
field, component: MY_COMPONENTS[k],
};
});
return { components }
}
You have an additional place where you're mutating state. I don't know if it's causing your particular problem, but it's worth mentioning anyway:
const pComponents = [].concat(this.state.components);
// const pComponents = [...this.state.components];
pComponents.forEach((c) => {
c.field.required = 0;
c.field.show = false;
});
response.data.forEach((r) => {
const ob = pComponents.find(c => c.field.name === r.name);
if (ob) {
ob.field.required = r.required;
ob.field.show = true;
}
});
You do at make a copy of state.components, but this will only be a shallow copy. The array is a new array, but the objects inside the array are the old objects. So when you set ob.field.required, you are mutating the old state as well as the new.
If you want to change properties in the objects, you need to copy those objects at every level you're making a change. The spread syntax is usually the most succinct way to do this:
let pComponents = this.state.components.map(c => {
return {
...c,
field: {
...c.field,
required: 0,
show: false
}
}
});
response.data.forEach(r => {
const ob = pComponents.find(c => c.field.name === r.name);
if (ob) {
// Here it's ok to mutate, but only because i already did the copying in the code above
ob.field.required = r.required;
ob.field.show = true;
}
})

MobX Store Not Updating In React Native

I have implemented a MobX store in my React-Native app to keep track if a user is being followed or unfollowed. The follow/unfollow is registering, but the MobX store is not being updated. I am trying to update it directly with the this.follows.items[index] = { ...user, isFollowing: !user.isFollowing } but for some reason the store does not trigger an update.
Here is the View Component
#observer
class FollowsListView extends Component<Props> {
follows =
this.props.followType === 'followers'
? followsStore.getFollowersListLoader(this.props.userId)
: followsStore.getFollowingListLoader(this.props.userId);
componentDidMount = () => {
this.follows.lazyLoad();
};
render() {
return (
<>
<AppHeader
title={
this.props.followType === 'followers' ? 'FOLLOWERS' : 'FOLLOWING'
}
/>
<FlatList
contentContainerStyle={{ padding: 15 }}
data={this.follows.items}
keyExtractor={this.getId}
onEndReached={this.follows.loadMore}
onEndReachedThreshold={0.2}
onRefresh={this.follows.loadFromStart}
refreshing={this.follows.isLoading}
renderItem={this.renderFollows}
/>
</>
);
}
private getId = (user: { id: string }) => user.id;
renderUserActionButton(user: UserContainer) {
console.log(user);
return (
user.id !== _SessionManager.id && (
<TouchableOpacity
onPress={() => this.openActionMenu(user.following || user.owner)}
>
<Image source={Images.moreDots} />
</TouchableOpacity>
)
);
}
openActionMenu(user: User) {
const name = user.name || user.username;
const followOption = { name: 'follow', title: `Follow #${name}` };
const unfollowOption = { name: 'unfollow', title: `Unfollow #${name}` };
const options = {
customButtons: [user.isFollowing ? unfollowOption : followOption],
title: null,
takePhotoButtonTitle: null,
chooseFromLibraryButtonTitle: null,
};
ImagePicker.showImagePicker(options, ({ customButton }) => {
if (customButton === 'follow') {
this.props.changeIsFollowingUser(user.id, false);
}
if (customButton === 'unfollow') {
this.props.changeIsFollowingUser(user.id, true);
}
const index = this.follows.items.findIndex((user) => user.id);
this.follows.items[index] = { ...user, isFollowing: !user.isFollowing };
});
}
private renderFollows: ListRenderItem<UserContainer> = ({ item: user }) => {
const userId = user.following ? user.following.id : user.id;
return (
<UserRow
actionContent={this.renderUserActionButton(user)}
onPress={() => this.props.navigateTo('ProfilePublic', { userId })}
user={user.following || user.owner}
/>
);
};
}
const mapDispatchToProps = (dispatch: Function): MappedDispatch =>
bindActionCreators(
{
changeIsFollowingUser,
navigateTo,
},
dispatch
);
export default connect(
null,
mapDispatchToProps
)(FollowsListView);
Here is the Follows Store
import ListLoader from 'Network/ListLoader';
import { Follows } from 'Follows/Types';
import _API from 'Network/API';
class FollowsStore {
followers = new Map<string, Follows>();
followersList = new Map<string, ListLoader<Follows>>();
following = new Map<string, Follows>();
followingList = new Map<string, ListLoader<Follows>>();
getFollowersListLoader(userId: string) {
const list = this.followersList.get(userId);
if (list) return list;
const newList = new ListLoader<Follows>({
fetchData: async (params) => {
const url = `users/${userId}/followers`;
const response = await _API.get(url, { params });
return response.data;
},
onLoad: (data) => {
for (const user of data.items) {
this.followers.set(user.id, user);
}
},
});
this.followersList.set(userId, newList);
return newList;
}
getFollowingListLoader(userId: string) {
const list = this.followingList.get(userId);
if (list) return list;
const newList = new ListLoader<Follows>({
fetchData: async (params) => {
const url = `users/${userId}/following`;
const response = await _API.get(url, { params });
return response.data;
},
onLoad: (data) => {
for (const user of data.items) {
this.following.set(user.id, user);
}
},
});
this.followingList.set(userId, newList);
console.log(newList);
return newList;
}
}
const followsStore = new FollowsStore();
export default followsStore;
In MobX in order to change the state you would need to use an action. Set/decorate your openActionMenu as an action or extract the state changing code to another function which you decorate as action to be cleaner.

Categories

Resources