React useState within mapped parent component - javascript

Im new to React and im trying to understand useState.
So im pulling in an array of products and then mapping the array to display each product card:
export default function ProductList({ products }) {
return (
<Grid templateColumns='repeat(3, 1fr)' columnGap={6} rowGap={10}>
{
products.map(product => (
<ProductCard key={product.node.id} product={product} productID={product.node.id} />
))
}
</Grid>
)
}
and inside of my ProductCard I have a bunch of useState, to handle product options, variants, etc.
const [available, setAvailable] = useState(true)
const [selectedVariant, setSelectedVariant] = useState('')
const [selectedOptions, setSelectedOptions] = useState('')
const { addToCart } = useContext(CartContext)
So my question is, do each of the ProductCard actually share the same useState, even though they are being mapped?

You map collection of separate components, they won't share logic or state. State is hermetic and other components wont have access to it unless allowed to.

Related

Variable passed trough state in Link can't update

I am updating my theme in my App per useState. This is passed to Topbar-Component per prop. console.log() gets triggered every time it changes. From Topbar theme is passed into a link to AboutMe-Copmponent as state, which works, but when i now change the state of theme it only updates in Topbar. I even tried Useeffect. Only when I refresh the site the change is noticed. I read hours about this but I cant solve it somehow.
AppComponent (not all code just the necessary):
function App() {
const [theme, setTheme] = useState('dark')
return (
<Topbar theme={theme}></Topbar>
<ToggleButton variant='light' onClick={() => setTheme('light')}>Light</ToggleButton>
<ToggleButton variant='dark' onClick={() => setTheme('dark')}>Dark</ToggleButton>
TopbarComponent:
export default function Topbar({theme}) {
console.log('Topbar',theme)
React.useEffect(()=>{
console.log('changed')
},[theme])
Output when I press the buttons:
Topbar light
changed
Topbar dark
changed
AboutMeComponent:
export default function AboutMe() {
const location = useLocation()
console.log(location.state)
React.useEffect(() => {
console.log('About-Me',location.state)
},[location])
Initial output:
dark
About-Me dark
When I now press the other Button I only get the Topbar Output
Only when refreshing I get the AboutMe Outputs again.
PS
The theme is changed anyway from dark to light but i need this state to change fonts etc.
I would suggest sticking with documentation's recommendation which is to use useContext for very this example of setting theme using context.
Check out: https://beta.reactjs.org/apis/react/useContext
Usage : Passing data deeply into the tree
import { useContext } from 'react';
function Button() {
const theme = useContext(ThemeContext);
useContext returns the context value for the context you passed. To determine the context value, React searches the component tree and finds the closest context provider above for that particular context.
To pass context to a Button, wrap it or one of its parent components into the corresponding context provider:
function MyPage() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}
function Form() {
// ... renders buttons inside ...
}
It doesn’t matter how many layers of components there are between the provider and the Button. When a Button anywhere inside of Form calls useContext(ThemeContext), it will receive "dark" as the value.
I have it working now with the useContext hook. Thank you i somehow forgot about it.
App:
export const ThemeContext = React.createContext()
function App() {
const [theme, setTheme] = useState('black')
console.log(theme)
return (
<ThemeContext.Provider value={{backgroundColor:theme}}>
<BrowserRouter>
<div className='App' id={theme}>
<Topbar/>
<div className="position-absolute top-0 start-0">
<ToggleButton variant='light' onClick={() => setTheme('white')}>Light</ToggleButton>
<ToggleButton variant='dark' onClick={() => setTheme('black')}>Dark</ToggleButton>
</div>
Topbar:
export default function Topbar() {
const {user,logout} = UserAuth()
const [error, setError] = useState('')
const navigate = useNavigate()
const style = useContext(ThemeContext)
console.log(style)
AboutMe:
export default function AboutMe() {
const style = useContext(ThemeContext)
console.log(style)
return (
<>
<div className='d-flex' style={style}>
I had to move my Routing from Index.js to App.js because it had to be wrapped in the Context provider, but now my theme gets passed into every single component.

Implementing a function to swap array elements in React without mutating the original array

I am coding a react app in which a user can click a button to swap an item in an array with the item to its left. I wrote a function to implement this without mutating the original items array that is rendering on the page, but this function is not doing anything to my code, nor is it returning any errors.
Here is my app component, which defines the function swapLeft then passes that function down to the Item component as props:
import React, { useState } from "react";
import Form from "./components/Form";
import Item from "./components/Item";
import { nanoid } from "nanoid";
import './App.css';
function App(props) {
const [items, setItems] = useState(props.items);
function deleteItem(id) {
const remainingItems = items.filter(item => id !== item.id);
setItems(remainingItems);
}
function swapLeft(index) {
const index2 = index - 1;
const newItems = items.slice();
newItems[index] = items[index2];
newItems[index2] = items[index];
return newItems;
}
const itemList = items
.map((item, index) => (
<Item
id={item.id}
index={index}
name={item.name}
key={item.id}
deleteItem={deleteItem}
swapLeft={swapLeft}
/>
));
function addItem(name) {
const newItem = { id: "item-" + nanoid(), name: name };
setItems([...items, newItem]);
}
return (
<div className="form">
<Form addItem={addItem} />
<ul className="names">
{itemList}
</ul>
</div>
);
}
export default App;
And the Item component:
import React from "react";
import { Button, Card, CardContent, CardHeader } from 'semantic-ui-react'
export default function Item(props) {
return (
<Card>
<CardContent>
<CardHeader> {props.name}</CardHeader>
<Button onClick={() => props.deleteItem(props.id)}>
Delete <span className="visually-hidden"> {props.name}</span>
</Button>
</CardContent>
<CardContent style={{ display: 'flex' }}>
<i className="arrow left icon" onClick={() => props.swapLeft(props.index)} style={{ color: 'blue'}}></i>
<i className="arrow right icon" style={{ color: 'blue'}}></i>
</CardContent>
</Card>
);
}
Is there a better way for me to write this function and implement this? I suppose I could do something with the React setState hook, but this seemed like an easier solution. I am new to React so any insight would be helpful
The way React knows if the state has changed is whether the state is refers to an entirely different address in memory. In case of arrays, if you want React to rerender the page because the array in the state changed, you need to provide it an entirely new array. Modifying the existing array will not trigger the render process.
Basically, what you need to do is changed the last line of swapLeft function to
setItems(newItems)
If you want the changes to take effect immediately (which is what I guess you want to do here)
You can also use the return value from the function and change the state in another component, FYI.
EDIT:
I looked at this again, and your implementation of swap is also wrong, but even if you corrected it you still wouldn't see a change, unless you did what I mentioned above
The full correct function would be
function swapLeft(index) {
const index2 = index - 1;
const newItems = items.slice();
const temp = items[index];
newItems[index] = items[index2];
newItems[index2] = temp;
setItems(newItems);
}
Just to maybe clarify the previous one. If you don't call setState, your component doesn't rerender. This means that no matter what you do with those arrays, it won't be visible on the screen.

How to avoid re-renders at the App level when I make a change to a child component (like a searchbar)?

I'm trying to make a searchbar React component that doesn't trigger an App-wide re-render when I type, yet allows me to use the query in other components/to make an API call.
Background:
I learned that stateless input components are good for reusability and creating controlled components. So state stays at parent (or App) level and the component's value gets passed in via props.
On the other hand, tracking the query's state at the App level causes ALL components to re-render (when the input's handleChange calls setQuery) and feels like a needless use of resources.
What am I missing here? Do I leave the query piece of state at the SearchBar level instead? Should I use React.memo or useCallback?
SearchBar component:
import React from 'react';
const Searchbar = ({ query, handleQueryChange }) => {
return (
<div className="field">
<label>Enter search term</label>
<input type="text" onChange={handleQueryChange} value={query}></input>
</div>
);
};
export default Searchbar;
And the App component
const App = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleQueryChange = (e) => {
setQuery(e.currentTarget.value);
};
useEffect(() => {
function search() {
...makeAPIcallwith(query).then((result) => {setResults(result)})
};
if (query) {
const timer = setTimeout(() => {
search()}, 1000);
return () => clearTimeout(timer);
}
}, [query]);
return (
<div className="content-container">
<SearchBar query={query} handleQueryChange={handleQueryChange} />
<...Other React component not needing to re-render... />
</div>
);
};
export default App;
The tiniest optimization that you could make here is this:
const handleQueryChange = useCallback((e) => {
setQuery(e.currentTarget.value);
},[]);
It's not worth making. What you've shown is good idomatic react code.
I guess the other thing that you could do, if you haven't already because you haven't shown the code, is to help React out by encapsulating the results in a component like this:
return (
<div className="content-container">
<SearchBar query={query} handleQueryChange={handleQueryChange} />
<ListOfThings results={results}/>
</div>
);
Super tiny components, so tiny they seem almost trivially simple, is the name of the game in React. If your components are over 30-40 lines long, then 👎

React-Native: how to update a components value (and display the updated value) when a separate component's value changes

In the code below, for React-Native, I would like the the NumericInput for 'count' to update and be displayed when the number of 'containers' changes. The input for 'count' must be able to be modified by user input as well as be the result of the calculation in the onChange function from 'containers'.
How can I do this in React-Native?
import React, { useState } from 'react';
import NumericInput from 'react-native-numeric-input';
const Plot = () => {
const [count, setCount] = useState(0);
const [countainers, setContainers] = useState(0);
return (
<View>
{/* input for number of containers ... each container has 10 items */}
<NumericInput
value={countainers}
onChange={c => { setContainers(c); setCount(containers * 10); }}
/>
{/* input for total count of items */}
<NumericInput
onChange={c => setCount(c)}
value={count}
/>
</View>
);
}
export default Plot;
It turns out the 'react-native-numeric-input' api is broken for the version and dependency versions I have. The code that was posted should have worked just fine as-is but just didn't. Changing it to any other component (and I chose a simple TextInput that allows 0-9 only) proved this to be the case.

React - functional components keep re-render when passing functions as props

i have an issue in my react app and i dont know how to solve it;
i have an array with values and chosen list
and a function to add item to the chosen list
import React, { useState } from "react";
import Parent from "./Parent";
export default function App() {
const [chosenList, setChosenList] = useState([]);
const array = ["dsadas", "dasdas", "dasdasd"];
const addToChosenList = string => {
setChosenList([...chosenList, string]);
};
return (
<div className="App">
<Parent
arr={array}
chosenList={chosenList}
addToChosenList={addToChosenList}
/>
</div>
);
}
Parent component that mapping through the array
and give the Nested component the props: item, addToChosenList, inList
import React from "react";
import Nested from "./Nested.js";
export default function Parent({ arr, addToChosenList, chosenList }) {
return (
<div className="App">
{arr.map((item, index) => (
<Nested
key={index}
item={item}
addToChosenList={addToChosenList}
inList={chosenList.findIndex(listitem => listitem === item) > -1}
/>
))}
</div>
);
}
Nested component that displays the item and giving it the addToChosenList function to add the item to the chosen list
import React, { memo } from "react";
export default memo(function Parent({ item, addToChosenList, inList }) {
const childFunctionToAddToChosenList = () => {
addToChosenList(item);
};
return (
<div className="App" onClick={childFunctionToAddToChosenList}>
<div>{item}</div>
{inList && <div>in List</div>}
</div>
);
});
every Nested component keeps re-render after i clicked only one item in the list
i believe it renders because of the function addToChosenList that changes when i change the state
anyone knows how to solve it ??
thanks :)
addToChosenList will point to a new reference on every re-render, wrap it in useCallback which will keep the same reference across re-renders unless one of the variables inside of the dependencies array has changed, if we pass an empty array the function will keep the same reference across the entire component lifecycle.
you will also need to use a functional update to avoid stale state due to the closure
const addToChosenList = useCallback(string => {
setChosenList(prevState => [...prevState, string]);
}, []);

Categories

Resources