import {useState} from 'react'
const CartItem = (props) => {
const [quantity, setAddQuantity] = useState(1);
const [, setSubQuantity] = useState(0)
const addquantityHandler = (event) => {
setAddQuantity(quantity+1)
}
const subquantityHandler = (event) => {
setSubQuantity(quantity-1)
}
return (
<div>
<div>CartItem</div>
<div>{props.title}</div>
<button onClick={addquantityHandler}>Add One</button>
<button onClick={subquantityHandler}>Subtract One</button>}
<p>Quantity : {quantity}</p>
</div>
)
}
export default CartItem;
I am starting to learn React. Is there any way to subtract quantity using setSubQuantity. Is this impossible to do as quantity was declared in a different useState?
You can achieve this using only one useState.
const [quantity, setQuantity] = useState(1)
const addquantityHandler = (event) => {
setQuantity(quantity+1)
}
const subquantityHandler = (event) => {
setQuantity(quantity-1)
}
How to use the same variable in two useStates?
You can't, and you shouldn't. If you want to track the state of a thing then put that thing in one state variable.
Instead have each of your click handler functions call setAddQuantity with the new value (and rename setAddQuantity to setQuantity.
Related
I want to use the data from props in the helper function as a parameter.
However, I still can't do it, I tried directly
const SuperHero = (props) => {
const [hero, setHero] = useState("");
const searchSuperHero = (props.name) => {}
Also using destructuring props
const SuperHero = (props) => {
const [hero, setHero] = useState("");
const {name} = props.name;
const searchSuperHero = (name) => {}
But I still can't capture the data and it shows the variable as declared but never used even though it is used right in the function below.
Please, Where is problem?
Thank you
Both of your code blocks are incorrect for the same reason: defining argument parameters.
const SuperHero = (props) => {
const [hero, setHero] = useState("");
const searchSuperHero = (props.name) => {} // you're trying to define a parameter called props.name for your new function
Try this:
const SuperHero = (props) => {
const [hero, setHero] = useState("");
const searchSuperHero = () => {
console.log(props.name);
}
}
Also, it's unrelated to your question, but the correct syntax for destructuring an object is:
const { name } = props;
destructuring plucks out the chosen value so you don't need to use .
My data from menProducts coming from store component just loops too many. Meaning it duplicates or renders too many when using my filter function. I've read using useEffect can render it only once but I don't know how to trigger its effect.
const [filter, setFilter] = useState('');
const menProducts = useSelector((state) => state.menProducts);
const SearchText = (event) => { <--- this function is for input search bar
setFilter(event.target.value);
}
useEffect(() => { <--- ??
}, []);
let dataSearch = menProducts.filter(id => { <-----Filter function
return Object.keys(id).some(key=>
id[key].toString().toLowerCase().includes(filter.toString().toLowerCase())
)
return (
<main>
{dataSearch.map((menproduct) => (
<ProductMen key={menproduct}/> <---imported <ProductMen/> component is a component that use useDispatch for store reducer and it also displayed products.
))}
</main>
)
}
You don't need to useEffect in this case, you just have to apply the filter at the right time like this:
const [filter, setFilter] = useState("");
const menProducts = useSelector((state) => state.menProducts);
const SearchText = (event) => {
setFilter(event.target.value);
};
return (
<main>
{menProducts
.filter((menproduct) =>
Object.keys(menproduct).some((key) =>
menproduct[key]
.toString()
.toLowerCase()
.includes(filter.toString().toLowerCase())
)
)
.map((menproduct) => (
<ProductMen key={menproduct} />
))}
</main>
);
Demo: https://stackblitz.com/edit/react-6lfqps?file=src/App.js
In the demo i've also included an alternative that use useEffect if you want to take a look at it
Try like this:
const [filter, setFilter] = useState("");
const menProducts = useSelector((state) => state.menProducts);
const searchText = (event) => {
setFilter(event.target.value);
};
useEffect(() => {
const dataSearch = (filter) =>
menProducts.filter((id) => {
// function
});
dataSearch(filter);
}, [filter]);
return (
<main>
{dataSearch.map((menproduct) => (
<ProductMen key={menproduct}/> <---imported <ProductMen/> component is a component that use useDispatch for store reducer and it also displayed products.
))}
</main>
)
To use the useEffect hook you have to pass a function and a dependencies array.
In this case I've used an anonymous function and inside of that I've defined the function dataSearch and on the dependencies array I've just included the filter so each time the filter value changes the useEffect gets executed.
What I would like to happen is when displayBtn() is clicked for the items in localStorage to display.
In useEffect() there is localStorage.setItem("localValue", JSON.stringify(myLeads)) MyLeads is an array which holds leads const const [myLeads, setMyLeads] = useState([]); myLeads state is changed when the saveBtn() is clicked setMyLeads((prev) => [...prev, leadValue.inputVal]);
In DevTools > Applications, localStorage is being updated but when the page is refreshed localStorage is empty []. How do you make localStorage persist state after refresh? I came across this article and have applied the logic but it hasn't solved the issue. I know it is something I have done incorrectly.
import List from './components/List'
import { SaveBtn } from './components/Buttons';
function App() {
const [myLeads, setMyLeads] = useState([]);
const [leadValue, setLeadValue] = useState({
inputVal: "",
});
const [display, setDisplay] = useState(false);
const handleChange = (event) => {
const { name, value } = event.target;
setLeadValue((prev) => {
return {
...prev,
[name]: value,
};
});
};
const localStoredValue = JSON.parse(localStorage.getItem("localValue")) ;
const [localItems] = useState(localStoredValue || []);
useEffect(() => {
localStorage.setItem("localValue", JSON.stringify(myLeads));
}, [myLeads]);
const saveBtn = () => {
setMyLeads((prev) => [...prev, leadValue.inputVal]);
// setLocalItems((prevItems) => [...prevItems, leadValue.inputVal]);
setDisplay(false);
};
const displayBtn = () => {
setDisplay(true);
};
const displayLocalItems = localItems.map((item) => {
return <List key={item} val={item} />;
});
return (
<main>
<input
name="inputVal"
value={leadValue.inputVal}
type="text"
onChange={handleChange}
required
/>
<SaveBtn saveBtn={saveBtn} />
<button onClick={displayBtn}>Display Leads</button>
{display && <ul>{displayLocalItems}</ul>}
</main>
);
}
export default App;```
You've fallen into a classic React Hooks trap - because using useState() is so easy, you're actually overusing it.
If localStorage is your storage mechanism, then you don't need useState() for that AT ALL. You'll end up having a fight at some point between your two sources about what is "the right state".
All you need for your use-case is something to hold the text that feeds your controlled input component (I've called it leadText), and something to hold your display boolean:
const [leadText, setLeadText] = useState('')
const [display, setDisplay] = useState(false)
const localStoredValues = JSON.parse(window.localStorage.getItem('localValue') || '[]')
const handleChange = (event) => {
const { name, value } = event.target
setLeadText(value)
}
const saveBtn = () => {
const updatedArray = [...localStoredValues, leadText]
localStorage.setItem('localValue', JSON.stringify(updatedArray))
setDisplay(false)
}
const displayBtn = () => {
setDisplay(true)
}
const displayLocalItems = localStoredValues.map((item) => {
return <li key={item}>{item}</li>
})
return (
<main>
<input name="inputVal" value={leadText} type="text" onChange={handleChange} required />
<button onClick={saveBtn}> Save </button>
<button onClick={displayBtn}>Display Leads</button>
{display && <ul>{displayLocalItems}</ul>}
</main>
)
At the moment, there is a component that is essentially copied 4 times. I would like to make it more abstract, and simply render it 4 times, and pass in the dynamic data each time. The data that's passed into each component are state hooks.
With that being the goal, could I get some help on the implementation?
Here's what a component call looks like in the parent:
const [allBlueItems, setAllBlueItems] = useState([]);
const [selectedBlueItems, setSelectedBlueItems] = useState([]);
const [allRedItems, setAllRedItems] = useState([]);
const [selectedRedItems, setSelectedRedItems] = useState([]);
<BlueSelection
allBlueItems={allBlueItems}
selectedBlueItems={setSelectedBlueItems}
setSelectedBlueItems={selectedBlueItems}
/>
<RedSelection
allRedItems={allRedItems}
selectedRedItems={setSelectedRedItems}
setSelectedRedItems={selectedRedItems}
/>
Then, the ItemSelection component uses those useState values passed in as props to render data, and updates the state accordingly:
const BlueSelection = ({ allBlueItems, selectedBlueItems, setSelectedBlueItems }) => {
useEffect(() => {
setSelectedBlueItems([]);
}
}, []);
Then I repeat myself and implement the exact same code to handle the RedItem
const RedSelection = ({ allRedItems, selectedRedItems, setSelectedRedItems }) => {
useEffect(() => {
setSelectedRedItems([]);
}
}, []);
Refactor
export default const Selection = () => {
const [allItems, setAllItems] = useState([]);
const [selectedItems, setSelectedItems] = useState([]);
return (
<Selection
allItems={allItems}
selectedItems={setSelectedItems}
setSelectedItems={selectedItems}
/>)}
import this wherever you need it like....
import Selection as RedSelection from ...
import Selection as BlueSelection from...
Since React hooks rely on the execution order one should generally not use hooks inside of loops. I ran into a couple of situations where I have a constant input to the hook and thus there should be no problem. The only thing I'm wondering about is how to enforce the input to be constant.
Following is a simplified example:
const useHookWithConstantInput = (constantIdArray) => {
const initialState = buildInitialState(constantIdArray);
const [state, changeState] = useState(initialState);
const callbacks = constantIdArray.map((id) => useCallback(() => {
const newState = buildNewState(id, constantIdArray);
changeState(newState);
}));
return { state, callbacks };
}
const idArray = ['id-1', 'id-2', 'id-3'];
const SomeComponent = () => {
const { state, callbacks } = useHookWithConstantInput(idArray);
return (
<div>
<div onClick={callbacks[0]}>
{state[0]}
</div>
<div onClick={callbacks[1]}>
{state[1]}
</div>
<div onClick={callbacks[2]}>
{state[2]}
</div>
</div>
)
}
Is there a pattern for how to enforce the constantIdArray not to change? My idea would be to use a creator function for the hook like this:
const createUseHookWithConstantInput = (constantIdArray) => () => {
...
}
const idArray = ['id-1', 'id-2', 'id-3'];
const useHookWithConstantInput = createUseHookWithConstantInput(idArray)
const SomeComponent = () => {
const { state, callbacks } = useHookWithConstantInput();
return (
...
)
}
How do you solve situations like this?
One way to do this is to use useEffect with an empty dependency list so it will only run once. Inside this you could set your callbacks and afterwards they will never change because the useEffect will not run again. That would look like the following:
const useHookWithConstantInput = (constantIdArray) => {
const [state, changeState] = useState({});
const [callbacks, setCallbacks] = useState([]);
useEffect(() => {
changeState(buildInitialState(constantIdArray));
const callbacksArray = constantIdArray.map((id) => {
const newState = buildNewState(id, constantIdArray);
changeState(newState);
});
setCallbacks(callbacksArray);
}, []);
return { state, callbacks };
}
Although this will set two states the first time it runs instead of giving them initial values, I would argue it's better than building the state and creating new callbacks everytime the hook is run.
If you don't like this route, you could alternatively just create a state like so const [constArray, setConstArray] = useState(constantIdArray); and because the parameter given to useState is only used as a default value, it'll never change even if constantIdArray changes. Then you'll just have to use constArray in the rest of the hook to make sure it'll always only be the initial value.
Another solution to go for would be with useMemo. This is what I ended up implementing.
const createCallback = (id, changeState) => () => {
const newState = buildNewState(id, constantIdArray);
changeState(newState);
};
const useHookWithConstantInput = (constantIdArray) => {
const initialState = buildInitialState(constantIdArray);
const [state, changeState] = useState(initialState);
const callbacks = useMemo(() =>
constantIdArray.map((id) => createCallback(id, changeState)),
[],
);
return { state, callbacks };
};