i had a game react matching card project to do.the game is consist to choose card in card grid if two carts contents is the same so this two cart will be her matched property true and will be displaying in green color and in the score component will mark 2 matched cards and so on if the card is not matched the cards color contents will be in red and another handler function will come here to make the two cards invisible by change the property of card object visible to false I had release this function it work well in the console but when I try it in the code application it not work at all please help me to fix it there is my code components :
the boardSlice:
const initialState = [
{id: 0, contents: 'Provider', visible: true, matched: true},
{id: 1, contents: 'Provider', visible: true, matched: true},
{id: 2, contents: 'selector', visible: true, matched: true},
{id: 3, contents: 'selector', visible: true, matched: true},
{id: 4, contents: 'useSelector()', visible: true, matched: true},
{id: 5, contents: 'useSelector()', visible: true, matched: true},
{id: 6, contents: 'useDispatch()', visible: true, matched: true},
{id: 7, contents: 'useDispatch()', visible: true, matched: true},
{id: 8, contents: 'Pure Function', visible: true, matched: true},
{id: 9, contents: 'Pure Function', visible: true, matched: true},
{id: 10, contents: 'react-redux', visible: true, matched: true},
{id: 11, contents: 'react-redux', visible: true, matched: true},
];
export const boardReducer = (state = initialState, action) => {
switch (action.type) {
case 'board/setBoard':
let setState = [];
action.payload.forEach((element, index) =>
setState.push({id: index,
contents: element,
visible: false,
matched: false})
);
return setState;
case 'board/flipCard':
let flipState = [...state];
const cardID = action.payload;
flipState[cardID] = {...state[cardID], visible:true}
const [index1, index2] = flipState.filter(card => card.visible).map(card => card.id);
if (index2 !== undefined) {
let card1 = flipState[index1];
let card2 = flipState[index2];
if (card1.contents === card2.contents) {
flipState[index1] = {...card1, visible: false, matched: true};
flipState[index2] = {...card2, visible: false, matched: true};
}
}
return flipState;
case 'board/resetUnmatchedCards':
let newState = [...state];
let [indexa, indexb] = newState.filter(card => card.visible === true && card.matched === false).map(card => card.id);
if (indexb !== undefined) {
let cardA = newState[indexa];
let cardB = newState[indexb];
newState[indexa] = {...cardA, visible: action.payload};
newState[indexb] = {...cardB, visible: action.payload}
}
return newState
case 'board/resetCards':
return state.map(card => ({...card, visible: false}));
default:
return state;
}
}
const wordPairs = [
'Provider', 'Provider',
'selector', 'selector',
'useSelector()', 'useSelector()',
'useDispatch()', 'useDispatch()',
'Pure Function', 'Pure Function',
'react-redux', 'react-redux',
]
const randomWords = () => {
let words = []
let newWordPairs = [...wordPairs]
const reps = newWordPairs.length
for (let i = 0 ; i < reps ; i++) {
const wordIndex = Math.floor(Math.random() * newWordPairs.length);
words.push(newWordPairs[wordIndex])
newWordPairs.splice(wordIndex, 1)
}
return words;
}
// action creators
export const setBoard = () => {
const words = randomWords()
return {
type: 'board/setBoard',
payload: words
}
}
export const flipCard = (id) => {
return {
type: 'board/flipCard',
payload: id
}
}
export const resetCards = (indices) => {
return {
type: 'board/resetCards'
}
};
export const resetUnmatchedCards = () => {
return {
type: 'board/resetUnmatchedCards',
payload: false
}
}
// Add selector export statments below
export const selectBoard = (state) => {
return ( state.board.map(card=>
({
id: card.id,
contents: card.contents
})
))}
export const selectVisibleIDs = state => {
return (
state.board.filter(card => card.visible)
.map(card => card.id)
)
}
export const selectMatchedIDs = state => {
return ( state.board.filter(card => card.matched)
.map(card => card.id));
};
the App component :
import './App.css';
import React from 'react';
import { Score } from './features/score/Score.js';
import { Board } from './features/board/Board.js';
import { useDispatch } from 'react-redux';
import { setBoard, resetCards } from './features/board/boardSlice';
// Add import statements below
const App = () => {
// Add dispatch variable below
const dispatch = useDispatch();
const startGameHandler = () => {
// Add action dispatch below
dispatch(setBoard())
};
const tryAgainHandler = () => {
// Add action dispatch below
dispatch(resetCards())
};
return (
<div className="App">
<Score />
<Board />
<footer className="footer">
<button onClick={startGameHandler} className="start-button">
Start Game
</button>
<button onClick={tryAgainHandler} className="try-new-pair-button">
Try New Pair
</button>
</footer>
</div>
);
};
export default App;
the board component :
import React from 'react';
import { CardRow } from './cardRow/CardRow.js';
// Add import statements below
import { useSelector } from 'react-redux';
import { selectBoard } from './boardSlice';
export const Board = () => {
// Add selected data variable and implement below
const currentBoard = useSelector(selectBoard);
const numberOfCards = currentBoard.length;
const columns = 3;
const rows = Math.floor(numberOfCards / columns);
const getRowCards = (row) => {
const rowCards = [];
for (let j = 0; j < columns; j++) {
const cardIndex = row * columns + j;
// Implement selected data below
rowCards.push(currentBoard[cardIndex]);
}
return rowCards;
};
console.log(currentBoard)
let content = [];
for (let row = 0; row < rows; row++) {
const rowCards = getRowCards(row);
content.push(
<CardRow
key={row}
cards={rowCards}
/>
);
}
return <div className="cards-container">{content}</div>;
};
the cardRow.js:
import React from 'react';
import { Card } from './card/Card.js';
import {selectMatchedIDs } from '../boardSlice'
export const CardRow = ({ cards }) => {
const content = cards.map(card =>
<Card
key={card.id}
id={card.id}
contents={card.contents}
/>)
return <>{content}</>;
};
the Card.js:
import React, {useEffect} from 'react';
// Add import statements below
import { useSelector, useDispatch } from 'react-redux';
import { selectVisibleIDs, flipCard, selectMatchedIDs, } from '../../boardSlice';
import { resetCards, resetUnmatchedCards } from '../../boardSlice'
let cardLogo = "https://static-assets.codecademy.com/Courses/Learn-Redux/matching-game/codecademy_logo.png";
export const Card = ({ id, contents }) => {
// Add selected data and dispatch variables below
const visibleIDs = useSelector(selectVisibleIDs)
const dispatch = useDispatch();
const matchedIDs = useSelector(selectMatchedIDs);
console.log(visibleIDs)
console.log(matchedIDs);
// flip card action
const flipHandler = (id) => {
// Add action dispatch below
dispatch(flipCard(id))
};
const resetHandler = () => {
dispatch(resetUnmatchedCards)
}
let cardStyle = 'resting';
let click = () => flipHandler(id);
let cardText = (
<img src={cardLogo} className="logo-placeholder" alt="Card option" />
);
// 1st if statement
// implement card id array membership check
if (visibleIDs.includes(id) || matchedIDs.includes(id)) {
cardText = contents;
click = () => {};
}
// 2nd if statement
// implement card id array membership check
if (matchedIDs.includes(id)) {
cardStyle = 'matched';
} else {
cardStyle = 'no-match';
}
// 3rd if statement
// implement number of flipped cards check
if (visibleIDs.length === 2) {
if (cardStyle === 'no-match' ) {
click = () => resetHandler();
}
click = ()=> {};
}
return (
<button onClick={click} className={`card ${cardStyle}`}>
{cardText}
</button>
);
};
so the problem is i want to change the click button to an other function to do so when first every card had this object {id:cardId, contents: cardContents, visible: false, matched: false} so first of when the user click two cards by the "FlipCard" action the card.visible become true then if the first card.contents is ht same as the second card.contents so the card.matched property for the two cards will become true so the require thing I want to add another function to reset the two card.visible to false when the first card.contents is not the same as the second card.contents so this is my code of the component Card :
import React, {useEffect} from 'react';
// Add import statements below
import { useSelector, useDispatch } from 'react-redux';
import { selectVisibleIDs, flipCard, selectMatchedIDs, } from '../../boardSlice';
import { resetCards, resetUnmatchedCards } from '../../boardSlice'
let cardLogo = "https://static-assets.codecademy.com/Courses/Learn-Redux/matching-game/codecademy_logo.png";
export const Card = ({ id, contents }) => {
// Add selected data and dispatch variables below
const visibleIDs = useSelector(selectVisibleIDs)
const dispatch = useDispatch();
const matchedIDs = useSelector(selectMatchedIDs);
console.log(matchedIDs);
// flip card action
const flipHandler = (id) => {
// Add action dispatch below
dispatch(flipCard(id))
};
const resetHandler = () => {
dispatch(resetCards)
}
let cardStyle = 'resting';
let click = () => flipHandler(id);
let cardText = (
<img src={cardLogo} className="logo-placeholder" alt="Card option" />
);
// 1st if statement
// implement card id array membership check
if (visibleIDs.includes(id) || matchedIDs.includes(id)) {
cardText = contents;
click = () => {};
}
// 2nd if statement
// implement card id array membership check
if (matchedIDs.includes(id)) {
cardStyle = 'matched';
} else {
cardStyle = 'no-match';
}
console.log(visibleIDs.every(id => matchedIDs.includes(id)))
// 3rd if statement
// implement number of flipped cards check
if (visibleIDs.length === 2 ) {
if (cardStyle === 'no-match' && visibleIDs.every(id => !matchedIDs.includes(id))) {
click = () => resetHandler();
} else {
click = () => {};
}
}
return (
<button onClick={click} className={`card ${cardStyle}`}>
{cardText}
</button>
);
};
Related
I try to build sliders with different categories that each user has his point.
The informant comes from the json server
What I need I do not succeed in having the customer choose a user that is numbered and the dot will be colored in the slider How do I do that?
In addition he has the option to delete and return the point.
I was able to delete the points by deleting them in the object. But I could not return, is there a possibility to return?
Broker.jsx
import React, { useEffect, useState } from 'react';
import './style.css';
import Combo from '../components/Combo/Combo';
import Sliders from '../components/Sliders/Sliders';
const GetUsersDataFromManipulation = (users, field) => {
const state = users.reduce((store, user) => {
const userId = user.user
const currentManipulationUserData = user.profileManipulation[field]
if (currentManipulationUserData.length === 0) {
return store
}
store[userId] = currentManipulationUserData[0].bid
return store;
}, {})
return state;
};
function Broker({ manipulations }) {
const users = manipulations[2].users
const [hiddenUser, setHiddenUser] = useState(() => {
const visible = {};
for (let user of users) {
visible[user.user] = true;
}
return visible;
})
const GetUsersBid = (profileManipulation) => {
const data = GetUsersDataFromManipulation(users, `${profileManipulation}`); if (!Object.keys(data).length) {
return null
}
return data;
};
const gender = GetUsersBid('gender');
const age = GetUsersBid('age');
const marital = GetUsersBid('marital');
const children = GetUsersBid('children');
const education = GetUsersBid('education');
const interests = GetUsersBid('interests');
const dynamicInterests = GetUsersBid('dynamicInterests');
const showUser = (user_id) => {
const new_hidden = { ...hiddenUser }
new_hidden[user_id] = true;
setHiddenUser(new_hidden);
}
const hideUser = (user_id) => {
const new_hidden = { ...hiddenUser }
console.log(user_id)
new_hidden[user_id] = false;
setHiddenUser(new_hidden);
}
const [userInformation, setUserInformation] = useState([
{ name: 'gender', bids: gender },
{ name: 'age', bids: age },
{ name: 'marital', bids: marital },
{ name: 'children', bids: children },
{ name: 'education', bids: education },
{ name: 'interests', bids: interests },
{ name: 'dynamicInterests ', bids: dynamicInterests },
]);
useEffect(() => {
const curret_User_Info = [...userInformation]
for (let user of Object.keys(hiddenUser)) {
for (let i = 0; i < curret_User_Info.length; i++) {
if (curret_User_Info[i].bids !== null) {
if (hiddenUser[user] === false) {
delete curret_User_Info[i].bids[user]
}
else {
//What am I returning here? So that the bids will return?
}
}
}
}
setUserInformation(curret_User_Info)
}, [hiddenUser])
return (
<div>
<div className="button" >
{userInformation && <Combo users={users} showUser={showUser} hideUser={hideUser} userInformation={userInformation} />}
</div>
<div className='slid'>
{userInformation.map(sliderDetails => {
return (
<div className={sliderDetails.name} key={sliderDetails.name} >
{sliderDetails.bids && (<Sliders className="sliders" hiddenUserChange={hiddenUser} name={sliderDetails.name} userBids={sliderDetails.bids} setUserInformation={setUserInformation} userInformation={userInformation} />)}
</div>
)
})}
</div>
</div>
);
}
export default Broker;
ComboBox.jsx
import React, { useEffect, useRef, useState } from 'react';
import ComboBox from 'react-responsive-combo-box';
import { Button } from '#mui/material';
import 'react-responsive-combo-box/dist/index.css';
import "./style.css"
function Combo({ users, showUser, hideUser, userInformation }) {
const [selectedOption, setSelectedOption] = useState();
const [choosing, setChoosing] = useState();
useEffect(() => {
}, [users])
const onShow = () => {
showUser(users[selectedOption - 1].user)
}
const onHide = () => {
hideUser(users[selectedOption - 1].user)
}
const colorChange = (numOption) => {
const id = users[numOption - 1].user
}
return (
<div className="combo_box">
<ComboBox
onSelect={(option) => { setSelectedOption(option); colorChange(option) }}
options={[...Array.from({ length: users.length }, (_, i) => i + 1)]}
/>
<div className='button' >
<Button style={{ "marginRight": 20 }} variant="contained" onClick={onShow}>Show</Button>
<Button variant="contained" onClick={onHide}>Hide</Button>
</div>
</div>
);
}
export default Combo;
Sliders.jsx
import React, { useEffect, useState } from 'react'
import "./style.css"
import 'rc-slider/assets/index.css';
import Slider from 'rc-slider';
const Sliders = ({ hiddenUserChange, name, userBids, setUserInformation, userInformation }) => {
const [bids, setBids] = useState()
useEffect(() => {
setBids(Object.values(userBids))
}, [hiddenUserChange, userBids])
const updateFieldChanged = (newValue, e) => {//OnChanged Slider
setUserInformation(state => {
return state.map(manipulation => {
if (manipulation.name === name) {
Object.entries(manipulation.bids).forEach(([userId, bidValue], index) => {
manipulation.bids[userId] = newValue[index]
console.log(manipulation.bids[userId])
})
}
return manipulation
})
});
}
const handleChange = (event, newValue) => {
setBids(event)
};
return (
<>
<h1 className='headers'>{name}</h1>
{
<Slider
style={{ "marginRight": "20rem", "width": "30rem", "left": "20%" }}
range={true}
trackStyle={[{ backgroundColor: '#3f51b5' }]}
max={100}
RcSlider={true}
railStyle={{ backgroundColor: '#3f51b5' }}
activeDotStyle={{ left: 'unset' }}
ariaLabelForHandle={Object.keys(hiddenUserChange)}
tabIndex={(Object.keys(userBids))}
ariaLabelledByForHandle={bids}
value={(bids)}
onChange={handleChange}
onAfterChange={updateFieldChanged}
tipProps
tipFormatter
/>
}
</>
)
}
export default Sliders
enter image description here
Thank you all!
In the below slice code I am Getting the data from a server using createAsyncThunk. Now I am trying to delete data locally for which I have written a reducer called removeData.
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import axios from "axios";
export const dataTableSlice = createSlice({
name: "dataTable",
initialState: {
isGridData: [],
isLoading: false,
},
reducers: {
removeData: (state, action) => {
const dataSource = [...state.isGridData];
const filteredData = dataSource.filter(
(item) => item.id !== action.payload.id
);
state.isGridData.push(filteredData);
},
},
extraReducers: (builder) => {
builder
.addCase(loadData.pending, (state, action) => {
state.isLoading = true;
})
.addCase(loadData.fulfilled, (state, action) => {
state.isGridData = [...action.payload.data];
state.isLoading = false;
});
},
});
export const loadData = createAsyncThunk("loadData", async () => {
return await axios.get("https://jsonplaceholder.typicode.com/comments");
});
export const { removeData } = dataTableSlice.actions;
export default dataTableSlice.reducer;
Component
import { Table,Popconfirm,Button } from 'antd';
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {loadData,removeData} from '../../features/DataTableState/DataTableSlice';
import "antd/dist/antd.css";
import './DataTable.scss';
const DataTable = () => {
const gridData = useSelector((state) => state.dataTable.isGridData);
const isLoading = useSelector((state) => state.dataTable.isLoading);
const dispatch = useDispatch();
useEffect(() => {
dispatch(loadData());
},[dispatch]);
const inventoryData = gridData.map((item) => ({
...item,
inventory:Math.floor(Math.random() * 6) + 20,
}));
const modifiedData = inventoryData.map(({body,...item}) =>({
...item,
key:item.id,
message:body,
}));
// const handleDelete = (record) =>{
// const dataSource = [...modifiedData];
// const filteredData = dataSource.filter((item) => item.id !== record.id);
// }
const columns = [
{
title:'Id',
dataIndex:'id',
align:'center',
},
{
title:'product',
dataIndex:'name',
align:'center',
editTable:true
},
{
title:'description',
dataIndex:'message',
align:'center',
editTable:true
},
{
title:'inventory',
dataIndex:'inventory',
align:'center',
editTable:false
},
{
title:'Action',
dataIndex:'action',
align:'center',
render: (_,record) =>
modifiedData.length >= 1 ? (
<Popconfirm title="Are you sure?" onConfirm={dispatch(removeData(record))}>
<Button danger type='primary'>Delete</Button>
</Popconfirm>
):null,
},
];
// const data = [
// {
// Id:1,
// product:'gas1',
// description:'18kg',
// inventory:52,
// },
// {
// Id:2,
// product:'gas1',
// description:'18kg',
// inventory:52,
// },
// {
// Id:3,
// product:'gas1',
// description:'18kg',
// inventory:52,
// },
// {
// Id:4,
// product:'gas1',
// description:'18kg',
// inventory:52,
// }
// ]
return (
<div className='data-table'>
<section className='space'></section>
<Table
className='table'
columns={columns}
dataSource={modifiedData}
bordered
loading={isLoading}
style={{flex:2}}/>
</div>
);
}
export default DataTable
Below given are errors in console:
1.serializableStateInvariantMiddleware.ts:195 A non-serializable value was detected in an action, in the path: payload.config.adapter. Value:
Take a look at the logic that dispatched this action: {type: 'loadData/fulfilled', payload: {…}, meta: {…}}
2.Warning: Cannot update a component (DataTable) while rendering a different component (Cell). To locate the bad setState() call inside Cell, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
I'm converting my app from JS to TS. Everything has been working good under JS but when started conversion to TS I'm getting plenty of errors with handle functions like for example handleVideoAdd. Does anyone has idea what am I'm doing wrong? Tried many things without success...
Property 'handleVideoAdd' does not exist on type 'undefined'. TS2339 - and it's pointing out to this fragment of code:
const { handleVideoAdd, inputURL, handleInputURLChange } = useContext(Context)
My code looks like that:
Header.tsx
import { Context } from "../Context";
import React, { useContext } from "react";
import { Navbar, Button, Form, FormControl } from "react-bootstrap";
export default function Header() {
const { handleVideoAdd, inputURL, handleInputURLChange } =
useContext(Context);
return (
<Navbar bg="light" expand="lg">
<Navbar.Brand href="#home">Video App</Navbar.Brand>
<Form onSubmit={handleVideoAdd} inline>
<FormControl
type="text"
name="url"
placeholder="Paste url"
value={inputURL}
onChange={handleInputURLChange}
className="mr-sm-2"
/>
<Button type="submit" variant="outline-success">
Add
</Button>
</Form>
</Navbar>
);
}
Context.tsx
import { useEffect, useMemo, useState } from "react";
import { youtubeApi } from "./APIs/youtubeAPI";
import { vimeoApi } from "./APIs/vimeoAPI";
import React from "react";
import type { FormEvent } from "react";
const Context = React.createContext(undefined);
function ContextProvider({ children }) {
const [inputURL, setInputURL] = useState("");
const [videoData, setVideoData] = useState(() => {
const videoData = localStorage.getItem("videoData");
if (videoData) {
return JSON.parse(videoData);
}
return [];
});
const [filterType, setFilterType] = useState("");
const [videoSources, setVideoSources] = useState([""]);
const [wasSortedBy, setWasSortedBy] = useState(false);
const [showVideoModal, setShowVideoModal] = useState(false);
const [modalData, setModalData] = useState({});
const [showWrongUrlModal, setShowWrongUrlModal] = useState(false);
const createModalSrc = (videoItem) => {
if (checkVideoSource(videoItem.id) === "youtube") {
setModalData({
src: `http://www.youtube.com/embed/${videoItem.id}`,
name: videoItem.name,
});
} else {
setModalData({
src: `https://player.vimeo.com/video/${videoItem.id}`,
name: videoItem.name,
});
}
};
const handleVideoModalShow = (videoID) => {
createModalSrc(videoID);
setShowVideoModal(true);
};
const handleVideoModalClose = () => setShowVideoModal(false);
const handleWrongUrlModalShow = () => setShowWrongUrlModal(true);
const handleWrongUrlModalClose = () => setShowWrongUrlModal(false);
const handleInputURLChange = (e) => {
setInputURL(e.currentTarget.value);
};
const handleVideoAdd = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const source = checkVideoSource(inputURL);
if (source === "youtube") {
handleYouTubeVideo(inputURL);
} else if (source === "vimeo") {
handleVimeoVideo(inputURL);
} else {
handleWrongUrlModalShow();
}
};
const checkVideoSource = (inputURL) => {
if (inputURL.includes("youtu") || inputURL.length === 11) {
return "youtube";
} else if (inputURL.includes("vimeo") || inputURL.length === 9) {
return "vimeo";
}
};
const checkURL = (inputURL) => {
if (!inputURL.includes("http")) {
const properURL = `https://${inputURL}`;
return properURL;
} else {
return inputURL;
}
};
const checkInputType = (inputURL) => {
if (!inputURL.includes("http") && inputURL.length === 11) {
return "id";
} else if (!inputURL.includes("http") && inputURL.length === 9) {
return "id";
} else {
return "url";
}
};
const fetchYouTubeData = async (videoID) => {
const data = await youtubeApi(videoID);
if (data.items.length === 0) {
handleWrongUrlModalShow();
} else {
setVideoData((state) => [
...state,
{
id: videoID,
key: `${videoID}${Math.random()}`,
name: data.items[0].snippet.title,
thumbnail: data.items[0].snippet.thumbnails.medium.url, //default, medium, high
viewCount: data.items[0].statistics.viewCount,
likeCount: data.items[0].statistics.likeCount,
savedDate: new Date(),
favourite: false,
source: "YouTube",
url: inputURL,
},
]);
setInputURL("");
}
};
const handleYouTubeVideo = (inputURL) => {
const inputType = checkInputType(inputURL);
if (inputType === "id") {
fetchYouTubeData(inputURL);
} else {
const checkedURL = checkURL(inputURL);
const url = new URL(checkedURL);
if (inputURL.includes("youtube.com")) {
const params = url.searchParams;
const videoID = params.get("v");
fetchYouTubeData(videoID);
} else {
const videoID = url.pathname.split("/");
fetchYouTubeData(videoID[1]);
}
}
};
const fetchVimeoData = async (videoID) => {
const data = await vimeoApi(videoID);
if (data.hasOwnProperty("error")) {
handleWrongUrlModalShow();
} else {
setVideoData((state) => [
...state,
{
id: videoID,
key: `${videoID}${Math.random()}`,
name: data.name,
thumbnail: data.pictures.sizes[2].link, //0-8
savedDate: new Date(),
viewCount: data.stats.plays,
likeCount: data.metadata.connections.likes.total,
savedDate: new Date(),
favourite: false,
source: "Vimeo",
url: inputURL,
},
]);
setInputURL("");
}
};
const handleVimeoVideo = (inputURL) => {
const inputType = checkInputType(inputURL);
if (inputType === "id") {
fetchVimeoData(inputURL);
} else {
const checkedURL = checkURL(inputURL);
const url = new URL(checkedURL);
const videoID = url.pathname.split("/");
fetchVimeoData(videoID[1]);
}
};
const deleteVideo = (key) => {
let newArray = [...videoData].filter((video) => video.key !== key);
setWasSortedBy(true);
setVideoData(newArray);
};
const deleteAllData = () => {
setVideoData([]);
};
const toggleFavourite = (key) => {
let newArray = [...videoData];
newArray.map((item) => {
if (item.key === key) {
item.favourite = !item.favourite;
}
});
setVideoData(newArray);
};
const handleFilterChange = (type) => {
setFilterType(type);
};
const sourceFiltering = useMemo(() => {
return filterType
? videoData.filter((item) => item.source === filterType)
: videoData;
}, [videoData, filterType]);
const sortDataBy = (sortBy) => {
if (wasSortedBy) {
const reversedArr = [...videoData].reverse();
setVideoData(reversedArr);
} else {
const sortedArr = [...videoData].sort((a, b) => b[sortBy] - a[sortBy]);
setWasSortedBy(true);
setVideoData(sortedArr);
}
};
const exportToJsonFile = () => {
let dataStr = JSON.stringify(videoData);
let dataUri =
"data:application/json;charset=utf-8," + encodeURIComponent(dataStr);
let exportFileDefaultName = "videoData.json";
let linkElement = document.createElement("a");
linkElement.setAttribute("href", dataUri);
linkElement.setAttribute("download", exportFileDefaultName);
linkElement.click();
};
const handleJsonImport = (e) => {
e.preventDefault();
const fileReader = new FileReader();
fileReader.readAsText(e.target.files[0], "UTF-8");
fileReader.onload = (e) => {
const convertedData = JSON.parse(e.target.result);
setVideoData([...convertedData]);
};
};
useEffect(() => {
localStorage.setItem("videoData", JSON.stringify(videoData));
}, [videoData]);
return (
<Context.Provider
value={{
inputURL,
videoData: sourceFiltering,
handleInputURLChange,
handleVideoAdd,
deleteVideo,
toggleFavourite,
handleFilterChange,
videoSources,
sortDataBy,
deleteAllData,
exportToJsonFile,
handleJsonImport,
handleVideoModalClose,
handleVideoModalShow,
showVideoModal,
modalData,
showWrongUrlModal,
handleWrongUrlModalShow,
handleWrongUrlModalClose,
}}
>
{children}
</Context.Provider>
);
}
export { ContextProvider, Context };
App.js (not converted to TS yet)
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import "bootstrap/dist/css/bootstrap.min.css";
import reportWebVitals from "./reportWebVitals";
import { ContextProvider } from "./Context.tsx";
ReactDOM.render(
<React.StrictMode>
<ContextProvider>
<App />
</ContextProvider>
</React.StrictMode>,
document.getElementById("root")
);
reportWebVitals();
This is because when you created your context you defaulted it to undefined.
This happens here: const Context = React.createContext(undefined)
You can't say undefined.handleVideoAdd. But you could theoretically say {}.handleVideoAdd.
So if you default your context to {} at the start like this: const Context = React.createContext({})
Your app shouldn't crash up front anymore.
EDIT: I see you're using TypeScript, in that case you're going to need to create an interface for your context. Something like this:
interface MyContext {
inputURL?: string,
videoData?: any,
handleInputURLChange?: () => void,
handleVideoAdd?: () => void,
deleteVideo?: () => void,
// and all the rest of your keys
}
Then when creating your context do this:
const Context = React.createContext<MyContext>(undefined);
I'm building a e-commerce store where I have the following data structure:
productsData.js
export const storeProducts = [
{
id: 1,
title: "Santos",
img: "img/pfs - black.png",
price: 59.70,
description: "Lorem Ipsum",
gender: ["Feminine", "Masculine"],
info:
"Lorem Ipsum",
inCart: false,
count: 0,
total: 0
}
];
export const detailProducts = {
id: 1,
title: "Pecadores Feitos Santos",
img: "img/pfs - black.png",
price: 59.70,
description: "Lorem ipsum",
gender: ["Feminine", "Masculine"],
info:
"Lorem ipsum.",
inCart: false,
count: 0,
total: 0
};
I have a Context React file that is my provider and consumer:
context.js
import React, { Component } from 'react'
import { storeProducts, detailProducts } from './productsData';
const ProductContext = React.createContext();
// Provider and Consumer
class ProductProvider extends Component {
state = {
products: [],
detailProducts: detailProducts,
cart: [],
modalOpen: false,
modalProduct: detailProducts,
cartTotal: 0
};
componentDidMount() {
this.setProducts();
}
setProducts = () => {
let tempProducts = [];
storeProducts.forEach(item => {
const singleItem = {...item};
tempProducts = [...tempProducts, singleItem];
});
this.setState(() => {
return { products: tempProducts };
// console.log("State products: ", storeProducts[0].color[0])
});
};
getItem = id => {
const product = this.state.products.find(item => item.id === id);
return product;
};
handleDetail = id => {
const product = this.getItem(id);
this.setState(() => {
return { detailProducts: product };
});
};
addToCart = id => {
let tempProducts = [...this.state.products];
const index = tempProducts.indexOf(this.getItem(id));
const product = tempProducts[index];
// console.log(product.gender);
product.inCart = true;
product.count = 1;
const price = product.price;
product.total = price;
this.setState(() => {
return { products: tempProducts, cart: [...this.state.cart,product] }
}, () => { this.addTotal();});
}
openModal = id => {
const product = this.getItem(id);
this.setState(() => {
return {modalProduct: product, modalOpen: true}
})
}
closeModal = () => {
this.setState(() => {
return {modalOpen: false}
})
}
increment = (id) => {
let tempCart = [...this.state.cart];
const selectedProduct = tempCart.find(item => item.id === id);
const index = tempCart.indexOf(selectedProduct);
const product = tempCart[index];
product.count = product.count + 1;
product.total = product.count * product.price;
this.setState(() => {
return {
cart: [...tempCart]
}
}, () => { this.addTotal() })
}
decrement = (id) => {
let tempCart = [...this.state.cart];
const selectedProduct = tempCart.find(item => item.id === id);
const index = tempCart.indexOf(selectedProduct);
const product = tempCart[index];
product.count = product.count - 1;
if(product.count === 0) {
this.removeItem(id);
}
else {
product.total = product.count * product.price;
this.setState(() => {
return {
cart: [...tempCart]
}
}, () => { this.addTotal() })
}
}
removeItem = (id) => {
let tempProducts = [...this.state.products];
let tempCart = [...this.state.cart];
tempCart = tempCart.filter(item => item.id !== id);
const index = tempProducts.indexOf(this.getItem(id));
let removedProduct = tempProducts[index];
removedProduct.inCart = false;
removedProduct.count = 0;
removedProduct.total = 0;
this.setState(() => {
return {
cart:[...tempCart],
products: [...tempProducts]
}
}, ()=> {
this.addTotal();
})
}
clearCart = () => {
this.setState(() => {
return { cart: [] };
}, () => {
this.setProducts();
this.addTotal();
})
}
addTotal = () => {
let total = 0;
this.state.cart.map(item => (total += item.total));
this.setState(() => {
return {
cartTotal: total
}
})
}
render() {
return (
<ProductContext.Provider value={{
...this.state,
handleDetail: this.handleDetail,
addToCart: this.addToCart,
openModal: this.openModal,
closeModal: this.closeModal,
increment: this.increment,
decrement: this.decrement,
removeItem: this.removeItem,
clearCart: this.clearCart
}}
>
{this.props.children}
</ProductContext.Provider>
)
}
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer };
Everything works just fine, but here's where the problems start:
Details.js
import React, { Component } from 'react';
import { ProductConsumer } from '../context';
import { Link } from 'react-router-dom';
import { ButtonContainerSecondary } from './ButtonSecondary';
import { ButtonDetails } from './ButtonDetails';
import { ColorButton } from './ColorButton';
import PropTypes from 'prop-types';
export default class Details extends Component {
render() {
return (
<ProductConsumer>
{value => {
const {id, img, price, description, color, gender, info, title, inCart} = value.detailProducts;
return (
<div className="container pb-5">
<div className="row">
<div className="col-10 mx-auto text-center text-slanted my-5">
{gender.map((item, key) => (
<span>{" "}<ButtonDetails key={key} onClick={() => { this.setState({gender: key}); console.log(key)}}
</div>
</div>
</div>
)
}}
</ProductConsumer>
)
}
}
Details.propTypes = {
product: PropTypes.shape({
color: PropTypes.arrayOf(PropTypes.string),
gender: PropTypes.arrayOf(PropTypes.string)
})
}
I can't seem to figure out how to pass in the state in the nested array gender to the next level. I do get the console.log(key) right, but it gets lost as I move up to the cart. I would like to give the user the chance to choose from a feminine or masculine shirt:
CartItem.js
import React from 'react'
export default function CartItem({ item, value }) {
const {id, title, img, price, gender, total, count} = item;
const {increment, decrement, removeItem} = value;
return (
<div className="col-10 mx-auto col-lg-2">
<span className="d-lg-none">Product: </span> {title} {gender} {console.log(gender)}
</div>
);
}
Here the console.log(gender) returns both items of the array altogether ("FeminineMasculine").
I'm a newbie and would really appreciate your help. Sorry if anything!
I'm trying to update the state of a parent component from a child using a callback. The state and call back are passed to a text input. The callback is being called, the state of the parent is changed, but it doesn't rerender. The value of the input field stays the same. If force rendering is used, the text field updates every time a new character is added (As desired). I'm not sure what could be causing this, from my understanding the setState hooks provided are supposed to rerender unless the state is unchanged.
EDIT: (Added the parent component not just the callback)
Below is the parent component
import Card from './Card'
import Instructions from './instructions'
import Title from './title'
import React, { useRef, useState, useCallback, useEffect } from 'react'
import { DropTarget } from 'react-dnd'
import ItemTypes from './ItemTypes'
import update from 'immutability-helper'
const Container = ({ connectDropTarget }) => {
const ref = useRef(null)
const titleRef = useRef()
const instructionsRef = useRef()
const appRef = useRef()
useEffect(() => {
// add when mounted
document.addEventListener("mousedown", handleClick);
// return function to be called when unmounted
return () => { document.removeEventListener("mousedown", handleClick);};
}, []);
const handleClick = e => {
if (titleRef.current.contains(e.target)) {
setFocus("Title");
return;
} // outside click
else if(instructionsRef.current.contains(e.target)){
setFocus("Instructions");
return;
}
setFocus(null);
};
const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);
const [focus,setFocus] = useState(null);
const [title, setTitle] = useState({id: "Title", text: "Default",type: "Title", data:[]});
const [instructions, setInstructions] = useState({id: "Instructions",type:"Instructions", text: "Instructions", data:[]});
const [cards, setCards] = useState([
{
id: 1,
text: 'Write a cool JS library',
},
{
id: 2,
text: 'Make it generic enough',
},
{
id: 3,
text: 'Write README',
},
{
id: 4,
text: 'Create some examples',
},
{
id: 5,
text: 'Spam in Twitter and IRC to promote it',
},
{
id: 6,
text: '???',
},
{
id: 7,
text: 'PROFIT',
},
])
const moveCard = useCallback(
(id, atIndex) => {
const { card, index } = findCard(id)
setCards(
update(cards, {
$splice: [[index, 1], [atIndex, 0, card]],
}),
)
},
[cards],
)
const findCard = useCallback(
id => {
const card = cards.filter(c => `${c.id}` === id)[0]
return {
card,
index: cards.indexOf(card),
}
},
[cards],
)
const updateItem = useCallback(
(id,field,additionalData,value) => {
return;
},
[cards], //WHat does this do?
)
const updateTitle = text => {
console.log("Updating title")
let tempTitle = title;
tempTitle['text'] = text;
//console.log(text);
//console.log(title);
//console.log(tempTitle);
setTitle(tempTitle);
//console.log(title);
//console.log("done");
forceUpdate(null);
}
connectDropTarget(ref)
return (
<div ref={appRef}>
<div ref={titleRef} >
<Title item={title} focus={focus} updateFunction={updateTitle}/>
</div>
<div ref={instructionsRef} >
<Instructions item={instructions} focus={focus}/>
</div>
<div className="Card-list" ref={ref}>
{cards.map(card => (
<Card
key={card.id}
id={`${card.id}`}
text={card.text}
moveCard={moveCard}
findCard={findCard}
item={card}
focus={focus}
/>
))}
</div>
</div>
)
}
export default DropTarget(ItemTypes.CARD, {}, connect => ({
connectDropTarget: connect.dropTarget(),
}))(Container)
The code of the component calling this function is:
import React from 'react'
function Title(props) {
if(props.focus === "Title")
return(
<input
id="Title"
class="Title"
type="text"
value={props.item['text']}
onChange = { e => props.updateFunction(e.target.value)}
/>
);
else
return (
<h1> {props.item['text']} </h1>
);
}
export default Title
The problem is here
const updateTitle = text => {
let tempTitle = title; // These two variables are the same object
tempTitle['text'] = text;
setTitle(tempTitle); // problem is here
}
React uses the object.is() method to compare two values before and after. Look at this
Object.is(title, tempTitle) // true
You should make "title" and "tempTitle" different objects, like this
const updateTitle = text => {
let tempTitle = {...title}; // tempTitle is a new object
tempTitle['text'] = text;
setTitle(tempTitle);
}
And this is a demo of mutable object.
var a= {name:1}
var b = a;
b.name=2
var result = Object.is(a,b)
console.log(result)
// true