setState not updating value with object - javascript

This is my initial state:
const [inputList, setInputList] = useState([
{ answer: "Answer 1", isCorrect: false, points: 0 },
]);
I am adding input field dynamically and initially there will be one field. There is an Add button through which I can add more fields and remove button with that I can remove fields. But the problem I'm facing, after submitting the value I want to clear all the fields and there should be only one field as it was initially.Suppose if I add 4 fields and after submitting there should be only one. But when I'm trying to do that its not working but add, remove and even setting empty the state is working but setting only one field is not working.I'm mapping depending on inputList value but still after submitting I'm getting all the fields. If I have added 4 fields then after submitting also its 4. The state value is not being changed at all.
ADD more function:
const handleAddClick = () => {
let count = inputList.length;
let newfield = {
answer: `Answer ${count + 1}`,
isCorrect: false,
points: 0,
};
setInputList([...inputList, newfield]);
};
remove function:
const handleRemoveClick = (index) => {
let list = [...inputList];
let newList = list.filter((item, ind) => ind !== index);
setInputList(newList);
};
submit function: This should reset the state to initial state that is only one field should be there But not working
const Save = () => {
setInputList([{ answer: "Answer 1", isCorrect: false, points: 0 }])
}
I tried to do loop also for deleting every index but it only removes one no matter how many times the loop runs
Whole Component:
import DeleteIcon from "#mui/icons-material/Delete";
import React from "react";
import Checkbox from "#mui/material/Checkbox";
import Radio from "#mui/material/Radio";
import FormControl from "#mui/material/FormControl";
import IconButton from "#mui/material/IconButton";
import Switch from "#mui/material/Switch";
import TextField from "#mui/material/TextField";
import { useState } from "react";
import PlusSign from "../../../../Files/img/plus.png";
import Header from "../../../Header/Header";
import Topbar from "../../../Topbar/Topbar";
import styles from "../MultipleResponse/MultipleResponse.module.scss";
import QuestionEditor from "../../Editor/QuestionEditor/QuestionEditor";
import ReactTag from "../../TagInput/ReactTag";
import FormControlLabel from "#mui/material/FormControlLabel";
import { styled } from "#mui/material/styles";
import axios from "axios";
import "react-quill/dist/quill.snow.css";
import AnswerEditor from "../../Editor/AnswerEditor/AnswerEditor";
import SelectComp from "../../SelectComp/SelectComp";
import {
timeOptions,
categoryOptions,
difficultyLevelOptions,
} from "../../../../utils/constants";
import { RadioGroup } from "#mui/material";
import Snackbars from "../../../Assesment/MyAssesment/Snackbar";
import { useEffect } from "react";
export default function MultipleChoice() {
const baseURL = "http://localhost:5000/api/question";
const [distributePoints, setDistributePoints] = useState(false);
const [inputList, setInputList] = useState([
{ answer: "Answer 1", isCorrect: false, points: 0 },
]);
const [question, setQuestion] = useState("");
const [tags, setTag] = useState([]);
const [title, setTitle] = useState("");
const [time, setTime] = useState("");
const [difficultyLevel, setdifficultyLevel] = useState("");
const [category, setCategory] = useState("");
const [whatToLook, setwhatToLook] = useState("");
const [questionRelevent, setQuestionRelevent] = useState("");
const [shuffle, setShuffle] = useState(false);
const [value, setValue] = React.useState("");
const [loading, setLoading] = useState(false);
const [loading2, setLoading2] = useState(false);
const [snackText, setSnackText] = useState("");
const [severity,setSeverity]=useState("")
useEffect(() => {
const items = JSON.parse(localStorage.getItem("tempData"));
if (items) {
setTitle(items[0].title);
setdifficultyLevel(items[0].level);
setwhatToLook(items[0].whatToLookFor);
setQuestionRelevent(items[0].whyQuestionIsRelevant);
setTag(items[0].tags);
setTime(items[0].time);
setInputList(items[0].answers);
setCategory(items[0].category);
setQuestion(items[0].detail);
setShuffle(items[0].shuffleAnswers);
setDistributePoints(items[0].distributePoints);
setValue(items[0].radioValue);
}
}, []);
const [snack, setSnack] = React.useState(false);
const closeSnackbar = (event, reason) => {
if (reason === "clickaway") {
return;
}
setSnack(false);
};
const IOSSwitch = styled((props) => (
<Switch
focusVisibleClassName=".Mui-focusVisible"
disableRipple
{...props}
/>
))(({ theme }) => ({
width: 40,
height: 24,
padding: 0,
marginRight: 10,
"& .MuiSwitch-switchBase": {
padding: 0,
margin: 2,
transitionDuration: "300ms",
"&.Mui-checked": {
transform: "translateX(16px)",
color: "#fff",
"& + .MuiSwitch-track": {
backgroundColor:
theme.palette.mode === "dark" ? "#3699FF" : "#3699FF",
opacity: 1,
border: 0,
},
"&.Mui-disabled + .MuiSwitch-track": {
opacity: 0.5,
},
},
"&.Mui-focusVisible .MuiSwitch-thumb": {
color: "#33cf4d",
border: "6px solid #fff",
},
"&.Mui-disabled .MuiSwitch-thumb": {
color:
theme.palette.mode === "light"
? theme.palette.grey[100]
: theme.palette.grey[600],
},
"&.Mui-disabled + .MuiSwitch-track": {
opacity: theme.palette.mode === "light" ? 0.7 : 0.3,
},
},
"& .MuiSwitch-thumb": {
boxSizing: "border-box",
width: 20,
height: 20,
backgroundColor: "#FFFFFF",
},
"& .MuiSwitch-track": {
borderRadius: 26 / 2,
backgroundColor: theme.palette.mode === "light" ? "#E9E9EA" : "#39393D",
opacity: 1,
transition: theme.transitions.create(["background-color"], {
duration: 500,
}),
},
}));
const switchHandler = (event) => {
setDistributePoints(event.target.checked);
};
const handleInputChange = (e, index) => {
const list = [...inputList];
list[index]["answer"] = e;
setInputList(list);
};
const handlePointChange = (e, index) => {
const { name, value } = e.target;
const list = [...inputList];
list[index][name] = value;
setInputList(list);
};
const handleRemoveClick = (index) => {
let list = [...inputList];
let newList = list.filter((item, ind) => ind !== index);
setInputList(newList);
};
const handleAddClick = () => {
let count = inputList.length;
let newfield = {
answer: `Answer ${count + 1}`,
isCorrect: false,
points: 0,
};
setInputList([...inputList, newfield]);
};
const SaveAndAddAnother = () => {
setLoading2(true);
const newInputList = inputList.map(({ points, ...rest }) => {
return rest;
});
try {
axios
.post(baseURL, {
questionType: "MULTIPLE_CHOICE",
time: time.toString(),
title: title,
level: difficultyLevel,
detail: question,
category: category,
tags: tags,
whatToLookFor: whatToLook,
whyQuestionIsRelevant: questionRelevent,
answers: distributePoints ? inputList : newInputList,
distributePoints: distributePoints,
shuffleAnswers: shuffle,
})
.then((response) => {
setLoading(false);
if (response.data.result === 1) {
setSnack(true);
setSeverity('success')
setTimeout(() => {
setLoading2(false);
}, 1000);
setSnackText("Question added successfully");
setTime("");
setTitle("");
setdifficultyLevel("");
setQuestion("");
setCategory("");
setTag([]);
setwhatToLook("");
setQuestionRelevent("");
setDistributePoints(false);
setShuffle(false);
setValue("A");
let newList = [{ answer: "Answer 1", isCorrect: false, points: 0 }];
setInputList(newList);
localStorage.removeItem("tempData");
} else {
setLoading(false);
}
})
.catch((err) => {
// Handle error
setLoading2(false);
setSnack(true)
setSnackText("Error Adding question")
setSeverity('error')
});
} catch (e) {
setLoading(false);
}
};
const Save = () => {
setInputList([{ answer: "Answer 1", isCorrect: false, points: 0 }])
// setSnack(true);
// setSnackText("Question Saved as draft");
// setSeverity('success')
// setLoading(true);
// const localData = [
// {
// questionType: "MULTIPLE_CHOICE",
// time: time.toString(),
// title: title,
// level: difficultyLevel,
// detail: question,
// category: category,
// tags: tags,
// whatToLookFor: whatToLook,
// whyQuestionIsRelevant: questionRelevent,
// answers: inputList,
// distributePoints: distributePoints,
// shuffleAnswers: shuffle,
// radioValue: value,
// },
// ];
// localStorage.setItem("tempData", JSON.stringify(localData));
// setTimeout(() => {
// setLoading(false);
// }, 2000);
};
const questionText = (value) => {
setQuestion(value);
};
const selectedTags = (tags) => {
setTag(tags);
};
const handleTitle = (e) => {
setTitle(e.target.value);
};
const handleTime = (e) => {
setTime(e.target.value);
};
const handleDifficulty = (e) => {
setdifficultyLevel(e.target.value);
};
const handleCategory = (e) => {
setCategory(e.target.value);
};
const handleWhatToLookFor = (e) => {
setwhatToLook(e.target.value);
};
const handleQuestionRelevent = (e) => {
setQuestionRelevent(e.target.value);
};
const handleShuffle = (event) => {
setShuffle(event.target.checked);
};
function handleRadioButton(event, i) {
if (event.target.value === value) {
setValue("");
} else {
setValue(event.target.value);
}
const list = [...inputList];
Object.keys(list).forEach((key) => {
list[key]["isCorrect"] = false;
});
list[i]["isCorrect"] = true;
setInputList(list);
}
return (
<div className={styles.mainContainer}>
<Header />
<Topbar
questionType="Multiple-response"
loading={loading}
loading2={loading2}
SaveAndAddAnother={SaveAndAddAnother}
save={Save}
/>
<div className={styles.Container}>
<div className={styles.questionContainer}>
<div className={styles.left}>
<p className={styles.question}>Question</p>
<div className={styles.input}>
<TextField
variant="standard"
InputProps={{
disableUnderline: true,
}}
className={styles.title}
placeholder="Title.."
value={title}
onChange={(e) => handleTitle(e)}
/>
<QuestionEditor
question={question}
questionText={questionText}
theme="snow"
/>
</div>
<div className={styles.category}>
<ReactTag tags={tags} selectedTags={selectedTags} />
</div>
</div>
<div className={styles.right}>
<div className={styles.rightText}>
<span className={styles.heading}>Select the right answer</span>
<div>
<IOSSwitch
checked={distributePoints}
onChange={switchHandler}
/>
<span className={styles.instructinHeading}>
Distribute points across answers
</span>
</div>
</div>
<div className={styles.answers}>
<div className={styles.radio}>
<FormControl style={{ display: "flex", flex: 1 }}>
<RadioGroup
aria-labelledby="demo-controlled-radio-buttons-group"
name="controlled-radio-buttons-group"
value={value}
>
{inputList.map((x, i) => {
return (
<div key={i}>
<div key={i} className={styles.options}>
{!distributePoints ? (
<FormControlLabel
key={i}
value={x.answer}
control={
<Radio
onClick={(e) => handleRadioButton(e, i)}
/>
}
defaultChecked={false}
/>
) : (
""
)}
<div className="editor">
<AnswerEditor
handleAnswer={handleInputChange}
index={i}
theme="bubble"
val={x.answer}
/>
</div>
{distributePoints ? (
<div className={styles.inputCounter}>
<TextField
variant="standard"
name="points"
InputProps={{
disableUnderline: true,
type: "number",
}}
inputProps={{
min: 0,
style: { textAlign: "center" },
}}
value={x.points}
onChange={(e) => handlePointChange(e, i)}
/>
</div>
) : (
""
)}
{inputList.length > 1 && (
<IconButton
onClick={() => handleRemoveClick(i)}
className={styles.icon}
>
<DeleteIcon
sx={{ fontSize: 24, color: "#3699FF" }}
/>
</IconButton>
)}
</div>
{inputList.length - 1 === i && (
<div className={styles.bottomItemContainer}>
{distributePoints ? (
<div className={styles.pointsInfoText}>
Allocate points across answers. Give the best
answer 5 points
</div>
) : (
""
)}
{!inputList.some((el) => el.isCorrect === true) &&
!distributePoints ? (
<div className={styles.pointsInfoText}>
Select the correct answer
</div>
) : (
""
)}
<div
style={{
display: "flex",
justifyContent: "space-between",
}}
>
<button
onClick={handleAddClick}
type="button"
className={styles.addButton}
>
<img src={PlusSign} alt="" /> Add another
answer
</button>
<div className={styles.label}>
<Checkbox
checked={shuffle}
onChange={handleShuffle}
style={{ color: "#00A3FF" }}
/>
Shuffle answer
</div>
</div>
</div>
)}
</div>
);
})}
</RadioGroup>
</FormControl>
</div>
</div>
</div>
</div>
<div className={styles.itemContainer}>
<div className={styles.timeContainer}>
<SelectComp
handleChange={handleTime}
title="Time to answer the question"
val={time}
optionData={timeOptions}
/>
</div>
<div className={styles.timeContainer}>
<SelectComp
handleChange={handleCategory}
title="Question Category"
val={category}
optionData={categoryOptions}
/>
</div>
</div>
<div className={styles.itemContainer}>
<div style={{ flex: 1 }}>What to look for in the answer?</div>
<div style={{ flex: 1 }}>Why is this question relevant?</div>
</div>
<div className={styles.itemContainer}>
<div className={styles.releventText}>
<TextField
variant="standard"
InputProps={{
disableUnderline: true,
}}
className={styles.text}
placeholder="What to look for in the answer..."
value={whatToLook}
onChange={(e) => handleWhatToLookFor(e)}
/>
</div>
<div className={styles.releventText}>
<TextField
variant="standard"
InputProps={{
disableUnderline: true,
}}
className={styles.text}
placeholder="Why is this question relevant.."
value={questionRelevent}
onChange={(e) => handleQuestionRelevent(e)}
/>
</div>
</div>
<div className={styles.itemContainer}>
<div className={styles.difficultyLevel}>
<SelectComp
handleChange={handleDifficulty}
title="Difficulty Level"
val={difficultyLevel}
optionData={difficultyLevelOptions}
/>
</div>
{/* <div style={{ flex: 1 }}>Why is this question relevant?</div> */}
</div>
<div className={styles.bottomInfo}>
<div className={styles.element}>
<div className={styles.circle}></div>
<div className={styles.text}>
How should I formate my questions?
</div>
</div>
<div className={styles.element}>
<div className={styles.circle2}></div>
<div className={styles.text}>
{" "}
How do I use the question editor?
</div>
</div>
<div className={styles.element}>
<div className={styles.circle3}></div>
<div className={styles.text}>How do I use the formula editor?</div>
</div>
</div>
</div>
{snack && (
<Snackbars
text={snackText}
snack={snack}
closeSnackbar={closeSnackbar}
severity={severity}
/>
)}
{/* <div style={{ marginTop: 20 }}>{JSON.stringify(inputList)}</div> */}
</div>
);
}

Related

What am I doing wrong? when my season dropdown (AsyncSelect) renders my seasons, it shows season 1 twice? not season 1 then season 2?

`What am I doing wrong? when my season dropdown (AsyncSelect) renders my seasons, it shows season 1 twice? not season 1 then season 2?
Also in the same modal I want to run the episode items via Flatlist, but initially season 1 then 2 and so on in asyncSelect, if yal know of a better dropdown or current picker for reactjs.
import { ReactNode, SetStateAction, useEffect, useRef, useState } from "react";
import { useRecoilState, useRecoilValue } from "recoil";
import { CheckIcon, HandThumbUpIcon, PlusIcon, SpeakerWaveIcon, SpeakerXMarkIcon, XMarkIcon } from "#heroicons/react/24/outline";
import MuiModal from "#mui/material/Modal";
import toast, { LoaderIcon, Toaster } from "react-hot-toast";
import { FaPlay } from "react-icons/fa";
import videojs from "videojs";
import VideoPlayer from "./../src/VideoPlayer";
import FlatList from "flatlist-react";
import Dots from "react-activity/dist/Dots";
import "react-activity/dist/Dots.css";
import Dropdown from 'react-dropdown';
import 'react-dropdown/style.css';
import Select, { ActionMeta, StylesConfig, Theme } from 'react-select';
import AsyncSelect, { useAsync } from 'react-select/async';
import useAuth from "../hooks/useAuth";
import { episodeState, genreState, modalState, seasonsState, vodAssetState } from "../atoms/modalAtom";
//import { Element } from "../typings";
import { VodAsset, Genre, VodSeason, Episode, VideoObject } from "../src/models";
import { View } from "#aws-amplify/ui-react";
import awsvideoconfig from "../src/aws-video-exports";
import { Storage } from "aws-amplify";
import EpisodeItem from "./EpisodeItem";
import { DropdownItem } from "reactstrap";
import VodAssetItem from "./VodAssetItem";
import { DropdownIndicator } from "react-select/dist/declarations/src/components/indicators";
interface ModalProps {
episode: Episode;
episodes: Episode[];
vodAsset: VodAsset;
//video: VideoObject;
seasons: VodSeason[];
season: VodSeason;
genres: Genre[];
genre: Genre;
}
function Modal (this: any, { vodAsset, episode, episodes, seasons, season, genres, genre }: ModalProps): JSX.Element {
const [showModal, setShowModal] = useRecoilState(modalState);
const [items, setItems] = useState([]);
const [selectedItem, setSelectedItem] = useState(null);
const [selectedItems, setSelectedItems] = useState([]);
const [inputValue, setInputValue] = useState('');
const [selectedValue, setSelectedValue] = useState(null);
const [itemValue, setItemValue] = useState('');
const [itemIndex, setItemIndex] = useState('');
const [currentVodAsset, setCurrentVodAsset] = useRecoilState(vodAssetState);
const [currentEpisode, setCurrentEpisode] = useRecoilState(episodeState);
const [currentSeason, setCurrentSeason] = useRecoilState(seasonsState);
const [currentGenre, setCurrentGenre] = useRecoilState(genreState);
//const [vodAssetName, setVodAssetName] = useState(vodAsset?.title);
//const [vodAssetDescription, setVodAssetDescription] = useState(vodAsset?.description);
//const [vodAssetGenre, setVodAssetGenre] = useState(vodAsset?.genreID); //genre?.name);
//const [vodAssetRating, setVodAssetRating] = useState(vodAsset?.popularity);
//const [vodAssetYear, setVodAssetYear] = useState(vodAsset?.release_date);
//const [vodAssetDuration, setVodAssetDuration] = useState(vodAsset?.duration); //duration is a number
//const [vodAssetImage, setVodAssetImage] = useState(vodAsset?.poster);
const [vodAssetVideo, setVodAssetVideo] = useState(vodAsset?.video);
const [vodAssetSeasons, setVodAssetSeasons] = useState(vodAsset?.seasons); //seasons is an array
//const [vodAssetEpisodes, setVodAssetEpisodes] = useState(vodAsset?.episode); //episode is a number
const [vodAssetSeasonsList, setVodAssetSeasonsList] = useState(vodAsset?.seasons); //seasons is an array
//const [vodAssetEpisodesList, setVodAssetEpisodesList] = useState(vodAsset?.episodes); //episode is a number
const seasonNames = seasons ? seasons.map(season => season?.name) : []; //season?.name);
const episodeNames = episodes ? episodes.map(episode => episode?.title) : [];
const [seasonsList, setSeasonsList] = useState(seasonNames);
const [episodesList, setEpisodesList] = useState(episodeNames);
const firstSeason = vodAsset?.seasons?[0]:season!; //season! is a non-null assertion operator, double check this
const firstEpisode = firstSeason?[0]:episode!; //episode! is a non-null assertion operator, double check this
const [selectedSeason, setSelectedSeason] = useState(null); //useState<VodSeason | null>(firstSeason);//(season?.name);
const [selectedEpisode, setSelectedEpisode] = useState(null); //useState<Episode | null>(firstEpisode);//(episode?.title);
const [seasonName, setSeasonName] = useState(seasonNames[0]); //season?.name);
const [selectedSeasonName, setSelectedSeasonName] = useState(seasonNames[0]); //useState<VodSeason | null>(firstSeason);//(season?.name);
const [selectedEpisodeName, setSelectedEpisodeName] = useState(episodeNames[0]); //useState<Episode | null>(firstEpisode);//(episode?.title);
const [muted, setMuted] = useState(false);
const [addedToList, setAddedToList] = useState(false);
const [videoURL, setVideoURL] = useState('');
const [status, setStatus] = useState({});
const [token, setToken] = useState('');
//const [video, setVideo] = useState<VideoObject>();
//const onChangeEpisode = (e: any) => {
//const episodeName = e.value;} //e.value is the selected episode name from the dropdown menu
const toastStyle = {
background: 'black',
color: '#d4af37',
fontWeight: 'bold',
fontSize: '16px',
padding: '15px',
borderRadius: '9999px',
maxWidth: '1000px',
};
//const onChangeSeason = (s: VodSeason) => {
// const seasonNames = seasons ? seasons.map(season => season?.name) : []};
//const seasonName = s.name;} //e.value is the selected season name from the dropdown menu
const [selectedOption, setSelectedOption] = useState(null); //useState<VodSeason | null>(firstSeason);//(season?.name);
const [togglePlaceholderClassName, setTogglePlaceholderClassName] = useState('hidden');
const [toggleMyOptionsClassName, setToggleMyOptionsClassName] = useState('hidden');
const [toggleClassName, setToggleClassName] = useState('hidden');
const [toggleMenuClassName, setToggleMenuClassName] = useState('hidden');
const options = [
{ id: season?.id, value: season?.name, name: season?.name, data: season?.name, label: 'Season 1'},
{ id: season?.id, value: season?.name, name: season?.name, data: season?.name, label: 'Season 2'},
];
{/*const customStyles = {
control: (base: any, state: any) => ({
...base,
background: 'black',
color: 'white',
// match with the menu
borderRadius: state.isFocused ? '3px 3px 0 0' : 3,
// Overwrittes the different states of border
borderColor: state.isFocused ? '#d4af37' : '#d4af37',
// Removes weird border around container
boxShadow: state.isFocused ? null : null,
'&:hover': {
// Overwrittes the different states of border
borderColor: state.isFocused ? '#d4af37' : '#d4af37',
},
}),
menu: (base: any) => ({
...base,
// override border radius to match the box
borderRadius: 0,
// kill the gap
marginTop: 0,
background: 'black',
color: 'white',
}),
menuList: (base: any) => ({
...base,
// kill the white space on first and last option
padding: 0,
}),
option: (base: any, state: any) => ({
...base,
background: 'black',
color: 'white',
// padding: '20px',
// color: state.isSelected ? 'red' : 'blue',
// backgroundColor: state.isSelected ? 'yellow' : 'green',
// the rest of your styles
}),
};*/}
const colorStyles = {
control: (styles: any) => ({ ...styles, backgroundColor: 'black' }),
option: (styles: any, { data, isDisabled, isFocused, isSelected }: any) => {
console.log('option', data, isDisabled, isFocused, isSelected);
return {
...styles,
//color: data.color,
backgroundColor: isDisabled
? null
: isSelected
? '#d4af37'
: isFocused
? '#d4af37'
: null,
color: isDisabled
? '#ccc'
: isSelected
? 'black'
: isFocused
? 'black'
: 'white',
cursor: isDisabled ? 'not-allowed' : 'default',
};
}
};
// handle selection
const handleChange = (selectedOption: any) => {
console.log('handleChange', selectedOption);
setSelectedValue(selectedOption);
};
{/*const handleChange = value => {
console.log('handleChange', value);
setSelectedValue(value);
};*/}
// handle input change
//const handleInputChange = (value: SetStateAction<string>) => {
//console.log('handleInputChange', value);
//setInputValue(value);
//};
const loadOptions = (searchValue: string, callback: (arg0: { id: string, data: string, name: string, value: string; label: string; }[]) => void) => {
setTimeout(() => {
const filteredOptions = options.filter(option =>
option.label.toLowerCase().includes(searchValue.toLowerCase())
);
console.log('loadOptions', searchValue, filteredOptions);
callback(filteredOptions);
}, 1000);
};
{/*const { data, error, isLoading } = useAsync({ promiseFn: loadOptions });*/}
const handleClose = () => {
setShowModal(false)
}
const defaultOption = options[0];
const VIDEOJS_OPTIONS = {
autoplay: "play",
controls: true,
responsive: true,
fluid: true,
preload: "auto",
width: 1080,
//muted: {muted},
onplaying,
sources: {
src: awsvideoconfig.awsOutputIVS,
},
useNativeControls: true,
html5: {
nativeAudioTracks: false,
nativeVideoTracks: false,
}
};
const videoOnDemandJsOptions = {
//ref: {vodAsset},
autoplay: "play",
controls: true,
responsive: true,
fluid: true,
preload: "auto",
width: 1080,
token: {token},
displayingVodAsset: true,
//VideoPlaybackQuality: "HIGH",
//muted: {muted},
onplaying,
sources: {
//ref: {vodAsset},
src: { uri: videoURL}, //`https://${awsvideoconfig.awsOutputVideo}/public/RPS-Trailer-2/RPS-Trailer-2.m3u8`, // Test Link: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" / or / uri: videoURL / {uri: episode?.video?.uri},
posterSource: {uri: episode?.poster},
data: {episodes},
//renderEpisodeItem: {EpisodeItem},
episode: currentEpisode,
chosenItem: episode,
posterStyle: {
resizeMode: 'cover',
},
type: "application/x-mpegURL" //"video/mp4"
},
usePoster: true,
useNativeControls: true,
resizeMode: "contain",
//onPlaybackStatusUpdate: {status: setStatus(() => status)},
//episode: currentEpisode,
//data: {episodes},
//renderEpisodeItem: {EpisodeItem}, // this is the component that renders the episode item
//chosenItem: item,
//token: item.video.token,
html5: {
nativeAudioTracks: false,
nativeVideoTracks: false,
}
};
useEffect(() => {
async function fetchVodAsset() {
const result = (await DataStore.query(VodAsset, {id: vodAsset.id})) // double check this
//setVodAsset(result);
console.log("VOD ASSET:", result)
}
fetchVodAsset();
}, [])
//console.log("VOD ASSET:", vodAsset);
useEffect(() => {
if (!vodAsset) {
return;
}
async function fetchSeasons() {
const vodAssetSeasons = (await DataStore.query(VodSeason)).filter(s => s.VodAsset?.id === vodAsset.id)
//setSeasons(seasons);
console.log("SEASONS:", vodAssetSeasons);
setCurrentSeason(vodAssetSeasons[0]);
}
fetchSeasons();
}, [vodAsset])
useEffect(() => {
if (!currentSeason) {
return;
}
async function fetchEpisodes() {
const seasonEpisode = (await DataStore.query(Episode)).filter(e => e.vodSeason?.id === currentSeason.id);
//setEpisodes(seasonEpisode);
console.log("EPISODES:", seasonEpisode);
setCurrentEpisode(seasonEpisode[0])
}
fetchEpisodes();
}, [currentSeason])
if (!vodAsset) {
return <Dots/>
}
console.log("season:", season);
console.log("seasonNames:", seasonNames);
{/*useEffect(() => {
if (episode?.video.startsWith('http')) {
setVideoURL(episode?.video);
return;
}
Storage.get(episode?.video, {level: 'public', contentType: 'application/x-mpegURL', region: 'us-east-1'}).then(setVideoURL);
}, [episode]);*/}
{/*useEffect(() => {
if (!video) {
return;
}
(async () => {
await video?.current?.unloadAsync();
await video?.current?.loadAsync(
{ uri: videoURL },
{},
false
);
})();
}, [videoURL]);*/}
//console.log(videoURL);
//if (videoURL === '') {
//return <View />;
//}
//OR
//useEffect(() => {
//if (!vodAsset) return
//async function fetchVodAsset() {
//const data = await fetch(
//`https://api.themoviedb.org/3/${
//{/*vodAsset?.media_type === 'tv' ? 'tv' : 'movie'*/}
//}/${vodAsset?.id}?api_key=${
//process.env.NEXT_PUBLIC_API_KEY
//}&language=en-US&append_to_response=videos`
//).then((response) => response.json())
//if (data?.videos) {
//const index = data.videos.results.findIndex(
//(element: Element) => element.type === 'Trailer'
//)
//setTrailer(data.videos?.results[index]?.key)
//}
//if (data?.genres) {
//setGenres(data.genres)
//}
//}
//fetchVodAsset()
//}, [vodAsset])
// Find all the movies in the user's list
{/*useEffect(() => {
if (user) {
return onSnapshot(
collection(db, 'customers', user.uid, 'myList'),
(snapshot) => setVodAssets(snapshot.docs)
)
}
}, [db, vodAsset?.id])*/}
// Check if the movie is already in the user's list
{/*useEffect(
() =>
setAddedToList(
vodAssets.findIndex((result) => result.data().id === vodAsset?.id) !== -1
),
[vodAssets]
)*/}
const handleList = async () => {/*
if (addedToList) {
await deleteDoc(
doc(db, 'customers', user!.uid, 'myList', vodAsset?.id.toString()!)
)
toast(
`${vodAsset?.title || vodAsset?.original_name} has been removed from My List`,
{
duration: 8000,
style: toastStyle,
}
)
} else {
await setDoc(
doc(db, 'customers', user!.uid, 'myList', vodAsset?.id.toString()!),
{ ...vodAsset }
)
toast(
`${vodAsset?.title || vodAsset?.original_name} has been added to My List`,
{
duration: 8000,
style: toastStyle,
}
)
}
*/}
return (
<MuiModal
id="modal"
open={showModal}
onClose={handleClose}
className="fixed !top-4 left-0 right-0 z-50 mx-auto w-full max-w-5xl overflow-hidden
overflow-y-scroll rounded-md scrollbar-hide"
>
<>
<Toaster position="bottom-center" />
<button
onClick={handleClose}
className="modalButton absolute right-5 top-5 !z-40 h-9 w-9 border-none
bg-[#181818] hover:bg-[#181818]"
>
<XMarkIcon className="h-6 w-6"/>
</button>
<div className="relative pt-[0%]" key={vodAsset?.id}>
<VideoPlayer episode={currentEpisode} {...videoOnDemandJsOptions} />
<FlatList
list={[]}
data={episodes}
renderItem={function (item: Episode, {/*key: string*/}): JSX.Element | ReactNode {
<EpisodeItem
key={episode.id}
episode={item}
/>;
throw new Error("Function not implemented.");
}}>
</FlatList>
{/*<EpisodeItem episode={episode} />*/}
<div className="absolute bottom-10 flex w-full items-center justify-between px-10">
<div className="flex space-x-2">
<button className="flex items-center gap-x-2 rounded bg-[#d4af37] px-8 text-xl
font-bold text-black transition hover:bg-[#e6e6e6]"
>
<FaPlay className="h-7 w-7 text-black" />
Play
</button>
<button className="modalButton" onClick={handleList}>
{addedToList ? (
<CheckIcon className="h-7 w-7" />
): (
<PlusIcon className="h-7 w-7" />
)}
</button>
<button className="modalButton">
<HandThumbUpIcon className="h-7 w-7" />
</button>
</div>
<button className="modalButton" onClick={() => setMuted(!muted)}>
{muted ? (
<SpeakerXMarkIcon className="h-6 w-6" />
) : (
<SpeakerWaveIcon className="h-6 w-6" />
)}
</button>
</div>
</div>
<div className="flex space-x-16 rounded-b-md bg-[#181818] px-10 py-8">
<div className="space-y-6 text-lg">
<div className="flex items-center space-x-2 text-sm">
<p className="font-semibold text-green-400">
{/*vodAsset!.vote_average * 10*/}98% Match
</p>
<p className="font-light">
{vodAsset?.release_date || vodAsset?.first_air_date}
</p>
<p className="font-light">
{vodAsset?.numberOfSeasons} Seasons
</p>
<div className="flex h-4 items-center justify-center rounded border
border-white/40 px-1.5 bg-[#d4af37] text-black text-xs">
12+
</div>
<div className="flex h-4 items-center justify-center rounded border
border-white/40 px-1.5 text-xs">
HD
</div>
</div>
{/*<View style={{ backgroundColor: 'white' }}>
</View>*/}
{/*currentSeason && (
<Picker
selectedValue={currentSeason}
onValueChange={(itemValue, itemIndex) => {
setCurrentSeason(seasons[itemIndex])
}}
style={{color: 'white', width: 150 }}
itemStyle={{backgroundColor: 'black', maxHeight: 50, marginBottom: -150, marginTop: 20 }}
dropdownIconColor={'white'}
>
{seasonNames?.map(seasonName => (
<Picker color='#d4af37' label={seasonName} value={seasonName} key={seasonName} />
))}
</Picker>
)*/}
{currentSeason &&
<>
{seasonNames?.map(seasonName => (
<option id={seasonName} label={seasonName} value={seasonName} key={season.id}>{seasonName}</option>
))}
<AsyncSelect value={seasons?.map(season => ({value: season.name, label: season.name}))} onChange={handleChange} //value={seasons?.map(season => ({ id: season?.id, name: season.name, value: season.name, label: season.name}))} onChange={handleChange} //value={seasonName}
//value={seasons?.map(season => ({value: season.name, label: season.name}))}
className="select"
classNamePrefix="select"
autoFocus={true}
defaultValue={options[0]}
//defaultValue={seasons?.map(season => ({value: season.name, label: season.name}))}
isSearchable={false}
name="season"
cacheOptions={false}
loadOptions={loadOptions}
defaultOptions={true}
options={options}
placeholder="Select a season"
styles={colorStyles}
//onChange={onChangeSeason}
//onChange={(itemValue, itemIndex) => {
//setCurrentSeason(seasons[itemIndex])
//}}
//controlShouldRenderValue={true}
//isClearable={true}
isMulti={true}
//isOptionDisabled={true}
//isOptionSelected={true}
//isSearchable={true}
//isDisabled={true}
//isFocused={true}
//isLoading={true}
//isRtl={true}
//isInvalid={true}
//menuIsOpen={true}
//isMenuFocused={true}
//isMenuSelected={true}
/>
</>}
<div className="flex flex-col gap-x-10 gap-y-4 font-light md:flex-row">
<p className="w-5/6">{/*vodAsset?.overview*/}{vodAsset?.description}
</p>
<div className="flex flex-col space-y-3 text-sm">
<div>
<span className="text-[gray]">Cast: </span>
{vodAsset?.cast}
</div>
<div>
<span className="text-[gray]">Creator: </span>
{vodAsset?.creator}
</div>
<div>
<span className="text-[gray]">Genres: </span>
{/*genres?.map((genre: { title: any; }) => genre?.title)*/}
{/*vodAsset?.genreID*/}
</div>
<div>
<span className="text-[gray]">Original Language: </span>
{vodAsset?.original_language}
</div>
<div>
<span className="text-[gray]">Original Country: </span>
{vodAsset?.origin_country}
</div>
<div>
<span className="text-[gray]">Total Votes: </span>
{vodAsset?.vote_count}</div>
</div>
</div>
</div>
</div>
</>
</MuiModal>
)
}
export default Modal`````

How to create a reusable component for ant design table columns filter that we can use it for all our table?

I want to add filter, search to ant design tables. Based on the documentation, we need to add some props to table column but it will be repeatative. Is there a way that we can creat a seperate component for the columns and filtering those columns?
I tried to develop that component but the column props' do not accept JSX.
I've done it in this way:
SearchHighliter:
const HighlighterWrapper = memo(({ searchWords, textToHighlight }) => (
<Highlighter
highlightStyle={{
backgroundColor: '#ffc069',
padding: 0,
}}
searchWords={searchWords}
autoEscape
textToHighlight={textToHighlight}
/>
))
FilterHook:
import { useRef, useState } from 'react'
import { Button, Input, Space } from 'antd'
import { SearchOutlined } from '#ant-design/icons'
const useTableFilter = () => {
const [searchText, setSearchText] = useState('')
const [searchedColumn, setSearchedColumn] = useState('')
const searchInput = useRef(null)
const handleSearch = (selectedKeys, confirm, dataIndex) => {
confirm()
setSearchText(selectedKeys[0])
setSearchedColumn(dataIndex)
}
const handleReset = (clearFilters, confirm) => {
clearFilters()
setSearchText('')
confirm()
}
const getColumnSearchProps = (dataIndex) => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div
style={{
padding: 8,
}}
>
<Input
ref={searchInput}
placeholder="Search text"
value={selectedKeys[0]}
onChange={(e) => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => handleSearch(selectedKeys, confirm, dataIndex)}
style={{
marginBottom: 8,
display: 'block',
}}
/>
<Space>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{
width: 90,
}}
>
Search
</Button>
<Button
onClick={() => clearFilters && handleReset(clearFilters, confirm)}
size="small"
style={{
width: 90,
}}
>
Reset
</Button>
</Space>
</div>
),
filterIcon: (filtered) => (
<SearchOutlined
style={{
color: filtered ? '#1890ff' : undefined,
marginRight: 10,
}}
/>
),
onFilterDropdownVisibleChange: (visible) => {
if (visible) {
setTimeout(() => searchInput.current?.select(), 100)
}
},
})
return [searchText, searchedColumn, getColumnSearchProps]
}
export default useTableFilter
Table:
const SummaryReport = () => {
const [data, setData] = useState([])
const [isLoading, setIsLoading] = useState(false)
const [searchText, searchedColumn, getColumnSearchProps] = useTableFilter()
const columns = [
{
title: () => <span style={{ paddingLeft: 8 }}>Name</span>,
key: 'name',
dataIndex: 'name',
className: 'no-padding-cell-report',
width: 250,
...getColumnSearchProps('name'),
onFilter: (value, record) => get(record, 'name').toString().toLowerCase().includes(value.toLowerCase()),
render: (_, record) => (
<div style={{ padding: 8 }}>
{searchedColumn === 'name' ? (
<HighlighterWrapper
searchWords={[searchText]}
textToHighlight={get(record, 'name') ? get(record, 'name').toString() : ''}
/>
) : (
get(record, 'name')
)}
</div>
),
},
]
....
}
All you need is just provide 2 functions to your column:
onFilter - here you describe your filter logic, if your data object is simple like in my case, the function also is trivial
render - you need to provide this function if you want to highlight text that the user inputs to filter input.

Is refactoring with a separate function possible?

I have a table component where many of the table columns are handled similarly.
Is there any way to optimize this code, maybe in a separate function?
import useTableStyles from 'admin/components/table/AdminTable.styles';
import useStyles from 'portal/pages/wasOperators/views/ViewEditOperators.style';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { toast } from 'react-toastify';
import { Form } from 'shared/components/Form';
import WssoService from 'shared/services/WssoService';
import DateFnsUtils from '#date-io/date-fns';
import { Button, Grid, TextField } from '#material-ui/core';
import { DatePicker, MuiPickersUtilsProvider } from '#material-ui/pickers';
interface Props {
isEditing?: boolean;
}
const BenchmarkingTable: React.FC<Props> = ({ isEditing }) => {
const tableClasses = useTableStyles();
const classes = useStyles();
const { t } = useTranslation();
const { operatorsId }: any = useParams();
const valuesForInverseCalculation = [
'continuityWaterSupply',
'totalLossesWaterSupplySystems',
'pressureWaterSupplySystem',
'sewerNetworkAccidents',
'floodsThirdPartyCausedBySewage',
];
const benchmarkingDetailsInit = {
levelWaterSupplyServices: {
target: '',
result: '',
status: '',
},
qualityDrinkWaterLargeAreas: {
target: '',
result: '',
status: '',
},
qualityDrinkWaterSmallAreas: {
target: '',
result: '',
status: '',
},
monitorQualityDrinkWater: {
target: '',
result: '',
status: '',
},
continuityWaterSupply: {
target: '',
result: '',
status: '',
},
totalLossesWaterSupplySystems: {
target: '',
result: '',
status: '',
},
pressureWaterSupplySystem: {
target: '',
result: '',
status: '',
},
levelCoverageServiceDisposalOfWastewater: {
target: '',
result: '',
status: '',
},
levelCoverageServiceTreatmentOfWastewater: {
target: '',
result: '',
status: '',
},
wastewaterQuality: {
target: '',
result: '',
status: '',
},
sewerNetworkAccidents: {
target: '',
result: '',
status: '',
},
floodsThirdPartyCausedBySewage: {
target: '',
result: '',
status: '',
},
};
const [data, setData] = useState<any>(benchmarkingDetailsInit);
const [KEY, setKEY] = useState<any>(new Date());
useEffect(() => {
(async () => {
const result: any = await WssoService.getBenchmarking(
operatorsId,
KEY.getFullYear()
);
if (result && result.data && result.data.json) {
setData(JSON.parse(result.data.json));
} else {
setData(null);
}
})();
/* eslint-disable react-hooks/exhaustive-deps */
}, [KEY, operatorsId]);
const HandleKEYChange = () => {
return (
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<DatePicker
variant="inline"
inputVariant="outlined"
format={'yyyy'}
views={['year']}
onChange={setKEY}
value={KEY}
/>
</MuiPickersUtilsProvider>
);
};
const updateBenchmarking = async (editValues: any) => {
const request = {
json: JSON.stringify(editValues),
};
const result: any = await WssoService.editAddBenchmarking(
operatorsId,
KEY.getFullYear(),
request
);
if (result && result.data && result.json) {
setData(JSON.parse(result.data.json));
}
};
const onUpdateSuccess = () => {
toast.success(t('itemUpdateSuccessfully'));
};
const handleSaveData = async () => {
if (data) {
await updateBenchmarking(data);
onUpdateSuccess();
}
};
const handleOnTextChange = (
key: string,
valueKey: string,
e: React.ChangeEvent<HTMLInputElement>
) => {
const value = e.target.value;
const newData = Object.assign({}, data, {
[key]: Object.assign({}, data[key], {
[valueKey]: value,
}),
});
setData(newData);
};
const handleColorChange = (name: any, target: any, result: any) => {
const removePercentFromTarget = target.includes('%')
? target.slice(0, -1)
: target;
const convertedTarget = Number(removePercentFromTarget);
const removePercentFromResult = result.includes('%')
? result.slice(0, -1)
: result;
const convertedResult = Number(removePercentFromResult);
target = convertedTarget;
result = convertedResult;
let final: any;
const arrOfNames = [];
arrOfNames.push(name);
if (arrOfNames.some(x => valuesForInverseCalculation.includes(x))) {
if (target < result) {
Object.keys(data).map(k => {
return (final = data[k].status = 'red');
});
} else if (target > result) {
Object.keys(data).map(k => {
return (final = data[k].status = 'green');
});
} else if (target === result) {
Object.keys(data).map(k => {
return (final = data[k].status = 'yellow');
});
}
} else {
if (target > result) {
Object.keys(data).map(k => {
return (final = data[k].status = 'red');
});
} else if (target < result || (target === result && result === 100)) {
Object.keys(data).map(k => {
return (final = data[k].status = 'green');
});
} else if (target === result) {
Object.keys(data).map(k => {
return (final = data[k].status = 'yellow');
});
}
}
return final;
};
const updateData = (newData: any[]) => {
setData(newData);
};
const renderBenchmarkDetails = () => {
if (isEditing) {
return data ? (
Object.keys(data).map(k => {
return (
<tr>
<td>{t(k)}</td>
<td className={classes.benchTextfieldAlign}>
<TextField
type="text"
onChange={(e: any) => handleOnTextChange(k, 'target', e)}
value={data[k].target}
inputProps={{ min: 0, style: { textAlign: 'center' } }}
required
/>
</td>
<td className={classes.benchTextfieldAlign}>
<TextField
type="text"
onChange={(e: any) => handleOnTextChange(k, 'result', e)}
value={data[k].result}
inputProps={{ min: 0, style: { textAlign: 'center' } }}
required
/>
</td>
</tr>
);
})
) : (
<tr>
<td>{t('noDetailsToDisplay')}</td>
<td>{t('noDetailsToDisplay')}</td>
<td>{t('noDetailsToDisplay')}</td>
<td>{t('noDetailsToDisplay')}</td>
</tr>
);
} else {
return data ? (
Object.keys(data).map((rowName, i) => {
return (
<tr key={i}>
<td>{t(rowName)}</td>
<td className={classes.benchTextfieldAlign}>
{data[rowName].target}
</td>
<td className={classes.benchTextfieldAlign}>
{data[rowName].result}
</td>
<td className={classes.benchTextfieldAlign}>
<div
style={{
width: '15px',
height: '15px',
borderRadius: '50%',
backgroundColor: handleColorChange(
rowName,
data[rowName].target,
data[rowName].result
),
}}
/>
</td>
</tr>
);
})
) : (
<tr>
<td>{t('noDetailsToDisplay')}</td>
<td>{t('noDetailsToDisplay')}</td>
<td>{t('noDetailsToDisplay')}</td>
<td>{t('noDetailsToDisplay')}</td>
</tr>
);
}
};
const handleExport = useCallback(async () => {
const dataRows: any = [];
if (data) {
Object.keys(data).map(rowName => {
dataRows.push([t(rowName), data[rowName].target, data[rowName].result]);
return null;
});
}
let dataToCSV: any[][] = [];
if (dataRows && dataRows.length > 0) {
dataToCSV = [[t('criteria'), t('targets'), t('results')], ...dataRows];
const csvContent: string =
'data:text/csv;charset=utf-8,\uFEFF' +
dataToCSV
.map(e =>
e
.map(r =>
r instanceof Array
? `"${(r ?? '').toString().replace(',', ', ')}"`
: (r ?? '').toString().replace(',', ' ')
)
.join(',')
)
.join('\n');
const encodedUri: string = encodeURI(csvContent);
const link: HTMLAnchorElement = document.createElement('a');
link.setAttribute('href', encodedUri);
link.setAttribute('download', `${t('exportedTableData')}.csv`);
link.click();
}
}, [data, t]);
console.log(operatorsId);
console.log(benchmarkingDetailsInit);
console.log(updateBenchmarking);
return (
<Grid>
<Form
onSubmit={(formData, { resetForm }) => {
const tempData = !!operatorsId
? {
...formData,
}
: {
...formData,
...(!!operatorsId && {
operatorsAreaEntity: {
id: parseInt(operatorsId, 0),
},
}),
id: operatorsId ? undefined : Date.now(),
};
updateData([tempData, ...data]);
resetForm();
}}
initialValues={benchmarkingDetailsInit}
enableReinitialize={true}
>
<div className={classes.tableContainer}>
<Grid item xs={12}>
<div className={classes.containerLegendBench}>
<div
style={{
display: 'flex',
justifyContent: 'flex-end',
}}
>
<div className={classes.yearBoxBenchmarketType}>
<HandleKEYChange />
</div>
<div className={classes.legendBoxBenchmarketType}>
{t('reachedResult')}
<div
style={{
marginLeft: '3px',
width: '11px',
height: '9px',
borderRadius: '50%',
backgroundColor: '#006400',
display: 'inline-block',
}}
/>
</div>
<div className={classes.legendBoxBenchmarketType}>
{t('almostReachedResult')}
<div
style={{
marginLeft: '3px',
width: '11px',
height: '9px',
borderRadius: '50%',
backgroundColor: '#FFDF00',
display: 'inline-block',
}}
/>
</div>
<div className={classes.legendBoxBenchmarketType}>
{t('notReachedResult')}
<tr
style={{
marginLeft: '3px',
width: '11px',
height: '9px',
borderRadius: '50%',
backgroundColor: '#FF0000',
display: 'inline-block',
}}
/>
</div>
</div>
</div>
</Grid>
<table className={tableClasses.table}>
<thead>
<tr>
<th>{t('criteria')}</th>
<th>{t('targets')}</th>
<th>{t('results')}</th>
<th />
</tr>
</thead>
<tbody>{renderBenchmarkDetails()}</tbody>
</table>
</div>
<Grid item xs={12}>
<Button
style={{ margin: '5px 0px 10px 10px' }}
type="button"
color="primary"
variant="contained"
onClick={handleExport}
disabled={!data}
>
{t('export')}
</Button>
</Grid>
{isEditing && (
<Button onClick={handleSaveData} className={classes.submitButton}>
{t('save')} <i className={`fas fa-save ${classes.submitIcon}`} />
</Button>
)}
</Form>
</Grid>
);
};
export default BenchmarkingTable;
You've got 60 lines of code just to initialize an empty object! It seems like each key of this object is a row of your table and each value shares the same format:
interface Benchmark {
target: string;
result: string;
status: string;
}
So the data can be described as Record<string, Benchmark>. Though it might make more sense to make the name be a property and let data be an array.
Your condition with the <td>{t('noDetailsToDisplay')}</td> will never be hit because you've initialized data to your empty object so data is always true.
If you want to show something different when there is not data then you should do any one of these:
Use an undefined initial state.
Have a separate isLoading state that you set with your useEffect hooks.
Check Object.keys(data).length > 0 instead of checking !! data.
Another part of your code that seems majorly illogical is the handleColorChange function. Despite the misleading name, it seems like this function is designed to compute a color string based on the result and target values. This color is the status property and you mutate state to get it there. Instead you should compute the color and add it as status after you fetch the data and before you set it to state.
If you want a row with 4 empty cells, you can do this:
<tr>
{Array.from({ length: 4 }).map((_, i) => (
<td key={i}>{t("noDetailsToDisplay")}</td>
))}
</tr>
You have that same JSX block in two places so you want to fix that.
I haven't fixed everything, but I have fixed quite a lot.
import useTableStyles from 'admin/components/table/AdminTable.styles';
import useStyles from 'portal/pages/wasOperators/views/ViewEditOperators.style';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { toast } from 'react-toastify';
import { Form } from 'shared/components/Form';
import WssoService from 'shared/services/WssoService';
import DateFnsUtils from '#date-io/date-fns';
import { Button, Grid, TextField } from '#material-ui/core';
import { DatePicker, MuiPickersUtilsProvider } from '#material-ui/pickers';
interface BenchmarkFromService {
target: string;
result: string;
}
interface BenchmarkRow extends BenchmarkFromService {
name: string;
status: string;
}
const BenchmarkingTable: React.FC<Props> = ({ isEditing }) => {
const tableClasses = useTableStyles();
const classes = useStyles();
const { t } = useTranslation();
const { operatorsId } = useParams<{ operatorsId: string }>();
/**
* each row of the table is a Benchmark
*/
const [data, setData] = useState<BenchmarkRow[]>([]);
const [KEY, setKEY] = useState(new Date());
// extract shared logic from "get" and "update" responses
// memoized so it's safe as a useEffect dependency
const handleResult = useCallback(
// is this really any? what is it when it's not { data: { json: string } }
(result: any) => {
/**
* regular colors and inverted colors have the same logic, just swap the target and result
*/
const computeColor = (a: number, b: number): string => {
if (a > b) {
return "red";
} else if (a < b || (a === b && b === 100)) {
return "green";
} else if (a === b) {
return "yellow";
}
};
const valuesForInverseCalculation = [
"continuityWaterSupply",
"totalLossesWaterSupplySystems",
"pressureWaterSupplySystem",
"sewerNetworkAccidents",
"floodsThirdPartyCausedBySewage"
];
const toNum = (value: string): number =>
parseFloat(value.replace("%", ""));
try {
const raw: Record<string, BenchmarkFromService> = JSON.parse(
result.data.json
);
const formatted = Object.entries(raw).map(
([name, { target, result }]) => ({
name,
target,
result,
status: valuesForInverseCalculation.includes(name)
? computeColor(toNum(result), toNum(target))
: computeColor(toNum(target), toNum(result))
})
);
setData(formatted);
} catch (e) {
// catch errors from malformatted response or unparsable JSON
setData([]);
}
},
[]
);
useEffect(() => {
(async () => {
const result: any = await WssoService.getBenchmarking(
operatorsId,
KEY.getFullYear()
);
handleResult(result);
})();
}, [KEY, operatorsId, handleResult]);
const updateBenchmarking = async (editValues: any) => {
const request = {
json: JSON.stringify(editValues)
};
const result: any = await WssoService.editAddBenchmarking(
operatorsId,
KEY.getFullYear(),
request
);
handleResult(result);
};
const onUpdateSuccess = () => {
toast.success(t("itemUpdateSuccessfully"));
};
const handleSaveData = async () => {
if (data) {
await updateBenchmarking(data);
onUpdateSuccess();
}
};
const renderCell = (property: keyof BenchmarkRow, row: BenchmarkRow) => {
const onChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
setData((previous) =>
previous.map((obj) =>
obj.name === row.name
? {
...obj,
[property]: e.target.value
}
: obj
)
);
};
return (
<td className={classes.benchTextfieldAlign}>
{isEditing ? (
<TextField
type="text"
onChange={onChange}
value={row[property]}
inputProps={{ min: 0, style: { textAlign: "center" } }}
required
/>
) : (
row[property]
)}
</td>
);
};
const renderBenchmarkDetails = () => {
if (data.length === 0) {
return (
<tr>
{Array.from({ length: 4 }).map((_, i) => (
<td key={i}>{t("noDetailsToDisplay")}</td>
))}
</tr>
);
}
return data.map((row) => (
<tr key={row.name}>
<th scope="row">{row.name}</th>
{renderCell("target", row)}
{renderCell("result", row)}
<td className={classes.benchTextfieldAlign}>
<div
style={{
width: "15px",
height: "15px",
borderRadius: "50%",
backgroundColor: row.status
}}
/>
</td>
</tr>
));
};
const handleExport = useCallback(async () => {
if (data.length === 0) {
return;
}
const dataRows = data.map((row) => [t(row.name), row.target, row.result]);
const dataToCSV = [
[t("criteria"), t("targets"), t("results")],
...dataRows
];
const csvContent: string =
"data:text/csv;charset=utf-8,\uFEFF" +
dataToCSV
.map((row) =>
row
.map(
(cell) => cell.replace(",", "\\,") // I think it's ok to escape the comma like this?
)
.join(",")
)
.join("\n");
const encodedUri: string = encodeURI(csvContent);
const link: HTMLAnchorElement = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", `${t("exportedTableData")}.csv`);
link.click();
}, [data, t]);
return (
<Grid>
<Form
onSubmit={(formData, { resetForm }) => {
const tempData = !!operatorsId
? {
...formData
}
: {
...formData,
...(!!operatorsId && {
operatorsAreaEntity: {
id: parseInt(operatorsId, 0)
}
}),
id: operatorsId ? undefined : Date.now()
};
setData([tempData, ...data]);
resetForm();
}}
initialValues={{}}
enableReinitialize={true}
>
<div className={classes.tableContainer}>
<Grid item xs={12}>
<div className={classes.containerLegendBench}>
<div
style={{
display: "flex",
justifyContent: "flex-end"
}}
>
<div className={classes.yearBoxBenchmarketType}>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<DatePicker
variant="inline"
inputVariant="outlined"
format={"yyyy"}
views={["year"]}
onChange={setKEY}
value={KEY}
/>
</MuiPickersUtilsProvider>
</div>
<div className={classes.legendBoxBenchmarketType}>
{t("reachedResult")}
<div
style={{
marginLeft: "3px",
width: "11px",
height: "9px",
borderRadius: "50%",
backgroundColor: "#006400",
display: "inline-block"
}}
/>
</div>
<div className={classes.legendBoxBenchmarketType}>
{t("almostReachedResult")}
<div
style={{
marginLeft: "3px",
width: "11px",
height: "9px",
borderRadius: "50%",
backgroundColor: "#FFDF00",
display: "inline-block"
}}
/>
</div>
<div className={classes.legendBoxBenchmarketType}>
{t("notReachedResult")}
<tr
style={{
marginLeft: "3px",
width: "11px",
height: "9px",
borderRadius: "50%",
backgroundColor: "#FF0000",
display: "inline-block"
}}
/>
</div>
</div>
</div>
</Grid>
<table className={tableClasses.table}>
<thead>
<tr>
<th>{t("criteria")}</th>
<th>{t("targets")}</th>
<th>{t("results")}</th>
<th />
</tr>
</thead>
<tbody>{renderBenchmarkDetails()}</tbody>
</table>
</div>
<Grid item xs={12}>
<Button
style={{ margin: "5px 0px 10px 10px" }}
type="button"
color="primary"
variant="contained"
onClick={handleExport}
disabled={!data}
>
{t("export")}
</Button>
</Grid>
{isEditing && (
<Button onClick={handleSaveData} className={classes.submitButton}>
{t("save")} <i className={`fas fa-save ${classes.submitIcon}`} />
</Button>
)}
</Form>
</Grid>
);
};
export default BenchmarkingTable;

React component not re-rendering when useEffect dependency changes

PROBLEM:
I am currently creating a react app that allows you to checkout a book to a professor. In this app it has a couple of things that need to obviously update when a user checks out a book.
So first off there is the number of totalBooks that is checked out, or just when the entire book object changes then the component should re-render.
I have a useEffect function that is making an api call to a mongodb database and is accessing a document that will yield a book object in the response to the react app. Here is that useEffect function:
useEffect(() => {
const getBook = async () => {
// console.log(book)
await api.getBookById(props.id).then(async book => {
setBook({...book.data.data})
setCopies([...book.data.data.copies])
var num = 0;
await book.data.data.copies.map(async (copy, index) => {
if(copy.checkedOut){
num++;
}
})
setNumCheckedOut(num)
}).catch(e => {console.log(e)})
}
getBook();
}, [book])
I have even subbed out the book object dependency for something like book.checkedOutCopies. Which should return a number and if that number is different from the last then it should re-render the component. This is however, not the case. No matter what I try I am unable to re-render the component when this document changes. I even created a number called reRender and updated it when the api call to checkout a book finished its call. This would be undesired even if it worked because it would not change for someone who was already on the page, but not on the same computer as the person that clicked the checkout button.
I simply just want this component to re-render when the book object in the mongo db database has changed. Which from my understanding the right way to do it is above. The problem is that even after I successfully checkout a book the state never updates. The number of checked out books on the screen stays static:
Here is a screen shot of what the page looks like:
The green books should turn to red when the update button is clicked and a success status is responded. The Total Checked Out should also change. Neither of these happen.
Here is the book object:
const Book = new Schema(
{
bookName: { type: String, required: true },
bookDesc: { type: String, required: false},
numCheckedOut: { type: Number, required: false },
copiesAvail: {type: Number},
whosChecked: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'prof'
}],
copies: [Copy],
ISBN: { type: Number, required: true },
nextDue: {type: String},
nextProf: {type: Schema.Types.ObjectId, ref: 'prof'}
},
{ timestamps: true },
)
I don't understand why it isn't updating would appreciate any help here is the file in its entirety:
import React, { useState, useEffect, useRef } from 'react'
import api from '../api'
import { InputGroup, Form, FormControl, Container, Button, Col, Row, Toast } from 'react-bootstrap'
import { FaBook } from 'react-icons/fa'
import BookIconFunc from '../components/helperComponents/bookIconFunc'
import Select from 'react-dropdown-select';
import './bookslist.css'
import Axios from "axios";
import BookIcoContext from '../context/BookIconContext';
import DatePicker from "react-datepicker";
import ColoredLine from '../components/helperComponents/ColoredLine'
import CheckoutBookHistroy from '../components/CheckoutBookHistory/CheckoutBookHistory'
import { useHistory } from 'react-router-dom';
const handleCheckout = async (e) => {
e.preventDefault();
}
const topLeftBook = {
marginTop: "1.0rem",
display: "flex",
width:"fit-content",
height: "fit-content",
justifyContent: "left",
flexDirection: "column",
alignItems: "center",
borderRadius: "0px",
border: "2px solid #73AD21",
padding: "0.5rem 2.5rem",
}
const booksRows = {
// marginTop: "4.0rem",
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
padding: "1.0rem",
justifyContent: "left",
// border: "2px solid black",
width: "50%",
marginLeft: "1.0rem"
}
const bottomForm = {
flexGrow: "1"
}
const indBook = {
margin: "0.5rem",
color: "green"
}
const updateButtonStyle = {
display: "flex",
width: "100%",
justifyContent:"center"
}
const topOfPage = {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
verticalAlign: "middle"
}
const bookIcon = {
width: "10.0rem",
}
const bottomOfPage = {
display: "flex",
flexDirection: "row",
marginBottom: "1.0rem",
verticalAlign: "middle",
flexGrow: "1",
marginLeft: "1.0rem"
}
const topForm = {
width: "100%",
marginLeft: "2.0rem",
verticalAlign: "middle",
alignItems: "center",
justifyContent: "center"
}
export default function BooksUpdate(props) {
/* TOP FORM VARIABLES */
const [book, setBook] = useState(null)
const [bookName, setBookName] = useState()
const [desc, setDesc] = useState()
const [ISBN, setISBN] = useState()
const [copies, setCopies] = useState()
const history = useHistory();
/* BOOK ICON CONTEXT STATE */
const [iconState, setIconState] = useState(false)
/* re render state when */
const [reRender, setReRender] = useState(0);
/* BOTTOM FORM VARIABLES */
const [newCopies, setNewCopies] = useState()
const [checkoutCopiesNum, setCheckoutCopiesNum] = useState(0)
const [numCheckedOut, setNumCheckedOut] = useState(0)
const [allProfs, setAllProfs] = useState()
const [profChosen, setProfChosen] = useState()
const [bookicoref, setIcoRefs] = useState([]);
const [dueDate, setDueDate] = useState(new Date());
var anotherRender = 0;
const submitCheckout = async (e) => {
e.preventDefault();
try {
const checkoutBookData = {book, checkoutCopiesNum, profChosen, dueDate};
const checkoutBookRes = await Axios.post("http://localhost:8174/api/book/checkout/" + props.id, checkoutBookData)
if(checkoutBookRes.statusText === 'OK'){
setShowCheckoutToast(!showCheckoutToast)
}
/* Display toast that confirms the book was checked out */
setReRender(reRender+1)
anotherRender = anotherRender + 1
// history.push("/Books/Update/" + props.id)
}
catch (err) {
alert(err)
}
}
const handleSetBook = (bookData) => {
setBook({...bookData})
}
useEffect(() => {
const getBook = async () => {
// console.log(book)
await api.getBookById(props.id).then(async book => {
await handleSetBook(book.data.data)
// setBook(book.data.data)
setCopies([...book.data.data.copies])
var num = 0;
await book.data.data.copies.map(async (copy, index) => {
if(copy.checkedOut){
num++;
}
})
setNumCheckedOut(num)
}).catch(e => {console.log(e)})
}
getBook();
}, [book])
useEffect( () => {
const getProfs = async () => {
await Axios.get('http://localhost:8174/user/professors').then((ps) => {
var array = []
ps.data.data.map((prof, index) => {
array.push({label: prof.name, value: prof, key: prof._id})
})
setAllProfs(array)
})
}
getProfs()
}, [])
/* EFFECT TO CREATE REFS FOR EACH BOOK ICON */
useEffect(() => {
// add or remove refs
copies &&
setIcoRefs(bookicorefs => (
Array(copies.length).fill().map((_, i) => bookicorefs[i] || React.createRef())
))
}, [copies]);
const handleUpdate = () => {
console.log("handling update")
}
const findCheckedOut = (array) => {
var numChecked = 0;
array.filter(arr => {
if(arr.checkedOut){
numChecked = numChecked + 1
}
})
return numChecked
}
const [showCheckoutToast, setShowCheckoutToast] = useState(false)
const toggleCheckToast = () => {
setShowCheckoutToast(!showCheckoutToast)
}
/* EFFECT TO VALIDATE THE INFORMATION INSIDE THE CHECKOUT BOOKS FIELD */
useEffect(() => {
if(!copies){
return
}
if(checkoutCopiesNum > copies.length){
alert("There isn't that much of this book available")
return;
}
// console.log(numCheckedOut)
if(checkoutCopiesNum > (copies.length - numCheckedOut)){
setCheckoutCopiesNum(0)
alert('You cannot checkout that many copies as there is already to many checked out')
return;
}
// for(var i = 0; i < checkoutCopiesNum; i++){
// }
},[checkoutCopiesNum, numCheckedOut])
return (
book ?
copies ?
<div>
<Container>
{/* Show the book icon with the title of the book underneath */}
<div style={topOfPage}>
<div style={topLeftBook}>
<FaBook style={bookIcon} size={200}/>
<h4 className="">{book.bookName}</h4>
</div>
<Form style={topForm}>
<Row>
<Col className="pl-0">
<InputGroup className="mb-3 mt-3">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon1">Book Name</InputGroup.Text>
</InputGroup.Prepend>
<FormControl onChange={(e) => setBookName(e.target.value)}
defaultValue={book.bookName}
aria-label="Name"
aria-describedby="basic-addon1"
/>
</InputGroup>
<Form.Group controlId="exampleForm.ControlTextarea1">
<Form.Control as="textarea" rows={5} defaultValue={book.bookDesc}/>
</Form.Group>
</Col>
<Col className="m-0 pr-0">
<InputGroup className="mb-4 mt-3">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon1">ISBN</InputGroup.Text>
</InputGroup.Prepend>
<FormControl onChange={(e) => setISBN(e.target.value)}
defaultValue={book.ISBN}
aria-label="Name"
aria-describedby="basic-addon1"
/>
</InputGroup>
<InputGroup className="mb-4 mt-4">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon1">Total Copies</InputGroup.Text>
</InputGroup.Prepend>
<FormControl onChange={(e) => setNewCopies(e.target.value)}
aria-label="Name"
aria-describedby="basic-addon1"
defaultValue={copies.length}
/>
</InputGroup>
<InputGroup className="mb-4">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon1">Total Checked Out</InputGroup.Text>
</InputGroup.Prepend>
<FormControl onChange={(e) => setNewCopies(e.target.value)}
aria-label="Name"
aria-describedby="basic-addon1"
defaultValue={findCheckedOut(book.copies)}
/>
</InputGroup>
</Col>
<Button style={updateButtonStyle} onClick={handleUpdate}>Update</Button>
</Row>
</Form>
</div>
<Row style={{justifyContent: "space-between", verticalAlign: "middle"}}>
<Toast
show={showCheckoutToast}
onClose={toggleCheckToast}
style={{
position: 'absolute',
top: 0,
right: 0,
}}
>
<Toast.Header>
<img
src="holder.js/20x20?text=%20"
className="rounded mr-2"
alt=""
/>
<strong className="mr-auto">Success!</strong>
</Toast.Header>
<Toast.Body>Successfully Checked out a Book</Toast.Body>
</Toast>
<div style={bottomOfPage}>
<Form style={bottomForm} onSubmit={submitCheckout}>
<InputGroup className="mt-4">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon4">Checkout Out Copies:</InputGroup.Text>
</InputGroup.Prepend>
<FormControl
onChange={(e) => setCheckoutCopiesNum(e.target.value)}
placeholder={checkoutCopiesNum}
aria-label="Name"
aria-describedby="basic-addon1"
/>
</InputGroup>
<Select
className="mt-4"
style={{width: "100%"}}
name="Select"
required
// loading
searchable
placeholder="To:"
options={allProfs}
onChange={(values) => {setProfChosen(values[0].value)}}
/>
<DatePicker className="mt-4" selected={dueDate} onChange={date => setDueDate(date)} />
<Button type="submit" className="mt-3 w-100">Checkout</Button>
</Form>
</div>
<BookIcoContext.Provider value={{iconState, setIconState}}>
<div style={booksRows} onClick={() => setIconState(true)} onMouseUp={() => setIconState(false)}>
{
copies ? copies.map((copy, index) => {
return <div
key={index}
>
<BookIconFunc
checkedOut={copy.checkedOut}
ref={bookicoref[index]}
>
</BookIconFunc>
</div>
})
:
<div>none</div>
}
</div>
</BookIcoContext.Provider>
</Row>
</Container>
<ColoredLine color="grey" m={20} height={1}/>
<Container>
{/* {book.whosChecked.map(prof => {
// console.log(prof)
// <Col>{prof}</Col>
})} */}
<CheckoutBookHistroy book_id={props.id} book={book} reRender={reRender}></CheckoutBookHistroy>
</Container>
</div>
:
<div>no data</div>
:
<div>no data</div>
)
}

DevExtreme React Grid

Anyone know how to change the fontSize of the TableHeaderRow in a DevExtreme React Grid?
Here's an example of code from the website (https://devexpress.github.io/devextreme-reactive/react/grid/demos/featured/data-editing/) that I have been working with
import * as React from 'react';
import {
SortingState, EditingState, PagingState,
IntegratedPaging, IntegratedSorting,
} from '#devexpress/dx-react-grid';
import {
Grid,
Table, TableHeaderRow, TableEditRow, TableEditColumn,
PagingPanel, DragDropProvider, TableColumnReordering,
} from '#devexpress/dx-react-grid-material-ui';
import Paper from '#material-ui/core/Paper';
import Dialog from '#material-ui/core/Dialog';
import DialogActions from '#material-ui/core/DialogActions';
import DialogContent from '#material-ui/core/DialogContent';
import DialogContentText from '#material-ui/core/DialogContentText';
import DialogTitle from '#material-ui/core/DialogTitle';
import Button from '#material-ui/core/Button';
import IconButton from '#material-ui/core/IconButton';
import Input from '#material-ui/core/Input';
import Select from '#material-ui/core/Select';
import MenuItem from '#material-ui/core/MenuItem';
import TableCell from '#material-ui/core/TableCell';
import DeleteIcon from '#material-ui/icons/Delete';
import EditIcon from '#material-ui/icons/Edit';
import SaveIcon from '#material-ui/icons/Save';
import CancelIcon from '#material-ui/icons/Cancel';
import { withStyles } from '#material-ui/core/styles';
import { ProgressBarCell } from '../../../theme-sources/material-ui/components/progress-bar-cell';
import { HighlightedCell } from '../../../theme-sources/material-ui/components/highlighted-cell';
import { CurrencyTypeProvider } from '../../../theme-sources/material-ui/components/currency-type-provider';
import { PercentTypeProvider } from '../../../theme-sources/material-ui/components/percent-type-provider';
import {
generateRows,
globalSalesValues,
} from '../../../demo-data/generator';
const styles = theme => ({
lookupEditCell: {
paddingTop: theme.spacing.unit * 0.875,
paddingRight: theme.spacing.unit,
paddingLeft: theme.spacing.unit,
},
dialog: {
width: 'calc(100% - 16px)',
},
inputRoot: {
width: '100%',
},
});
const AddButton = ({ onExecute }) => (
<div style={{ textAlign: 'center' }}>
<Button
color="primary"
onClick={onExecute}
title="Create new row"
>
New
</Button>
</div>
);
const EditButton = ({ onExecute }) => (
<IconButton onClick={onExecute} title="Edit row">
<EditIcon />
</IconButton>
);
const DeleteButton = ({ onExecute }) => (
<IconButton onClick={onExecute} title="Delete row">
<DeleteIcon />
</IconButton>
);
const CommitButton = ({ onExecute }) => (
<IconButton onClick={onExecute} title="Save changes">
<SaveIcon />
</IconButton>
);
const CancelButton = ({ onExecute }) => (
<IconButton color="secondary" onClick={onExecute} title="Cancel changes">
<CancelIcon />
</IconButton>
);
const commandComponents = {
add: AddButton,
edit: EditButton,
delete: DeleteButton,
commit: CommitButton,
cancel: CancelButton,
};
const Command = ({ id, onExecute }) => {
const CommandButton = commandComponents[id];
return (
<CommandButton
onExecute={onExecute}
/>
);
};
const availableValues = {
product: globalSalesValues.product,
region: globalSalesValues.region,
customer: globalSalesValues.customer,
};
const LookupEditCellBase = ({
availableColumnValues, value, onValueChange, classes,
}) => (
<TableCell
className={classes.lookupEditCell}
>
<Select
value={value}
onChange={event => onValueChange(event.target.value)}
input={(
<Input
classes={{ root: classes.inputRoot }}
/>
)}
>
{availableColumnValues.map(item => (
<MenuItem key={item} value={item}>
{item}
</MenuItem>
))}
</Select>
</TableCell>
);
export const LookupEditCell = withStyles(styles, { name: 'ControlledModeDemo' })(LookupEditCellBase);
const Cell = (props) => {
const { column } = props;
if (column.name === 'discount') {
return <ProgressBarCell {...props} />;
}
if (column.name === 'amount') {
return <HighlightedCell {...props} />;
}
return <Table.Cell {...props} />;
};
const EditCell = (props) => {
const { column } = props;
const availableColumnValues = availableValues[column.name];
if (availableColumnValues) {
return <LookupEditCell {...props} availableColumnValues={availableColumnValues} />;
}
return <TableEditRow.Cell {...props} />;
};
const getRowId = row => row.id;
class DemoBase extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
columns: [
{ name: 'product', title: 'Product' },
{ name: 'region', title: 'Region' },
{ name: 'amount', title: 'Sale Amount' },
{ name: 'discount', title: 'Discount' },
{ name: 'saleDate', title: 'Sale Date' },
{ name: 'customer', title: 'Customer' },
],
tableColumnExtensions: [
{ columnName: 'amount', align: 'right' },
],
rows: generateRows({
columnValues: { id: ({ index }) => index, ...globalSalesValues },
length: 12,
}),
sorting: [],
editingRowIds: [],
addedRows: [],
rowChanges: {},
currentPage: 0,
deletingRows: [],
pageSize: 0,
pageSizes: [5, 10, 0],
columnOrder: ['product', 'region', 'amount', 'discount', 'saleDate', 'customer'],
currencyColumns: ['amount'],
percentColumns: ['discount'],
};
const getStateDeletingRows = () => {
const { deletingRows } = this.state;
return deletingRows;
};
const getStateRows = () => {
const { rows } = this.state;
return rows;
};
this.changeSorting = sorting => this.setState({ sorting });
this.changeEditingRowIds = editingRowIds => this.setState({ editingRowIds });
this.changeAddedRows = addedRows => this.setState({
addedRows: addedRows.map(row => (Object.keys(row).length ? row : {
amount: 0,
discount: 0,
saleDate: new Date().toISOString().split('T')[0],
product: availableValues.product[0],
region: availableValues.region[0],
customer: availableValues.customer[0],
})),
});
this.changeRowChanges = rowChanges => this.setState({ rowChanges });
this.changeCurrentPage = currentPage => this.setState({ currentPage });
this.changePageSize = pageSize => this.setState({ pageSize });
this.commitChanges = ({ added, changed, deleted }) => {
let { rows } = this.state;
if (added) {
const startingAddedId = rows.length > 0 ? rows[rows.length - 1].id + 1 : 0;
rows = [
...rows,
...added.map((row, index) => ({
id: startingAddedId + index,
...row,
})),
];
}
if (changed) {
rows = rows.map(row => (changed[row.id] ? { ...row, ...changed[row.id] } : row));
}
this.setState({ rows, deletingRows: deleted || getStateDeletingRows() });
};
this.cancelDelete = () => this.setState({ deletingRows: [] });
this.deleteRows = () => {
const rows = getStateRows().slice();
getStateDeletingRows().forEach((rowId) => {
const index = rows.findIndex(row => row.id === rowId);
if (index > -1) {
rows.splice(index, 1);
}
});
this.setState({ rows, deletingRows: [] });
};
this.changeColumnOrder = (order) => {
this.setState({ columnOrder: order });
};
}
render() {
const {
classes,
} = this.props;
const {
rows,
columns,
tableColumnExtensions,
sorting,
editingRowIds,
addedRows,
rowChanges,
currentPage,
deletingRows,
pageSize,
pageSizes,
columnOrder,
currencyColumns,
percentColumns,
} = this.state;
return (
<Paper>
<Grid
rows={rows}
columns={columns}
getRowId={getRowId}
>
<SortingState
sorting={sorting}
onSortingChange={this.changeSorting}
/>
<PagingState
currentPage={currentPage}
onCurrentPageChange={this.changeCurrentPage}
pageSize={pageSize}
onPageSizeChange={this.changePageSize}
/>
<IntegratedSorting />
<IntegratedPaging />
<CurrencyTypeProvider for={currencyColumns} />
<PercentTypeProvider for={percentColumns} />
<EditingState
editingRowIds={editingRowIds}
onEditingRowIdsChange={this.changeEditingRowIds}
rowChanges={rowChanges}
onRowChangesChange={this.changeRowChanges}
addedRows={addedRows}
onAddedRowsChange={this.changeAddedRows}
onCommitChanges={this.commitChanges}
/>
<DragDropProvider />
<Table
columnExtensions={tableColumnExtensions}
cellComponent={Cell}
/>
<TableColumnReordering
order={columnOrder}
onOrderChange={this.changeColumnOrder}
/>
<TableHeaderRow showSortingControls />
<TableEditRow
cellComponent={EditCell}
/>
<TableEditColumn
width={120}
showAddCommand={!addedRows.length}
showEditCommand
showDeleteCommand
commandComponent={Command}
/>
<PagingPanel
pageSizes={pageSizes}
/>
</Grid>
<Dialog
open={!!deletingRows.length}
onClose={this.cancelDelete}
classes={{ paper: classes.dialog }}
>
<DialogTitle>
Delete Row
</DialogTitle>
<DialogContent>
<DialogContentText>
Are you sure to delete the following row?
</DialogContentText>
<Paper>
<Grid
rows={rows.filter(row => deletingRows.indexOf(row.id) > -1)}
columns={columns}
>
<CurrencyTypeProvider for={currencyColumns} />
<PercentTypeProvider for={percentColumns} />
<Table
columnExtensions={tableColumnExtensions}
cellComponent={Cell}
/>
<TableHeaderRow />
</Grid>
</Paper>
</DialogContent>
<DialogActions>
<Button onClick={this.cancelDelete} color="primary">
Cancel
</Button>
<Button onClick={this.deleteRows} color="secondary">
Delete
</Button>
</DialogActions>
</Dialog>
</Paper>
);
}
}
export default withStyles(styles, { name: 'ControlledModeDemo' })(DemoBase);
The font size of the text labelling the columns (e.g. product, region, amount) is fixed, and I see no parameters that can change it. Any ideas?
I think there are a few ways around this, the way I have used is having a fully controlled component.
Looks a little like this
<TableHeaderRow cellComponent={this.ExampleHeaderCell} />
Where ExampleHeaderCell is a component that would look something like this
ExampleHeaderCell = (props: any) => (<TableHeaderRow.Cell
className={exampleClass}
{...props}
key={column.name}
getMessage={() => column.title}
/>)
From there you can pass it a class as shown with exampleClass
You can take this further and have it customised for a particular column.
ExampleHeaderCells = (props: any) => {
const exampleClass = css({ backgroundColor: "blue" })
const { column } = props
if (column.name === "name") {
return (
<TableHeaderRow.Cell
className={exampleClass}
{...props}
key={column.name}
getMessage={() => column.title}
/>
)
}
return <TableHeaderRow.Cell {...props} key={column.name} getMessage={() => column.title} />
}
The example above is returning a specific cell with the exampleClass if the column name is equal to "name". Otherwise it just returns the regular TableHeaderRow.Cell

Categories

Resources