React.js: useState Hook Setter is not updating the state - reactjs

I am trying to update the state conditionally to trigger visibility.
However when I pass a initial value to the useState hook, it updates only once.
Code 1: Updating only once when there is an initial value
const ImageRef = useRef(null);
// Initial Value Passed
const [isHidden, setShown] = useState(true);
const _HandleShown = () => {
const nextState = window.scrollY > ImageRef.current.offsetHeight;
if (isHidden !== nextState) {
setShown(nextState);
}
};
Code 2: Working as expected when there is no initial value
const ImageRef = useRef(null);
// No Initial Value Passed
const [isHidden, setShown] = useState();
const _HandleShown = () => {
const nextState = window.scrollY > ImageRef.current.offsetHeight;
if (isHidden !== nextState) {
setShown(nextState);
}
};

I guess _HandleShown is use in addEventListener..., in that case you want to use isHidden to compare you need to do this, because of closure in JS.
const ImageRef = useRef(null);
// Initial Value Passed
const [isHidden, setShown] = useState(true);
const _HandleShown = () => {
const nextState = window.scrollY > ImageRef.current.offsetHeight;
setShown(prevIsHidden =>{
if (prevIsHidden !== nextState) {
return nextState
}
return prevIsHidden
});
};

Related

A variable doesn't update when using the useState() hook

I'm doing a social networking project on React
I wanted to replace one component from class - to functional and use hooks, and a global problem appeared:
When I go to a new user, the page displays the status of the previous one
I use useState() hook, debugged everything, but for some reason when a new status component is rendered, it doesn't update
const ProfileStatus = (props) => {
const [edditMode, setEdditMode] = useState(false);
const [status, setValue] = useState(props.status || "Empty");
const onInputChange = (e) => {
setValue(e.target.value);
};
const activateMode = () => {
setEdditMode(true);
};
const deactivateMode = () => {
setEdditMode(false);
props.updateUserStatus(status);
};
I thought the problem was that the container component was still a class component, but by redoing it, nothing has changed
One way to solve this is by using the useEffect hook to trigger an update when props change. You can use the hook to do comparison between current props and previous props, then update status in the state.
Use this as reference and adapt according to your own code.
const ProfileStatus = (props) => {
const [edditMode, setEdditMode] = useState(false);
const [status, setValue] = useState(props.status || "Empty");
useEffect(() => {
setValue(props.status || "Empty");
}, [props.status]);
const onInputChange = (e) => {
setValue(e.target.value);
};
const activateMode = () => {
setEdditMode(true);
};
const deactivateMode = () => {
setEdditMode(false);
props.updateUserStatus(status);
};

usestate is not getting updated on component rerender

I have one customer component. And it's getting called twice. One to create the customer and the second to edit it. When I call this component first-time values are getting initialized and updated correctly. When I render a component a second time for editing customer values are not getting updated via the use effect.
const AddCustomer = (props) => {
console.log("Props",props)
const {customerId} = useParams();
const [customerName, setcustomerName] = useState("");
const [customerKey, setcustomerKey] = useState("");
const [formErrors, setFormErrors] = useState({});
const [apiError, setApiError] = useState(null);
const [isSubmit, setIsSubmit] = useState(false);
const [loadSpinner, setLoadSpinner] = useState(false);
useEffect(() => {
if(props.isEdit) {
populateExistingFormData();
}
if (Object.keys(formErrors).length === 0 && isSubmit) {
callAPI();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formErrors, isSubmit, props.isEdit, props]);
const populateExistingFormData = () => {
setCustomerId(props.customerId)
setCustomerName(props.serviceProviderCode)
setKey(props.key)
setIsSubmit(props.isSubmit); //false value from props
setLoadSpinner(props.loadSpinner);
} }
As I am passing submit value as a false from props. But it's set up as true again (value showing from previous component render) and its calls then call API. how can I update the state on component re-render?

How to access state inside a listener inside a useEffect?

I understand that without dependencies you cannot access state inside useEffect. An example as such :
const [state, setState] = React.useState('')
const ws = new WebSocket(`ws://localhost:5000`)
React.useEffect(() => {
ws.onopen = () => {
console.log(state)
},[])
In this case state comes back as empty even though it has changed in other parts of the code. To solve this in principle I should add state as dependency to the useEffect hook. However, this will trigger the listener again and I do not want to have 2 active listeners on my websocket or being forced to close and reopen it again as such :
React.useEffect(() => {
ws.onopen = () => {
console.log(state)
},[state])
What is good practice when it comes to accessing state inside a listener that sits inside useEffect hook?
IF you need to re-render the component when the state changes try this:
const [state, setState] = React.useState('');
const stateRef = React.useRef(state);
React.useEffect(() => {
stateRef.current = state;
}, [state])
const ws = React.useMemo(() => {
const newWS = new WebSocket(`ws://localhost:5000`);
newWS.onopen = () => {
console.log(stateRef.current);
}
return newWS;
}, []);
This way you create the ws only once, and it will use the state reference which will be up to date because of the useEffect.
If you don't need to re-render the component when the state updates you can remove const [state, setState] = React.useState(''); and the useEffect and just update the stateRef like this when you need.
Like this:
const stateRef = React.useRef(null);
const ws = React.useMemo(() => {
const newWS = new WebSocket(`ws://localhost:5000`);
newWS.onopen = () => {
console.log(stateRef.current);
}
return newWS;
}, []);
// Update the state ref when you need:
stateRef.current = newState;
Best Practice get data in listeners.[UPDATED]!
const [socketData , setSocketData] = useState(null);
useEffect(() => {
websocet.open( (data) => {
setSocketData(data);
})
},[])
//second useEffect to check socketData
useEffect(() => {
if(socketData){
// access to data which come from websock et
}
},[socketData])

How to differentiate context values if their state changed in React useEffect

const someFunction = () = {
...
const { data, setData, activeIndex, setActiveIndex } = useContext(MusicContext);
useEffect(() => {
console.log(" useEffect called");
// another component called setData or setActiveIndex which resulted here
// how to compare data to its prevState if it changed
// how to compare activeIndex to its prevState if it changed
}, [activeIndex, data]);
...
}
Above is some function which has a useEffect for two different context variables
data is an object proptype {} and activeIndex is a number
how do i compare data to its prevState if it changed?
how do i compare activeIndex to its prevState if it changed?
can i do it in a single useEffect block and need to open multiple?
You can useRef to store the last seen value. This is often extracted into a "usePrevious" custom hook.
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
Now in your component, you can check against the previous value.
const SomeFunction = () = {
...
const { data, setData, activeIndex, setActiveIndex } = useContext(MusicContext);
const prevData = usePrevious( data );
const prevActiveIndex = usePrevious( activeIndex );
useEffect(() => {
if ( data !== prevData ) {
// do something
}
if ( activeIndex !== prevActiveIndex ) {
// do something
}
}, [activeIndex, data]);
...
}

React Hook useState Constructor with Asyncstorage

I am trying to set the initial state with asyncstorage. Is there a way to do this?
const _retrieveUser = async () => {
return await AsyncStorage.getItem('authUser');
}
const [user, setUser] = useState(_retrieveUser().then(user => user) || null);
The initial value will have to be something that's synchronously available. Then you can do your async work in an effect. If necessary, you can render a placeholder while this work is in progress
const [user, setUser] = useState(null);
useEffect(() => {
_retrieveUser().then(setUser);
}, []);
if (user === null) {
return <div>Loading...</div>
} else {
// return the real component
}
you can pass a function inside useState method to manage the initial state in another simple way:
const [user, setUser] = useState(async () => {
const data = await AsyncStorage.getItem('authUser') //get data and store them in constat
setUser(data || null) //here the state set self
}
);

Resources