Re-rendering throws off my logic when using react-dnd - javascript

Right now I'm building a DnD game.
It looks like this:
When I drop an answer over a slot, the answer should be in that exact place, so the order matters.
My approach kind of works, but not perfectly.
So, in the parent component I have two arrays which store the slots' content and the answers' content.
The problem is easy to understand, you don't have to look at the code in-depth, just mostly follow my text :)
import DraggableGameAnswers from './DraggableGameAnswers';
import DraggableGameSlots from './DraggableGameSlots';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { useState } from 'react';
type DraggableGameStartProps = {
gameImages: Array<string>,
gameAnswers: Array<string>,
numberOfGameAnswers?: number,
typeOfAnswers: string,
correctAnswer: string
}
function DraggableGameStart(props: DraggableGameStartProps) {
const [slots, setSlots] = useState<string[]>(["", "", "", ""]);
const [answers, setAnswers] = useState<string[]>(props.gameAnswers);
return (
<DndProvider backend={HTML5Backend}>
<div className="draggable-game-content">
<div className="draggable-game">
<DraggableGameSlots
answers={answers}
slots={slots}
setAnswers={setAnswers}
setSlots={setSlots}
numberOfAnswers={4}
slotFor={props.typeOfAnswers}
/>
<DraggableGameAnswers
gameAnswers={answers}
numberOfGameAnswers={props.numberOfGameAnswers}
typeOfAnswers={props.typeOfAnswers}
/>
</div>
</div>
</DndProvider>
);
}
export default DraggableGameStart;
DraggableGameSlots is then a container, which loops through props.slots and renders each slot.
import React, { useState } from "react";
import DraggableGameSlot from "./DraggableGameSlot";
type DraggableGameSlotsProps = {
answers: string[],
slots: string[],
setAnswers: any,
setSlots: any,
numberOfAnswers: number,
slotFor: string
}
function DraggableGameSlots(props: DraggableGameSlotsProps) {
return (
<div className="draggable-game-slots">
{
props.slots.map((val, index) => (
<DraggableGameSlot
index={index}
typeOf={props.slotFor === "text" ? "text" : "image"}
key={index}
answers={props.answers}
slots={props.slots}
setAnswers={props.setAnswers}
setSlots={props.setSlots}
toDisplay={val === "" ? "Drop here" : val}
/>
))
}
</div>
);
}
export default DraggableGameSlots;
In DraggableGameSlot I make my slots a drop target. When I drop an element, I iterate through the slots, and when I reach that element's position in the array, I modify it with the answer.
Until now everything works fine, as expected, I just wrote this for context.
E.g. if the drop target's index is 2, I modify the 3rd position in the slots array. (slots2 for 0-index based)
import { useEffect } from 'react';
import { useDrop } from 'react-dnd';
import './css/DraggableGameSlot.css';
import DraggableGameImage from './DraggableGameImage';
type DraggableGameSlotProps = {
index: number,
typeOf: string,
answers: string[],
slots: string[],
setAnswers: any,
setSlots: any,
toDisplay: string
}
function DraggableGameSlot(props: DraggableGameSlotProps) {
const [{ isOver }, drop] = useDrop(() => ({
accept: "image",
drop(item: { id: string, toDisplay: string }) {
props.setAnswers(props.answers.filter((val, index) => index !== parseInt(item.id)));
props.setSlots(props.slots.map((val, index) => {
if (props.index === index) {
console.log("ITEM " + item.toDisplay);
return item.toDisplay;
}
return val;
}));
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
})
}), [props.slots])
// useEffect(() => console.log("answers " +props.answers), [props.answers]);
// useEffect(() => console.log("slots " + props.slots), [props.slots]);
const dragClass: string = isOver ? "is-dragged-on" : "";
return (
<>
{(props.typeOf === "image" && props.toDisplay !== "Drop here") ?
<>
<span>da</span>
<DraggableGameImage
className={`game-answers-image ${dragClass}`}
imgSrc={props.toDisplay}
imgAlt={"test"}
dndRef={drop}
/>
</>
:
<div className={`draggable-game-slot draggable-game-slot-for-${props.typeOf} ${dragClass}`} ref={drop}>
<span>{props.toDisplay}</span>
</div>
}
</>
)
}
export default DraggableGameSlot;
The problem comes with the logic in DraggableGameAnswer.
First, the container, DraggableGameAnswer. Using the loop, I pass that "type" (it is important because from there the error happens)
DraggableGameAnswers.tsx
import './css/DraggableGameAnswers.css';
import GameNext from '../GameComponents/GameNext';
import DraggableGameAnswer from './DraggableGameAnswer';
import { useEffect } from 'react';
type DraggableGameAnswersProps = {
gameAnswers: Array<string>,
numberOfGameAnswers?: number,
typeOfAnswers: string
}
function DraggableGameAnswers(props: DraggableGameAnswersProps) {
let toDisplay: Array<string>;
if(props.numberOfGameAnswers !== undefined)
{
toDisplay = props.gameAnswers.slice(props.numberOfGameAnswers);
}
else
{
toDisplay = props.gameAnswers;
}
useEffect(() => console.log(toDisplay));
return (
<div className="draggable-game-answers">
{
toDisplay.map((val, index) => (
<DraggableGameAnswer
key={index}
answer={val}
typeOfAnswer={props.typeOfAnswers}
type={`answer${index}`}
/>
))}
<GameNext className="draggable-game-verify" buttonText="Verify"/>
</div>
);
}
export default DraggableGameAnswers;
In DraggableGameAnswer, I render the draggable answer:
import DraggableGameImage from "./DraggableGameImage";
import "./css/DraggableGameAnswer.css";
import { useDrag } from "react-dnd";
import { ItemTypes } from "./Constants";
type DraggableGameAnswerProps = {
answer: string,
typeOfAnswer: string,
type: string
}
function DraggableGameAnswer(props: DraggableGameAnswerProps) {
const [{ isDragging }, drag] = useDrag(() => ({
type: "image",
item: { id: ItemTypes[props.type],
toDisplay: props.answer,
typeOfAnswer: props.typeOfAnswer },
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
})
}))
return (
<>
{props.typeOfAnswer === "image" ?
<DraggableGameImage
className="draggable-game-image-answer"
imgSrc={props.answer}
imgAlt="test"
dndRef={drag}
/> :
<div className="draggable-game-text-answer" ref={drag}>
{props.answer}
</div>
}
</>
);
}
export default DraggableGameAnswer;
Constants.tsx (for ItemTypes)
export const ItemTypes: Record<string, any> = {
answer0: '0',
answer1: '1',
answer2: '2',
answer3: '3'
}
Okay, now let me explain what's happening.
The error starts because of the logic in DraggableGameAnswers container - I set the type using the index for the loop.
Everything works fine at the beginning. Then, let's say, I move answer3 in in the 4th box. It looks like this now:
It is working fine until now.
But now, the components have re-rendered, the map runs again, but the array is shorter with 1 and answer1's type will be answer1, answer2's type will be answer2, and answer4's type will be answer3.
So, when I drag answer4 over any box, I will get another answer3, like this:
How can I avoid this behavior and still get answer4 in this situation? I'm looking for a react-style way, better than my idea that I'll describe below.
So, here is how I delete an answer from the answers array:
props.setAnswers(props.answers.filter((val, index) => index !== parseInt(item.id)));
I can just set the deleted's answer position to NULL, to keep my array length for that index, and when I render the answers if the value is NULL, just skip it.

Related

List from useContext is emptying right after pushing another element

While I am trying to add a new Subject to the specific Year and Quarter it is getting dragged into, my list is updating on screen, I am getting all of the element in the list, but when I try to check for things like duplicates inside the list (to not allow them), the list appears to be empty. I might suspect that it has something to do with the rerendering of the component without "fetching" the context again, but to be fair I have no clue what to try next to solve this.
import { Box, Typography } from "#mui/material";
import { useDrop } from "react-dnd";
import SubjectCard from "./SubjectCard";
import { Subject } from "../models/Subject";
import { useContext, useEffect } from "react";
import {
CurricullumContext,
CurricullumContextType,
} from "../context/CurricullumContext";
interface Props {
year: number;
quarter: number;
}
function QuarterComponent({ year, quarter }: Props) {
const yearProp = "year" + year;
const quarterProp = "quarter" + quarter;
const { curricullum, dispatch } = useContext(
CurricullumContext
) as CurricullumContextType;
const subjects = curricullum[yearProp]![quarterProp]!.subjects;
useEffect(() => {
console.log("subjects useEffect", subjects);
}, [curricullum]);
const [{ isOver }, drop] = useDrop(() => ({
accept: "subject",
drop: (item: Subject) => {
console.log("dropped", item);
console.log("subjects in drop", subjects);
addSubjectToYear(item);
},
collect: (monitor) => ({
isOver: !!monitor.isOver({ shallow: true }),
}),
}));
const addSubjectToYear = (subject: Subject) => {
console.log("subjects: ", curricullum[yearProp]![quarterProp]!.subjects);
if (!subjects.some((s: any) => s.courseName === subject.courseName)) {
dispatch({
type: "ADD_SUBJECT_TO_QUARTER",
payload: {
year: yearProp,
quarter: quarterProp,
subject: subject,
},
});
}
};
return (
<Box
display="flex"
flexDirection="column"
justifyContent="center"
alignItems="center"
ml={2}
>
<Typography variant="h5">Quartile {quarter}</Typography>
<Box
display="flex"
flexDirection="column"
width={200}
height={400}
border={isOver ? "2px solid red" : "2px solid black"}
ref={drop}
bgcolor={isOver ? "lightsalmon" : "white"}
>
{subjects.length > 0 &&
subjects.map((subject: Subject) => <SubjectCard subject={subject} />)}
{subjects.length === 0 && (
<Typography variant="h6">Drop subjects here</Typography>
)}
</Box>
</Box>
);
}
export default QuarterComponent;
I tried adding a console.log using useEffect to try and capture the list at every render and on each drop of a new item i get around 16 console.logs, but the interesting part is that always the first log shows the list with all of the items that it should have, onlyt after the first one all of the rest are empty arrays.
QuarterComponent.tsx:28 subjects useEffect (2) [{…}, {…}]0: {id: 1, language: 'English', credits: 5, courseName: 'OOP', version: '1', …}1: {id: 2, language: 'English', credits: 5, courseName: 'Databases', version: '2', …}length: 2[[Prototype]]: Array(0)
QuarterComponent.tsx:28 subjects useEffect []
QuarterComponent.tsx:28 subjects useEffect []

Get Id Based on Dynamic Selected Values in React

I have several productOptionTypes with dynamic values. My problem is how do I match it with its variants. I want to get the variant id based on it.
Pls check codesandbox here CLICK HERE
{productOptionTypes.map((item, index) => (
<>
<span className="title">{item.optionName}</span>
<Select
placeholder={`${item?.optionValues?.length} ${item.optionName}s`}
options={item.optionValues.map((option) => {
return { label: option, value: option };
})}
isSearchable={false}
onChange={(value) => handleSelectVariant(value, index)}
/>
</>
))}
Best I could come up with is this:
Update the option mapping to return the option name, to be used as the foreign key into the variants array objects, i.e. "Color", "Size", etc.
options={item.optionValues.map((option) => {
return {
label: option,
value: option,
name: item.optionName
};
})}
Update the variantType state to be an object, and update the handleSelectVariant handler to store the variant types by the foreign key "name" and the selected "value"
const [variantType, setSelectedVariant] = useState({});
const handleSelectVariant = (value, index) => {
setSelectedVariant((state) => ({
...state,
[value.name]: value.value
}));
};
Use a filter and every function to reduce the option types and values to a filtered result of variants that can be easily mapped to the id properties.
const matches = variants
.filter((variant) => {
return [
["option1Value", "option1Type"],
["option2Value", "option2Type"],
["option3Value", "option3Type"],
["option4Value", "option4Type"],
["option5Value", "option5Type"],
["option6Value", "option6Type"],
["option7Value", "option7Type"],
["option8Value", "option8Type"],
["option9Value", "option9Type"],
["option10Value", "option10Type"]
].every(([key, valueKey]) =>
variantType[variant[valueKey]]
? variant[key] === variantType[variant[valueKey]]
: true
);
})
.map(({ id }) => id);
Demo
When i understand it correctly you want to safe every selected value, then compare the result to your variants and select the variantId of the item matching all selctboxes based on it?
index.js
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import Select from "react-select";
import "./styles.css";
import { productOptionTypes } from "./productOptionTypes";
import { variants } from "./variants";
function App() {
const [variantType, setSelectedVariant] = useState({});
const [result, setResult] = useState(null);
const mapIndexKey = {
0: "option1Value",
1: "option2Value",
2: "option3Value",
3: "option4Value"
};
useEffect(() => {
setResult(
variants.filter(
(el) =>
el.option1Value == variantType.option1Value &&
el.option2Value == variantType.option2Value &&
el.option3Value == variantType.option3Value &&
el.option4Value == variantType.option4Value
)
);
}, [variantType]);
useEffect(() => {
console.log(result);
}, [result]);
const handleSelectVariant = (value, index) => {
setSelectedVariant({ ...variantType, [mapIndexKey[index]]: value.value });
console.log(variantType, value, index);
};
return (
<div className="App">
<form>
{productOptionTypes.map((item, index) => (
<>
<span className="title">{item.optionName}</span>
<Select
placeholder={`${item?.optionValues?.length} ${item.optionName}s`}
options={item.optionValues.map((option) => {
return { label: option, value: option };
})}
isSearchable={false}
onChange={(value) => handleSelectVariant(value, index)}
/>
</>
))}
</form>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
variants.js
export const variants = [
{
id: "60451fd290aeb720d96d8459",
name: "Women's Bellarina Half Toe Grip Yoga Pilates Barre Socks",
wholesalePrice: "8.0",
sku: "s01524blk",
option1Type: "Color",
option1Value: "Black",
option2Type: "Size",
option2Value: "XS",
option3Type: "Toe Type",
option3Value: "Half Toe",
option4Value: "Bellarina",
retailPrice: "16.0",
__typename: "Variant"
},
{
id: "60451fd790aeb720d96d8463",
name: "Women's Bellarina Half Toe Grip Yoga Pilates Barre Socks",
wholesalePrice: "8.0",
sku: "s01525blk",
option1Type: "Color",
option1Value: "Black",
option2Type: "Size",
option2Value: "S",
option3Type: "Toe Type",
option3Value: "Half Toe",
retailPrice: "16.0",
__typename: "Variant"
}
}
Basicly what i do is saving all selected values in a map and everytime a value is changed just comparing it to all variants, if a variant is equal i select it.
For one variant i added the fourth key "Bellarina".
I hope this solution solves your problem.
When you want to verify the solution just select the first value of every selectbox and have a look at the console.

Handle enabling and disabling radio inputs depending on product variants in React hooks form

Well, you know when there's a 128 GB phone but it doesn't have a blue color so the next options section colors disables the blue color button? and when a 256 GB has only one color, this is exactly it
// react
import React, {SyntheticEvent, useCallback, useMemo, useState} from 'react';
// third-party
import classNames from 'classnames';
import {UncontrolledTooltip} from 'reactstrap';
import {useFormContext} from 'react-hook-form';
// application
import {colorType} from '~/services/color';
import {IProduct, IProductOption, IProductOptionValueBase, IProductVariant} from '~/interfaces/product';
interface Props extends React.HTMLAttributes<HTMLElement> {
product: IProduct;
namespace?: string;
}
function ProductForm(props: Props) {
const {
product,
namespace,
className,
...rootProps
} = props;
const {options} = product; // IProductOption
const {register, getValues} = useFormContext();
const ns = useMemo(() => (namespace ? `${namespace}.` : ''), [namespace]);
const optionsTemplate = options.map((option, optionIdx) => (
<div key={optionIdx} className="product-form__row">
<div className="product-form__title">{option.name}</div>
<div className="product-form__control">
{option.type === 'default' && (
<div className="input-radio-label">
<div className="input-radio-label__list">
{option.values.map((value, valueIdx) => (
<label key={valueIdx} className="input-radio-label__item">
<input
type="radio"
name={`${ns}${option.slug}`}
className="input-radio-label__input"
value={value.slug}
ref={register({required: true})}
onChange={(evt) => handleOptionChange(evt, option, value)}
/>
<span className="input-radio-label__title">{value.name}</span>
</label>
))}
</div>
</div>
)}
{option.type === 'color' && (
<div className="input-radio-color">
<div className="input-radio-color__list">
{option.values.map((value, valueIdx) => (
<React.Fragment key={valueIdx}>
<label
className={classNames('input-radio-color__item', {
'input-radio-color__item--white': colorType(value.color) === 'white',
})}
id={`product-option-${optionIdx}-${valueIdx}`}
style={{color: value.color}}
title={value.name}
>
<input
type="radio"
name={`${ns}${option.slug}`}
value={value.slug}
ref={register({required: true})}
onChange={(evt) => handleOptionChange(evt, option, value)}
/>
<span/>
</label>
<UncontrolledTooltip
target={`product-option-${optionIdx}-${valueIdx}`}
fade={false}
delay={{show: 0, hide: 0}}
>
{value.name}
</UncontrolledTooltip>
</React.Fragment>
))}
</div>
</div>
)}
</div>
</div>
));
const rootClasses = classNames('product-form', className);
return (
<div className={rootClasses} {...rootProps}>
<div className="product-form__body">
{optionsTemplate}
</div>
</div>
);
}
export default ProductForm;
The product has .attributes (fully working) and .variants (I'm still implementing the full interface and .options working
NOTE: This was a prebuilt theme, it has attributes and options, I added variants (still doesn't have all needed attributes for variants tho).
export interface IProductAttributeValue {
name: string;
slug: string;
}
export interface IProductAttribute {
name: string;
slug: string;
featured: boolean;
values: IProductAttributeValue[];
}
export interface IProductVariant {
sku: string;
name: string;
price: number;
stock: IProductStock;
attributes: IProductAttribute[];
}
export type IProductOption = IProductOptionDefault | IProductOptionColor;
export interface IProductOptionValueBase {
name: string;
slug: string;
customFields?: ICustomFields;
}
export interface IProductOptionValueColor extends IProductOptionValueBase {
color: string;
}
export interface IProductOptionBase {
type: string;
name: string;
slug: string;
values: IProductOptionValueBase[];
customFields?: ICustomFields;
}
export interface IProductOptionDefault extends IProductOptionBase {
type: 'default';
}
export interface IProductOptionColor extends IProductOptionBase {
type: 'color';
values: IProductOptionValueColor[];
}
Well, the variant should have a storage of maybe 128GB, speaking about abstraction, it can have any x: y, I've made a simple graph for this
All the possible choices are options1.length * options2.length = 9, but perhaps the 2 can't exist with an A, the 3 can only exist with A, ...etc (random, depends on the phone)
When I say options (plural), it's equivalent to a row of the picture above. and an option is just one input from that row.
What I've thought (and apparently failed many times in implementing) is, maybe the onChange of each input can take (evt) => handleOptionChange(evt, option, value)
and then, when optionsN is selected, iterate all the options which are optionsN+1, so when 1 is selected from the first options, iterate all the rest, and see if there exists a variant, if not, disable it
here's the idea
1 is selected, iterate the rest, 1, A ? exists, 1, B? doesn't, 1,C ? doesn't, sounds good
I said to myself, we can get the index of the last selected option as , getValues returns an object (not a Map!) and I've got a workarond
const handleOptionChange = (evt: SyntheticEvent, option: any, value: any) => {
const currentOptionIdx = product.options.findIndex(o => o.slug == option.slug)
const optionsToFilterWith = product.options.slice(0, currentOptionIdx + 1);
const optionsValues = getValues();
};
// ??
Here comes another problem, what if a user selected 1, then selected B, then selected 2, the optionsToFilterWith will be an array of only 1 option, the first one.
There're utility hooks here, useVariant, useSetVariant to set the variant (because when the variant changes, I'll change the price and some other details .
EDIT 1: I've just got an idea, we may use product.stocks or variant.stock to handle this, I'll update this once any progress is made.
EDIT 2: seems like the order idea is the one making the problem I'm encountering?
EDIT 3: here's a helper method to get variants by options (from the form)
const getVariantByOptions = useCallback(() => {
const formValues = getValues();
// form values which are empty are "", filter them out
const currentOptions = Object.entries(formValues[namespace]).filter(o => o[1])
// current product variants
let variants = product.variants;
// for each selected option (loops -rows count- times)
currentOptions.forEach(currentOpt => {
// each option is on the form [attributeSlug, valueSlug]
const [optName, optValue] = currentOpt;
variants = variants.filter(variant => {
return variant.attributes.find(attr => {
if (attr.slug === optName) {
return !!attr.values.find(v => v.slug === optValue)
}
})
})
})
return variants.length === 1 ? variants[0] : null
}, [product])
options is the name of the input group.
and here's how the form getValues returns
{
storage: "256-gb",
test: "v1"
}
and if nothing is selected in an options field, here's how it returns
{
storage: "256-gb",
test: ""
}
I'll appreciate any help, Thanks in advance.

Unexpected mutation of array in React

I am just learning to program and am writing one of my first applications in React. I am having trouble with an unexpected mutation that I cannot find the roots of. The snippet is part of a functional component and is as follows:
const players = props.gameList[gameIndex].players.map((item, index) => {
const readyPlayer = [];
props.gameList[gameIndex].players.forEach(item => {
readyPlayer.push({
id: item.id,
name: item.name,
ready: item.ready
})
})
console.log(readyPlayer);
readyPlayer[index].test = "test";
console.log(readyPlayer);
return (
<li key={item.id}>
{/* not relevant to the question */}
</li>
)
})
Now the problem is that readyPlayer seems to be mutated before it is supposed to. Both console.log's read the same exact thing. That is the array with the object inside having the test key as "test". forEach does not mutate the original array, and all the key values, that is id, name and ready, are primitives being either boolean or string. I am also not implementing any asynchronous actions here, so why do I get such an output? Any help would be greatly appreciated.
Below is the entire component for reference in its original composition ( here also the test key is replaced with the actual key I was needing, but the problem persists either way.
import React from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
// import styles from './Lobby.module.css';
const Lobby = ( props ) => {
const gameIndex = props.gameList.findIndex(item => item.id === props.current.id);
const isHost = props.gameList[gameIndex].hostId === props.user.uid;
const players = props.gameList[gameIndex].players.map((item, index) => {
const isPlayer = item.id === props.user.uid;
const withoutPlayer = [...props.gameList[gameIndex].players];
withoutPlayer.splice(index, 1);
const readyPlayer = [];
props.gameList[gameIndex].players.forEach(item => {
readyPlayer.push({
id: item.id,
name: item.name,
ready: item.ready
})
})
const isReady = readyPlayer[index].ready;
console.log(readyPlayer);
console.log(!isReady);
readyPlayer[index].ready = !isReady;
console.log(readyPlayer);
return (
<li key={item.id}>
{isHost && index !== 0 && <button onClick={() => props.updatePlayers(props.gameList[gameIndex].id, withoutPlayer)}>Kick Player</button>}
<p>{item.name}</p>
{isPlayer && <button onClick={() =>props.updatePlayers(props.gameList[gameIndex].id, readyPlayer)}>Ready</button>}
</li>
)
})
let showStart = props.gameList[gameIndex].players.length >= 2;
props.gameList[gameIndex].players.forEach(item => {
if (item.ready === false) {
showStart = false;
}
})
console.log(showStart);
return (
<main>
<div>
{showStart && <Link to="/gameboard" onClick={props.start}>Start Game</Link>}
<Link to="/main-menu">Go back to Main Menu</Link>
</div>
<div>
<h3>Players: {props.gameList[gameIndex].players.length}/4</h3>
{players}
</div>
</main>
);
}
Lobby.propTypes = {
start: PropTypes.func.isRequired,
current: PropTypes.object.isRequired,
gameList: PropTypes.array.isRequired,
updatePlayers: PropTypes.func.isRequired,
user: PropTypes.object.isRequired
}
export default Lobby;
Note: I did manage to make the component actually do what it is supposed, but the aforementioned unexpected mutation persists and is still a mystery to me.
I have created a basic working example using the code snippet you provided. Both console.log statements return a different value here. The first one returns readyPlayer.test as undefined, the second one as "test". Are you certain that the issue happens within your code snippet? Or am I missing something?
(Note: This answer should be a comment, but I am unable to create a code snippet in comments.)
const players = [
{
id: 0,
name: "John",
ready: false,
},
{
id: 1,
name: "Jack",
ready: false,
},
{
id: 2,
name: "Eric",
ready: false
}
];
players.map((player, index) => {
const readyPlayer = [];
players.forEach((item)=> {
readyPlayer.push({
id: item.id,
name: item.name,
ready: item.ready
});
});
// console.log(`${index}:${readyPlayer[index].test}`);
readyPlayer[index].test = "test";
// console.log(`${index}:${readyPlayer[index].test}`);
});
console.log(players)

TypeError: "Cannot read property 'toLowerCase' of undefined" when trying to .filter()

So i've been trying to troubleshoot this error and I've been coming up blank. I tried replacing title in .filter() with subreddit, key, link, type, and body, they all work fine without throwing an error, only title gives me the error. During troubleshooting I also placed that console.log to see if something was actually stored, and it printed the title to console like it should have, yet saved.title is still undefined in .filter. I also tried just saved.title.includes without toLowerCase() to no avail.
The code in question
renderPostTitles = () => {
const importantValues = this.props.totalUserSaves.map((saved) => {
return {
subreddit: saved.data.subreddit,
title: saved.data.title,
key: saved.data.id,
link: `https://www.reddit.com${saved.data.permalink}`,
type: saved.kind,
body: saved.data.body
}
})
console.log(importantValues[0].title)
const threadsArray_searched = importantValues.filter(saved => saved.title.toLowerCase().includes(this.props.searchQuery.toLowerCase()) )
More of the component:
import React from 'react';
import { connect } from 'react-redux';
class DisplaySaves extends React.Component {
renderPostTitles = () => {
const importantValues = this.props.totalUserSaves.map((saved) => {
return {
subreddit: saved.data.subreddit,
title: saved.data.title,
key: saved.data.id,
link: `https://www.reddit.com${saved.data.permalink}`,
type: saved.kind,
body: saved.data.body
}
})
const threadsArray_searched = importantValues.filter(saved => saved.title.toLowerCase().includes(this.props.searchQuery.toLowerCase()) )
return threadsArray_searched.map((saved, i) => {
return (
<div key={saved.key}>
<div> {i+1}. </div>
<div> r/{saved.subreddit}: </div>
<p> {saved.title} </p>
</div>
)
})
}
render () {
return (
<div>
<div>{this.renderPostTitles()}</div>
</div>
)
}
}
const mapStateToProps = state => {
console.log(state)
return {
totalUserSaves: state.userData.totalUserSaves,
searchQuery: state.userData.searchQuery
}
}
export default connect(mapStateToProps)(DisplaySaves);
Make sure title is a string in all cases (an undefined is possibly sneaking through in one of the elements), you could use a default or string constructor:
return {
title: saved.data.title || '',
or
return {
title: String(saved.data.title),

Categories