State value not showing - reactjs

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])

Related

how to use useState like this? what does it mean?

Can anyone please explain me what const rerender = React.useState(0)[1] is this?
import React from 'react'
import axios from 'axios'
import {
useQuery,
useQueryClient,
QueryClient,
QueryClientProvider,
} from "#tanstack/react-query"
import { ReactQueryDevtools } from "#tanstack/react-query-devtools"
const getCharacters = async () => {
await new Promise((r) => setTimeout(r, 500))
const { data } = await axios.get('https://rickandmortyapi.com/api/character/')
return data
}
const getCharacter = async (selectedChar) => {
await new Promise((r) => setTimeout(r, 500))
const { data } = await axios.get(
`https://rickandmortyapi.com/api/character/${selectedChar}`,
)
return data
}
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
function Example() {
const queryClient = useQueryClient()
**const rerender = React.useState(0)[1]**
const [selectedChar, setSelectedChar] = React.useState(1)
const charactersQuery = useQuery(['characters'], getCharacters)
const characterQuery = useQuery(['character', selectedChar], () =>
getCharacter(selectedChar),
)
return (
<div className="App">
<p>
Hovering over a character will prefetch it, and when it's been
prefetched it will turn <strong>bold</strong>. Clicking on a prefetched
character will show their stats below immediately.
</p>
<h2>Characters</h2>
{charactersQuery.isLoading ? (
'Loading...'
) : (
<>
<ul>
{charactersQuery.data?.results.map((char) => (
<li
key={char.id}
onClick={() => {
setSelectedChar(char.id)
}}
onMouseEnter={async () => {
await queryClient.prefetchQuery(
['character', char.id],
() => getCharacter(char.id),
{
staleTime: 10 * 1000, // only prefetch if older than 10 seconds
},
)
setTimeout(() => {
**rerender({})**
}, 1)
}}
>
<div
style={
queryClient.getQueryData(['character', char.id])
? {
fontWeight: 'bold',
}
: {}
}
>
{char.id} - {char.name}
</div>
</li>
))}
</ul>
<h3>Selected Character</h3>
{characterQuery.isLoading ? (
'Loading...'
) : (
<>
<pre>{JSON.stringify(characterQuery.data, null, 2)}</pre>
</>
)}
<ReactQueryDevtools initialIsOpen />
</>
)}
</div>
)
}
I want to know what it means, i am unable to understand this useState syntax I never seen this type of syntax. Can anyone share something about this?
useState has two parts the value and a function to update the value.
Take a look at the below snippet which explains how the useState hook is assigning values normally.
var fruitStateVariable = useState('banana'); // Returns a pair
var fruit = fruitStateVariable[0]; // The value of the state
var setFruit = fruitStateVariable[1]; // A asynchronous function to update the state.
By accessing the item with index 1 you are assigning rerender a function to update the state, which will trigger a rerender, as React sees the value going from 0 -> {}.
As to why the code is doing this, it seems the author is trying to get around useState being asynchronous to update.
This is not a good pattern and should be avoided!. As you rightly have said you have not seen this syntax before, because it is not an appropriate way of triggering a function to rerender.
React Docs recommends this way if you really need to force the retrigger:
const forceUpdate = useReducer(x => x + 1, 0)[1]
But a key line from this resource is to Try to avoid this pattern if possible.

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: scrollIntoView not working following by dependency of useEffect

I have got a dependency imageNo in useEffect() as I want the element to go up when it's being hidden, but scrollIntoView() does not work properly whenever imageNo changes, but it works when clicking a button.
Updated
import React, { useEffect, useRef, useState } from 'react';
const Product = ({ product }) => {
const moveRef = useRef(product.galleryImages.edges.map(() => React.createRef()));
const [imageNo, setImageNo] = useState(0);
useEffect(() => {
const position = moveRef.current[imageNo]?.current.getBoundingClientRect().y;
console.log('imageNo', imageNo); // <<<<----- This is also called whenever scrolling excutes.
if (position > 560) {
moveRef.current[imageNo]?.current.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
}, [imageNo]);
const test = () => {
const position = moveRef.current[imageNo]?.current.getBoundingClientRect().y;
if (position > 560) {
moveRef.current[imageNo]?.current.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
};
// This changes `imageNo`
const handleScroll = () => {
let id = 0;
console.log('refs.current[id]?.current?.getBoundingClientRect().y', refs.current[id]?.current?.getBoundingClientRect().y);
const temp = imgArr?.find((el, id) => refs.current[id]?.current?.getBoundingClientRect().y >= 78);
if (!temp) id = 0;
else id = temp.id;
if (refs.current[id]?.current?.getBoundingClientRect().y >= 78) {
setImageNo(id);
}
};
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
return (
<div className="flex flex-row layout-width ">
{/* aside */}
<div className="sticky flex self-start top-[76px] overflow-y-auto !min-w-[110px] max-h-[90vh]">
<div className="">
{product.galleryImages.edges.map((image, i) => {
return (
<div ref={moveRef.current[i]} key={image.node.id}>
<Image />
</div>
);
})}
</div>
</div>
<button onClick={test}>btn</button>
</div>
);
};
export default Product;
Any suggestion will be greatly appreciated.
I couldn't see where the imageNo is coming from?
If it is just a normal javascript variable then it wouldn't trigger re-render even after putting it inside the useEffect's dependencies array.
If you want to make the re-render happen based on imageNo then make a useState variable for imageNo and change it using setter function that useState provides.
Helpful note - read about useState and useEffect hooks in React.

Updating array context state in component updates strangely

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;

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>
)
}

Resources