Difference between React.useCallBack, React.useMemo and React.Memo with examples? - reactjs

what are the different kind of use cases of React.useCallBack, React.useMemo and React.Memo ?

import _ from 'lodash';
import React, { useState, useRef, useCallback } from 'react';
import PropTypes from 'prop-types';
import { useOutsideClickHandler } from 'utilities/method';
import { useTranslation } from 'react-i18next';
function FilterIndicators({
filters,
onDeleteFilter,
onRemoveFilter,
removeAllFilters,
}) {
const { t } = useTranslation();
const [openedTagIndex, setOpenedTagIndex] = useState(null);
const handleFilterButtonClick = useCallback(
(index) => {
if (openedTagIndex === index) {
setOpenedTagIndex(null);
} else {
setOpenedTagIndex(index);
}
},
[openedTagIndex],
);
const hideOptions = useCallback(() => {
setOpenedTagIndex(null);
}, []);
const deleteFilters = useCallback(
(e, filterName) => {
e.preventDefault();
onDeleteFilter(filterName);
hideOptions();
},
[onDeleteFilter, hideOptions],
);
const removeFilter = useCallback(
(e, filterOption, filterName) => {
e.preventDefault();
onRemoveFilter(filterOption, filterName);
hideOptions();
},
[onRemoveFilter, hideOptions],
);
const wrapperRef = useRef(null);
useOutsideClickHandler(wrapperRef, hideOptions);
return (
<FilterIndicatorsSC data-testid="admin-filter-indicators" ref={wrapperRef}>
{filters.map((filter, index) => {
if (filter.isShow) {
return (
<FilterTag
key={filter.id}
data-testid="admin-filter-sub-indicators"
>
<FilterButton onClick={() => handleFilterButtonClick(index)}>
<FilterName>{filter.name}</FilterName>
<CaretIcon className="fa fa-caret-down" />
<CloseIcon
className="fa fa-times"
title={t('asset_configuration_panel.remove_filter')}
onClick={(e) => deleteFilters(e, filter.filterName)}
/>
</FilterButton>
{openedTagIndex === index ? (
<FilterPopup>
{filter.filterOptions &&
filter.filterOptions.map((filterOption) => (
<FilterOption key={filterOption}>
<FilterOptionLabel>
{'' + filterOption}
</FilterOptionLabel>
<FilterOptionCloseIcon
className="fa fa-times"
onClick={(e) =>
removeFilter(e, filterOption, filter.filterName)
}
/>
</FilterOption>
))}
</FilterPopup>
) : null}
</FilterTag>
);
} else {
return null;
}
})}
{filters.filter((filter) => filter.isShow).length > 0 && (
<FilterBarClearButton
title={t('clear_all_filters')}
onClick={removeAllFilters}
>
{t('clear_all')}
</FilterBarClearButton>
)}
</FilterIndicatorsSC>
);
}
FilterIndicators.propTypes = {
/** List of all the filters applied */
filters: PropTypes.array,
onRemoveFilter: PropTypes.func.isRequired,
onDeleteFilter: PropTypes.func.isRequired,
removeAllFilters: PropTypes.func.isRequired,
};
FilterIndicators.defaultProps = {
filters: [],
onRemoveFilter: () => {},
onDeleteFilter: () => {},
removeAllFilters: () => {},
};
const areEqual = (prevProps, nextProps) => {
return _.isEqual(prevProps, nextProps);
};
export default React.memo(FilterIndicators, areEqual);
You can see above example in which I'm using React.memo, useCallback
for optimizing the app performance, it saved many re-rendering but be
careful when using these optimization.

Related

MobX doesn't rerender my FC for some reason, I can't explain why

So there is a storage containing 2 lists, one of the lists is fetching from server (using axios).
Both the lists renders by FC - PlayerList.
RondomizerPage contains these 2 lists.
So when I F5 my page on "localhost:3000/randomizer" nothing appears to be in my fetched list(fetched confirmed). But when I click anything(link to "randomizer" for example or any button) rerender goes in, and my list appears.
The 'store':
import { makeAutoObservable } from "mobx";
import { IPlayerInfo } from "../types/player";
import axios from "../axios";
import IMove from "./../types/movement";
class Player {
playerList: IPlayerInfo[] = [];
randomPlayerList: IPlayerInfo[] = [];
isAuth: boolean = false;
constructor() {
makeAutoObservable(this);
}
addPlayer(playerdata: IPlayerInfo) {
this.playerList.push(playerdata);
}
move(from: IMove, to: IMove) {
if (from.list === to.list) {
const list = from.list;
if (list === "randomList") {
const [temp] = this.randomPlayerList.splice(from.index, 1);
this.randomPlayerList.splice(to.index, 0, temp);
}
if (list === "playerList") {
const [temp] = this.playerList.splice(from.index, 1);
this.playerList.splice(to.index, 0, temp);
}
} else {
if (from.list === "playerList") {
const [temp] = this.playerList.splice(from.index, 1);
this.randomPlayerList.splice(to.index, 0, temp);
}
if (from.list === "randomList") {
const [temp] = this.randomPlayerList.splice(from.index, 1);
this.playerList.splice(to.index, 0, temp);
}
}
}
fetchPlayers = async () => {
try {
const response = await axios.get("/players");
this.playerList = response.data;
return
} catch (error) {
console.log(error);
}
};
randomPlayers = async () => {
if (
this.randomPlayerList.length % 2 === 0 &&
this.randomPlayerList.length > 1
) {
const response = await axios.post("/random", {
amount: this.randomPlayerList.length,
});
const data = response.data.result.random.data[0];
let playerList = [];
for (let index = 0; index < data.length; index++) {
playerList.push(this.randomPlayerList[data[index] - 1]);
}
return playerList;
}
return false;
};
}
const playerStore = new Player()
export default playerStore;
And I have a 'Randomizer page':
import React, { useEffect, useState } from "react";
import PlayerList from "../../components/Lists/PlayerList";
import players from "../../store/players";
import styles from "./RandomizerPage.module.scss";
import { useNavigate } from "react-router";
import axios from "../../axios";
import { DragDropContext, Droppable, DropResult } from "react-beautiful-dnd";
import IMove from "./../../types/movement";
import PlayerCreation from "../../components/Modals/PlayerCreationModal/PlayerCreation";
import MyButton from "../../components/UI/MyButton/MyButton";
import RandomedPlayersModal from "../../components/Modals/RandomedPlayersModal/RandomedPlayersModal";
import { observer } from "mobx-react-lite";
const RandomizerPage = observer(() => {
const [playerCreationModalisOn, setPlayerCreationModalisOn] =
useState(false);
const [randomedPlayerModalIsOn, setRandomedPlayerModalIsOn] =
useState(false);
const nav = useNavigate();
const onDragEndHandle = (result: DropResult) => {
if (!result.destination) return;
const from: IMove = {
index: result.source.index,
list: result.source.droppableId,
};
const to: IMove = {
index: result.destination.index,
list: result.destination.droppableId,
};
players.move(from, to);
};
const playerCreationHandler = () => {
setPlayerCreationModalisOn(true);
};
const playerCreationAbortion = () => {
setPlayerCreationModalisOn(false);
};
const randomPlayersHandler = () => {
const response = players.randomPlayers();
console.log(response);
setRandomedPlayerModalIsOn(true);
};
const randomPlayerAbortion = () => {
setRandomedPlayerModalIsOn(false);
};
useEffect(() => {
const user = JSON.parse(localStorage.getItem("user") || "null");
if (user) {
axios.defaults.headers["authorization"] = user.token;
players.fetchPlayers();
} else nav("/login");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
{playerCreationModalisOn && (
<PlayerCreation
abort={playerCreationAbortion}
title={"Создание персонажа"}
/>
)}
{randomedPlayerModalIsOn && (
<RandomedPlayersModal
abort={randomPlayerAbortion}
title={"random.org выбрал команды"}
/>
)}
<DragDropContext onDragEnd={onDragEndHandle}>
<div className={styles.wrapper}>
<div className={styles.column}>
<div>
<h4>Переместите сюда игроков</h4>
<Droppable droppableId="randomList">
{(provided) => (
<div
className={styles.list}
{...provided.droppableProps}
ref={provided.innerRef}
>
<PlayerList list={players.randomPlayerList} />
{provided.placeholder}
</div>
)}
</Droppable>
</div>
<MyButton click={randomPlayersHandler}>
Сгенерировать случайные команды
</MyButton>
</div>
<div className={styles.column}>
<div>
<h4>Игроки</h4>
<Droppable droppableId="playerList">
{(provided) => (
<div
className={styles.list}
{...provided.droppableProps}
ref={provided.innerRef}
>
<PlayerList list={players.playerList} />
{provided.placeholder}
</div>
)}
</Droppable>
</div>
<MyButton click={playerCreationHandler}>
Добавить игрока
</MyButton>
</div>
</div>
</DragDropContext>
</>
);
});
export default RandomizerPage;
And my PlayerList looks like that, btw:
import React from "react";
import { observer } from "mobx-react-lite";
import { Draggable } from "react-beautiful-dnd";
import PlayerCard from "../PlayerCard/PlayerCard";
import { IPlayerInfo } from "../../types/player";
interface PlayerListProps {
list: IPlayerInfo[];
}
const PlayerList = observer(({ list }: PlayerListProps) => {
console.log('rerender', list);
return (
<>
{list.map((player: IPlayerInfo, index: number) => (
<Draggable
key={player.steamId}
draggableId={player.steamId}
index={index}
>
{(provided) => (
<PlayerCard
playerinfo={player}
{...provided.draggableProps}
{...provided.dragHandleProps}
innerRef={provided.innerRef}
/>
)}
</Draggable>
))}
</>
);
});
export default PlayerList;
So, I found the crutch, if I add in RandomizerPage and change list={playerList}:
const playerList = players.playerList

React functions arent getting invoked in a functional component

import { useState } from 'react'
import { AiOutlinePlus } from 'react-icons/ai'
import { TimerForm } from './TimerForm'
export const ToggleAddTimer = () => {
const [formState, setFormState] = useState(false);
const handleFormOpen = () => {
setFormState(true);
}
const handleFormClose = () => {
setFormState(false);
}
if (formState) {
return (
<TimerForm formClose={handleFormClose()} />
)
}
else {
return (
<button onClick={() => {handleFormOpen()}}> this function isn't getting invoked
<AiOutlinePlus/>
</button>
)
}
}
Try <TimerForm formClose={handleFormClose} />
and in your else
<button onClick={() => handleFormOpen()}>
Do this:
const [formState, setFormState] = useState(false)
const toggleFormState = () => {
setFormState(state => !state);
}
<button onClick={toggleFormState}>
<AiOutlinePlus/>
</button>
Test:
function App() {
const [formState, setFormState] = useState(false)
const toggleFormState = () => {
setFormState(state => !state);
}
return (
<button onClick={toggleFormState}>
Form State is {formState ? "true": "false"}
</button>
)
}

Still re-rendering using React.memo when the prop remain unchanged

I am creating a dropzone uploader for video-uploading (using react-dropzone-uploader package),
and also get its thumbnail with package ThumbnailExtractor.
I used React.Memo(base64) to ensure if the thumbnail's base64 remain unchanged, React will not re-render.
However, when I click submit button, the thumbnail keep re-rendering even the base64 remain unchanged.
Any clue?
import React, { useEffect, useState } from "react";
import axios from "axios";
import produce from "immer";
import Dropzone from "react-dropzone-uploader";
import ThumbnailContainer from "./ThumbnailContainer";
require("./dropzone.scss");
const DropZoneUploader = ({ api, setFiles }) => {
const [clonedFiles, setClonedFiles] = useState([]);
const UploadFileWithProgress = async (f, formData) => {
try {
const options = {
onUploadProgress: (progressEvent) => {
const { loaded, total } = progressEvent;
let percent = Math.floor((loaded * 100) / total);
console.log(`${loaded}kb of ${total}kb | ${percent}%`);
if (percent < 100) {
setClonedFiles(
produce((draft) => {
const newItem = draft.find(
(item) => item.meta.id === f.meta.id
);
newItem.percent = percent;
})
);
} else {
setClonedFiles(
produce((draft) => {
const newItem = draft.find(
(item) => item.meta.id === f.meta.id
);
newItem.percent = 99;
})
);
}
}
};
await axios
.post("https://reqres.in/api/users", formData, options)
.then(async (res) => {
console.log(res);
f.meta.percent = 100;
setClonedFiles(
produce((draft) => {
const newItem = draft.find((item) => item.meta.id === f.meta.id);
newItem.true_status = "uploaded";
})
);
setFiles(
produce((draft) => {
draft.unshift(res.data.data);
})
);
f.remove();
});
} catch (err) {
console.log(err);
return null;
}
};
// called every time a file's `status` changes
const handleChangeStatus = ({ meta, file }, status) => {
if (status === "done") {
setClonedFiles(
produce((draft) => {
draft.push({
file,
meta,
true_status: "ready"
});
})
);
}
if (status === "removed") {
setClonedFiles(
produce((draft) => {
const newItemIndex = draft.findIndex(
(item) => item.meta.id === meta.id && item.true_status === "ready"
);
if (newItemIndex !== -1) {
draft.splice(newItemIndex, 1);
}
})
);
}
};
// receives array of files that are done uploading when submit button is clicked
const handleSubmit = (files, allFiles) => {
files.map(async (f) => {
const formData = new FormData();
formData.append("title", `${f.meta.name}`);
formData.append("type", f.meta.type);
formData.append("file_purpose", "private");
formData.append("file", f.file);
const reee = await UploadFileWithProgress(f, formData);
if (reee) {
console.log("pushed");
}
});
};
const PreviewOutput = ({ files }) => {
const iconByFn = {
cancel: {
backgroundImage: `url(https://upload.wikimedia.org/wikipedia/commons/d/dc/Cancel_icon.svg)`
},
remove: {
backgroundImage: `url(https://upload.wikimedia.org/wikipedia/commons/d/dc/Cancel_icon.svg)`
},
restart: {
backgroundImage: `url(https://upload.wikimedia.org/wikipedia/commons/d/dc/Cancel_icon.svg)`
}
};
return (
<div className="dropzone__imgs-wrapper">
{clonedFiles?.map((f) => (
<div key={f.meta.id}>
<ThumbnailContainer f={f} files={files} />
</div>
))}
</div>
);
};
const MyCustomPreview = ({
input,
previews,
submitButton,
dropzoneProps,
files,
}) => (
<div className="dropzone">
<div {...dropzoneProps}>
<PreviewOutput files={files} />
{input}
</div>
{submitButton}
</div>
);
return (
<Dropzone
onChangeStatus={handleChangeStatus}
onSubmit={handleSubmit}
autoUpload={true}
accept="video/*"
LayoutComponent={(props) => <MyCustomPreview {...props} />}
/>
);
};
export default DropZoneUploader;
ThumbnailContainer.jsx
import React, { useEffect, useState } from "react";
import ThumbnailExtractor from "react-thumbnail-extractor";
import ThumbnailImageSrc from "./ThumbnailImageSrc";
const ThumbnailContainer = ({ f, files }) => {
const [vFile, setVFile] = useState();
const [vImage, setVImage] = useState();
const [isCaptured, setIsCaptured] = useState(false);
const fileFunction = files.find((a) => a.meta.id === f.meta.id);
const { name, previewUrl } = f.meta;
const { true_status, percent } = f;
useEffect(() => {
setVFile(f.file);
console.log("fFile render");
}, [f.file]);
return (
<div key={f.meta.id} className="dropzone__img">
{!previewUrl && (
<>
<ThumbnailExtractor
maxWidth={600}
videoFile={vFile}
count={1}
onCapture={(image) => {
if (!isCaptured) {
console.log("capture Render");
setVImage(image[0]);
setIsCaptured(true);
}
}}
/>
<span className="dzu-previewFileName">{name}</span>
</>
)}
<div className="dzu-previewStatusContainer">
{fileFunction &&
fileFunction.meta.percent !== 100 &&
f.true_status !== "uploaded" ? (
<progress max={100} value={percent} />
) : (
""
)}
{true_status === "ready" && (
<button
className="dropzone__img-delete"
type="button"
onClick={fileFunction?.remove}
>
<i className="las la-times"></i>
</button>
)}
{vImage && <ThumbnailImageSrc src={vImage} />}
</div>
</div>
);
};
export default ThumbnailContainer;
ThumbnailImageSrc.jsx (React.memo)
import React from "react";
export const ThumbnailImageSrc = React.memo(function ThumbnailImageSrc({
src
}) {
return (
<div
key={src}
className="dropzone_thumbnail-preview"
style={{ backgroundImage: `url(${src}` }}
></div>
);
});
export default ThumbnailImageSrc;
Here is the codesandbox sample.

Why is my React search function not working when I hit backspace?

I'm trying to create a simple search function that filters through a list of movies obtained from an API.
The filter function works perfectly when I enter letters one after another, but when I hit backspace the function returns an empty array.
In the filter function I've logged the filtered array to the console and it works perfectly, so I'm thinking it must be something to do with how I've set up global state using Redux.
Here's the filter function:
import {useSelector, useDispatch} from 'react-redux'
import { setMoviesToDisplay } from "../features/general/generalSlice";
function SearchBar() {
const dispatch = useDispatch()
const {filter} = useSelector((state) => state.general)
const {moviesToDisplay} = useSelector((state) => state.general)
const startSearch = (searchTerm) => {
let filteredList = []
filteredList = moviesToDisplay.filter(movie => movie.title.toLowerCase().includes(searchTerm.toLowerCase()))
dispatch(setMoviesToDisplay(filteredList))
}
return (
<div>
<input
onInput={(e) => startSearch(e.target.value)}
type="text"
placeholder={filter === 'all' ? 'Search for movies or TV series'
: filter === 'movies' ? 'Search for movies'
: filter === 'tv' ? 'Search for TV series'
: filter === 'bookmarks' ? 'Search for bookmarked shows'
: ''
}>
</input>
</div>
)
}
export default SearchBar
This is the Dashboard on which the results are shown:
import Sidebar from "../components/Sidebar";
import { useSelector, useDispatch } from "react-redux";
import { getMovies } from "../features/movies/moviesSlice";
import { reset } from "../features/movies/moviesSlice";
import { setMoviesToDisplay } from "../features/general/generalSlice";
import { useNavigate } from "react-router-dom";
import MovieTile from "../components/MovieTile";
import SearchBar from "../components/SearchBar";
function Dashboard() {
const navigate = useNavigate();
const dispatch = useDispatch();
const { user } = useSelector((state) => state.auth);
const { moviesToDisplay } = useSelector((state) => state.general);
const { filter } = useSelector((state) => state.general);
const { movies, isError, message, isSuccess } = useSelector((state) => state.movies);
useEffect(() => {
if(filter === 'all') {
dispatch(setMoviesToDisplay(movies))
}
if(filter === 'movies') {
dispatch(setMoviesToDisplay(movies.filter((movie) => movie.category === 'Movie')))
}
if(filter === 'tv') {
dispatch(setMoviesToDisplay(movies.filter((movie) => movie.category === 'TV Series')))
}
if(filter === 'bookmarks') {
let bookmarkedMovies = []
movies.forEach((movie) => {
user.bookmarks.forEach((bookmarkedMovie) => {
if(movie._id === bookmarkedMovie) {
bookmarkedMovies.push(movie)
}
})
})
dispatch(setMoviesToDisplay(bookmarkedMovies))
}
}, [filter, isSuccess])
useEffect(() => {
if (!user) {
navigate("/");
}
dispatch(getMovies());
return () => {
dispatch(reset());
};
}, [user, navigate, isError, message, dispatch]);
return (
<>
<div>
<Sidebar />
<SearchBar />
{filter !== 'bookmarks' ? (
<>
<h2>Trending</h2>
<ul>
{moviesToDisplay && moviesToDisplay.map((movie, index) => {
return movie.isTrending &&
<MovieTile key={index} movie={movie} />
}
)}
</ul>
<h2>Recomended for You</h2>
<ul>
{moviesToDisplay && moviesToDisplay.map((movie, index) => {
return !movie.isTrending &&
<MovieTile key={index} movie={movie} />
}
)}
</ul>
</>
) : (
<>
<h2>Bookmarked movies</h2>
{moviesToDisplay && moviesToDisplay.map((movie, index) => {
return movie.category === 'Movie' &&
<MovieTile key={index} movie={movie} />
}
)}
<h2>Bookmarked tv shows</h2>
{moviesToDisplay && moviesToDisplay.map((movie, index) => {
return movie.category === 'TV Series' &&
<MovieTile key={index} movie={movie} />
}
)}
</>
)}
</div>
</>
);
}
export default Dashboard;
And the this is the slice where the setMoviesToDisplay is created:
const initialState = {
filter: 'all',
moviesToDisplay: []
}
const generalSlice = createSlice({
name: 'general',
initialState,
reducers: {
setFilter: (state, action) => {
state.filter = action.payload
},
resetFilter: (state) => {
state.filter = 'all'
} ,
setMoviesToDisplay: (state, action) => {
state.moviesToDisplay = action.payload
}
}
})
export const {setFilter, resetFilter, setMoviesToDisplay} = generalSlice.actions
export default generalSlice.reducer

React Redux state cannot rerender component and state is UNDEFINED

There is some issues with state in Redux. I want to change color of a component with state update. But the state in TargetList.js has error: TypeError: Cannot read property 'includes' of undefined.
The Reducer file is:
import {
SENSOR_HI,
SENSOR_UNHI,
} from "redux/actionTypes";
const initialState = {
title: "",
text: "",
detailedList: "hide",
};
const Reducer = (state = initialState, action) => {
switch (action.type) {
case SENSOR_HI:
return {
...state,
sensorarray: action.data,
};
case SENSOR_UNHI:
return {
...state,
sensorarray: [],
};
case FILTER_TAGS_CHANGED:
return {
...state,
filterTags: action.data,
};
default:
return state;
}
};
export default Reducer;
And the TargetList.js is:
import "assets/scss/global.scss";
import moment from "jalali-moment";
import React, { useState, useEffect, useRef, memo } from "react";
import { OverlayTrigger, Tooltip } from "react-bootstrap";
import { GiMovementSensor, GiRadarSweep } from "react-icons/gi";
import { MdDescription, MdSignalWifiOff,MdNetworkWifi } from "react-icons/md";
import { connect } from "react-redux";
import ToggleButton from "react-switch";
import socketConn from "sockets";
import "./Leaflet.scss";
import classes from "./TargetsList.module.scss";
import { targetTypes, targetsIconsHandlerImage } from "helpers/icons";
import { BackButton } from "assets/js/Icons";
import ReactDOMServer from "react-dom/server";
let TEMP_TARGETS = [];
let counter = 0;
let now = new Date();
const TargetsList = memo((props) => {
const [sensorFiltersSelected, setSensorFiltersSelected] = useState([]);
const [sensorFiltersSelectedIds, setSensorFiltersSelectedIds] = useState([]);
const [sensorSelected, setSensorSelected] = useState(null);
const [targetFiltersSelected, setTargetFiltersSelected] = useState([]);
const [hi, sethi] = useState(false);
let filterType = (sensors) => {
if (sensorFiltersSelected.length == 0) {
return sensors;
} else {
let ids = sensorFiltersSelected.map((s) => s["_id"]);
return sensors.filter((s) => ids.includes(s.sensorType["_id"]));
}
};
let filterTextSensors = (sensors) => {
return sensors.filter((s) => s.latinName.includes(searchInput));
};
let toggleFilter = (sensor) => {
let filters = sensorFiltersSelected;
let ids = filters.map((f) => f["_id"]);
if (filters.length && ids.includes(sensor["_id"])) {
filters = filters.filter((f) => f["_id"] != sensor["_id"]);
} else {
filters.push(sensor);
}
props.dispatch({
type: "FILTER_TAGS_CHANGED",
data: [...filters],
});
setSensorFiltersSelected(filters);
setSensorFiltersSelectedIds(filters.map(s=>s.latinName))
props.dispatch({
type: "SENSOR_HI",
data: [filters.map(s=>s.latinName)],
});
};
return (
<div>
<div
className="container"
style={{
padding: 0,
}}
>
<div className="rowx my-2 flex-wrap">
{props.sensors && props.sensors.length
? filterTextSensors(props.sensors).map(
(s, sensorTypeIndex) => (
<div
key={s._id}
/////////////////////////////////////// Here is Error ///////////////////////////////////////////////
className={`cursor-pointer badge badge-pill m-1 ${props.sensorarray.includes(s.latinName) ? `badge-success` : `badge-light`}`}
onClick={() => {
showLogsSensor(s)
toggleFilter(s);
}}
>
{s.persianName}
</div>
)
)
: ""}
</div>
});
const mapStateToProps = (state) => {
return {
sensorarray: state.sensorarray,
};
};
export default connect(mapStateToProps)(TargetsList);
And the Drawer.js is change state that should color change in TargetList.js.
Drawer.js:
import React from "react";
import clsx from "clsx";
import {makeStyles} from "#material-ui/core/styles";
import Drawer from "#material-ui/core/Drawer";
import {connect} from "react-redux";
import "assets/scss/global.scss";
import "./style.scss";
import socketConn from "sockets";
import moment from "jalali-moment";
import {MdSave} from "react-icons/md";
import axios from "config.js";
const useStyles = makeStyles({
fullList: {
width: "auto",
},
});
function BottomDrawer(props) {
const classes = useStyles();
const [tab, setTab] = React.useState("sensors");
const [state, setState] = React.useState({bottom: false});
const [type, setType] = React.useState(null);
const [info, setInfo] = React.useState(null);
const [logs, setLogs] = React.useState([]);
const toggleDrawer = (anchor, open) => {
setState({
...state,
[anchor]: open,
});
// stop receiving target logs
targetsLogAPIHandler(false);
};
const sensorunhi = () => {
props.dispatch({
type: "SENSOR_UNHI",
});
};
let setLogSocket = (type, info) => {
setLogs([]);
if (type === "target") {
targetsLogAPIHandler(true, info);
}
if (type === "sensor") {
sensorsLogAPIHandler(info);
}
};
// handler for API request to NodeJS to get logs of specific target (! ! ! requestMode => {true: when you want to START coming logs | false: when you want to STOP coming logs})
const targetsLogAPIHandler = async (requestMode, info) => {
try {
const userId = localStorage.getItem('user_id');
socketConn().removeAllListeners("show_specific_target_log");
const {data} = await axios.post(`/targets/log/${userId}`, {
requestMode,
unique_id: info ? info.unique_id : undefined
});
socketConn().on("show_specific_target_log", targetLogs => {
setLogs((prev) => {
return [targetLogs, ...prev].filter((s, index) => index < 500);
});
});
} catch (err) {
console.log(err);
}
};
// handler for API request to NodeJS to get logs of specific sensor
const sensorsLogAPIHandler = async info => {
try {
socketConn().removeAllListeners("sensor_moment_log");
let userId = localStorage.getItem("user_id");
const logs = await axios.post(`sensors/sensors/logs`, {topic: info.latinName, userId});
socketConn().on("sensor_moment_log", (data) => {
let enc = new TextDecoder("utf-8");
let decoded = enc.decode(data.result.content);
decoded = JSON.parse(decoded);
setLogs((prev) => {
return [decoded, ...prev].filter((s, index) => index < 500);
});
});
socketConn().on("sensor_moment_log_error", (data) => {
console.log(data);
});
} catch (err) {
console.log(err);
}
};
React.useEffect(() => {
setTab((prev) => {
if (prev != props.selectedTab) {
socketConn().removeAllListeners("sensor_moment_log");
socketConn().removeAllListeners("show_specific_target_log");
setLogs([]);
return props.selectedTab;
}
return prev;
});
}, [props.selectedTab]);
React.useEffect(() => {
if (props.showLogs && state["bottom"] == false) {
toggleDrawer("bottom", true);
}
}, [props.showLogs]);
React.useEffect(() => {
if (props.logInfo && props.logType) {
if (info != null) {
if (
type != props.logType ||
info.Identification + info.sensorName !=
props.logInfo.Identification + props.logInfo.sensorName
) {
setType(props.logType);
setInfo(props.logInfo);
setLogSocket(props.logType, props.logInfo);
}
} else {
setType(props.logType);
setInfo(props.logInfo);
setLogSocket(props.logType, props.logInfo);
}
}
}, [props.logInfo, props.logType]);
const list = anchor => (
<div className={clsx(classes.list, {[classes.fullList]: anchor === "bottom",})} role="presentation">
<div className="log-container p-4">
<React.Fragment>
<div className="rowx log-headers mb-3">
<div className="colx log-header font-weight-bold">نام سنسور</div>
<div className="colx log-header font-weight-bold">نوع هدف</div>
<div className="colx log-header font-weight-bold">طول</div>
<div className="colx log-header font-weight-bold">عرض</div>
<div className="colx log-header font-weight-bold">ارتفاع</div>
</div>
<div className="log-elements">
{logs &&
logs.length &&
logs.map((log, index) => (
<div className="rowx log-element mb-2" key={index}>
<div className="colx log-text">{log && log[0] && log[0].sensorName}</div>
<div className="colx log-text">{log && log[0] && log[0].sensorType}</div>
<div className="colx log-text">{log && log[0] && log[0].longitude}</div>
<div className="colx log-text">{log && log[0] && log[0].latitude}</div>
<div className="colx log-text">{log && log[0] && log[0].altitude}</div>
</div>
))}
</div>
</React.Fragment>
)}
</div>
</div>
);
return (
<div>
<React.Fragment>
<Drawer
classes={{root: "drawer-root"}}
ModalProps={{hideBackdrop: true}}
anchor="bottom"
open={state["bottom"]}
onClose={() => {
toggleDrawer("bottom", false);
sensorunhi(); /////////////////// CHANGE REDUX STATE
}}
>
{list("bottom")}
</Drawer>
</React.Fragment>
</div>
);
}
const mapStateToProps = (state) => {
return {
sensorarray: state.sensorarray,
};
};
export default connect(mapStateToProps)(BottomDrawer);
In other word, I want to change the color component TargetList.js by change the state in Drawer.js.

Resources