How to clear the FullCalendar cache events - reactjs

After drop an event from an external list and save this event on me server side i keep seeing the dropped event which is not in my source array.
I tried a lot of solutions to clear events using EventSources as in this topic, but what is happening is that all the events who came from the API are cleared but not the one who I dropped the blue one (CACHED).
In action : I have already red event in my view, and I add another red event from my drop list, but after the drop you will see the new red event that I add + another blue event (CACHED EVENT) this blue event is not in my source event list.
Using calendar.getEventSources() to clear the event is clearning only the event from the API, results is like this
Any suggestions?
Thanks in advance
CODE :
import React, { Component } from "react";
import $ from 'jquery';
import moment from 'moment';
import { Trans } from 'react-i18next';
import { DebounceInput } from 'react-debounce-input';
import FullCalendar from '#fullcalendar/react';
import dayGridPlugin from "#fullcalendar/daygrid";
import timeGridPlugin from "#fullcalendar/timegrid";
import listPlugin from "#fullcalendar/list";
import interactionPlugin, { Draggable } from "#fullcalendar/interaction";
import { Module } from '../Calender/Module';
import { CalendarLoad } from '../../../../../Util/Svg';
import { getFlyerById } from '../../../../../Util/Api/Cms';
import { withAlert } from 'react-alert';
import "#fullcalendar/core/main.css";
import "#fullcalendar/daygrid/main.css";
import "#fullcalendar/timegrid/main.css";
import "#fullcalendar/list/main.css";
import './CalendarV4.css';
class CalendarDemo extends Component
{
calendarComponentRef = React.createRef()
state = {
modalData: null,
isModalOpen: false,
eventReceiveStartDate: null,
isEventinTheView: null,
isEventinTheViewValue: ''
};
componentDidMount()
{
const draggableEl = document.getElementById("externalEvents");
new Draggable(draggableEl, {
itemSelector: ".draggable-events",
eventData: function(eventEl)
{
const title = eventEl.getAttribute("title");
const id = eventEl.getAttribute("id");
const duration = eventEl.getAttribute("data-duration");
return {
title: title,
id: id,
duration: duration
};
}
});
}
eventDrop = info =>
{
const string = info.event.title;
const stringId = string.split(' - ')[0];
const id = parseInt(stringId)
this.props.schedulesEvent(id, info.event.start, info.event.end)
};
eventReceive = info =>
{
this.setState({ eventReceiveStartDate : moment(info.event.start).format("YYYY-MM-DD") })
this.props.addToCalendarHandler(info.event.id, info.event.start , info.event.end);
};
drop = info =>
{
if ($('#RemoveAfterDrop').is(':checked')) {
$(`#externalEvents #${info.draggedEl.id}`).each(function() {
$(this).remove();
})
}
};
eventClick = info =>
{
const string = info.event.title;
const stringId = string.split(' - ')[0];
const id = parseInt(stringId)
this.setState({ isModalOpen: true });
getFlyerById(info.event.id).then( res => {
this.setState({ modalData: {...res.data ,eventId: id} });
})
};
eventResize = info =>
{
const string = info.event.title;
const stringId = string.split(' - ')[0];
const id = parseInt(stringId)
this.props.schedulesEvent(id, info.event.start, info.event.end)
};
datesRender = info =>
{
const viewActiveStart = moment(info.view.activeStart).format("YYYY-MM-DD");
const viewActiveEnd = moment(info.view.activeEnd).format("YYYY-MM-DD");
const range = `${viewActiveStart}/${viewActiveEnd}`
this.props.datesRangeHandler(range);
}
deleterFlyer = id =>
{
this.setState({
modalData: null,
isModalOpen: false
});
this.props.deletingEventHandler(id)
}
modalToggleHandler = () =>
{
this.setState({ isModalOpen : false, modalData: null})
};
isEventinTheView = id =>
{
const calendar = this.calendarComponentRef.current.getApi();
this.setState({
isEventinTheView : calendar.getEventById( id ) === null ? false : true,
isEventinTheViewValue: calendar.getEventById( id ) === null ? 'No' : 'YES'
})
if ( id === '' ) {
this.setState({ isEventinTheViewValue : '' })
}
};
testingAndDebugging = () => {
const calendar = this.calendarComponentRef.current.getApi();
let eventSources = calendar.getEventSources();
for (var i = 0; i < eventSources.length; i++) {
eventSources[i].remove();
}
};
render()
{
const { modalData, isModalOpen, isEventinTheViewValue, isEventinTheView } = this.state;
const { modifiedEvents } = this.props
return (
<div>
<FullCalendar
plugins={[
dayGridPlugin,
timeGridPlugin,
listPlugin,
interactionPlugin
]}
header= {[{
center: "title",
right : "today prev,next",
}]}
firstDay ={1}
timezone ="UTC"
defaultView ="dayGridMonth"
editable ={true}
droppable ={true}
selectable ={true}
eventLimit ={true}
forceEventDuration ={true}
events ={modifiedEvents}
drop ={this.drop }
eventDrop ={this.eventDrop}
eventClick ={this.eventClick}
eventResize ={this.eventResize}
datesRender ={this.datesRender}
eventReceive ={this.eventReceive}
ref ={this.calendarComponentRef}
/>
<Module
modalData ={modalData}
isModalOpen ={isModalOpen}
deletingEventHandler ={this.deleterFlyer}
modalToggleHandler ={this.modalToggleHandler}
/>
<div className="calendarNotLoad">
<CalendarLoad />
</div>
<div className="mt-5">
<h4 className="mt-4 mb-4"><Trans>Is this Flyer ID exist in this view</Trans></h4>
<DebounceInput
className={`calendar-flyer-search ${isEventinTheView ? 'green' : 'red'}`}
type="text"
placeholder="Search for a Flyer in this month"
minLength={1}
debounceTimeout={700}
value={ isEventinTheViewValue }
onChange={ e => this.isEventinTheView(e.target.value) }
onBlur={ () => this.setState({ isEventinTheViewValue : '', isEventinTheView : null })}
/>
</div>
<div className="text-right">
<button className="btn mt-5 fc-prev-button fc-button fc-button-primary" onClick={ this.testingAndDebugging }>calendar.getEventSources()</button>
</div>
</div>
);
}
}
export default withAlert(CalendarDemo)
I add a button with a test function to start a playing around with the calendar functionality, and end up with two groups WORKING & NOT WORKING functions, like.
testingAndDebugging = () => {
const calendar = this.calendarComponentRef.current.getApi();
// WORKING EVENTS
//===============
calendar.render();
calendar.refetchEvents();
calendar.addEventSource(modifiedEvents);
calendar.batchRendering(function() {
calendar.changeView('timeGridWeek');
})
calendar.getEventSources();
calendar.on('dateClick', function(info) {
console.log('clicked view ' + info.view );
});
// NOT WORKING
//===============
calendar.removeEventSource();
calendar.removeEvents();
calendar.removeEvents(10157);
calendar.removeEvent();
calendar.removeEvent(10157);
}

Related

React-table does not show valid page number when filter is applied again

In my web application there is the table which displays '50' records on each page depending on the number of pages are present in the applied filter. Problem is, ex-> for a 1 JAN 2021 to 2 FEB 2021 there are 2 pages each displaying 50 records if I switch to 2 page it shows 2 of 2 pages and then again I change the the filter but pagination of react table shows 2 of 2 pages instead of 1 of 2 pages
While the data show of 1 pages according to the filter applied.
The whole code of component
import _ from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import ReactTable from 'react-table';
import { STORAGE_KEYS } from '../../constants/localStorageKeys';
import { PAGINATION_PROPS } from '../../constants/paginationProps';
import { APPOINTMENTS_MODAL_CONTENT } from '../../constants/status';
import {
areDemographicsEnabled,
areEmailOrTextRemindersEnabled,
areEmailRemindersEnabled,
areTextRemindersEnabled,
getAppointmentTableData,
hasPrecheckForms,
isBalanceEnabled,
isCopayEnabled,
isInsuranceEnabled,
isPrecheckEnabled,
areBroadcastEnabled
} from './AppointmentsUtils';
import BalanceExpandedCell from './tableCells/BalanceExpandedCell';
import CopayExpandedCell from './tableCells/CopayExpandedCell';
import DemographicsExpandedCell from './tableCells/DemographicsExpandedCell';
import FormsExpandedCell from './tableCells/FormsExpandedCell';
import InsuranceExpandedCell from './tableCells/InsuranceExpandedCell';
import RemindersExpandedCell from './tableCells/RemindersExpandedCell';
import selectTableHOC from 'react-table/lib/hoc/selectTable';
const SelectTable = selectTableHOC(ReactTable);
class AppointmentsTable extends React.PureComponent {
static propTypes = {
clickedOnReminderStatusesViewAll: PropTypes.func.isRequired,
error: PropTypes.bool,
fetchAppointments: PropTypes.func.isRequired,
filteredAppointments: PropTypes.array,
onRowClick: PropTypes.func.isRequired,
pageNumber: PropTypes.number,
practiceConfig: PropTypes.object,
selectAll: PropTypes.bool,
selectedAppointmentIds: PropTypes.array,
toggleAll: PropTypes.func.isRequired,
toggleSelection: PropTypes.func.isRequired,
totalPages: PropTypes.number
};
constructor(props) {
super(props);
const { practiceConfig } = props;
this.state = {
isExpanded: false,
expanded: _.fill(Array(PAGINATION_PROPS.PAGE_SIZE_VALUE), false),
displayAllColumns:
isPrecheckEnabled(practiceConfig) || areEmailOrTextRemindersEnabled(practiceConfig)
? this.getColumnsVisibilitySettings()
: true
};
this.isSelected = this.isSelected.bind(this);
this.toggleHeaderExpansion = this.toggleHeaderExpansion.bind(this);
this.toggleColumnsVisibility = this.toggleColumnsVisibility.bind(this);
this.getExpandedRowSubComponent = this.getExpandedRowSubComponent.bind(this);
this.fetchData = this.fetchData.bind(this);
this.setRef = this.setRef.bind(this);
this.getTdProps = this.getTdProps.bind(this);
this.onExpandedChange = this.onExpandedChange.bind(this);
this.onPatientNameClick = this.onPatientNameClick.bind(this);
}
componentDidUpdate(prevProps) {
const { filteredAppointments } = this.props;
if (filteredAppointments !== prevProps.filteredAppointments) {
this.setRowsExpansion(this.state.isExpanded);
}
}
getColumnsVisibilitySettings() {
let displayAllColumns = true;
const localStorageValue = localStorage.getItem(STORAGE_KEYS.DISPLAY_ALL);
if (localStorageValue != null) {
displayAllColumns = JSON.parse(localStorageValue);
}
return displayAllColumns;
}
isSelected(id) {
const { selectedAppointmentIds } = this.props;
return selectedAppointmentIds.includes(id);
}
toggleHeaderExpansion() {
const isExpanded = !this.state.isExpanded;
this.setRowsExpansion(isExpanded);
this.setState({ isExpanded });
}
setRowsExpansion(isExpanded) {
const expanded = _.fill(Array(PAGINATION_PROPS.PAGE_SIZE_VALUE), isExpanded);
if (!_.isEqual(expanded, this.state.expanded)) {
this.setState({ expanded });
}
}
toggleColumnsVisibility() {
const displayAllColumns = !this.state.displayAllColumns;
localStorage.setItem(STORAGE_KEYS.DISPLAY_ALL, displayAllColumns);
this.setState({ displayAllColumns });
}
getExpandedRowPrecheckDiv(row) {
const { practiceConfig } = this.props;
return (
<div className="expanded-row">
{areDemographicsEnabled(practiceConfig) && (
<DemographicsExpandedCell status={row.demographics} />
)}
{isInsuranceEnabled(practiceConfig) && (
<InsuranceExpandedCell
insuranceData={row.insurance}
clickedOnInsuranceDetails={() =>
this.props.onRowClick(
row._original,
APPOINTMENTS_MODAL_CONTENT.INSURANCE
)
}
/>
)}
{isCopayEnabled(practiceConfig) && <CopayExpandedCell paymentData={row.copay} />}
{isBalanceEnabled(practiceConfig) && (
<BalanceExpandedCell paymentData={row.balance} />
)}
{hasPrecheckForms(practiceConfig) && (
<FormsExpandedCell patientForms={_.get(row, 'forms', null)} />
)}
</div>
);
}
getExpandedRowSubComponent({ original: appointment, row }) {
const { clickedOnReminderStatusesViewAll, practiceConfig } = this.props;
return (
<div className="rt-tr expanded-row">
<div className="rt-td precheck-cell expander-cell" />
{isPrecheckEnabled(practiceConfig) && this.getExpandedRowPrecheckDiv(row)}
<div className="expanded-row">
{areEmailRemindersEnabled(practiceConfig) && (
<RemindersExpandedCell
appointment={appointment}
clickedOnViewAll={clickedOnReminderStatusesViewAll}
practiceConfig={practiceConfig}
type="email"
/>
)}
{areTextRemindersEnabled(practiceConfig) && (
<RemindersExpandedCell
appointment={appointment}
clickedOnViewAll={clickedOnReminderStatusesViewAll}
practiceConfig={practiceConfig}
type="text"
/>
)}
{areBroadcastEnabled(practiceConfig) && (
<div className="rt-td precheck-cell expanded-precheck-cell"></div>
)}
{areBroadcastEnabled(practiceConfig) && (
<div className="rt-td precheck-cell expanded-precheck-cell"></div>
)}
</div>
</div>
);
}
fetchData(reactTableState) {
const { pageNumber } = this.props;
// This function gets called twice on table page change. Once for the page change itself,
// and once for the data change. Don't re-fetch on data change. By the way, the table's
// page is 0-based, but the prop's is 1-based.
//
// This function also gets called on initial load. In that case, the table's page is 0,
// and the pageNumber prop is undefined.
if (pageNumber !== reactTableState.page + 1) {
this.props.fetchAppointments(reactTableState.page);
}
}
setRef(r) {
this.reactTable = r;
}
onPatientNameClick(rowInfo) {
this.props.onRowClick(rowInfo.original, APPOINTMENTS_MODAL_CONTENT.ALL_PATIENT_DATA);
}
onTextClicked = rowInfo => {
this.props.onRowClick(rowInfo.original, APPOINTMENTS_MODAL_CONTENT.BROADCASTTEXT);
};
onEmailClicked = rowInfo => {
this.props.onRowClick(rowInfo.original, APPOINTMENTS_MODAL_CONTENT.BROADCASTMAIL);
};
getTdProps(_state, rowInfo, column) {
const props = {};
if (column.id === 'patientName') {
props.onClick = () => this.onPatientNameClick(rowInfo);
}
if (column.id === 'textBroadcast') {
props.onClick = () => this.onTextClicked(rowInfo);
}
if (column.id === 'emailBroadcast') {
props.onClick = () => this.onEmailClicked(rowInfo);
}
return props;
}
onExpandedChange(_newExpanded, index) {
const expanded = this.state.expanded.slice();
expanded[index] = !expanded[index];
this.setState({ expanded });
}
render() {
const {
error,
filteredAppointments,
pageNumber = 1,
practiceConfig,
selectAll,
toggleAll,
toggleSelection,
totalPages = 1
} = this.props;
const data = error ? [] : filteredAppointments || [];
const pages = error ? 0 : totalPages;
const tableText = error
? 'Appointments are currently unavailable. Please try back later.'
: 'There are no appointments for selected filters';
const expandedRowSubComponent =
isPrecheckEnabled(practiceConfig) || areEmailOrTextRemindersEnabled(practiceConfig)
? { SubComponent: this.getExpandedRowSubComponent }
: {};
const reactTableProps = {
className: 'mf-appointments-table',
columns: getAppointmentTableData(
this.toggleHeaderExpansion,
this.toggleColumnsVisibility,
this.state.isExpanded,
this.state.displayAllColumns,
practiceConfig
),
data,
defaultPageSize: PAGINATION_PROPS.PAGE_SIZE_VALUE,
expanded: this.state.expanded,
getTdProps: this.getTdProps,
id: 'appointmentsTable',
loadingText: 'Loading...',
manual: true, // informs React Table that you'll be handling sorting and pagination server-side
minRows: 0,
nextText: '',
noDataText: tableText,
ofText: 'of',
onExpandedChange: this.onExpandedChange,
onFetchData: this.fetchData, // Request new data when things change
page: pageNumber - 1,
pages,
pageText: '',
previousText: '',
ref: this.setRef,
rowsText: '',
showPageSizeOptions: false,
showPagination: pages > 1,
showPaginationBottom: true,
sortable: false,
...expandedRowSubComponent
};
const selectTableProps = {
isSelected: this.isSelected,
selectAll,
selectType: 'checkbox',
toggleAll,
toggleSelection
};
return (
<div>
<SelectTable {...reactTableProps} {...selectTableProps} />
</div>
);
}
}
export default AppointmentsTable;
The Pagination has an input in which you can edit the page

React - Render Key Press Event

I cannot seem to find a fitting example anywhere online. I have little experience with javaScript and React, and my issue might be trivial. The keypress event function works fine if run it by itself. However, if I try to implement it into the class app, and call the function from the render section I get this error: Error message. Any ideas? Thanks in advance. I have added the code.
import React, { Component, useEffect, useState } from 'react';
import './App.css';
import Spotify from 'spotify-web-api-js';
const spotifyWebApi = new Spotify();
class App extends Component {
constructor(){
super();
const params = this.getHashParams();
this.state = {
loggedIn: params.access_token ? true : false,
nowPlaying: {
name: 'Not Checked',
image: '',
device: '',
user_id: '',
playlists: []
}
}
if (params.access_token){
spotifyWebApi.setAccessToken(params.access_token)
}
};
useKeyPress(targetKey) {
const [keyPressed, setKeyPressed] = useState();
// Når du trykker på knappen - sætter vi keyPressed til true for at vise resultatet.
function downHandler({ key }) {
if (key === targetKey) {
this.setKeyPressed(true);
}
}
// Når du releaser knappen - sætter vi keyPressed til false for at fjerne resultatet igen.
const upHandler = ({ key }) => {
if (key === targetKey) {
this.setKeyPressed(false);
}
};
useEffect(() => {
window.addEventListener('keydown', downHandler);
window.addEventListener('keyup', upHandler);
// Det er altid "pænt" at ryde op efter sig selv, så vi fjerner eventListeners i return metoden
return () => {
window.removeEventListener('keydown', downHandler);
window.removeEventListener('keyup', upHandler);
};
}, []);
return this.keyPressed;
}
Fapp() {
const aPressed = this.useKeyPress('a');
const sPressed = this.useKeyPress('s');
const dPressed = this.useKeyPress('d');
const fPressed = this.useKeyPress('f');
return (
<div>
{ aPressed ? 'a' : 'not a'}
</div>
);
}
getHashParams() {
var hashParams = {};
var e, r = /([^&;=]+)=?([^&;]*)/g,
q = window.location.hash.substring(1);
while ( e = r.exec(q)) {
hashParams[e[1]] = decodeURIComponent(e[2]);
}
return hashParams;
}
getNowPlaying(){
spotifyWebApi.getMyCurrentPlaybackState()
.then((response) => {
this.setState({
nowPlaying: {
name: response.item.name,
image: response.item.album.images[0].url
}
}
)
})
}
handleKeyDown(event) {
if(event.keyCode === 13) {
console.log('Enter key pressed')
}
}
render() {
return (
<div className="App">
<a href='http://localhost:8888'>
<button>Login with Spotify</button>
</a>
<div> Now Playing: { this.state.nowPlaying.name } </div>
<div> user: { this.state.nowPlaying.user_id } </div>
<div>
<img src={ this.state.nowPlaying.image } style={{width: 100}}/>
</div>
<button onClick={() => this.getNowPlaying()}>
Check Now Playing
</button>
</div>
);
};
}
export default App;

Passing props to Parent component

I am really novice to React and I am stuck with this one.
I want to pass data from NewAction component to its parent NewActionSet.
I dont know what i am missing.
I am developing an on-boarding platform with a lot a components and I aim to send all the data entered into all the components to a server.
React parent Component:
import React from 'react'
import './NewActionSet.css'
import axios from 'axios'
import { Container, Segment, Header, Input } from 'semantic-ui-react'
import NewAction from './NewAction'
import 'bootstrap/dist/css/bootstrap.min.css'
class NewActionSet extends React.Component {
constructor (props) {
super(props)
this.state = {
actions: [],
actionType: '',
actionValue: '',
creationStatus: undefined
}
}
handleActions = value => {
this.setState({
actionsList: value
})
console.log(this.state.actionsList)
}
handleSubmit = event => {
event.preventDefault()
console.log(this.state)
axios
.post(
'/assistant/actions/',
{ ...this.state.values },
{ headers: {
xsrfHeaderName: 'X-CSRFToken',
xsrfCookieName: 'csrftoken'
},
withCredentials: true
}
)
.then(response => {
console.log(response)
this.setState({
creationStatus: true
})
})
.catch(error => {
console.log(error)
this.setState({
creationStatus: false
})
})
}
addNewAction = () => {
let { actions } = this.state
this.setState({
actions: [...actions, <NewAction onNewAction={this.handleActionstoParent} />]
})
}
handleActionstoParent = (action2Value, selectedAction) => {
this.setState({
actionType : selectedAction,
actionValue : action2Value
})
// console.log(this.state.actionType, this.state.actiondValue)
}
renderActions () {
return this.state.actions.map((action, index) => {
return (
<NewAction
key={index}
type={this.props.actionType}
content={action.content}
onNewAction={this.handleActionstoParent}
/>
)
})
}
render () {
let index = 0
return (
<Container>
<Header> Action sets </Header>
<Header color='grey' as='h3'>
SET #{index + 1}
</Header>
{this.renderActions()}
<button onClick={() => this.addNewAction()}> New Action </button>
</Container>
)
}
}
export default NewActionSet
React child component
import React from 'react'
import './NewActionSet.css'
import { Header, Dropdown } from 'semantic-ui-react'
import NewSpeechText from './NewSpeechText'
import NewAddPageURL from './NewAddPageURL'
import 'bootstrap/dist/css/bootstrap.min.css'
class NewAction extends React.Component {
constructor (props) {
super(props)
this.state = {
availableActions: [
{ key: 1, text: 'Navigate to page', value: 'Navigate to page' },
{ key: 2, text: 'Play speech', value: 'Play speech' }
],
selectedAction: '',
actionValue: '',
currentElement: ''
}
}
handleActionURL = (value) => {
this.setState({
actionValue: value
})
console.log(this.state.selectedAction, this.state.actionValue)
}
handleActionSpeech = (value) => {
this.setState({
actionValue: value
})
console.log(this.state.selectedAction, this.state.actionValue)
}
// Props to pass data to parent component --> NewActionSet.js
handleActionstoParent = (selected) => {
var action2Value = this.state.actionValue;
console.log(action2Value)
var action2Type = this.state.actionType
this.props.onNewAction(action2Value, action2Type)
console.log(action2Type)
// console.log(this.state.actionValue, this.state.selectedAction)
}
handleChange = (e, { value }) => {
let element
this.setState({
selectedAction: value
})
if (value === 'Navigate to page') {
element = <NewAddPageURL onNewAddPageURL={this.handleActionURL} onChange={this.handleActionstoParent()} />
} else if (value === 'Play speech') {
element = <NewSpeechText onNewSpeechText={this.handleActionSpeech} onChange={this.handleActionstoParent()} />
}
this.setState({
currentElement: element
})
}
render () {
const { value } = this.state
let index = 0
return (
<div className='action'>
<div className='container'>
<Header color='grey' as='h4'>
ACTION #{index + 1}
</Header>
<div className='row'>
<div className='col-md-4'>
<Dropdown
onChange={this.handleChange}
options={this.state.availableActions}
placeholder='Choose an action'
selection
value={value}
/>
</div>
<div className='col-md-4' />
<div className='col-md-4' />
</div>
<div style={{ marginBottom: '20px' }} />
{this.state.currentElement}
</div>
</div>
)
}
}
export default NewAction
Can you please assist?
Thanks a lot
The handleActionstoParent function in NewAction component is the problem.
When you send data from child to parent, actually the data is not updated data.
// Props to pass data to parent component --> NewActionSet.js
handleActionstoParent = (e) => {
this.setState({ [e.target.name]: e.target.value }, () => {
var action2Value = this.state.actionValue;
var action2Type = this.state.actionType;
this.props.onNewAction(action2Value, action2Type);
});
}
You could pass a function to NewAction, in example below we pass handleDataFlow function to our child component and then use it in our child component to pass data higher:
import React from 'react'
import './NewActionSet.css'
import { Header, Dropdown } from 'semantic-ui-react'
import NewSpeechText from './NewSpeechText'
import NewAddPageURL from './NewAddPageURL'
import 'bootstrap/dist/css/bootstrap.min.css'
class NewAction extends React.Component {
constructor (props) {
super(props)
this.state = {
availableActions: [
{ key: 1, text: 'Navigate to page', value: 'Navigate to page' },
{ key: 2, text: 'Play speech', value: 'Play speech' }
],
selectedAction: '',
actionValue: '',
currentElement: ''
}
}
handleActionURL = (value) => {
this.setState({
actionValue: value
})
console.log(this.state.selectedAction, this.state.actionValue)
}
handleActionSpeech = (value) => {
this.setState({
actionValue: value
})
console.log(this.state.selectedAction, this.state.actionValue)
}
// Props to pass data to parent component --> NewActionSet.js
handleActionstoParent = (selected) => {
var action2Value = this.state.actionValue;
console.log(action2Value)
var action2Type = this.state.actionType
this.props.onNewAction(action2Value, action2Type)
console.log(action2Type)
// console.log(this.state.actionValue, this.state.selectedAction)
}
handleChange = (e, { value }) => {
let element
this.setState({
selectedAction: value
})
this.props.handleDataFlow(value)
if (value === 'Navigate to page') {
element = <NewAddPageURL onNewAddPageURL={this.handleActionURL} onChange={this.handleActionstoParent()} />
} else if (value === 'Play speech') {
element = <NewSpeechText onNewSpeechText={this.handleActionSpeech} onChange={this.handleActionstoParent()} />
}
this.setState({
currentElement: element
})
}
render () {
const { value } = this.state
let index = 0
return (
<div className='action'>
<div className='container'>
<Header color='grey' as='h4'>
ACTION #{index + 1}
</Header>
<div className='row'>
<div className='col-md-4'>
<Dropdown
onChange={this.handleChange}
options={this.state.availableActions}
placeholder='Choose an action'
selection
value={value}
/>
</div>
<div className='col-md-4' />
<div className='col-md-4' />
</div>
<div style={{ marginBottom: '20px' }} />
{this.state.currentElement}
</div>
</div>
)
}
}
export default NewAction
Data flow in React is unidirectional. Data has one, and only one, way to be transferred: from parent to child.
To update parent state from child you have to send action (in props).
<NewAction updateParentState={this.doSmth} />
...
const doSmth = params => { this.setState({ ... })
and in NewAction you can call it in specific case
let parentUpdateState = ....
this.props.updateParentState(parentUpdateState);

Redux with Immer not updating component

I'm trying to add an element to an array in an object in my Redux store. I see the object get added to the store but it is not updating the component. If I leave the page and return it is showing up.
I'm pretty sure this is a state mutation issue but I can't figure out where I'm going wrong unless I fundamentally misunderstand what Immer is doing. In the component I'm using produce to add the string to the array, passing the new object to my reducer and using produce to add that object to an array of those objects.
I've looked through a ton of similar questions that all relate to state mutation, but the way I understand it the return from the component's call to produce should be a fully new object. Then in the reducer the call to produce should be returning a new object array.
This is the first time using Immer in a large project so it's entirely possible I don't fully get how it's working it's magic.
Component
import produce from 'immer';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import uuid from 'uuid/v4';
import { generate } from 'generate-password';
import { updateLeague } from '../../actions/leagues';
import { addTeam } from '../../actions/teams';
import { addUser } from '../../actions/users';
import Team from '../../classes/Team';
import User from '../../classes/User';
import UserWidget from '../utils/user/UserWidget';
class ViewLeague extends Component {
state = {
league : null,
isOwner : false,
owner : '',
teams : [],
inviteEmail: ''
};
componentWillMount() {
console.log('mount props', this.props.leagues);
const { leagues, uuid, leagueId, users, teams } = this.props;
if (leagues.length > 0) {
const league = leagues.find(league => league.uuid === leagueId);
const owner = users.find(user => league.leagueManager === user.uuid);
const leagueTeams = teams.filter(team => league.teams.includes(team.uuid));
this.setState({
league,
isOwner: league.leagueManager === uuid,
owner,
teams : leagueTeams
});
}
}
handleUpdate(event, fieldName) {
this.setState({ [ fieldName ]: event.target.value });
}
findUserByEmail(email) {
//Todo if not found here hit server
return this.props.users.find(user => user.email === email);
}
sendInvite = () => {
const { addTeam, addUser, updateLeague } = this.props;
const { league } = this.state;
const newManager = this.findUserByEmail(this.state.inviteEmail);
const newTeamUuid = uuid();
let newLeague = {};
if (newManager) {
const newTeam = new Team('New Team', newManager.uuid, newTeamUuid);
addTeam(newTeam);
} else {
const newPass = generate({
length : 10,
number : true,
uppercase: true,
strict : true
});
const newUserUuid = uuid();
const newUser = new User('', this.state.inviteEmail, newPass, '', '', newUserUuid);
addUser(newUser);
const newTeam = new Team('New Team', newUserUuid, newTeamUuid);
addTeam(newTeam);
newLeague = produce(league, draft => {draft.teams.push(newTeamUuid);});
updateLeague(newLeague);
console.log('invite props', this.props);
console.log('league same', league === newLeague);
}
//Todo handle sending email invite send password and link to new team
console.log('Invite a friend', this.state.inviteEmail);
};
renderInvite() {
const { isOwner, league, teams } = this.state;
if (isOwner) {
if ((league.leagueType === 'draft' && teams.length < 8) || league.leagueType !== 'draft') {
return (
<div>
<p>You have an empty team slot. Invite a fried to join!</p>
<input type="text"
placeholder={'email'}
onChange={() => this.handleUpdate(event, 'inviteEmail')}/>
<button onClick={this.sendInvite}>Invite</button>
</div>
);
}
}
}
renderViewLeague() {
console.log('render props', this.props.leagues);
const { league, owner, teams } = this.state;
const editLink = this.state.isOwner ?
<Link to={`/leagues/edit/${this.props.leagueId}`}>Edit</Link> :
'';
return (
<div>
<h2>{league.leagueName} </h2>
<h3>League Manager: <UserWidget user={owner}/> - {editLink}</h3>
<p>League Type: {league.leagueType}</p>
{this.renderInvite()}
<br/>
<hr/>
<h2>Teams</h2>
<span>{teams.map((team) => (<p key={team.uuid}>{team.teamName}</p>))}</span>
<span>
<h2>Scoring: </h2>
{league.scoring.map((score, index) => (
<p key={index}>{`Round ${index + 1}: ${score} points`}</p>
)
)}
</span>
</div>
);
}
render() {
if (!this.state.league) {
return (
<div>
<h2>No league Found</h2>
</div>
);
} else {
return (
<div>
{this.renderViewLeague()}
</div>
);
}
}
}
export default connect(
({ leagues: { leagues }, teams: { teams }, users: { users }, auth: { uuid } },
{ match: { params: { leagueId } } }) => ({
leagues,
teams,
users,
uuid,
leagueId
}), ({
addTeam : (team) => addTeam(team),
addUser : (user) => addUser(user),
updateLeague: (league) => updateLeague(league)
})
)(ViewLeague);
Reducer
import produce from 'immer';
import {
ADD_LEAGUE,
UPDATE_LEAGUE
} from '../actions/types';
const DEFAULT_LEAGUES = {
leagues: [ {
leagueName : 'Test League',
leagueManager: 'testUser12345',
uuid : 'testLeague12345',
teams : [ 'testTeam12345', 'testTeam23456' ],
scoring : [ 25, 20, 15, 10, 5, -5 ],
leagueType : 'draft'
} ]
};
const leaguesReducer = (state = DEFAULT_LEAGUES, action) =>
produce(state, draft => {
// noinspection FallThroughInSwitchStatementJS
switch (action.type) {
case ADD_LEAGUE:
draft.leagues.push(action.league);
case UPDATE_LEAGUE:
console.log('updating league', action.league);
const { league } = action;
const leagueIndex = draft.leagues.findIndex(fLeague => league.uuid === fLeague.uuid);
draft.leagues.splice(leagueIndex, 1, league);
}
});
export default leaguesReducer;
Any help is greatly appreciated!! More info available if needed
Try adding return; at the end of your case blocks.
You can read more about returning data from producers and see examples of what to do and what not to do here.

React unable to set two way binding

Here are my two components. I just need to update my state in the login component. I am not sure what I am doing wrong here. I am trying to pass the data on change to the login component. The data is getting captured in e.target.value for each character, but then it resets the state.
I have tried to move the userObj inside the state as well,but does not work
import React, { Component } from 'react';
import FormHeader from './FormHeader'
class NonLoggenInForm extends Component {
render() {
return (
<div className="marginTop1 formPanel">
<FormHeader label={this.props.label}/>
{this.props.content.map((key)=>{
return <input type={key.type}
value = {key.value}
placeholder = {key.name}
required = {key.required}
onChange = {e=>this.props.onChange(e)}
className = "formInput"
name = {key.name}
key = {key.id}
/>;
})}
<button onClick={this.props.onSubmit}> Sign in</button>
</div>
);
}
}
export default NonLoggenInForm;
import React, { Component } from 'react';
import Logo from '../shared/Logo';
import NonLoggenInForm from '../shared/NonLoggenInForm';
class Login extends Component {
changeHandler = (e) => {
console.log(e.target.value);
this.setState({
[e.target.name] : e.target.value
});
}
loginHandler = (e) => {
e.preventDefault();
console.log(this.state);
}
render() {
let userObj = [
{
name : 'userId',
type: 'text',
required: true,
value : '',
id : 1
},
{
name : 'password',
type : 'password',
required : true,
value : '',
id : 2
}
];
return (
<div className="nonLoggedInPages">
<Logo/>
<NonLoggenInForm content={userObj} label="Sign in" onSubmit={this.loginHandler} onChange={this.changeHandler}/>
</div>
);
}
}
export default Login;
Moved the user Obj to state again and changed the onChange function as below
changeHandler = (e) => {
let objIndex = this.state.userObj.findIndex((ele)=>{
return ele.name === e.target.name;
});
let upadtedObject = [...this.state.userObj];
upadtedObject[objIndex].value = e.target.value;
this.setState({
userObj : upadtedObject
});
e.target.value = this.state.userObj[objIndex].value;
}

Resources