useState updates twice - reactjs

I have a component that gets a value from the local storage and does a useQuery to get some data:
const DashboardComponent = () => {
const [filterState, setFilter] = useState(false);
const returnFilteredState = async () => {
return await localforage.getItem<boolean>('watchedAndReviewedFilterd') || false;
};
useEffect(() => {
returnFilteredState().then((value) => {
setFilter(value);
});
}, []);
const {error, loading, data: {moviesFromUser: movies} = {}} =
useQuery(resolvers.queries.ReturnMoviesFromUser, {
variables: {
userId: currentUserVar().id,
filter: filterState,
},
});
The problem is that the ReturnMoviesFromUser query is called twice. I think it's because of the filterState variable. If I set the filter: true the ReturnMoviesFromUser is only called once.

Related

Jest-React hook Testing : How to call a setState inside a custom function using useEffect

I am trying to set mockPatient data and wanted to test if the 'sortByCaseFn ' function is called by the useEffect.
Here is my sourcecode:
Patient.tsx
const [patients, setPatients] = useState([]);
const [sortBy, setSortBy] = useState('events');
const [fetched, setFetched] = useState(false);
const dispatch = useAppDispatch();
const props = useAppSelector((state) => state.myPatientProps);
const getPatientData = (): void => {
dispatch(MyPatientActions.getMyPatientsData());
};
const sortByCaseFn = (sortBy, list) => {
let patientsToSort = [...list];
if (sortBy.includes('events'))
patientsToSort.sort(
sorter.byPropertiesOf(['-ActiveEventsCount', 'LastName'])
);
if (sortBy.includes('vae'))
patientsToSort.sort(sorter.byPropertiesOf(['-VaeStatus']));
console.log('patientsToSort---', patientsToSort);
setPatients(patientsToSort);
};
useEffect(() => {
if (!fetched) {
getPatientData();
}
}, []);
useEffect(() => {
console.log('setpatients called .. ', patients);
}, [patients]);
useEffect(() => {
const saved_sortby = localStorage.getItem('sortby');
if (saved_sortby) {
sortByCaseFn(saved_sortby, props.myPatientDetails);
} else sortByCaseFn('events', props.myPatientDetails);
setFetched(true);
}, [props.myPatientDetails]);
useEffect(() => {
sortByCaseFn(sortBy, patients);
}, [sortBy]);
return (
<> Render Patient List </> )
My Test Code :
Patients.test.tsx
jest.mock('react-redux', () => ({
useSelector: jest.fn(),
useDispatch: jest.fn()
}));
export const setHookTestState = (newState: any) => {
const setStateMockFn = () => {};
return Object.keys(newState).reduce((acc, val) => {
acc = acc?.mockImplementationOnce(() => [newState[val], setStateMockFn]);
return acc;
}, jest.fn());
};
describe('My Patient Screen', () => {
const useSelectorMock = reactRedux.useSelector as jest.Mock<any>;
const useDispatchMock = reactRedux.useDispatch as jest.Mock<any>;
beforeEach(() => {
useSelectorMock.mockImplementation((selector) => selector(mockStore));
useDispatchMock.mockImplementation(() => () => {});
});
afterEach(() => {
useDispatchMock.mockClear();
useSelectorMock.mockClear();
});
const mockInitialState = {
myPatientDetails: vaeMock,
fetching: false,
failedMsg: '',
requestPayload: {}
};
const mockStore = {
counter: undefined,
menu: undefined,
selectPatientProps: undefined,
myPatientProps: mockInitialState
};
test('validate sorting by events', async (done) => {
React.useState = setHookTestState({
patients: vaeMock,
sortBy: 'vae',
fetched: 'false'
});
const {
getByText,
getByRole,
getByTestId,
getAllByTestId,
findAllByTestId,
queryByText,
container
} = render(<Mypatient />);
await waitFor(() => {
expect(getByText('Ander, Sam')).toBeDefined();
});
const list = getAllByTestId('patientname');
expect(within(list[0]).getByText('Sara, Jone')).toBeInTheDocument(); //Fails here as Sorting doesnt happen
console.log('....list ', list);
});
});
My Observations:
The 'vaeMock' data that I set in redux state 'mockInitialState' is successfully sent as props
The 'vaeMock' data that I set in component state using setHookTestState is also set successfully.
The lifecycle events happens like this -
a. setPatients() is called using the component state data.
b. using props that is sent , sortByCaseFn is called but setPatients is not called.
c. again using the component state , sortByCaseFn is called but setPatients is not set.
Without setting the component state variables runs into a TypeError: Undefined is not iterable.
All Iam trying to do is - send a mockData to a component that uses useDispatch, useEffects
and sort the data on the component mount and initialize to local state variable.

Calling a function after updating the state more than 1 time

I am storing some data in the state. I first call 1 API and get the update the data in the state 3 times. After the data update, I have to call another API using the updated state data. I am unable to do so . If the state was updated just once, I would have use useEffect. But, here it is changing 3 times.
const [data, setData] = useState(() => getDataInit());
const setDataKey = key => value =>
setData(d => ({
...d,
[key]: value,
}));
const postFn = () => {
const response = await updateData({ body: data });
onSave({
response,
widget,
wrapperId,
});
};
const resetFn = () => {
const defaultData = await getDefaultData({
query: { id: data.default_id },
});
setDataKey('quantity')(defaultData.quantity);
setDataKey('name')(defaultData.name);
setDataKey('amount')(defaultData.amount);
postFn();
};
You can update to call only one setData. So you can add useEffect to call postFn
const resetFn = () => {
const defaultData = await getDefaultData({
query: { id: data.default_id },
});
const newData = {
quantity: defaultData.quantity,
name: defaultData.name,
amount: defaultData.amount,
};
setData((d) => ({
...d,
...newData,
}));
}
useEffect(() => {
postFn();
}, [data]);

react state not updating inside callabck

I'm not understanding why the following code, the callback onSocketMessage is not using the new acquisition state. inside the useEffect the state is correctly updated, but the function is not evaluated again...i've also tryed using useCallback with acquisition as dependency but nothing changed.
const Ac = () => {
const [acquisition, setAcquisition] = useState({ data: {} })
const [loading, setLoading] = useState(true)
const socket = useRef(null);
const onSocketMessage = (message) => {
console.log(acquisition) // this is always initial state
let { data } = acquisition
data.input[message.index] = message.input
setAcquisition(prevState => ({ ...prevState, data }));
}
useEffect(() => {
fetchCurrentAcquisition(acquisition => {
setAcquisition(acquisition)
setLoading(false)
socket.current = newSocket('/acquisition', () => console.log('connected'), onSocketMessage);
})
return () => socket.current.disconnect()
}, [])
console.log(acquisition)
You are logging a stale closure you should try the following instead:
const onSocketMessage = useCallback((message) => {
setAcquisition((acquisition) => {
//use acquisition in the callback
console.log(acquisition);
//you were mutating state here before
return {
...acquisition,
data: {
...acquisition.data,
input: {
//not sure if this is an array or not
//assimung it is an object
...acquisition.data.input,
[message.index]: message.input,
},
},
};
});
}, []); //only created on mount
useEffect(() => {
fetchCurrentAcquisition((acquisition) => {
setAcquisition(acquisition);
setLoading(false);
socket.current = newSocket(
'/acquisition',
() => console.log('connected'),
onSocketMessage
);
});
return () => socket.current.disconnect();
//onSocketMessage is a dependency of the effect
}, [onSocketMessage]);

react hooks useState consuming object

I am not sure how to make it correctly so I can pass object to useState
const App = () => {
const [weatherData, setWeatherData] = useState({data: "", time: ""});
useEffect(() => {
axios.get(apiUrl).then(response => {
setWeatherData({...weatherData, data: response.data, time: timestamp});
});
}, []);
return <div>{weatherData && <Weather data={weatherData.data} />}</div>;
};
when I do the same just with useState() and setWeatherData(response.data) it works fine but I would like to add the time
Have you tried the following:
setWeatherData({
...response.data,
time: timestamp,
});
P.S. Let me know if I understood you correctly.
UPD
Other option, if you need to access the current state:
useEffect(() => {
axios.get(apiUrl).then(response => {
const timestamp = Date.now().timestamp;
setWeatherData((prevWeatherData) => ({
...prevWeatherData,
data: response.data,
time: timestamp,
}));
});
}, []);
Try this:
const App = () => {
const [weatherData, setWeatherData] = useState(null);
useEffect(() => {
async function fetchWeather () {
const response = await axios.get(apiUrl)
setWeatherData({data: response.data, time: new Date().getTime()});
}
fetchWeather()
}, [weatherData]);
return (
<>
{weatherData && <Weather data={weatherData.data} />}
</>
);
};

useQuery hook didn't update on response

I'm try to return user to previous step after some updates on backend via useQuery apollo hook
export const useOrderUnlock = () => {
const [isLoading, setIsLoading] = useState(false);
const isBlocked = useSelector(getCheckinIsBlockedForPayment);
const orderId = useSelector(getCheckinOrderId);
const dispatch = useDispatch();
const { goToNextStep } = useSteps();
const [resetOrderPaymentBlock, { loading: resetOrderPaymentLoading, data: resetOrderPaymentData }] = useMutation<
ResetOrderPaymentBlock,
ResetOrderPaymentBlockVariables
>(ResetOrderPaymentBlockMutation.ResetOrderPaymentBlock);
const { refetch, loading: refetchLoading, data: refetchData } = useCheckinOrder(orderId, {
skip: true,
variables: { id: orderId }
});
useEffect(() => {
if (refetchLoading || resetOrderPaymentLoading) {
setIsLoading(refetchLoading || resetOrderPaymentLoading);
}
}, [refetchLoading, resetOrderPaymentLoading]);
useEffect(() => {
console.log(refetchData, refetchLoading); // <-- didn't call after on response
if (refetchData?.CheckinOrder) {
console.log('order refetched, fill and go to passengers');
dispatch(fillCheckinOrder(refetchData.CheckinOrder));
goToNextStep(CheckinStep.Passengers);
console.log('go to passengers');
}
}, [refetchData, refetchLoading]);
useEffect(() => {
if (resetOrderPaymentData?.ResetOrderPaymentBlock) {
console.log('order unlocked, refetch');
refetch();
}
}, [resetOrderPaymentLoading, resetOrderPaymentData]);
const unlock = useCallback(() => {
if (isBlocked) {
console.log('starting to unlock');
resetOrderPaymentBlock({ variables: { id: orderId } });
} else {
goToNextStep(CheckinStep.Passengers);
console.log('go to passengers');
}
}, [isBlocked]);
return {
unlock,
isLoading
};
};
but there is problem with refetch call,
useEffect(() => {
console.log(refetchData, refetchLoading); // <-- didn't call after on response
if (refetchData?.CheckinOrder) {
console.log('order refetched, fill and go to passengers');
dispatch(fillCheckinOrder(refetchData.CheckinOrder));
goToNextStep(CheckinStep.Passengers);
console.log('go to passengers');
}
}, [refetchData, refetchLoading]);
so my question is why refetchData didn't update and why this useEffect hook didn't call?
My useCheckinOrder hook looks like:
export const useCheckinOrder = (
orderId: string,
options?: QueryHookOptions<GetCheckinOrder, GetCheckinOrderVariables>
) => {
return useQuery<GetCheckinOrder, GetCheckinOrderVariables>(CheckinOrderQuery.GetCheckinOrder, {
variables: {
id: orderId,
...options.variables
},
...options
});
};
there is what console print:
starting to unlock
order unlocked, refetch
The default fetchPolicy of apollo client is cache-first which means it does not perform a network request if the data is in the cache (see: https://www.apollographql.com/docs/tutorial/queries/#customizing-the-fetch-policy)
Try changing the fetchPolicy to cache-and-network

Resources