I am creating a React App which will be used to display Dynamic Maps for DnD (vid files). Based on the User Input in a select field, a video player will get the specific video file from the firebase firestore, where I store the video files.
const Streamplayer = ({vidSelection}) => { //vidSelection is the event.target.value from the Select Input Field (basically a String)
const [links, setLinks] = useState([])
console.log(vidSelection);
useEffect(() => {
getLinks()
}, [])
useEffect(() => {
console.log(links)
}, [links])
function getLinks(){
const vidRef = collection(db, 'links');
getDocs(vidRef).then(response => {
console.log(response.docs)
const lnks = response.docs.map(doc => ({
data: doc.data(),
id: doc.id,
}))
setLinks(lnks)
}).catch(error => console.log(error.message));
}
function getVid(){
const toReturn = links.map((link) => link.data.vidSelection);//I want to change whatever gets returned based on input
console.log(toReturn)
return toReturn;
}
return (
<video
controls={false}
autoPlay
muted
loop
className="Video"
key={getVid()}
>
<source src={getVid()} type="video/mp4" />
</video>
)
}
export default Streamplayer
So in the method getVid() I request the data stored in the Firebase Firestore "Links" Collection and I want to change whichever I get based on the userInput. So if the User chooses the map "Grotto", I want to make the call "link.data.grotto". But since vidSelection is a String, I can't simply insert it in this call. Any Ideas how to solve this?
You can make useEffect get re-called when props change, by providing the prop in the dependency list for useEffect. Simply change your first useEffect call to:
useEffect(() => {
getLinks()
}, [vidSelection])
When the selection changes, useEffect will fire and call getLinks, which will update your state.
(Also, minor suggestion: remove the getVid() function, and just set a const videoSource = links.map((link) => link.data.vidSelection) in the function body. Right now you're calling the function twice which will cause the mapping to happen twice, and I think it's generally clearer to have consts flow directly from state, makes it easier to reason about state.)
Related
I have created a hook in a component as below.
useEffect(() => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
getStatusCode(response.data.code);
console.log("responseCode",responseCode);
getMatchdata(response.data.result);
setInfo(<MatchData responseCode={responseCode} matchdata={matchdata} />);
})
.catch((error) => console.log(error));
},[]);
This is a state function used in the above effect
const [info, setInfo] = useState();
I expected the above useEffect should return me some data in the below block
<div> {info} </div>
but it is showing wrong data, whereas I have created another function to trigger on Refresh button as
function refresh() {
setInfo(<MatchData responseCode={responseCode} matchdata={matchdata} />);
}
this function is returning me correct data. I want to create a functionality that will dynamically update the div element with change in state of {info}, by default when the page is loaded first, it should fetch data from the endpoint used here only. I'm new to React. Where I'm going wrong and how do I achieve it?
I don't want to say this is wrong, but this seems like an atypical approach from what I've seen in the wild. Specifically I am talking about storing a JS/JSX or TS/TSX element in a state object. I have more commonly seen a value stored in that type of variable and that value changing when necessary via the set dispatch function. Then the state object is passed to the component who needs it to do something. In react, when the value of that state object changes, it will cause the component who uses it to re-render. If I were coding this, this is what my code would look like.
const [info, setInfo] = useState();
const getData = () => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
setInfo(response.json())
})
.catch((error) => console.log(error));
}
const divComponent = ({info}) => (
<div>
<p>{info.data.code}</p>
<p>{info.data.result}</p>
</div>
)
const refreshButton = () => (
<button onClick(()=>getData())>Refresh</button>
)
Unless you only specifically want something to happen once at component mount, you would not use useEffect() like you did in your code. If the decision to refresh were coming from an external object with state instead of the refresh button, you could add that object whose state changes to the dependency array of the useEffect function. This would cause the refresh to run any time that object's state value changes. In the code above, getData() (which might need to be async) will only run when called. Then you have a component called divComponent which is expecting info to have value. When rendering this component you would want a null check like I coded below. Finally the refreshButton component will call getData() when it is clicked.
Then in your code that renders this, I would have something like this:
<>
{info ? <divComponent info={info} /> : <p>There is no info</p>}
<refreshButton />
</>
The code above will check if the state object info has value, and if it does it will render the divComponent with your data values. If it does not, instead it will show the p tag explaining that there is no data. Either way it will render the refreshButton, which would run the getData() function again when clicked.
** EDIT **
Based on your comment, here is another approach so you can have a value on page load and update when necessary:
import {useState, useEffect} from "react";
const [info, setInfo] = useState();
const getData = () => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
setInfo(response.json())
})
.catch((error) => console.log(error));
}
useEffect(()=> {
getData();
}, [])
const divComponent = ({info}) => (
<div>
<p>{info.data.code}</p>
<p>{info.data.result}</p>
</div>
)
const refreshButton = () => (
<button onClick(()=>getData())>Refresh</button>
)
export const Page = () => (
<>
{info ? <divComponent info={info} /> : <p>There is no info</p>}
<refreshButton />
</>
);
your method is quite complex. I believe you need to add your MatchData Component Inside the div in this way.Also Don't Need To Call State Method setInfo() in useEffect hook.Only responseCode and matchdata Needed that is already adjusted by you in useEffect Hook.
I'm having a problem, I made an API call to find out the city of the user who accessed my application.
So far so good, I can show on the screen the city the user is in, but only after I press the CTRL + S keys (save my file) then the API GET works. Same in the example below
When loading the application:
After I save the application in my code editor:
Can you tell me how I can make the location appear immediately without me having to save my code?
Below is my code for this component
Location.tsx
import axios from "axios";
import { useEffect, useState } from "react"
export function Location(){
const API_endpoint = `https://api.openweathermap.org/data/2.5/weather?`
const API_key = `e3b1d84b18bc5ef312403e0caf94b698`
const [latitude, setLatitude] = useState<number>(0);
const [longitude, setLongitude] = useState<number>(0);
const [cityName, setCityName] = useState<string>('')
const [isFetching,setIsFetching] = useState<boolean>(true);
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
setLatitude(position.coords.latitude)
setLongitude(position.coords.longitude)
})
let finalApiEndPoint = `${API_endpoint}lat=${latitude}&lon=${longitude}&exclude=hourly,daily&appid=${API_key}`
axios.get(finalApiEndPoint)
.then((response) =>{
setCityName(response.data.name)
console.log(response.data.name)
})
.catch(err => {
setError(err)
})
.finally(() => {
setIsFetching(false)
})
}, [])
return(
<>
<div>
<h1>
<>
Your Location: {cityName}
</>
</h1>
</div>
</>
)
}
The issue is a combination of:
Asynchronous code trying to use a result before it is received
Synchronous usage of React state after change, before the change takes effect (next render)
1. Using result from asynchronous code
In the useEffect, the call to navigator.geolocation.getCurrentPosition properly uses its result within a callback, because it will take some time before the gelocation is known.
So anything that depends on this data should also be executed only after that data is received, i.e. typically within said callback. Or through the states where you store the geolocation results for decoupling.
2. React state changes on next render
However, when setting a new React state value, that state still holds the previous value for the rest of this render pass: any synchronous code after setLatitude will still see the old value of latitude, until next render (it is a const, after all).
So either use directly position.coords.latitude (which shows that it must be executed within the callback), or push the decoupling further by using a separate useEffect, so that the API call is executed only once the states have actually changed their value.
const [latitude, setLatitude] = useState<number>(0);
const [longitude, setLongitude] = useState<number>(0);
const [cityName, setCityName] = useState<string>('')
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
setLatitude(position.coords.latitude)
setLongitude(position.coords.longitude)
})
}, []) // Get geolocation only once
useEffect(() => {
let finalApiEndPoint = `${API_endpoint}lat=${latitude}&lon=${longitude}&exclude=hourly,daily&appid=${API_key}`
axios.get(finalApiEndPoint)
.then((response) =>{
setCityName(response.data.name)
console.log(response.data.name)
})
}, [latitude, longitude]) // Call API whenever lat or long change
The component suddenly displays the expected state value after saving your file, because it triggers a hot replacement. The latter preserves the state (hence the latitude and longitude of first geolocation), but the new component is re-mounted, which triggers a new execution of the useEffect, where the API call is re-made, using the preserved (now correct) states.
I have a question about the "proper" (or most idiomatic) way to implement network fetch behavior in React based on a single changing property.
A simplified example of the functionality I'm building is below: I am looking to build a multi-page form that "auto-saves" a draft of form inputs as the user navigates back/forth between pages.
TL;DR - I thought useEffect hooks would be the right way to save a draft to the backend every time a url slug prop changes, but I'm running into issues, and wondering about suggestions for the "right" tool for this type of behavior.
Here is my attempt so far. My code is technically working how I want it to, but violates React's recommended hook dependency pattern (and breaks the exhaustive-deps ESLint rule).
import React from 'react';
const ALL_SLUGS = [
'apple',
'banana',
'coconut',
];
function randomUrlSlug() {
return ALL_SLUGS[Math.floor((Math.random() * ALL_SLUGS.length))];
}
// just resovles the same object passed in
const dummySaveDraftToBackend = (input) => {
return new Promise((resolve, _reject) => {
setTimeout(() => {
resolve(input);
}, 1000);
});
};
export function App() {
const [urlSlug, setUrlSlug] = React.useState(randomUrlSlug());
return (
<MyComponent urlSlug={urlSlug} setUrlSlug={setUrlSlug} />
);
}
export function MyComponent({ urlSlug, setUrlSlug }) {
const [loading, setLoading] = React.useState(false);
const [complexState, setComplexState] = React.useState({ foo: 'bar', baz: 'wow', responseCount: 0 });
// useCallback memoization is technically unnecessary as written here,
// but if i follow the linter's advice (listing handleSave as a dependency of the useEffect below), it also suggests memoizing here.
// However, complexState is also technically a dependency of this callback memo, which causes the fetch to trigger every time state changes.
//
// Similarly, moving all of this inside the effect hook, makes the hook dependent on `complexState`, which means the call to the backend happens every time a user changes input data.
const handleSave = React.useCallback(() => {
console.log('*** : start fetch');
setLoading(true);
dummySaveDraftToBackend(complexState).then((resp) => {
console.log('fetch response: ', resp);
// to keep this example simple, here we are just updating
// a dummy "responseCount", but in the actual implementation,
// I'm using a state reducer, and want to make some updates to form state based on error handling, backend validation, etc.
setComplexState((s) => ({
...resp,
responseCount: s.responseCount + 1,
}));
setLoading(false);
});
}, [complexState]);
// I know this triggers on mount and am aware of strategies to prevent that.
// Just leaving that behavior as-is for the simplified example.
React.useEffect(() => {
if (urlSlug) {
handleSave();
}
}, [urlSlug]); // <- React wants me to also include my memoized handleSave function here, whose reference changes every time state changes. If I include it, the fetch fires every time state changes.
return (
<div className="App">
<h2>the current slug is:</h2>
<h3>{urlSlug}</h3>
<div>the current state is:</div>
<pre>{JSON.stringify(complexState, null, 2)}</pre>
<div>
<h2>edit foo</h2>
<input value={complexState.foo} onChange={(e) => setComplexState((s) => ({ ...s, foo: e.target.value }))} disabled={loading} />
</div>
<div>
<h2>edit baz</h2>
<input value={complexState.baz} onChange={(e) => setComplexState((s) => ({ ...s, baz: e.target.value }))} disabled={loading} />
</div>
<div>
<button
type="button"
onClick={() => setUrlSlug(randomUrlSlug())}
disabled={loading}
>
click to change to a random URL slug
</button>
</div>
</div>
);
}
As written, this does what I want it to do, but I had to omit my handleSave function as a dependency of my useEffect to get it to work. If I list handleSave as a dependency, the hook then relies on complexState, which changes (and thus fires the effect) every time the user modifies input.
I'm concerned about violating React's guidance for not including dependencies. As-is, I would also need to manually prevent the effect from running on mount. But because of the warning, I'm wondering if I should not use a useEffect pattern for this, and if there's a better way.
I believe I could also manually read/write state to a ref to accomplish this, but haven't explored that in much depth yet. I have also explored using event listeners on browser popstate events, which is leading me down another rabbit hole of bugginess.
I know that useEffect hooks are typically intended to be used for side effects based on event behavior (e.g. trigger a fetch on a button click). In my use case however, I can't rely solely on user interactions with elements on the page, since I also want to trigger autosave behavior when the user navigates with their browser back/forward controls (I'm using react-router; current version of react-router has hooks for this behavior, but I'm unfortunately locked in to an old version for the project I'm working on).
Through this process, I realized my understanding might be a bit off on proper usage of hook dependencies, and would love some clarity on what the pitfalls of this current implementation could be. Specifically:
In my snippet above, could somebody clarify to me why ignoring the ESLint rule could be "bad"? Specifically, why might ignoring a dependency on some complex state can be problematic, especially since I dont want to trigger an effect when that state changes?
Is there a better pattern I could use here - instead of relying on a useEffect hook - that is more idiomatic? I basically want to implement a subscriber pattern, i.e. "do something every time a prop changes, and ONLY when that prop changes"
If all the "state" that is updated after saving it to backend is only a call count, declare this as a separate chunk of state. This eliminates creating a render loop on complexState.
Use a React ref to cache the current state value and reference the ref in the useEffect callback. This is to separate the concerns of updating the local form state from the action of saving it in the backend on a different schedule.
Ideally each useState hook's "state" should be closely related properties/values. The complexState appears to be your form data that is being saved in the backend while the responseCount is completely unrelated to the actual form data, but rather it is related to how many times the data has been synchronized.
Example:
export function MyComponent({ urlSlug, setUrlSlug }) {
const [loading, setLoading] = React.useState(false);
const [complexState, setComplexState] = React.useState({ foo: 'bar', baz: 'wow' });
const [responseCount, setResponseCount] = React.useState(0);
const complexStateRef = React.useRef();
React.useEffect(() => {
complexStateRef.current = complexState;
}, [complexState]);
React.useEffect(() => {
const handleSave = async (complexState) => {
console.log('*** : start fetch');
setLoading(true);
try {
const resp = await dummySaveDraftToBackend(complexState);
console.log('fetch response: ', resp);
setResponseCount(count => count + 1);
} catch(error) {
// handle any rejected Promises, errors, etc...
} finally {
setLoading(false);
}
};
if (urlSlug) {
handleSave(complexStateRef.current);
}
}, [urlSlug]);
return (
...
);
}
This feels like a move in the wrong direction (towards more complexity), but introducing an additional state to determine if the urlSlug has changed seems to work.
export function MyComponent({ urlSlug, setUrlSlug }) {
const [slug, setSlug] = React.useState(urlSlug);
const [loading, setLoading] = React.useState(false);
const [complexState, setComplexState] = React.useState({ foo: 'bar', baz: 'wow', responseCount: 0 });
const handleSave = React.useCallback(() => {
if (urlSlug === slug) return // only when slug changes and not on mount
console.log('*** : start fetch');
setLoading(true);
dummyFetch(complexState).then((resp) => {
console.log('fetch response: ', resp);
setComplexState((s) => ({
...resp,
responseCount: s.responseCount + 1,
}));
setLoading(false);
});
}, [complexState, urlSlug, slug]);
React.useEffect(() => {
if (urlSlug) {
handleSave();
setSlug(urlSlug)
}
}, [urlSlug, handleSave]);
Or move handleSave inside the useEffect (with additional slug check)
Updated with better semantics
export function MyComponent({ urlSlug, setUrlSlug }) {
const [autoSave, setAutoSave] = React.useState(false); // false for not on mount
React.useEffect(() => {
setAutoSave(true)
}, [urlSlug])
const [loading, setLoading] = React.useState(false);
const [complexState, setComplexState] = React.useState({ foo: 'bar', baz: 'wow', responseCount: 0 });
React.useEffect(() => {
const handleSave = () => {
if(!autoSave) return
console.log('*** : start fetch');
setLoading(true);
dummyFetch(complexState).then((resp) => {
console.log('fetch response: ', resp);
setComplexState((s) => ({
...resp,
responseCount: s.responseCount + 1,
}));
setLoading(false);
});
}
if (urlSlug) {
handleSave();
setAutoSave(false)
}
}, [autoSave, complexState]);
I am learning react, and given this simple example of using SWR to fetch some items from an API and showing the items with groups using fluentui DetailedList - I am running into a problem with the groups.
Whenever I click a group in UI to collapse/uncollapse, that seems to trigger a rerender, and then the component will createGroups(data) again which resets the UI again back to original state as the groups object is recalculated.
Where am I supposed to actually store / calculate the groups information of my data? Initial it needs to be created, but from there it seems that it should only needs to be reevaluated whenvere the swr api returns new data - and then i still properly would want to merge in the current state from collapsed groups that the user might have changed in the UI.
Is it because i properly should not use SWR as it refreshes data live - and only do it on page refresh?
const SWR = ({ children, listid, onSuccess }: { children: ((args: SWRResponse<any, any>) => any), listid: string, onSuccess?: any }) => {
const url = `http://localhost:7071/api/Lists/${listid}`;
console.log(url);
const {data,error } = useSWR(url, { fetcher: fetcher, isPaused: () => listid === undefined, onSuccess });
const items = data.value;
const groups = createGroups(data)
return <... DetailsList group={groups} items={items} ... >; // ... left out a few details ...
};
What about adding a state for holding the groups and an useEffect for when data changes and insde the useEffect you should check if the content has changed before updating the groupState.
const hasChanged(data) => {
return data.notEquals(state.data)); // write your own logic for comparing the result
};
useEffect(() => { if (hasChanged(data)) {
setState(prev=> ({ ...prev, group: createGroup(data), data: data });
}}, [data]);
You dont actually need to store the group, you can just hold the data in your state, but the important part is to be able to check if any change actually took place before changing the state.
Another thing worth trying is the compare option in the useSWR hook. So instead of placing the "hasChanged" logic inside an useEffect hook, perhaps it could be in the compare function. Haven't had the chanse to test this myself though.
A third and final option would be to place the creation of groups inside your fetcher. Perhaps the most intuitive solution for this particular case, though I'm not completely sure it will prevent the unnecessary re-renders.
const fetcher = url => axios.get(url).then(res=> {
return {
items: res.data.value,
groups: createGroups(res.data),
};
});
const SWR = ({ children, listid, onSuccess }: { children: ((args: SWRResponse<any, any>) => any), listid: string, onSuccess?: any }) => {
const { data, error } = useSWR(url, fetcher, ...);
return <... DetailsList group={data.groups} items={data.items} ... >; // ... left out a few details ...
};
I use react hook call an API and set the data to state and I still have some control view can change the value, but when I change it, my API function trigger again, it cause my view re render multiple times.
How do I use my fetchData function just like in class component componentDidMount function ?
const [brightness, setBrightness] = useState(0);
useEffect(() => {
fetchData();
});
const fetchData = async () => {
const value = await something(); // call API get the first value
setBrightness(value);
};
return (
<View>
<SomeView value={brightness} onComplete={(value) => setBrightness(value)}
</View>
);
Your useEffect will be triggered on every render because you haven't provided the 2nd argument.
The 2nd argument is what the effect is conditional upon. In your case you only want it to run once so you can provide an empty array.
useEffect(() => {
// Run once
}, [])
Lets say you wanted it to fetch anytime some prop changed, you could write
useEffect(() => {
// run every time props.myValue changes
}, [props.myValue])
See https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects