updated value for generic hooks - reactjs

I have this hook
export const useLegalFooter = ({ customContent, isOpenOnPageLoad = false }) => {
const dispatch = useContext(LegalFooterDispatchContext);
useEffect(() => {
dispatch({ customContent, isOpenOnPageLoad });
return () => {
dispatch({ customContent: null, isOpenOnPageLoad: false });
};
}, [customContent, dispatch, isOpenOnPageLoad]);
};
and the way I use it is
useLegalFooter({
isOpenOnPageLoad: !showModalLoader,
customContent: renderQuoteAndPurchaseDisclaimers(policyType),
});
However, I noticed when the showModalLoader is updated the hook doesn't get an updated value for isOpenonPageLoad.
for example, showModalLoader is true when the component mounts, and then it changes to false. but still, the footer is showing open.
How to fix this?

Related

Arrow function does not notify redux state changes

I have a react component in react native that I want to handle hardwareBackButton manually.
I have different behavior when a redux state is true or false in backHandler function that I pass to hardwareBackPressListener.
const brandSelected = useSelector(state => state.map.brandSelected);
I have this useSelector in my component to access the state.
and I have useEffect function that I monitor the changes of this state: (that has correctly work and log the state when it changes to true or false.
React.useEffect(() => {
console.log(brandSelected); // this is false correctly
}, [brandSelected]);
and finally I have a backHandler function that I pass it to hardwareBackPress Listener.
React.useEffect(() => {
BackHandler.addEventListener('hardwareBackPress', backHandler);
return () => {
BackHandler.removeEventListener('hardwareBackPress', backHandler);
};
}, []);
and backHandler function:
const backHandler = () => {
console.log('check, backhandler', brandSelected) // it logs true continuously
if (brandSelected === true) {
dispatch(
dispatchItemToRedux({
type: CATEGORIES_SELECTION,
payload: {
brandSelected: false,
},
}),
);
return true;
}
popScreen(Screens.Map);
return true;
};
But this function does not notify that the brandSelected state changed. the first time it works correctly and dispatch function and changes the redux state correctly and useEffect function log false correctly. but in other tries it does not work correctly and nothing changed!!
The issue here is a stale enclosure of the brandSelected in the backHandler function you passed to the "hardwareBackPress" event listener on the initial render cycle. backHandler function only ever has the value from the initial render cycle and will never update/re-enclose an updated value.
To resolve you should cache the backHandler state value in a React ref that you can reference in the callback handler.
const brandSelected = useSelector(state => state.map.brandSelected);
const brandSelectedRef = React.useRef(brandSelected);
useEffect(() => {
brandSelectedRef.current = brandSelected;
}, [brandSelected]);
...
const backHandler = () => {
console.log('check, backhandler', brandSelectedRef.current)
if (brandSelectedRef.current) {
dispatch(
dispatchItemToRedux({
type: CATEGORIES_SELECTION,
payload: {
brandSelected: false,
},
}),
);
return true;
}
popScreen(Screens.Map);
return true;
};
An alternative would be to move backHandler into the useEffect hook setting the event listeners and use the brandSelected state as a dependency so the updated state value is re-enclosed in the callback.
React.useEffect(() => {
const backHandler = () => {
console.log('check, backhandler', brandSelected)
if (brandSelected) {
dispatch(
dispatchItemToRedux({
type: CATEGORIES_SELECTION,
payload: {
brandSelected: false,
},
}),
);
return true;
}
popScreen(Screens.Map);
return true;
};
BackHandler.addEventListener('hardwareBackPress', backHandler);
return () => {
BackHandler.removeEventListener('hardwareBackPress', backHandler);
};
}, [brandSelected]);

How to get useEffect to only update on changes when the change is from another component

I am using zustand for state management and am trying to update a component in real time only without refreshing the page when there are changes in the state of the component after retrieving the data once.
Here is my store
export const useStore = create((set) => ({
reservations: [],
getReservations: async () => {
const response = await axios.get(baseUrl);
set({ reservations: response.data });
},
setRev: (reservations) => {
set((state) => ({
...state,
reservations,
}));
},
addReservation: (reservation) => {
set((state) => ({ reservations: [...state.reservations, reservation] }));
},
removeReservation: (id) => {
set((state) => ({
reservations: state.reservations.filter(
(reservation) => id !== reservation._id
),
}));
},
}));
I have tried using the useEffect hook to retrieve the data as shown in the code below, I have another component which calls the addReservation function. The code below results in calling useEffect infinitely instead of updating only when there are changes to const reservations, when another component calls the addReservation function.
const getAllReservation = useStore((state) => state.getReservations);
const reservations = useStore((state) => state.reservations);
const reservationsRef = useRef(useStore.getState().reservations);
useEffect(() => {
getAllReservation()
useStore.subscribe(
(reservations) => (reservationsRef.current = reservations),
(state) => state.reservations
);
}, [reservations]);
I have tried splitting the useEffects as such but the page needs to be refreshed again to show the updated data. (desired outcome is without refreshing)
useEffect(() => {
getAllReservation()
}, [])
useEffect(() => {
useStore.subscribe(
(reservations) => (reservationsRef.current = reservations),
(state) => state.reservations
);
}, [reservations]);
I have tried putting [] as the dependency array as the second argument in the useEffect hook but it does not work as well.
Thank you for your help.
React can't detect changes inside of Ref. So need to pass updated reservations as props or save in local state.
Zustand docs
The subscribe function allows components to bind to a state-portion
without forcing re-render on changes. Best combine it with useEffect
for automatic unsubscribe on unmount. This can make a drastic
performance impact when you are allowed to mutate the view directly.
const useStore = create(set => ({ scratches: 0, ... }))
function Component() {
// Fetch initial state
const scratchRef = useRef(useStore.getState().scratches)
// Connect to the store on mount, disconnect on unmount, catch state-changes in a reference
useEffect(() => useStore.subscribe(
scratches => (scratchRef.current = scratches),
state => state.scratches
), [])

code using useParams, useEffect and useState hooks

Can someone please tell me the equivalent code using hooks for the following:
componentDidMount() {
const { match: { params } } = this.props;
axios.get(`/api/users/${params.userId}`)
.then(({ data: user }) => {
console.log('user', user);
this.setState({ user });
});
}
The exact functionality to match your class component into a functional component with hooks would be the following:
import * as React from "react";
import { useParams } from "react-router-dom";
const Component = () => {
const { userId } = useParams();
const [state, setState] = React.useState({ user: null });
React.useEffect(() => {
axios.get(`/api/users/${userId}`)
.then(({ data: user }) => {
console.log('user', user);
setState({ user });
});
}, []);
}
React.useEffect(() => {}, []) with an empty dependency array essentially works the same way as the componentDidMount lifecycle method.
The React.useState hook returns an array with the state and a method to update the state setState.
References:
https://reactjs.org/docs/hooks-state.html
https://reactjs.org/docs/hooks-effect.html
As an aside, and pointed out by #Yoshi:
The snippet provided is error prone, and the "moving to hooks" snippet will have the same errors that occur in the example. For example, as the request is in componentDidMount, if the userId changes it won't trigger a fetch to get the user data for the userId. To ensure this works in the hook, all you need to do is provide the userId in the dependency array in the useEffect...
const latestRequest = React.useRef(null);
React.useEffect(() => {
latestRequest.current = userId;
axios.get(`/api/users/${userId}`)
.then(({ data: user }) => {
if (latestRequest.current == userId) {
setState({ user });
}
});
}, [userId]);

How can i rewrite react-method with setState and callback using hooks?

I have a react component that contains the method with setState with a callback. I need to rewrite it to hooks. Please tell me how can i rewrite this method ?
beforeSubmitModal = action => (args) => {
this.setState({
visible: false,
selectedMenuItem: null,
companyCodeModal: {}
}, () => action(args));
};
const onDeleteCode = (id) => {
dispatch(actions.deleteCode.request({ codeId: id }));
};
const modalProps = {
onSaveOrUpdate: beforeSubmitModal(dispatch(actions.insertOrEditCode.request())),
onDelete: beforeSubmitModal(onDeleteCode),
};
you will need to use useEffect to do this
const [visible,setVisible] = useState(ture);
const doSomething = () => {
setVisible(false);
}
useEffect(() => {
//this will render every time the visible state changes
}, [visible]);
to define the states in hooks
const [visible,setVisible]=useState(false) // initial value false
const [selectedMenuItem,setCompanyCodeModal]=useState(null) // initial value null
const [companyCodeModal,setCompanyCodeModal]=useState('')
you need when they change do some action
useEffect(()=> doSomething() ,[visible,selectedMenuItem,companyCodeModal])

The action always dispatch and it does not stop, it runs infinitely

I have question about dispatch action. I do not know why my dispatch redux run infinitely.
Below is my ListUser component
import { ListUsersAction } from "../actions/ListUsersAction";
const ListUsers = props => {
var resPerPage = configList.users.resPerPage;
props.ListUsersAction(resPerPage, 1);
if (props.listUsersReducer.thanhvien.length > 0) {
const listUsersReducer = props.listUsersReducer;
const propToSend = {
currentPage: listUsersReducer.currentPage,
pages: listUsersReducer.pages,
resPerPage: listUsersReducer.resPerPage
};
return (
<Fragment>
<Pagination pageProp={propToSend} />
</Fragment>
);
} else {
return null;
}
};
const mapStateToProp = state => ({
listUsersReducer: state.listUsersReducer
});
export default connect(mapStateToProp, { ListUsersAction })(ListUsers);
and here is ListUserAction
export const ListUsersAction = (resPerPage, currentPage) => async dispatch => {
if (localStorage.token) {
setAuthToken(localStorage.token);
}
try {
const res = await axios.get('/api/admin/users/page/:page', {
params: {
page: currentPage,
resPerPage: resPerPage
}
});
dispatch({
type: LOADUSERS,
payload: res.data
});
}catch(err){
console.log(err);
dispatch({
type: STOPLOADUSERS
})
}
}
You can see the action always render
Can you tell me why and how to fix it?
You are calling your action every time your Component re renders, and calling your action is causing your Component to re render, creating an infinite loop.
Put your action inside a useEffect to prevent this and only call it once on component mount or whenever you want based on the dependency array:
useEffect(() => {
var resPerPage = configList.users.resPerPage;
props.ListUsersAction(resPerPage, 1);
},[])
const ListUsers = props => {
React.useEffect(()=>{
var resPerPage = configList.users.resPerPage;
props.ListUsersAction(resPerPage, 1);
},[])
// your code
};
try this
functional component render every times,
thats why it happend
check hooks API useEffect

Resources