I have a problem with a library react-speech-recognition .
newcontent is a state when modify this state inside useeffect
print undefined
and I also want modify this state for transcript
also print undefined
const Room = () => {
let{
transcript,
} = useSpeechRecognition();
const [newContent,setnewcontent]=useState('')
}
console.log(transcript)-->//here successful
useEffect(() => {
console.log(transcript)-->//here undefined
setnewcontent(transcript)
console.log(setnewcontent)-->//here undefined
},[])
Use 2 separate useEffect. One to update the state and other to keep track on it and do the console.log as follows.
// This useEffect will trigger if any change detected in transcript variable
useEffect(() => {
setnewcontent(transcript)
},[transcript])
// This useEffect will trigger if any change detected in newContent state
useEffect(() => {
console.log(newContent)
},[newContent])
Related
I am having a subscription, which I set up in the useEffect() Hook. Based on a variable from the store, I want to execute code (or not) which is also part of the body of the subscription.
const variableFromStore = useSelector(state => state.variableFromStore);
const dispatch = useDisptach();
useEffect(() => {
const subscriptionEvent = SomeModule.AddSubscription(event => {
console.log(variableFromStore);
if(variableFromStore) {
dispatch(foo());
}
});
})
Initially, variableFromStore is false. However, during interaction with the App (which includes unmounting the component, because the App is moved to background), it gets set to true. Then, some time later, subscriptionEvent gets fired.
But, when I console.log(variableFromStore)in there, it is always false, eventhough the redux debugger tells me it is true...
Is it not possible to pass a state/store variable into a subscription?
I assume this is because I am setting up the subscription in the useEffect() hook, which only gets executed once. But, if I set it up like this
useEffect(() => {
const subscriptionEvent = SomeModule.AddSubscription(event => {
console.log(variableFromStore);
if(variableFromStore) {
dispatch(foo());
}
});
},[variableFromStore])
Wouldn't that reinitialize the subscription every time variableFromStore changes?
This happens because your useEffect callback runs only once, and the variableFromStore is enclosed by callback closure, to fix that, redux provides another way of getting the state from the store by calling the store getState method, which returns your whole state object, so you need to import your store first, from wherever it was created, and call getState method, this way the same last value will be returned as per using the selector, but that is not affected by closure:
import store from '...'
useEffect(() => {
const subscriptionEvent = SomeModule.AddSubscription(event => {
const { variableFromStore } = store.getState()
console.log(variableFromStore);
if(variableFromStore) {
dispatch(foo());
}
});
})
If you use the useEffect with empty or without a dependency array, it will only run on the first render.
If you add the variable you want to use or run the effect on the variable's value change add that variable to the useEffect's dependency array.
Try this code down below and also check out this documentation for more.
const variableFromStore = useSelector(state => state.variableFromStore);
useEffect(() => {
const subscriptionEvent = SomeModule.AddSubscription(event => {
console.log(variableFromStore);
if(variableFromStore) {
dispatch(foo())
}
});
}, [variableFromStore])
For my react-app/Express app I am trying to update the component state using the useEffects to run once when the component renders. Within useEffect I make a fetch to the express server.
const Favorites = ({ user }) => {
const loggedIn = user.loginname === "" ? false : true;
const [favs, setFavs] = useState([]);
useEffect(() => {
if (loggedIn) {
fetch(`/user/favs/${user.loginname}`)
.then((resp) => resp.json())
.then((data) => {
console.log(data);
setFavs([...data]);
console.log(favs);
});
}
}, []);
return (
<div className="mt-d d-flex justify-content-center">
{loggedIn ? (
<FavoritesList favs={favs} />
) : (
<h3 className="my-2">Please login to use this feature</h3>
)}
</div>
);
};
I make a fetch call on line 11 and am able to print the results on line 14. I then try to update the
component state using setFavs. My issue is that the state seems to not be updated or maybe
there is some async issue.
const FavoritesList = ({ favs, prop }) => {
const [data, setData] = useState([]);
useEffect(() => {
console.log(favs);
// favs.forEach(item => console.log(item))
}, []);
return <h5>Dummy component</h5>;
};
When I try to print favs on line 16 or print favs within the child(FavoritesList) component it is being passed down to, I get an empty array.
When I try to print favs on line 16 or print favs within the child(FavoritesList) component it is being passed down to, I get an empty array.
You're right, it is an async issue: your call to setFavs is async and favs is not yet set on line 16. Calling setFavs will cause your UI to re-render, eventually.
You won't see it in your other useEffect on line 35, either, because that useEffect hook also only runs on first render ([]), so that value is not there yet on first render. To see all updates to favs, try adding it to the dependencies array (like [favs]) or remove the dependencies array altogether.
Change your useEffect on the FavoritesList to:
useEffect(() => {
console.log(favs)
}, [favs])
That way the useEffect will watch for any changes in the favs props. Using an empty array means that the useEffect will only trigger in the first render of the functional component. Meaning that it will not be triggered in any of the props change.
I think above two answers made everyhing clean, here is your lifecycle;
1 -> Favorites component initialized and rendered (state favs = [empty])
2 -> FavoritesList component initialized and rendered (prop favs = [empty])
3 -> FavoritesList useEffect called (console.log(favs) => [empty array])
4 -> Favorites useEffect called (state favs = [now has data])
5 -> FavoritesList component props is updated (prop favs = [has data]
(prop is updated but you didnt call console.log again, after update)
As you can see FavoritesList useEffect called before Favorites useEffect so it called when favs has no data.
So if you add favs prop to array in useEffect in FavoritesList component, you will have one more step;
6 -> FavoritesList useEffect called again (console.log(favs) => [has data])
Because useEffect looks for that arrays elements and if one the elements changed it will triger itself again.
Here is your code with working example: codesandbox.io
On every click of increment button:
Expectation: current count is logged
Reality: initial value of count, i.e. 3 is logged
import React, { useState, useEffect } from "react";
function SomeLibrary(props) {
const [mapState, setMapState] = useState(undefined);
useEffect(() => {
console.log("setting map");
// Run exactly once at mount of component
setMapState(props.map);
}, []);
useEffect(() => {
if (mapState) {
mapState.key();
}
}, [props]);
return <div> ... </div>;
}
export default function App() {
const [count, setCount] = React.useState(3);
const map = { key: () => {
console.log("fn", count);
}};
return (
<div>
Active count: {count} <br />
<button onClick={() => {
setCount(count + 1);
}}
>
Increment
</button>
<SomeLibrary map={map} />
</div>
);
}
Run here
Does the object in JS locks the values of variables inside it after initializing?
I want to know the reason why function in object doesn't use the current value of count whenever invoked but React ref gets the current value in that same scenario
I don't understand why this works:
Replace the map variable with this:
const [count, setCount] = React.useState(3);
const stateRef = useRef();
stateRef.current = count;
const map = { key: () => {
console.log("fn", stateRef.current);
}};
Does the object in JS locks the values of variables inside it after initializing?
No.
You're effectively setting state of SomeLibrary with an initial value when it mounts, and never again updating that state, so it continually logs its initial value.
const [mapState, setMapState] = useState(undefined);
useEffect(() => {
console.log("setting map");
// Run only once at mount of component
setMapState(props.map); // <-- no other `setMapState` exists
}, []); // <-- runs once when mounting
By simply adding props.map to the dependency array this effect runs only when map updates, and correctly updates state.
useEffect(() => {
console.log("setting map");
// Run only once at mount of component
setMapState(props.map);
}, [props.map]);
Notice, however, the state of SomeLibrary is a render cycle behind that of App. This is because the value of the queued state update in SomeLibrary isn't available until the next render cycle. It is also an anti-pattern to store passed props in local component state (with few exceptions).
Why React ref gets the current value in that same scenario?
const [count, setCount] = React.useState(3);
const stateRef = useRef();
stateRef.current = count; // <-- stateRef is a stable reference
const map = { key: () => {
console.log("fn", stateRef.current); // <-- ref enclosed in callback
}};
When react component props or state update, a re-render is triggered. The useRef does not, it's used to hold values between or through render cycles, i.e. it is a stable object reference. This reference is enclosed in the callback function in the map object passed as a prop. When the count state updates in App a rerender is triggered and stateRef.current = count; updates the value stored in the ref, i.e. this is akin to an object mutation.
Another piece to the puzzle is functional components are always rerendered when their parent rerenders. The passed map object is a new object when passed in props.
It's this rerendering that allows SomeLibrary to run the second effect to invoke the non-updated-in-state callback mapState.key, but this time the object reference being console logged has been mutated.
I have got an hook who catch getBoundingClientRect object of a ref DOM element. The problem is, at the first render, it return null and I need to get the value only on first render on my component.
I use it like that in a functional component:
const App = () => {
// create ref
const rootRef = useRef(null);
// get Client Rect of rootRef
const refRect = useBoundingClientRect(rootRef);
useEffect(()=> {
// return "null" the first time
// return "DOMRect" when refRect is update
console.log(refRect)
}, [refRect])
return <div ref={rootRef} >App</div>
}
Here the useBoundingClientRect hook, I call in App Component.
export function useBoundingClientRect(pRef) {
const getBoundingClientRect = useCallback(() => {
return pRef && pRef.current && pRef.current.getBoundingClientRect();
}, [pRef]);
const [rect, setRect] = useState(null);
useEffect(() => {
setRect(getBoundingClientRect());
},[]);
return rect;
}
The problem is I would like to cache boundingClientRect object on init and not the second time component is rerender :
// App Component
useEffect(()=> {
// I would like to get boundingClientRect the 1st time useEffect is call.
console.log(refRect)
// empty array allow to not re-execute the code in this useEffect
}, [])
I've check few tutorials and documentations and finds some people use useRef instead of useState hook to keep value. So I tried to use it in my useboundingClientRect hook to catch and return the boundingClientRect value on the first render of my App component. And it works... partially:
export function useBoundingClientRect(pRef) {
const getBoundingClientRect = useCallback(() => {
return pRef && pRef.current && pRef.current.getBoundingClientRect();
}, [pRef]);
const [rect, setRect] = useState(null);
// create a new ref
const rectRef = useRef(null)
useEffect(() => {
setRect(getBoundingClientRect());
// set value in ref
const rectRef = getBoundingClientRect()
},[]);
// return rectRef for the first time
return rect === null ? rectRef : rect;
}
Now the console.log(rectRef) in App Component allow to access the value on first render:
// App Component
useEffect(()=> {
console.log(refRect.current)
}, [])
But If I try to return refRect.current from useBoundingClientRect hook return null. (What?!)
if anyone can explain theses mistakes to me. Thanks in advance!
You need to understand references, mututation, and the asynchronous nature of updates here.
Firstly, when you use state to store the clientRect properties when your custom hook in useEffect runs, it sets value in state which will reflect in the next render cycle since state updates are asynchronous. This is why on first render you see undefined.
Secondly, when you are returning rectRef, you are essentially returning an object in which you later mutate when the useEffect in useBoundingClientRect runs. The data is returned before the useEffect is ran as it runs after the render cycle. Now when useEffect within the component runs, which is after the useEffect within the custom hook runs, the data is already there and has been updated at its reference by the previous useEffect and hence you see the correct data.
Lastly, if you return rectRef.current which is now a immutable value, the custom hook updates the value but at a new reference since the previous one was null and hence you don't see the change in your components useEffect method.
I've been learning React and I read that the function returned from useEffect is meant to do cleanup and React performs the cleanup when the component unmounts.
So I experimented with it a bit but found in the following example that the function was called every time the component re-renders as opposed to only the time it got unmounted from the DOM, i.e. it console.log("unmount"); every time the component re-renders.
Why is that?
function Something({ setShow }) {
const [array, setArray] = useState([]);
const myRef = useRef(null);
useEffect(() => {
const id = setInterval(() => {
setArray(array.concat("hello"));
}, 3000);
myRef.current = id;
return () => {
console.log("unmount");
clearInterval(myRef.current);
};
}, [array]);
const unmount = () => {
setShow(false);
};
return (
<div>
{array.map((item, index) => {
return (
<p key={index}>
{Array(index + 1)
.fill(item)
.join("")}
</p>
);
})}
<button onClick={() => unmount()}>close</button>
</div>
);
}
function App() {
const [show, setShow] = useState(true);
return show ? <Something setShow={setShow} /> : null;
}
Live example: https://codesandbox.io/s/vigilant-leavitt-z1jd2
React performs the cleanup when the component unmounts.
I'm not sure where you read this but this statement is incorrect. React performs the cleanup when the dependencies to that hook changes and the effect hook needs to run again with new values. This behaviour is intentional to maintain the reactivity of the view to changing data. Going off the official example, let's say an app subscribes to status updates from a friends' profile. Being the great friend you are, you are decide to unfriend them and befriend someone else. Now the app needs to unsubscribe from the previous friend's status updates and listen to updates from your new friend. This is natural and easy to achieve with the way useEffect works.
useEffect(() => {
chatAPI.subscribe(props.friend.id);
return () => chatAPI.unsubscribe(props.friend.id);
}, [ props.friend.id ])
By including the friend id in the dependency list, we can indicate that the hook needs to run only when the friend id changes.
In your example you have specified the array in the dependency list and you are changing the array at a set interval. Every time you change the array, the hook reruns.
You can achieve the correct functionality simply by removing the array from the dependency list and using the callback version of the setState hook. The callback version always operates on the previous version of the state, so there is no need to refresh the hook every time the array changes.
useEffect(() => {
const id = setInterval(() => setArray(array => [ ...array, "hello" ]), 3000);
return () => {
console.log("unmount");
clearInterval(id);
};
}, []);
Some additional feedback would be to use the id directly in clearInterval as the value is closed upon (captured) when you create the cleanup function. There is no need to save it to a ref.
The React docs have an explanation section exactly on this.
In short, the reason is because such design protects against stale data and update bugs.
The useEffect hook in React is designed to handle both the initial render and any subsequent renders (here's more about it).
Effects are controlled via their dependencies, not by the lifecycle of the component that uses them.
Anytime dependencies of an effect change, useEffect will cleanup the previous effect and run the new effect.
Such design is more predictable - each render has its own independent (pure) behavioral effect. This makes sure that the UI always shows the correct data (since the UI in React's mental model is a screenshot of the state for a particular render).
The way we control effects is through their dependencies.
To prevent cleanup from running on every render, we just have to not change the dependencies of the effect.
In your case concretely, the cleanup is happening because array is changing, i.e. Object.is(oldArray, newArray) === false
useEffect(() => {
// ...
}, [array]);
// ^^^^^ you're changing the dependency of the effect
You're causing this change with the following line:
useEffect(() => {
const id = setInterval(() => {
setArray(array.concat("hello")); // <-- changing the array changes the effect dep
}, 3000);
myRef.current = id;
return () => {
clearInterval(myRef.current);
};
}, [array]); // <-- the array is the effect dep
As others have said, the useEffect was depending on the changes of "array" that was specified in the 2nd parameter in the useEffect. So by setting it to empty array, that'd help to trigger useEffect once when the component mounted.
The trick here is to change the previous state of the Array.
setArray((arr) => arr.concat("hello"));
See below:
useEffect(() => {
const id = setInterval(() => {
setArray((arr) => arr.concat("hello"));
}, 3000);
myRef.current = id;
return () => {
console.log("unmount");
clearInterval(myRef.current);
};
}, []);
I forked your CodeSandbox for demonstration:
https://codesandbox.io/s/heuristic-maxwell-gcuf7?file=/src/index.js
Looking at the code I could guess its because of the second param [array]. You are updating it, so it will call a re-render. Try setting an empty array.
Every state update will call a re-render and unmount, and that array is changing.
It seems expected. As per the documentation here, useEffect is called after first render, every update and unmount.
https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
Tip
If you’re familiar with React class lifecycle methods, you can think
of useEffect Hook as componentDidMount, componentDidUpdate and before
componentWillUnmount combined.
This is a Jest test that shows the render and effect order.
As you can see from the expect, once the dependency foo changes due to the state update it triggers a NEW render followed by the cleanup function of the first render.
it("with useEffect async set state and timeout and cleanup", async () => {
jest.useFakeTimers();
let theRenderCount = 0;
const trackFn = jest.fn((label: string) => { });
function MyComponent() {
const renderCount = theRenderCount;
const [foo, setFoo] = useState("foo");
useEffect(() => {
trackFn(`useEffect ${renderCount}`);
(async () => {
await new Promise<string>((resolve) =>
setTimeout(() => resolve("bar"), 5000)
);
setFoo("bar");
})();
return () => trackFn(`useEffect cleanup ${renderCount}`);
}, [foo]);
++theRenderCount;
trackFn(`render ${renderCount}`);
return <span data-testid="asdf">{foo}</span>;
}
const { unmount } = render(<MyComponent></MyComponent>);
expect(screen.getByTestId("asdf").textContent).toBe("foo");
jest.advanceTimersByTime(4999);
expect(screen.getByTestId("asdf").textContent).toBe("foo");
jest.advanceTimersByTime(1);
await waitFor(() =>
expect(screen.getByTestId("asdf").textContent).toBe("bar")
);
trackFn("before unmount");
unmount();
expect(trackFn.mock.calls).toEqual([
['render 0'],
['useEffect 0'],
['render 1'],
['useEffect cleanup 0'],
['useEffect 1'],
['before unmount'],
['useEffect cleanup 1']
])
});