I created one functional component and declared some variables in it.
const Foo = (props) => {
let instanceVariable = null;
const selectValue = (value) => {
instanceVariable = value;
}
const proccessVariable = () => {
// Use instanceVariable and do some task
}
return(
<SelectionOption onChange={selectValue} />
);
}
I observed that whenever parent component of Foo is re-rendered or sometime Foo itself re-renders instanceVariable set back to null. Instead of this I want to save selectedValue init and proccess it later on proccessVariable() method.
If I set instanceVariable as state it will work and there is no need of state to just hold the selected value.
I know useEffect(()=>{}, []) only run one time but how can I declare instanceVariable there and use it in other functions.
Can you please let me know what I'm doing wrong.
Since you declare a variable directly in your functional component, its value is reset everytime your component re-renders. You can make use of useRef to declare instance variables in functional component
const Foo = (props) => {
let instanceVariable = useRef(null);
const selectValue = (value) => {
instanceVariable.current = value; // make sure to use instanceVariable.current
}
const proccessVariable = () => {
// Use instanceVariable.current and do some task
}
return(
<SelectionOption onChange={selectValue} />
);
}
Related
I have the following code:
export default function Parent() {
const children1 = someArrayWithSeveralElements.map(foo => <SomeView />);
const children2 = someArrayWithSeveralElements.map(foo => <SomeCheckbox />);
return (<>
{children1}
{/*Some other components*/}
{children2}
</>)
};
For a given element foo, there is a SomeView component that is conditionally rendered based on the state of a SomeCheckbox. I'm having trouble figuring out a way to have the state from the checkbox affect the rendering of the sibling view component.
Normally the solution would be to just declare the state hook in the parent component and pass them down to each child, but since the siblings are rendered via foreach loops it's impossible to do so.
My current solution is to also generate the state hooks for each foo in a loop as well, but that feels a bit hacky since it's better to avoid creating hooks inside of loops (it's worth nothing that someArrayWithSeveralElements is not intended to change after mounting).
Is there a more elegant alternative to solve this?
The solution is what you side, you need to create a state in the parent component and pass it to the children. and this will work for single component or bunch of them, the difference is just simple: use array or object as state.
const [checkboxesStatus, setCheckboxesStatus] = useState({// fill initial data});
const children1 = someArrayWithSeveralElements.map(foo =>
<SomeView
visibile={checkBoxesStatus[foo.id]}
/>);
const children2 = someArrayWithSeveralElements.map(foo =>
<SomeCheckbox
checked={checkBoxesStatus[foo.id]}
onChange={// set new value to foo.id key}
/>)
export default function Parent() {
const [states, setStates] = React.useState([]);
const children1 = someArrayWithSeveralElements.map((foo, i) => <SomeView state={states[i]} />);
const children2 = someArrayWithSeveralElements.map((foo, i) => {
const onStateChange = (state) => {
setStates(oldStates => {
const newStates = [...(oldStates || [])]
newStates[i] = state;
return newStates;
})
}
return <SomeCheckbox state={states[i]} onStateChange={onStateChange} />;
});
return (<>
{children1}
{/*Some other components*/}
{children2}
</>)
};
Use states in the parent componet.
Note: the element of states may be undefined.
I have this setup so that the render is forces when they click by simply updating the state of a hook. Is there a nicer or cleaner way to do this.. here is some code...
const [click, setClick] = useState();
function handle1Click() {
props.UserInfoObject.setWhichPlot(1)
setClick(1000 * 60 * 5)
}
return (
<div>
<button onClick={handle1Click}>5 Minutes</button>
</div>
I came accross this which is another option but I am trying to be as optimal as possible so I am unsure which to use, or if there is another method?
handleClick = () => {
// force a re-render
this.forceUpdate();
};
I only mention this because of the warning that pops up stating this "'click' is assigned a value but never used no-unused-vars
***EDIT
adding the UserInfoObject class for reference
class UserInformation {
constructor(airValue, waterValue){
this.airValue = airValue;
this.waterValue = waterValue;
this.getCalibrationsFlag = false;
this.numberDevices = 0;
this.deviceName = 'defaultName';
this.currentlyChangingName = false;
this.whichPlot = 1;
}
setAirValue(number) {
this.airValue = number;
}
setWaterValue(number) {
this.waterValue = number;
}
setNumberDevices(int){
this.numberDevices = int;
}
setDeviceName(name){
this.deviceName = name;
}
setCurrentlyChangingName(boolean){
this.currentlyChangingName = boolean;
}
setWhichPlot(number){
this.whichPlot = number;
}
}
let UserInfoObject = new UserInformation(10000, -10);
With React, you should generally use pure, functional programming when possible. Mutating objects makes it much, much harder to do things properly.
Create state of the UserInformation instead. When it needs to be changed, instead of mutating the existing object, create a new object. The fact that this object is new will tell React that the component needs to re-render.
const [userInformation, setUserInformation] = useState({
airValue, // this should be in the outer scope
waterValue, // this should be in the outer scope
getCalibrationsFlag: false,
numberDevices: 0,
// ...
});
Do that in the parent component, then pass both userInformation and setUserInformation down as props. In the child, handle1Click can then be changed to:
const handle1Click = () => setUserInformation({
...userInformation,
whichPlot: 1,
});
Neither state nor props should ever be mutated in React.
I have the following function component:
const Player = () => {
const [spotifyPlayer, setSpotifyPlayer] = React.useState<SpotifyPlayer | null>(null);
return (
<SongControls togglePlay={togglePlay} isPlaying={spotifyPlayer.isPlaying} />
);
};
the SongControls component is responsible for showing the correct control button (start/pause):
const SongControls: React.FC<Props> = ({ isPlaying }: Props) => {
return (
{isPlaying ? (
<Pause style={iconBig} onClick={togglePlay} />
) : (
<PlayArrow style={iconBig} onClick={togglePlay} />
)}
);
};
the spotifyPlayer state in the Player component is a class that has a field isPlaying of type boolean. It also has a function defined togglePlay() which toggles isPlaying.
The problem is that when togglePlay() is called React doesn't rerender the components. I understand why (since the instance of SpotifyPlayer is not changing, thats why you shouldn't update react state directly).
I can't call setSpotifyPlayer() to update the state because of 2 reasons:
the instance of SpotifyPlayer is responsible for toggling isPlaying
when I change the state use js deconstructing I lose the prototype of the spotifyPlayer state and the function won't be available anymore.
I am not able to figure this out.
Your understanding of the problem is correct. You cannot rely on changes to the internal state of the SpotifyPlayer object to trigger a re-render. I recommend using a local state to store the value of isPlaying and update the SpotifyPlayer based on changes to that value.
If the player object has some sort of onChange listener, then I would use the player to set the local state rather than the other way around.
I'm not sure what package the player comes from, so I am assuming this interface:
class SpotifyPlayer {
isPlaying: boolean = false;
play(): void {
this.isPlaying = true;
}
pause(): void {
this.isPlaying = false;
}
}
We can use useRef to store a consistent reference to a player object.
const spotifyPlayer = React.useRef<SpotifyPlayer>(new SpotifyPlayer()).current;
We will store the value of isPlaying in local state. We can get the initial value from the player.
const [isPlaying, setIsPlaying] = React.useState(spotifyPlayer.isPlaying);
In order to keep isPlaying in sync with the player, we could use useEffect.
const togglePlay = () => setIsPlaying(!isPlaying);
React.useEffect(() => {
if ( isPlaying && ! spotifyPlayer.isPlaying ) {
spotifyPlayer.play();
} else if ( ! isPlaying && spotifyPlayer.isPlaying ) {
spotifyPlayer.pause();
}
}, [spotifyPlayer, isPlaying]);
But if togglePlay is the only function which will change the value, then we could handle the changes in that function.
const togglePlay = () => {
if (isPlaying ) {
spotifyPlayer.pause();
setIsPlaying(false);
} else {
spotifyPlayer.play();
setIsPlaying(true);
}
}
The parent component contains an array of objects.
It maps over the array and returns a child component for every object, populating it with the info of that object.
Inside each child component there is an input field that I'm hoping will allow the user to update the object, but I can't figure out how to go about doing that.
Between the hooks, props, and object immutability, I'm lost conceptually.
Here's a simplified version of the parent component:
const Parent = () => {
const [categories, setCategories] = useState([]);
useEffect(()=>{
// makes an axios call and triggers setCategories() with the response
}
return(
categories.map((element, index) => {
return(
<Child
key = {index}
id = {element.id}
firstName = {element.firstName}
lastName = {element.lastName}
setCategories = {setCategories}
})
)
}
And here's a simplified version of the child component:
const Child = (props) => {
return(
<h1>{props.firstName}</h1>
<input
defaultValue = {props.lastName}
onChange={()=>{
// This is what I need help with.
// I'm a new developer and I don't even know where to start.
// I need this to update the object's lastName property in the parent's array.
}}
)
}
Maybe without knowing it, you have lifted the state: basically, instead of having the state in the Child component, you keep it in the Parent.
This is an used pattern, and there's nothing wrong: you just miss a handle function that allows the children to update the state of the Parent: in order to do that, you need to implement a handleChange on Parent component, and then pass it as props to every Child.
Take a look at this code example:
const Parent = () => {
const [categories, setCategories] = useState([]);
useEffect(() => {
// Making your AXIOS request.
}, []);
const handleChange = (index, property, value) => {
const newCategories = [...categories];
newCategories[index][property] = value;
setCategories(newCategories);
}
return categories.map((c, i) => {
return (
<Child
key={i}
categoryIndex={i}
firstName={c.firstName}
lastName={c.lastName}
handleChange={handleChange} />
);
});
}
const Child = (props) => {
...
const onInputChange = (e) => {
props.handleChange(props.categoryIndex, e.target.name, e.target.value);
}
return (
...
<input name={'firstName'} value={props.firstName} onChange={onInputChange} />
<input name={'lastName'} value={props.lastName} onChange={onInputChange} />
);
}
Few things you may not know:
By using the attribute name for the input, you can use just one handler function for all the input elements. Inside the function, in this case onInputChange, you can retrieve that information using e.target.name;
Notice that I've added an empty array dependecies in your useEffect: without it, the useEffect would have run at EVERY render. I don't think that is what you would like to have.
Instead, I guest you wanted to perform the request only when the component was mount, and that is achievable with n empty array dependecies;
I created useBanner hooks
const useBanner = (array, yardage) => {
const [bannArr, setBannArr] = useState(array.slice(0, yardage));
const [bannListIndex, setBannIndex] = useState(1);
return {
....
};
};
Am I doing the right thing and the props throw in useState.
It’s permissible to use useBanner.
const Banner= ({
array,
yardage
}) => {
const { bannForth, bannBeck, bannArr } = useBanner(array, yardage);
return (
...
);
};
when props will change here.
Will change the state in useBanner.
or is it considered anti-patterns I have to write all this in useMemo
const useBanner = (array, yardage) => {
const [bannArr, setBannArr] = useState([]);
const [bannListIndex, setBannIndex] = useState(1);
useMemo(() => {
setBannArr(array.slice(0, yardage));
setBannIndex(1);
}, [array, yardage]);
return {
....
};
};
Yes, custom hooks are possible in React. Here is separate document discussing custom hooks.
But exactly you sample may require additional code depending on what is your final goal.
If you want initialize state only once, when component Banner is first created, you can just do as in your first sample
const Banner= ({
array,
yardage
}) => {
const { bannForth, bannBeck, bannArr } = useBanner(array, yardage);
return (
...
);
};
This will work perfectly. But if props array and yardage will change, this will not be reflected in component. So props will be used only once as initial values and then will not be used in useBanner even if changed (And it doesn't matter whether you'll use useBanner or useState directly). This answer highlight this.
If you want to update inital values on each props change, you can go with useEffect like below
const Banner= ({
array,
yardage
}) => {
const { bannForth, bannBeck, bannArr, setBannArr } = useBanner(array, yardage);
useEffect (() => {
// setBannArr should also be returned from useBanner. Or bannArr should be changed with any other suitable function returned from useBanner.
setBannArr(array.slice(0, yardage));
}, [array, yardage, setBannArr])
return (
...
);
};
In this case Banner component can control state itself and when parent component change props, state in Banner component will be reset to new props.
Here is small sample to showcase second option.