Updating array context state in component updates strangely - reactjs

I have a brand filter toggle that when it is triggered it adds or deletes a element to a brand's array and then in my context when i detect that the brand's array changed i make a second api call to add the brand filters to my api call.
And the moment when i update my state for the first time it works perfectly, the array updates and adds a new brand to the stack, but when i add a second one while looking at the components tab in developer's mode the state doesn't update but when i close developers tool's and open them again it appears as the delete or aggregation to the state worked, but the useEffect isn't triggered.
Empty state at first:
Working state when i click on the checkbox for the first time:
Not working at first when i update for the second time:
State updates when i close and open developers tools, but the useEffect isn't triggered adding a new parameter to the url:
If i check and then uncheck one box it updates correctly.
I don't really know why is this happening, if it's the context's state not updating properly or my component state not updating properly, it might be the component state not working but it is really weird that it updates after closing and opening and when i console log it it appears as updated.
Here is my sidebar component:
import "../App.css";
import React, { useContext } from "react";
import { ProductContext } from "../context/productContext";
const Sidebar = () => {
const { products, setProducts, brandFilter, setBrandFilter } =
useContext(ProductContext);
var brandsArray = [];
brandsArray.push(products[0].company);
for (var i = 1; i < products.length; i++) {
var isEqual = false;
for (var x = 0; x < brandsArray.length; x++) {
if (products[i].company == brandsArray[x]) {
isEqual = true;
}
}
if (isEqual == false) {
brandsArray.push(products[i].company);
}
}
const handleClick = (e) => {
var arrayFilters = [];
var isEqual = -1;
if (brandFilter.length > 0) {
arrayFilters = brandFilter;
}
for (var i = 0; i < arrayFilters.length; i++) {
if (e.target.value == arrayFilters[i]) {
isEqual = i;
}
}
if (isEqual == -1) {
arrayFilters.push(e.target.value);
} else {
arrayFilters.splice(isEqual, 1);
}
setBrandFilter(arrayFilters);
};
return (
<div className="sidebar p-2">
<div className="heading d-flex justify-content-between align-items-center">
<h6 className="text-uppercase">Brands</h6>
</div>
{brandsArray.map((brand) => (
<div className="d-flex justify-content-between mt-2">
<div className="form-check">
<input
className="form-check-input"
type="checkbox"
value={brand}
id="flexCheckDefault"
onClick={handleClick}
></input>
<label className="form-check-label"> {brand} </label>
</div>
</div>
))}
</div>
);
};
export default Sidebar;
And here is the context:
import React, { useState, createContext, useEffect } from "react";
export const ProductContext = createContext();
export const ProductProvider = (props) => {
var url = "/api/v1/products?";
const [productList, setProductList] = useState([]);
const [products, setProducts] = useState([]);
const [brandFilter, setBrandFilter] = useState([]);
const getProductList = async () => {
const response = await fetch(url);
const responseJson = await response.json();
if (responseJson) {
setProductList(responseJson.products);
}
};
const getProducts = async () => {
const response = await fetch("/api/v1/products?all=true");
const responseJson = await response.json();
if (responseJson) {
setProducts(responseJson.products);
}
};
useEffect(() => {
getProductList();
getProducts();
}, []);
useEffect(() => {
console.log("Something changed");
changeUrl();
getProductList();
}, [brandFilter]);
const changeUrl = () => {
if (brandFilter.length > 0) {
for (var i = 0; i < brandFilter.length; i++) {
url += `company=${brandFilter[i]}&`;
}
}
};
return (
<ProductContext.Provider
value={{
productList,
setProductList,
products,
setProducts,
brandFilter,
setBrandFilter,
}}
>
{props.children}
</ProductContext.Provider>
);
};
export default ProductProvider;

Related

component doesn't render after mergin two APIS

After I request two endpoints and store it in a new state variable I'm not being able to render the component after the state changes. When i assign the state variable to the dependency array of useEffect it renders infinitely.
I tried a few things but the only way that i've being able to do to render the component after it loads has been just adding the merge state to the dependency array.
import { ChangeEvent, FC, useEffect, useState } from "react";
import spacex from "../api/spacex";
import CardGrid from "../components/CardGrid";
import Header from "../components/Header";
import Pagination from "../components/Pagination";
import SkeletonGrid from "../components/SkeletonGrid";
type Launch = {
mission_name: string;
};
const LaunchesMain: FC = () => {
const [launches, setLaunches] = useState<any>([]);
const [rockets, setRockets] = useState<any>([]);
const [merged, setMerged] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage, setPostsPerPage] = useState(9);
const [searchTerm, setSearchTerm] = useState("");
const [filteredResult, setFilteredResult] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchRockets = async () => {
const responseRocket = await spacex.get("/rockets");
const responseLaunches = await spacex.get("/launches");
setRockets(responseRocket.data);
setLaunches(responseLaunches.data);
};
fetchRockets().then(() => {
const mergedApis = () => {
const launchesCopy: any = [...launches];
for (let i = 0; i < launches.length; i++) {
for (let j = 0; j < rockets.length; j++) {
if (launches[i].rocket.rocket_name === rockets[j].rocket_name) {
launchesCopy[i].rocket = rockets[j];
}
}
}
setMerged(launchesCopy);
setIsLoading(false);
};
mergedApis();
});
}, []);
console.log(merged);
const handleSearchChange = (event: ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value);
if (searchTerm.length === 0) {
setFilteredResult(merged);
} else if (searchTerm.length > 0) {
const filteredData = merged.filter((launch: Launch) => {
return `${launch.mission_name}`
.toLowerCase()
.includes(searchTerm.toLowerCase());
});
setFilteredResult(filteredData);
}
};
const lastPostIndex = currentPage * postsPerPage;
const firstPostIndex = lastPostIndex - postsPerPage;
const currentPosts = merged.slice(firstPostIndex, lastPostIndex);
return (
<>
<Header />
<div className="text-white">
<div>
<input
style={{
background:
"linear-gradient(0deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05)), #121212",
}}
onChange={(event) => handleSearchChange(event)}
placeholder="Search all launches..."
value={searchTerm}
className="md:w-[26rem] w-[16rem] h-[3rem] rounded-lg mt-10 mx-5 md:mx-24 rounded-3"
/>
</div>
<div className="mx-5 md:ml-24 mt-5 opacity-40">
Total({currentPosts.length})
</div>
{isLoading ? (
<SkeletonGrid cards={postsPerPage} />
) : (
<CardGrid
postsData={currentPosts}
filteredResult={filteredResult}
searchTerm={searchTerm}
/>
)}
<Pagination
totalPosts={merged.length}
postsPerPage={postsPerPage}
setCurrentPage={setCurrentPage}
currentPage={currentPage}
/>
</div>
</>
);
};
export default LaunchesMain;
This is the code of the component. How can i solve this issue?
Since you need rockets and launches as a dependency of the useEffect, whenever they change, the useEffect is called, which calls the api, which changes, etc... However, you don't use rockets and launches states beyond merging them, and then you use the merged state.
So you don't have to store rockets and launches in the state. Use Promise.all() to get both data arrays in to .then() block, merge them, and store only the merged state:
useEffect(() => {
const fetchRockets = () => Promise.all(
spacex.get("/rockets"),
spacex.get("/launches")
])
fetchRockets()
.then(([responseRocket, responseLaunches]) => {
const rockets = responseRocket.data;
const launches = responseLaunches.data;
for (let i = 0; i < launches.length; i++) {
for (let j = 0; j < rockets.length; j++) {
if (launches[i].rocket.rocket_name === rockets[j].rocket_name) {
launches[i].rocket = rockets[j];
}
}
}
setMerged(launches);
setIsLoading(false);
});
}, []);

Import function from another page

I need to call function resetToken() from another page when i click on button.
resetToken() should change useState to generate new code. I don't know how to import this function to another page and use it.
I have import
import Captcha from '../../../components/Captcha/Captcha'; and displayed with <Captcha/> in return( ... )
So when i click on button I need to call function resetToken() to generate new code or call again import because I have in <Captcha/>
React.useEffect(() => {
resetToken();
},[]);
This code is Captcha.jsx
import React from 'react';
import './Captcha.css';
function Captcha({statusOfCaptcha}){
const [status, setStatus] = React.useState(undefined);
const [code, setCode] = React.useState(undefined);
const [text, setText] = React.useState("");
const [seconds, setSeconds] = React.useState(120);
function resetToken(){
//generate code
var codeGenerated = "";
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz123456789";
for (var i = 0; i < 6; i++){
codeGenerated += possible.charAt(Math.floor(Math.random() * possible.length));
}
setCode(codeGenerated);
//reset every 120 second
setInterval(function(){
var codeGenerated = "";
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz123456789";
for (var i = 0; i < 6; i++){
codeGenerated += possible.charAt(Math.floor(Math.random() * possible.length));
}
setCode(codeGenerated);
setSeconds(120);
setStatus(undefined);
setText("");
}, 120000);
const interval = setInterval(() => {
setSeconds(seconds => seconds - 1);
}, 1000);
return () => clearInterval(interval);
}
React.useEffect(() => {
resetToken();
},[]);
function checkCaptcha(e){
if(e === code){
setStatus(true);
statusOfCaptcha(true);
} else{
setStatus(false);
statusOfCaptcha(false);
}
}
return (
<div className='captcha'>
<div className="background">
<p onCopy={(e) => e.preventDefault()} className="unselectable">{code}</p>
<a>{seconds}</a>
</div>
<div className='input-captcha'>
<input type="text" placeholder="Zadejte kód" value={text} onChange={(e) => {checkCaptcha(e.target.value); setText(e.target.value)}}/>
{status === false && (<i class='bx bx-x text-color-red'></i>)}
{status === true && (<i class='bx bx-check text-color-green'></i>)}
</div>
</div>
)
}
export default Captcha;
This code is index.jsx
import React from 'react'
import Captcha from '../../../components/Captcha/Captcha';
function Index() {
function change(){
//here i need to call function from Captcha.jsx - resetToken();
}
return (
<div>
<Captcha statusOfCaptcha={resCaptchaData}/>
<button onclick={change}>Reset captcha code</button>
</div>
)
}
export default Index
It would be better to use a custom hook, to store your state, and resetToken function, So you can use it in multiple places.
For more resources about custom hooks.
https://reactjs.org/docs/hooks-custom.html
You can do this in several ways
for example you can use state manager like context api or redux.
In order to have access to your states or functions everywhere and in all pages and components
Or you can put the resetToken function in the parent component and have access to it in the child components.
export const ParentComponent = (children) => {
function resetToken {
....
}
return (
<Recapcha resetToken={resetToken} />
)
}
const Recapcha = ({resetToken}) => {
return (...)
}

React listen to child's state from parent

Damn, two days, two noob questions, sorry guys.
Yesterday, I spent the whole afternoon reading the docs but my fart-ey brain cannot process how to use react hooks to pass data from a child to a parent.
I want to create a button on my parent that can listen to his child's state to check on it and change the background color depending on its value.
Thing is, the child component is mapping some stuff so I cannot create a button (otherwhise it would be rendered multiple times and not only once like I want).
I've thought about moving all the data to my parent component but I cannot understand how since I'm fairly new to React and it's been only two months of learning how to code for me basically.
I will now provide the code for the parent and the child component.
The parent :
import React from "react";
import Quizz from "./components/Quizz";
export default function App() {
const [quizz, setQuizz] = React.useState([]);
React.useEffect(() => {
async function getData() {
const res = await fetch(
"https://opentdb.com/api.php?amount=5&category=27&type=multiple"
);
const data = await res.json();
setQuizz(data.results)
}
getData();
}, []);
function checkOnChild(){ /* <== the function I'd like to use to check on my Quizz component's "activeAnswer" state */
console.log(quizz);
}
const cards = quizz.map((item, key) => {
return <Quizz {...item} key={key}/>;
});
return (
<div>
{cards}
<button onClick={checkOnChild}>Check answers</button> /* <== the button that will use the function */
</div>
);
}
and the child :
import React from "react";
import { useRef } from "react";
export default function Quizz(props) {
const [activeAnswer, setActiveAnswer] = React.useState('');/* <== the state I'd like to check on from my parent component */
function toggle(answer) {
setActiveAnswer(answer);
}
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
let temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
let answers = props.incorrect_answers;
const ref = useRef(false);
if (!ref.current) {
answers.push(props.correct_answer);
shuffleArray(answers);
ref.current = true;
}
const answerDiv = answers.map((answer, key) => (
<div key={key} className="individuals" onClick={()=> toggle(answer)}
style={{background: answer == activeAnswer ? "#D6DBF5" : "transparent" }}>
{answer}
</div>
));
console.log(answers);
console.log(activeAnswer);
console.log(props.correct_answer);
return (
<div className="questions">
<div>
<h2>{props.question}</h2>
</div>
<div className="individuals__container">{answerDiv}</div>
<hr />
</div>
);
}
I'm really sorry If it seems dumb or if I'm making forbidden things lmao, but thanks in advance for your help guys!
This should get you a bit further I think.
export default function App() {
const [quizData, setQuizData] = useState([])
const [quizState, setQuizState] = useState({})
useEffect(() => {
async function getData() {
const res = await fetch('https://opentdb.com/api.php?amount=5&category=27&type=multiple')
const data = await res.json()
const results = data.results
setQuizData(results)
setQuizState(results.reduce((acc, curr) => ({ ...acc, [curr.question]: '' }), {}))
}
getData()
}, [])
function checkOnChild() {
console.log(quizState)
}
const cards = quizData.map((item) => {
return <Quizz {...item} key={item.question} quizState={quizState} setQuizState={setQuizState} />
})
return (
<div>
{cards}
<button onClick={checkOnChild}>Check answers</button>
</div>
)
}
export default function Quizz(props) {
function handleOnClick(answer) {
props.setQuizState(prevState => ({
...prevState,
[props.question]: answer,
}))
}
const answers = useMemo(() => {
const arr = [...props.incorrect_answers, props.correct_answer]
return shuffleArray(arr)
}, [props.incorrect_answers, props.correct_answer])
const answerDiv = answers.map((answer) => (
<div
className="individuals"
key={answer}
onClick={() => handleOnClick(answer)}
style={{ background: answer == props.quizState[props.question] ? '#D6DBF5' : 'transparent' }}
>
{answer}
</div>
))
return (
<div className="questions">
<div>
<h2>{props.question}</h2>
</div>
<div className="individuals__container">{answerDiv}</div>
<hr />
</div>
)
}

How to conditionally render a list item based on if id matches a websocket object's id

My goal is to use the button on the page to open a websocket connection, subscribe to the ticker feed, then update each list item's price based on the ID of the list item. Currently I have a list that maps through the initial API call's response and saves each object's ID to an array, which in turn is used to build a <li> for each ID. This creates 96 list items. I have also gotten the price to update live via a <p> element in each <li>.
I am having trouble targeting the price for just the matching row ID to the incoming data object's ID so that only that matching row is re-rendered when it gets a match. Below is my code:
ProductRow.js
import React from 'react';
export default function ProductRow(props) {
return <li key={props.id}><p>{ props.id }</p><p>{props.price}</p></li>;
}
WatchList.js
import React, { useState, useEffect, useRef } from "react";
import { Button } from 'react-bootstrap';
import ProductRow from "./ProductRow";
export default function WatchList() {
const [currencies, setcurrencies] = useState([]);
const product_ids = currencies.map((cur) => cur.id);
const [price, setprice] = useState("0.00");
const [isToggle, setToggle] = useState();
const ws = useRef(null);
let first = useRef(false);
const url = "https://api.pro.coinbase.com";
useEffect(() => {
ws.current = new WebSocket("wss://ws-feed.pro.coinbase.com");
let pairs = [];
const apiCall = async () => {
await fetch(url + "/products")
.then((res) => res.json())
.then((data) => (pairs = data));
let filtered = pairs.filter((pair) => {
if (pair.quote_currency === "USD") {
return pair;
}
});
filtered = filtered.sort((a, b) => {
if (a.base_currency < b.base_currency) {
return -1;
}
if (a.base_currency > b.base_currency) {
return 1;
}
return 0;
});
setcurrencies(filtered);
first.current = true;
};
apiCall();
}, []);
useEffect(() => {
ws.current.onmessage = (e) => {
if (!first.current) {
return;
}
let data = JSON.parse(e.data);
if (data.type !== "ticker") {
return;
}
setprice(data.price);
console.log(data.product_id, price);
};
}, [price]);
const handleToggleClick = (e) => {
if (!isToggle) {
let msg = {
type: "subscribe",
product_ids: product_ids,
channels: ["ticker"]
};
let jsonMsg = JSON.stringify(msg);
ws.current.send(jsonMsg);
setToggle(true);
console.log('Toggled On');
}
else {
let unsubMsg = {
type: "unsubscribe",
product_ids: product_ids,
channels: ["ticker"]
};
let unsub = JSON.stringify(unsubMsg);
ws.current.send(unsub);
setToggle(false);
console.log('Toggled Off');
}
};
return (
<div className="container">
<Button onClick={handleToggleClick}><p className="mb-0">Toggle</p></Button>
<ul>
{currencies.map((cur) => {
return <ProductRow id={cur.id} price={price}></ProductRow>
})}
</ul>
</div>
);
}
App.js
import React from "react";
import WatchList from "./components/Watchlist";
import "./scss/App.scss";
export default class App extends React.Component {
render() {
return (
<WatchList></WatchList>
)
}
}
Initialize the price state to be an empty object i.e. {}. We'll refer the price values by the the product_id on getting the response from websocket
// Initialize to empty object
const [price, setprice] = useState({});
...
// Refer and update/add the price by the product_id
useEffect(() => {
ws.current.onmessage = (e) => {
if (!first.current) {
return;
}
let data = JSON.parse(e.data);
if (data.type !== "ticker") {
return;
}
// setprice(data.price)
setprice(prev => ({ ...prev, [data.product_id]: data.price}));
console.log(data.product_id, price);
};
}, [price]);
Render your ProductRows as
<ul>
{currencies.map((cur) => {
return <ProductRow key={cur.id} id={cur.id} price={price[cur.id]}></ProductRow>
})}
</ul>
You don't have to manage anykind of sorting or searching for the relevant prices for products.

State value not showing

I'm new to React.js ,I'm creating a new application using react hooks, I get data from API and set it to state using setProductTypes() and then when i reading the that sate value productTypes.length it not showing the values (observed it from debugger), I display data based on that and it shows the data .
import React, { useState, useEffect } from "react";
import ProductTypeDataService from "../../services/ProductTypeService";
import Card from 'react-bootstrap/Card'
import { CardGroup } from "react-bootstrap";
const ProductTypeList = () => {
const [productTypes, setProductTypes] = useState([]);
const [nestedArray, setNestedArray]= useState([]);
useEffect(() => {
debugger ;
retrieveTutorials();
},[]);
const retrieveTutorials = () => {
ProductTypeDataService.getAll()
.then(response => {
setProductTypes(response.data);
createNestedArray();;
})
.catch(e => {
console.log(e);
});
};
const createNestedArray= ()=>{
var arr = new Array([]);
var arrayLength = productTypes.length;
for (var i = 0; i < arrayLength; i+4) {
for (var k = 0; k < 4; k++) {
arr[i,k] =productTypes[i+k];
}
}
setNestedArray(arr);
console.log(nestedArray);
};
return (
<div className="col-md-6">
<h4>Product Type List</h4>
<CardGroup>
{productTypes &&
productTypes.map((productType, index) => (
<Card>
<Card.Body>
<Card.Img variant="top" src={productType.imageUrl} />
</Card.Body>
<Card.Footer>
<small className="text-muted">{productType.name}</small>
</Card.Footer>
</Card>
))}
</CardGroup>
</div>
)
}
export default ProductTypeList
State is updated asynchronously.
You either have to pass productTypes received from API call response as parameter to createNestedArray(response.data) or move createdNestedArray inside a useEffect.
useEffect(() => {
// function will be called when productTypes changes
createNestedArray()
}, [productTypes])
That won't do because the productTypes are not yet available. If you need to call the createNestedArray function there, you should move the createNestedArray call in setTimeout, if not you should call the createNestedArray function out of retrieveTutorials function.
Լook at this example:
useEffect(() => {
if (productTypes !== undefined || productTypes.length != 0) {
createNestedArray();
}
}, [productTypes])

Resources