Yow guys, React beginner here.
So basically, I am trying to fetch the updated state using React useContext hook.
The state is set inside a function call where the dispatch is placed, and the function call is bind to the button onClick event.
The function where the dispatch is called:
const fetchLocation = async (id: number) => {
const [, result] = await getLatestLocation()
dispatch({
type: "LOCATION_FETCHED",
payload: result
})
console.log(result) //this prints the latest/updated location even on button first click
}
Reducer:
case "LOCATION_FETCHED": {
return {
...state,
location: payload,
}
}
The function call in the component:
const {
fetchLocation,
location
} = React.useContext(locationContext)
const [fetchingLocation, setFetchingLocation] = useState(false)
const getLocation = (id: number) => {
fetchLocation(id)
.then(() => {
setFetchingLocation(true)
})
.finally(() => {
setFetchingLocation(false)
console.log(location) //this prints nothing or empty on the first button click
})
}
Button onClick function bind:
onClick={() => getLocation(143)}
I'm not sure what is happening, the first click will log nothing but on the second click, I got the updated location state.
As the comments say, the dispatch works asynchronously. So if you want to know the new value you should use the useEffect hook like this.
useEffect(() => {
console.log(location)
}, [location])
You can read more about here: https://reactjs.org/docs/hooks-effect.html
Related
i have five a snapshot listener in useEffect and i have another call api to get data from firestore and update state
but I am facing a problem is every initial mount all listener got called , my goal is i want to all listener called only when document changed
i tried with useRef it works but listener do not trigger
As you can see in the example below, onSnapshot is printed during the initial mounted
useEffect(() => {
if (isFirstMount.current) return;
someFirestoreAPICall.onSnapshot((snap) => {
//called every initial mount
});
someFirestoreAPICall.onSnapshot((snap) => {
//called every initial mount
});
}, []);
useEffect(() => {
if (isFirstMount.current) {
isFirstMount.current = false;
return;
}
}, []);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
let snap = await someFirestoreAPICall.get();
setData(snap.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
setLoading(false);
};
fetchData();
}, []);
Codesandbox
You can use a condition inside your useEffect block as you are doing, I think. But perhaps useState would be more appropriate here than useRef.
eg:
const [state, setState] = useState(null)
useEffect(()=>{
if (state) {
// do something
}
}, [state])
The useEffect will run on mount and every time you change the value of state, but code inside the condition will only run if you change the state to a truthy value.
I created a custom react hook to fetch data. Unfortunately when the useGetData gets called from a component, the component will render for each useState that is performed inside the hook. How can I prevent the additional renderings?
export default function useGetData(
setData: (fetchData) => void
): [(id: string) => void, boolean, boolean] {
const [loadingData, setLoading] = useState(false)
const [successData, setSuccess] = useState(false)
const getData = (id: string) => {
if (!id || !Number(id)) {
setData(null)
return
}
setSuccess(false)
setLoading(true)
Api.getData(Number(id))
.then((response) => {
setSuccess(true)
setData(response)
})
.finally(() => {
setLoading(false)
})
}
return [getData, loadingData, successData]
}
React < 18.x does not do automatical batching for promise callbacks, timeouts, intervals, .... It does it only for event handlers registered via JSX props and in lifecycle methods / effects.
Solution 1: You can use "unstable" API ReactDOM.unstable_batchedUpdates which, despite being marked as unstable, has been working for years just fine:
Api.getData(Number(id))
.then((response) => ReactDOM.unstable_batchedUpdates(() => {
setSuccess(true)
setData(response)
}))
.finally(() => {
setLoading(false)
})
The function will batch all state updates that are executed inside of passed function (synchronous); in the example setSuccess and setData; so that that they will be merged into single re-render, just like React does it in onClick and other event handlers.
Other possibilities: I can think of 2 other options that I don't really like but might work for you:
You can merge all your state into single state variable so that you have single setter.
Instead of calling setSuccess(true); setData(response); in promise callback, you can do it in an effect by introducing another state variable.
const [response, setResponse] = useState();
useEffect(() => {
if (!response) return;
setSuccess(true);
setData(response);
}, [response]);
...
Api.getData(Number(id))
.then(setResponse)
.finally(() => {
setLoading(false)
})
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
), [])
I have 2 actions in redux (both async) and I'm calling them both within my functional component via dispatch; the first using useEffect and the second via a button click. What I want to do is dispatch the actions to retrieve them from an async function, then use them within my component via useState. But using the useState is not rendering.
Here is my component:
export default function Hello()
{
const { first, second } = useSelector(state => state.myReducer);
const dispatch = useDispatch();
const fetchFirst = async () => dispatch(getFirst());
const fetchSecond = async () => dispatch(getSecond());
const fetchFixturesForDate = (date: Date) => dispatch(getFixturesForDate(date));
const [superValue, setSuperValue] = useState('value not set');
useEffect(() => {
const fetch = async () => {
fetchFirst();
setSuperValue(first);
};
fetch();
}, []);
const getSecondOnClickHandler = async () =>
{
console.log('a')
await fetchSecond();
setSuperValue(second);
}
return (
<div>
<p>The super value should first display the value "first item" once retrieved, then display "second value" once you click the button and the value is retrieved</p>
<p>Super Value: {superValue}</p>
<p>First Value: {first}</p>
<p>Second Value: {second}</p>
<button onClick={async () => await getSecondOnClickHandler()}>Get Second</button>
</div>
)
}
The superValue never renders even though I am setting it, although the value from first and second is retrieved and displayed.
StackBlitz.
Any help?
The value of first and second inside your two useEffects is set when the component mounts (I guess at that point they are undefined). So in both cases you will be setting superValue to that initial value.
You have two options:
Return the first/second values back from fetchFirst and fetchSecond, so that you can retrieve them directly from the executed function, and then set superValue:
useEffect(() => {
const fetch = async () => {
const newFirst = await fetchFirst();
setSuperValue(newFirst);
};
fetch();
}, []);
Add separate useEffects that listen for changes to first and second
useEffect(() => {
setSuperValue(first)
},[first])
useEffect(() => {
setSuperValue(second)
},[second])
The value in the reducer is not necessarily set when the action is dispatched, e.g. after fetchFirst() is called. Also the await that you do in await fetchSecond();
doesn't help since the reducer function is not executed.
You could add useEffect hooks and remove the setSuperValue from the other methods, but I think the code gets quite complicated.
What problem are you trying to solve in the first place?
useEffect(() => setSuperValue(first), [first]);
useEffect(() => setSuperValue(second), [second]);
useEffect(() => {
const fetch = async () => {
fetchFirst();
};
fetch();
}, []);
const getSecondOnClickHandler = async () => {
console.log('a');
await fetchSecond();
};
https://stackblitz.com/edit/react-ts-hsqd3x?file=Hello.tsx
I have a search component, when an input is given to the input filed and the search button is pressed I want to get a response immediately, process it and redirect to another page,
I want to get the immediate state of the store after the dispatch event
this my hook to get store state and dispatch action
export function useSpIdCheckRedirect(): [ISPPerfSummaryCardsState, SpIdData] {
const dispatch = useDispatch();
return [
useSelector<IAppstate, ISPPerfSummaryCardsState>(
(state) => state.spPerfSummaryCardsState,
shallowEqual
),
{
getSpIdData(spId: string, dataFilter: string) {
dispatch(
getDataStartSPPerfSummaryCardsAction({
spId: spId,
dateFilter: "LAST30DAYS",
})
);
},
},
];
}
this is my event handler function
const handleSPPerformanceClick = () => {
dispatch(viewTopSPPerformancePage());
};
const HandleSPSearchClick = () => {
getSpIdData(searchState, "LAST30DAYS");
console.log(state);
if (ref.current.isFetching == LoadingStatus.LOADING_SUCCESS) {
console.log(state);
}
};
this is the place where the hook is used
function SPHighlights({ spData }: Props) {
let [state, { getSpIdData }] = useSpIdCheckRedirect();
const HandleSPSearchClick = () => {
getSpIdData(searchState, "LAST30DAYS");
console.log(state);
if (state.isFetching == LoadingStatus.LOADING_SUCCESS) {
console.log(state);
}
};}
but the condition check inside the click handler becomes true only in the second click.
I want to get the store update immediately in the click handler, how can I do it?
Furthermore, I have used redux-saga middleware too.