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

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.

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

Unable to update react state with an array

I can make a successful call to getApiSuggestions with data returned. However I'm unable to assign this to my state.
As you can see my console output shows that the value for response has an array. However, when attempting to assign it to wikiResults:response the array remains empty.
note that this is a modification of react-search-autocomplete
Am I attempting to pass the variables incorrectly?
NarrativeSearch.js
import React, {useContext, useState, useEffect} from "react";
import './search.css'
import { ReactSearchAutocomplete } from 'react-search-autocomplete'
import { getApiSuggestions } from '../../requests/requests';
import {TextSearchContext} from "../../contexts/TextSearchContext"
import {SearchContext} from "../../contexts/SearchContext"
function Search() {
const {textFilterState, setTextFilterState} = useContext(TextSearchContext);
const [wikiTitleResults, setWikiTitleResults] = useState({wikiResults:[]});
var cnJson = wikiTitleResults;
const items = wikiTitleResults.wikiResults;
const handleOnSearch = (string, results) => {
console.log("STRING: ", string)
getApiSuggestions(string).then(response => {
console.log("RESPONSE: ", response);
setWikiTitleResults({wikiResults:response}); //<---- This doesn't update the state
console.log("WikiTitle: ", wikiTitleResults.wikiResults);
console.log("Items: ", items);
})
}
const handleOnHover = (result) => {
// the item hovered
console.log(result)
}
const handleOnSelect = (item) => {
// the item selected
setTextFilterState({textFilter:item.name});
console.log(item)
}
const handleOnFocus = () => {
console.log('Focused')
}
const handleOnClear = () => {
setTextFilterState({textFilter:""});
}
const formatResult = (item) => {
return (
<>
<span style={{ display: 'block', textAlign: 'left' }}>id: {item.title}</span>
</>
)
}
return (
<div >
<div className="searchbar">
<ReactSearchAutocomplete
items={items}
onSearch={handleOnSearch}
onHover={handleOnHover}
onSelect={handleOnSelect}
onFocus={handleOnFocus}
onClear={handleOnClear}
styling={{ zIndex: 4 }} // To display it on top of the search box below
autoFocus
/>
</div>
</div>
)
}
export default Search
getApiSuggesetions
const getApiSuggestions = (title) => {
//console.log("URL Being called"+ urlSingleResult);
//console.log(title);
let result = urlMultiResult
.get(`${title}`)
.then((response) => {
console.log(Object.values(response.data.query.pages))
return Object.values(response.data.query.pages);
})
.catch((error) => {
return error;
console.log(error);
});
console.log(result);
return result;
};
I fixed this by including a useEffect and a context from the parent component.
function Search() {
const {textFilterState, setTextFilterState} = useContext(TextSearchContext);
const {wikiTitleResults, setWikiTitleResults} = useContext(SearchContext);
var items = wikiTitleResults.wikiTitles;
useEffect(() => {
const fetchData = async () => {
const data = await getApiSuggestions(textFilterState.textFilter)
setWikiTitleResults({wikiTitles:data})
}
fetchData();
},
[textFilterState])
const handleOnSearch = (string, results) => {
setTextFilterState({textFilter:string});
}

I have a question about an issue where checkboxes are automatically checked when dropping a file into a drag and drop zone

When selecting files and dropping them into the drag-and-drop zone
I implemented it so that the file row list is output.
There is a problem.
The check box is automatically checked.
In developer tools, the confirmChecked function is called as many as the number of file rows.
Could this be related?
Why is the onChecked function automatically executed when drag and drop?
If anyone knows the cause of this problem or how to fix it, please let me know.
import React, { useCallback, useState } from 'react';
import { useDropzone } from 'react-dropzone';
import '../styles/FileAttach2.scss';
function FileAttach2(props) {
// state
// 체크된 항목
const [indexesArrayForChekedList, setIndexesArrayForChekedList] = useState([]);
const [filesToUpload, setFilesToUpload] = useState([]);
// 1122
const moveRowUp = params => {
};
const moveRowDown = params => {
};
// checkbox 관련
const confirmChecked = useCallback(
index => {
if (indexesArrayForChekedList.includes(index)) {
return false;
} else {
return true;
}
},
[indexesArrayForChekedList],
);
const handleCheckedStatus = useCallback(
(e, idx) => {
const checked = e.target.checked;
if (checked) {
const indexArrayForUpdate = indexesArrayForChekedList.filter(el => {
return el != idx;
});
setIndexesArrayForChekedList(indexArrayForUpdate);
} else {
setIndexesArrayForChekedList(prev => [...prev, idx]);
}
},
[indexesArrayForChekedList],
);
const deleteRowByIndex = useCallback(
idx => {
const afterDeleteRow = filesToUpload.filter(file => {
return file.index != idx;
});
setFilesToUpload(afterDeleteRow);
},
[filesToUpload],
);
// dropzone 관련
const onDrop = acceptedFiles => {
const filesData = acceptedFiles.map((file, index) => {
return { index: filesToUpload.length + index, name: file.name, size: file.size };
});
setFilesToUpload(prev => [...prev, ...filesData]);
};
const { getRootProps, getInputProps, open, acceptedFiles } = useDropzone({
noClick: true,
noKeyboard: true,
onDrop,
});
// FileRow template
const files = filesToUpload.map(file => (
<div className="fileRow" key={file.index}>
<div>
<input
type="checkBox"
onChange={e => handleCheckedStatus(e, file.index)}
checked={confirmChecked(file.index)}
/>
</div>
<div>{file.name}</div>
<div>{file.size} (bytes)</div>
<button type="button" onClick={() => deleteRowByIndex(file.index)}>
삭제
</button>
</div>
));
return (
<div className="">
<button type="button" onClick={open}>
Open File Dialog
</button>
<div {...getRootProps({ className: 'dropzone' })}>
<input {...getInputProps()} />
{filesToUpload.length !== 0 ? (
<div>
<div className="fileRowHeader">
<button onClick={moveRowDown}>아래로</button>
<button onClick={moveRowUp}>위로</button>
</div>
<div>{files}</div>
</div>
) : (
''
)}
<div></div>
</div>
</div>
);
}
export default FileAttach2;
You are returning true when indexesArrayForChekedList includes your index which will always be true at the beginning.
Try below code in your confirmChecked function
const confirmChecked = useCallback(
index => indexesArrayForChekedList.includes(index),
[indexesArrayForChekedList],
);

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.

Why does my map-function break Swiperjs component in React?

I'm trying to build a Movie Search web app with React. I query themoviedb.com for movie information and display the poster/movie title to the user.
I'm using Swiperjs to display the movie posters and titles to the users horizontally. Unfortunately, when I map() the movie information into a SwiperSlide component, the slider won't slide. It seems to stutter and refuse to move from the first movie poster. Can anyone send me in the right direction as to why it won't work?
Weirdly enough, on random refreshes, random Genres will work completely fine until a reload, but only one Genre component will do this.
Genre Component:
import React from "react";
import { Swiper, SwiperSlide } from 'swiper/react';
import MovieCard from '../components/movieCard';
class Genre extends React.Component {
constructor(props) {
super(props);
this.state = {
genre: this.props.genre,
genreName: "",
movies: []
};
}
componentDidMount() {
const genreNum = this.state.genre;
const url = `apiURlRequestHere`;
try {
fetch(url)
.then((response) => response.json())
.then((data) => this.setState({ movies: data.results }));
} catch (err) {
console.error(err);
}
this.getGenreName(genreNum);
}
getGenreName(genreNum) {
let title = "";
switch (genreNum) {
case "28":
title = "Action";
break;
case "12":
title = "Adventure";
break;
case "16":
title = "Animation";
break;
case "35":
title = "Comedy";
break;
case "80":
title = "Crime";
break;
case "99":
title = "Documentary";
break;
case "18":
title = "Drama";
break;
case "14":
title = "Fantasy";
break;
case "27":
title = "Horror";
break;
case "9648":
title = "Mystery";
break;
case "10749":
title = "Romance";
break;
case "878":
title = "Science Fiction";
break;
case "53":
title = "Thriller";
break;
default:
title = "";
break;
}
this.setState({ genreName: title });
}
render() {
const movies = this.state.movies;
let genreCat = this.state.genreName;
return (
<>
<h2 className="category-title">{genreCat}</h2>
<Swiper spaceBetween={0} slidesPerView={1}>
{movies
.filter((movie) => movie.poster_path)
.map((movie) => (
<SwiperSlide key={movie.id}>
<MovieCard movie={movie} />
</SwiperSlide>
))}
</Swiper>
</>
);
}
}
export default Genre;
MovieCard Component:
import React from "react";
export default function MovieCard({movie}) {
return (
<div className="card">
<img
className="card--image"
src={`https://image.tmdb.org/t/p/w185_and_h278_bestv2/${movie.poster_path}`}
alt={movie.title + ' poster'}
/>
<div className="card--content">
<h3 className="card--title">{movie.title}</h3>
<p className="card--rating">{movie.vote_average * 10}%</p>
</div>
</div>
)
}
--- UPDATE ---
Here is the Swiper Component I am using from SwiperJs
import React, { useRef, useState, useEffect, forwardRef } from 'react';
import { getParams } from './get-params';
import { initSwiper } from './init-swiper';
import { needsScrollbar, needsNavigation, needsPagination, uniqueClasses } from './utils';
import { renderLoop, calcLoopedSlides } from './loop';
import { getChangedParams } from './get-changed-params';
import { getChildren } from './get-children';
import { updateSwiper } from './update-swiper';
import { renderVirtual, updateOnVirtualData } from './virtual';
import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';
const Swiper = forwardRef(
(
{
className,
tag: Tag = 'div',
wrapperTag: WrapperTag = 'div',
children,
onSwiper,
...rest
} = {},
externalElRef,
) => {
const [containerClasses, setContainerClasses] = useState('swiper-container');
const [virtualData, setVirtualData] = useState(null);
const [breakpointChanged, setBreakpointChanged] = useState(false);
const initializedRef = useRef(false);
const swiperElRef = useRef(null);
const swiperRef = useRef(null);
const oldPassedParamsRef = useRef(null);
const oldSlides = useRef(null);
const nextElRef = useRef(null);
const prevElRef = useRef(null);
const paginationElRef = useRef(null);
const scrollbarElRef = useRef(null);
const { params: swiperParams, passedParams, rest: restProps } = getParams(rest);
const { slides, slots } = getChildren(children);
const changedParams = getChangedParams(
passedParams,
oldPassedParamsRef.current,
slides,
oldSlides.current,
);
oldPassedParamsRef.current = passedParams;
oldSlides.current = slides;
const onBeforeBreakpoint = () => {
setBreakpointChanged(!breakpointChanged);
};
Object.assign(swiperParams.on, {
_containerClasses(swiper, classes) {
setContainerClasses(classes);
},
_swiper(swiper) {
swiper.loopCreate = () => {};
swiper.loopDestroy = () => {};
if (swiperParams.loop) {
swiper.loopedSlides = calcLoopedSlides(slides, swiperParams);
}
swiperRef.current = swiper;
if (swiper.virtual && swiper.params.virtual.enabled) {
swiper.virtual.slides = slides;
swiper.params.virtual.cache = false;
swiper.params.virtual.renderExternal = setVirtualData;
swiper.params.virtual.renderExternalUpdate = false;
}
},
});
if (swiperRef.current) {
swiperRef.current.on('_beforeBreakpoint', onBeforeBreakpoint);
}
useEffect(() => {
return () => {
if (swiperRef.current) swiperRef.current.off('_beforeBreakpoint', onBeforeBreakpoint);
};
});
// set initialized flag
useEffect(() => {
if (!initializedRef.current && swiperRef.current) {
swiperRef.current.emitSlidesClasses();
initializedRef.current = true;
}
});
// watch for params change
useIsomorphicLayoutEffect(() => {
if (changedParams.length && swiperRef.current && !swiperRef.current.destroyed) {
updateSwiper(swiperRef.current, slides, passedParams, changedParams);
}
});
// update on virtual update
useIsomorphicLayoutEffect(() => {
updateOnVirtualData(swiperRef.current);
}, [virtualData]);
// init swiper
useIsomorphicLayoutEffect(() => {
if (externalElRef) {
externalElRef.current = swiperElRef.current;
}
if (!swiperElRef.current) return;
initSwiper(
{
el: swiperElRef.current,
nextEl: nextElRef.current,
prevEl: prevElRef.current,
paginationEl: paginationElRef.current,
scrollbarEl: scrollbarElRef.current,
},
swiperParams,
);
if (onSwiper) onSwiper(swiperRef.current);
// eslint-disable-next-line
return () => {
if (swiperRef.current && !swiperRef.current.destroyed) {
swiperRef.current.destroy();
}
};
}, []);
// bypass swiper instance to slides
function renderSlides() {
if (swiperParams.virtual) {
return renderVirtual(swiperRef.current, slides, virtualData);
}
if (!swiperParams.loop || (swiperRef.current && swiperRef.current.destroyed)) {
return slides.map((child) => {
return React.cloneElement(child, { swiper: swiperRef.current });
});
}
return renderLoop(swiperRef.current, slides, swiperParams);
}
return (
<Tag
ref={swiperElRef}
className={uniqueClasses(`${containerClasses}${className ? ` ${className}` : ''}`)}
{...restProps}
>
{slots['container-start']}
{needsNavigation(swiperParams) && (
<>
<div ref={prevElRef} className="swiper-button-prev" />
<div ref={nextElRef} className="swiper-button-next" />
</>
)}
{needsScrollbar(swiperParams) && <div ref={scrollbarElRef} className="swiper-scrollbar" />}
{needsPagination(swiperParams) && (
<div ref={paginationElRef} className="swiper-pagination" />
)}
<WrapperTag className="swiper-wrapper">
{slots['wrapper-start']}
{renderSlides()}
{slots['wrapper-end']}
</WrapperTag>
{slots['container-end']}
</Tag>
);
},
);
Swiper.displayName = 'Swiper';
export { Swiper };
And here is the SwiperSlide Component:
import React, { useRef, useState, forwardRef } from 'react';
import { uniqueClasses } from './utils';
import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';
const SwiperSlide = forwardRef(
(
{ tag: Tag = 'div', children, className = '', swiper, zoom, virtualIndex, ...rest } = {},
externalRef,
) => {
const slideElRef = useRef(null);
const [slideClasses, setSlideClasses] = useState('swiper-slide');
function updateClasses(swiper, el, classNames) {
if (el === slideElRef.current) {
setSlideClasses(classNames);
}
}
useIsomorphicLayoutEffect(() => {
if (externalRef) {
externalRef.current = slideElRef.current;
}
if (!slideElRef.current || !swiper) return;
if (swiper.destroyed) {
if (slideClasses !== 'swiper-slide') {
setSlideClasses('swiper-slide');
}
return;
}
swiper.on('_slideClass', updateClasses);
// eslint-disable-next-line
return () => {
if (!swiper) return;
swiper.off('_slideClass', updateClasses);
};
});
let slideData;
if (typeof children === 'function') {
slideData = {
isActive:
slideClasses.indexOf('swiper-slide-active') >= 0 ||
slideClasses.indexOf('swiper-slide-duplicate-active') >= 0,
isVisible: slideClasses.indexOf('swiper-slide-visible') >= 0,
isDuplicate: slideClasses.indexOf('swiper-slide-duplicate') >= 0,
isPrev:
slideClasses.indexOf('swiper-slide-prev') >= 0 ||
slideClasses.indexOf('swiper-slide-duplicate-prev') >= 0,
isNext:
slideClasses.indexOf('swiper-slide-next') >= 0 ||
slideClasses.indexOf('swiper-slide-duplicate next') >= 0,
};
}
const renderChildren = () => {
return typeof children === 'function' ? children(slideData) : children;
};
return (
<Tag
ref={slideElRef}
className={uniqueClasses(`${slideClasses}${className ? ` ${className}` : ''}`)}
data-swiper-slide-index={virtualIndex}
{...rest}
>
{zoom ? (
<div
className="swiper-zoom-container"
data-swiper-zoom={typeof zoom === 'number' ? zoom : undefined}
>
{renderChildren()}
</div>
) : (
renderChildren()
)}
</Tag>
);
},
);
SwiperSlide.displayName = 'SwiperSlide';
export { SwiperSlide };
Also, I have a working example with the issue here... https://codesandbox.io/s/blue-mountain-0fjyc?file=/src/App.js
in this example, hit refresh on the browser and that is what I see.
--- Update ---
Still working on this project and I created a movie detail page that displays the cast with a Swiper(what a fool, I know). It works perfectly fine but if I replace the code in the GenreComponent code with the working code from the below MovieDetail component, it still will not work...
import React from "react";
import { Swiper, SwiperSlide } from 'swiper/react';
import MovieGenre from '../components/movieGenreBtn';
class MovieDetails extends React.Component {
constructor(props) {
super(props);
this.state = {
movie: [],
movieGenres: [],
credits: [],
director: [],
foundDirector: false
}
}
componentDidMount() {
const movieId = this.props.location.pathname.replace("/", "");
this.fetchMovie(movieId);
this.fetchCrew(movieId);
}
fetchMovie(movieId) {
const url = `https://api.themoviedb.org/3/movie/${movieId}?api_key=${process.env.REACT_APP_MOVIE_API_KEY}&language=en-US
`;
try {
fetch(url)
.then((response) => response.json())
.then((data) => this.setState({ movie: data }));
} catch (err) {
console.error(err);
}
}
fetchCrew(movieId) {
const url = `https://api.themoviedb.org/3/movie/${movieId}/credits?api_key=${process.env.REACT_APP_MOVIE_API_KEY}`
try {
fetch(url)
.then((response) => response.json())
.then((data) => this.setState({ credits: data }));
} catch (err) {
console.error(err);
}
}
getDirector() {
let director = [];
if (this.state.credits !== null){
const crew = this.state.credits.crew;
let i;
for(i=0; i < crew.length; i++) {
if (crew[i].job === 'Director') {
director = crew[i];
}
}
}
this.setState({ director: director, foundDirector: true });
}
getGenres() {
const movie = this.state.movie;
let genres = [];
let i;
for (i=0; i < movie.genres.length; i++) {
genres.push(movie.genres[i]);
}
this.setState({ movieGenres: genres });
}
render() {
const movie = this.state.movie;
const cast = this.state.credits.cast;
if (cast != null && !this.state.foundDirector) {
this.getDirector();
this.getGenres();
}
const currCast = this.state.credits.cast;
const movieGenres = this.state.movieGenres;
return (
<div className="movie-details-wrapper">
<div className="details-header">
<img
className="movie-backdrop"
src={`https://image.tmdb.org/t/p/w1000_and_h450_multi_faces/${movie.backdrop_path}`} />
<h3 className="details-title">
{movie.title}
</h3>
<h5 className="details-tagling">
{movie.tagline}
</h5>
</div>
<div className="details-content">
<div className="details-director-rating">
<p className="basic-details">
Director: {this.state.director.name}
</p>
<p className="details-rating">
{movie.vote_average * 10}%
</p>
</div>
<div className="details-genres">
{movieGenres.map(genre => (
<MovieGenre genre={genre.id} />
))}
</div>
<div className="details-cast-wrapper">
<h3>Cast</h3>
<div className="details-cast">
{currCast ?
<Swiper>
{currCast.map(person => (
<SwiperSlide>
<img className="cast-img"src={`https://image.tmdb.org/t/p/w220_and_h330_bestv2/${person.profile_path}`} />
// </SwiperSlide>
))}
</Swiper>
:
<h2>No cast</h2>
}
</div>
</div>
</div>
</div>
);
}
} export default MovieDetails
--- Last Update ---
Problem Solved!
I think the Swiper was initialized before anything was in it so the Swiper thought it had zero slides and wouldn't function. I fixed this by adding some conditional rendering to check the length of the movies variable. Initially, this did not work with the conditional rendering because I forgot to add the length check.
Before:
<h2 className="category-title">{genreCat}</h2>
{movies ?
<Swiper>
{movies
.filter((movie) => movie.poster_path)
.map((movie) => (
<SwiperSlide key={movie.id}>
<MovieCard movie={movie}/>
</SwiperSlide>
))}
</Swiper>
:
<h2>No Movies</h2>
}
After:
<h2 className="category-title">{genreCat}</h2>
{movies.length > 0 ?
<Swiper>
{movies
.filter((movie) => movie.poster_path)
.map((movie) => (
<SwiperSlide key={movie.id}>
<MovieCard movie={movie}/>
</SwiperSlide>
))}
</Swiper>
:
<h2>No Movies</h2>
}
Problem Solved! I think the Swiper was initialized before anything was in it so the Swiper thought it had zero slides and wouldn't function. I fixed this by adding some conditional rendering to check the length of the movies variable. Initially, this did not work with the conditional rendering because I forgot to add the length check.
Before:
<h2 className="category-title">{genreCat}</h2>
{movies ?
<Swiper>
{movies
.filter((movie) => movie.poster_path)
.map((movie) => (
<SwiperSlide key={movie.id}>
<MovieCard movie={movie}/>
</SwiperSlide>
))}
</Swiper>
:
<h2>No Movies</h2>
}
After:
<h2 className="category-title">{genreCat}</h2>
{movies.length > 0 ?
<Swiper>
{movies
.filter((movie) => movie.poster_path)
.map((movie) => (
<SwiperSlide key={movie.id}>
<MovieCard movie={movie}/>
</SwiperSlide>
))}
</Swiper>
:
<h2>No Movies</h2>
}
You should add "return"
movies.map((movie) => {
return <SwiperSlide></SwiperSlide>
}

Resources