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 .
Related
I am facing an type issue in my React typescript project. Especially when setting the state using find method inside useEffect method. I have below code and also defined the type for that but still getting that error. Can you please let me know what went wrong ?
type CountryType = {
id: "string",
desc: "string"
}
const test = () => {
const [list, setList] = useState([]);
const [countryId, setCountryId] = useState("");
const [desc, setDesc] = useState("")
useEffect(() => {
running some api calling to set the list
const data = axios.get("api");
setList(data)
// setting id
const fetchID = new URLSearchParams(window.location.search).get('id');
setCountryId(fetchID)
}, [])
useEffect(() => {
const country = list.find((item: CountryType ) => item.id === countryId);
if(country && Object.keys(country.length)) {
setDesc(country.desc); // here i am getting error Property doesn't exist on type 'never'
}
}, [list, countryId])
}
You need to type your state:
const [list, setList] = useState<CountryType>([]); // otherwise, it thinks that the array must be empty (never)
const [countryId, setCountryId] = useState(""); //string
const [desc, setDesc] = useState("") //string
//...
const country: CountryType | undefined = list.find((item ) => item.id === countryId);
I am using a context like the following:
const placeCurrentOrder = async () => {
alert(`placing order for ${mealQuantity} and ${drinkQuantity}`)
}
<OrderContext.Provider
value={{
placeCurrentOrder,
setMealQuantity,
setDrinkQuantity,
}}
>
and I'm calling this context deep down with something like this (when the user clicks a button):
const x = () => {
orderContext.setMealQuantity(newMealQuantity)
orderContext.setDrinkQuantity(newDrinkQuantity)
await orderContext.placeCurrentOrder()
}
Sort of like I expect, the state doesn't update in time, and I always get the previous value of the state. I don't want to have a useEffect, because I want control over exactly when I call it (for example, if mealQuantity and drinkQuantity both get new values here, I don't want it being called twice. The real function is far more complex.)
What is the best way to resolve this? I run into issues like this all the time but I haven't really gotten a satisfactory answer yet.
You can set them in a ref. Then use the current value when you want to use it. The easiest way is probably to just create a custom hook something like:
const useStateWithRef = (initialValue) => {
const ref = useRef(initialValue)
const [state, setState] = useState(initialValue)
const updateState = (newState) => {
ref.current = typeof newState === 'function' ? newState(state) : newState
setState(ref.current)
}
return [state, updateState, ref]
}
then in your context provider component you can use it like:
const [mealQuantity, setMealQuantity, mealQuantityRef] = useStateWithRef(0)
const [drinkQuantity, setDrinkQuantity, drinkQuantityRef] = useStateWithRef(0)
const placeOrder = () => {
console.log(mealQuantityRef.current, drinkQuantityRef.current)
}
You can also just add a ref specifically for the order and then just update it with a useEffect hook when a value changes.
const [drinkQuantity, setDrinkQuantity] = useState(0)
const [mealQuantity, setMealQuantity] = useState(0)
const orderRef = useRef({
drinkQuantity,
mealQuantity
})
useEffect(() => {
orderRef.current = {
drinkQuantity,
mealQuantity,
}
}, [drinkQuantity, mealQuantity])
const placeOrder = () => {
console.log(orderRef.current)
}
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...
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.
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 };
};