I am following a tutorial and just learned about useEffect.
The issue I am having is that I want to get result.hours[0].is_open_now. I've discovered that the component renders twice, and since hours is undefined at first, it fails.
Is there a way to make this only run once.
const ResultsShowScreen = ({ navigation }) => {
const id = navigation.getParam('id');
const [result, setResult] = React.useState({})
const getResult = async (id) => {
const response = await yelp.get(`/${id}`)
setResult(response.data)
}
useEffect(() => {
getResult(id)
}, [])
if (!result || result.length <= 0) {
return null
}
const {name,
display_address,
price,
display_phone,
photos,
review_count,
rating,
hours
} = result
if (hours && hours.length > 0 ) {
const is_open_now = hours[0].is_open_now
console.log('running')
} else {
console.log('nooooo')
}
return (
<View>
<Text style={styles.title}>{name}</Text>
<Text>Price: {price}</Text>
<Text>Rating: {rating}</Text>
<Text>Reviews: {review_count}</Text>
<Text>Phone: {display_phone}</Text>
<Text>Is Open Now: </Text>
</View>
)
}
export default ResultsShowScreen
Following your code you can not prevent useEffect render twice because following react hook lifecycle,
If youβre familiar with React class lifecycle methods, you can think
of useEffect Hook as componentDidMount, componentDidUpdate, and
componentWillUnmount combined.
That's mean useEffect will be executed after component render the first time (Your code fiirst time is null) and executed more time if the second arg has values.
In your useEffect, you call setState to set new state and component auto render if state change. So your component will be rendered at least twice.
Related
I am trying to call the reddit API. The post titles are showing up, but I want them to rerender when my query changes. I just want to know how to call a method when a piece of my state changes(aka my query). Iβm using useEffect from react to do it but that calls it whenever anything changes in the component, causing it to call the method to get posts way to many times. I only want to get the posts when my query changes.
import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
function Results()
{
const query = useSelector(state => state.query);
const results = useSelector(state => state.results);
const dispatch = useDispatch();
let fetchResults = () =>
{
let postTitles = [];
let postSrcs = [];
fetch('https://www.reddit.com/r/' + query + '.json')
.then(response => response.json())
.then(body => {
for (let i = 0; i < body.data.children.length; ++i) {
if (body.data.children[i].data.post_hint === 'image')
{
let img_url = body.data.children[i].data.url_overridden_by_dest;
postSrcs.push(img_url);
}
let title = body.data.children[i].data.title;
postTitles.push(title);
}
dispatch({type: "QUERY_RESULTS", payload: postTitles})
}).catch((err) => {
console.log(err)
});
}
useEffect(() => {
fetchResults();
console.log("use effect triggered")
})
return (
<>
<h1>Query: {query}</h1>
{ !results
? <h1>No Results</h1>
: results.map(p => <h6> {p} </h6>)
}
</>
)
}
export default Results;
For example in the console log that tells me when use effect is triggered. and when i search for a post the use effect triggered is stacking up.
useEffect has a differents mode. You can check how to use in official document https://reactjs.org/docs/hooks-reference.html#useeffect
So the main you must know is 3 things
useEffect is the last render in react. So first render a components and read other code when it finish useEffect run.
useEffect may run code only one time adding []. for example
useEffect ( () => {
...code
}, [])
This code will run only one time.
useEffect may run code watching variables adding variables into []. For example
useEffect ( () => {
...code
}, [ count, name , ... ])
This code will run first time and later would run if count or name change
To achieve that you need to prevent useEffect to be called on any changes, and only once the query changes.
NOTE: Since you're using dispatch within fetchResults, it's better to make sure that dispatch is ready before calling fetchResults.
Your useEffect may look like the following to achieve that:
useEffect(() => {
// To prevent call fetchResults if dispatch only is changed
if (query) {
fetchResults();
console.log("use effect triggered");
}
}, [dispatch, query]);
Hooks like useEffect are used in function components. The Class component comparison to useEffect are the methods componentDidMount, componentDidUpdate, and componentWillUnmount.
useEffect will run when the component renders, which might be more times than you think.
So useEffect takes a second parameter
The second param is an array of variables that the component will check to make sure changed before re-rendering. You could put whatever bits of props and state you want in here to check against.
In your case add [query] as a second para:
useEffect(() => {
fetchResults();
console.log("use effect triggered")
},[query])
https://css-tricks.com/run-useeffect-only-once/
so i have a bit of a weird problem i dont know how to solve.
In my code i have a custom hook with a bunch of functionality for a fetching a list
of train journeys. I have some useEffects to that keeps loading in new journeys untill the last journey of the day.
When i change route, while it is still loading in new journeys. I get the "changes to unmounted component" React error.
I understand that i get this error because the component is doing an async fetch that finishes after i've gone to a new page.
The problem i can't figure out is HOW do i prevent it from doing that? the "unmounted" error always occur on one of the 4 lines listed in the code snippet.
Mock of the code:
const [loading, setLoading] = useState(true);
const [journeys, setJourneys] = useState([]);
const [hasLaterDepartures, setHasLaterDepartures] = useState(true);
const getJourneys = async (date, journeys) => {
setLoading(true);
setHasLaterDepartures(true);
const selectedDateJourneys = await fetchJourney(date); // Fetch that returns 0-3 journeys
if (condition1) setHasLaterDepartures(false); // trying to update unmounted component
if (condition2) {
if (condition3) {
setJourneys(something1); // trying to update unmounted component
} else {
setJourneys(something2) // trying to update unmounted component
}
} else {
setJourneys(something3); // trying to update unmounted component
}
};
// useEffects for continous loading of journeys.
useEffect(() => {
if (!hasLaterDepartures) setLoading(false);
}, [hasLaterDepartures]);
useEffect(() => {
if (hasLaterDepartures && journeys.length > 0) {
const latestStart = ... // just a date
if (latestStart.addMinutes(5).isSameDay(latestStart)) {
getJourneys(latestStart.addMinutes(5), journeys);
} else {
setLoading(false);
}
}
}, [journeys]);
I can't use a variable like isMounted = true in the useEffect beacuse it would reach inside the if statement and reach a "setState" by the time i'm on another page.
Moving the entire call into a useEffect doesn't seem to work either. I am at a loss.
Create a variable called mounted with useRef, initialised as true. Then add an effect to set mounted.current to false when the component unmounts.
You can use mounted.current anywhere inside the component to see if it's mounted, and check that before setting any state.
useRef gives you a variable you can mutate but which doesn't cause a rerender.
When you use useEffect hook with action which can be done after component change you should also take care about clean effect when needed. Maybe example help you, also check this page.
useEffect(() => {
let isClosed = false
const fetchData = async () => {
const data = await response.json()
if ( !isClosed ) {
setState( data )
}
};
fetchData()
return () => {
isClosed = true
};
}, []);
In your use case, you probably want to create a Store that doesn't reload everytime you change route (client side).
Example of a store using useContext();
const MyStoreContext = createContext()
export function useMyStore() {
const context = useContext(MyStoreContext)
if (!context && typeof window !== 'undefined') {
throw new Error(`useMyStore must be used within a MyStoreContext`)
}
return context
}
export function MyStoreProvider(props) {
const [ myState, setMyState ] = useState()
//....whatever codes u doing with ur hook.
const exampleCustomFunction = () => {
return myState
}
const getAllRoutes = async (mydestination) => {
return await getAllMyRoutesFromApi(mydestination)
}
// you return all your "getter" and "setter" in value props so you can use them outside the store.
return <MyStoreContext.Provider value={{ myState, setMyState, exampleCustomFunction, getAllRoutes }}>{props.children}</MyStoreContext.Provider>
}
You will wrap the store around your entire App, e.g.
<MyStoreProvider>
<App />
</MyStoreProvider>
In your page where you want to use your hook, you can do
const { myState, setMyState, exampleCustomFunction, getAllRoutes } = useMyStore()
const onClick = async () => getAllRouters(mydestination)
Considering if you have client side routing (not server side), this doesn't get reloaded every time you change your route.
I needed a hook to get the previous distinct value of a specific state. It looks like this and it seems to work:
function usePreviousDistinct(state) {
const prevRef = useRef();
useEffect(() => {
prevRef.current = state;
}, [state]);
return prevRef.current;
}
I've also seen there is a usePreviousDistinct hook in the react-use package but the approach is different than mine.
import { useRef } from 'react';
import { useFirstMountState } from './useFirstMountState';
export type Predicate<T> = (prev: T | undefined, next: T) => boolean;
const strictEquals = <T>(prev: T | undefined, next: T) => prev === next;
export default function usePreviousDistinct<T>(value: T, compare: Predicate<T> = strictEquals): T | undefined {
const prevRef = useRef<T>();
const curRef = useRef<T>(value);
const isFirstMount = useFirstMountState();
if (!isFirstMount && !compare(curRef.current, value)) {
prevRef.current = curRef.current;
curRef.current = value;
}
return prevRef.current;
}
I wonder if I have not understood something or am missing something. Is my version also correct?
In my test I could not find a difference:
https://codesandbox.io/s/distracted-mayer-zpym8?file=/src/App.js
useEffect() together with useRef() (your version) does not show the latest value.
useRef() without useEffect() gives you the correct value, because it runs synchronously, but doesn't have the advantages that come with asynchronicity.
useEffect() together with useState() gives you the correct value, but might trigger unnecessary renders (adds potentially unnecessary overhead).
Your version looks like it works as expected, because the old value that is shown is the one that you expect to see as the new value. But the actual new value is not the one you want.
Example:
import React, { useState, useEffect, useRef } from 'react';
export const MyComponent = function(props){
const [state, setState ] = useState(0);
return <React.Fragment>
state: { state },<br />
with useEffect: { usePreviousDistinctUE( state ) },<br />
w/o useEffect: { usePreviousDistinctR( state ) },<br />
<button onClick={ function(){
setState( state + 1 );
} }>
increment
</button>
</React.Fragment>;
};
const usePreviousDistinctUE = function( value ){
const prevRef = useRef();
useEffect(() => {
prevRef.current = value;
console.log('with useEffect, prev:', prevRef.current, ', current:', value);
}, [value]);
return prevRef.current;
};
const usePreviousDistinctR = function( value ){
const prevRef = useRef();
const curRef = useRef( value );
if( curRef.current !== value ){
prevRef.current = curRef.current;
curRef.current = value;
}
console.log('w/o useEffect, prev:', prevRef.current, ', current:', curRef.current);
return prevRef.current;
};
The values shown on the page are the same, but in the console they are different. That means the value in the useEffect() version is changed, it is only not yet shown on the page.
If you just add another hook that updates anything unrelated (leaving everything else unchanged), then the page (might*) magically show the updated value again, because the page is re-rendered and the previously already changed value is shown. The value is now wrong in your eyes, but it is not changed, only shown:
// ...
with useEffect: { usePreviousDistinctUE( state ) },<br />
w/o useEffect: { usePreviousDistinctR( state ) },<br />
anything updated: { useAnythingUpdating( state ) },<br />
// ...
const useAnythingUpdating = function(state){
const [result, setResult ] = useState(0);
useEffect(() => {
setResult( state );
console.log('anything updated');
});
return result;
};
*But you shouldn't rely on something else triggering a re-render. I'm not even sure this would update as expected under all circumstances.
more Details:
useEffect() is triggered at some time when react decides that the prop must have been changed. The ref is changed then, but react will not 'get informed' about a ref change, so it doesn't find it necessary to re-render the page to show the changed value.
In the example without useEffect() the change happens synchronously.
React doesn't 'know' about this change either, but (if everything else runs as expected) there will be always a re-render when necessary anyway (you will have called that function from another function that is rendered at the end).
(Not informing react about the change is basically the point in using useRef(): sometimes you want just a value, under your own control, without react doing magic things with it.)
I have a component that makes a request and displays a list of jobs.
import React, { useState, useEffect, Fragment } from 'react';
import { Jobs } from '../components/Jobs.component';
export const Fixed = () => {
const [jobs, setJobs] = useState([]);
useEffect(() => {
getItems();
}, []);
async function getItems() {
const url = 'http://localhost:8081/api/fixed/list';
const res = await fetch(url, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
});
const data = await res.json();
console.log(data);
setJobs(data.jobsList);
console.log(jobs);
}
return (
<Fragment>
{jobs.map(job => (
<div>
<Jobs job={job} />
</div>
))}
</Fragment>
);
};
My problem is that the first console outputs an array of jobs, but the second console displays an empty array. And an empty value is passed to the job component, which causes an error.
He does not have time to write the work in a state? Where am I wrong?
Method setJobs needs some time to change state so console.log runs faster than value changes.
You should render list if the array length is bigger than 0.
{jobs.length && jobs.map(job => <Jobs job={job} />)}
State updates are run asynchroniously
The reason your console.log shows an empty array is because setJobs runs asynchroniously and will update jobs value on next render. Looking at react setState documentation (same as useState react hooks) :
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state.
And so
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.
const ... jobs ... is a constant - it will be a different constant in 2 different renders, but it will not change value during a single render
The jobs inside getItems is a closure and will reference to the value from the first render, while setJobs will only change the value in second render.
It's similar to the following:
const rememberJobs = (jobs) => () => console.log(jobs)
const first = rememberJobs([])
const second = rememberJobs([1, 2, 3])
first()
second()
I have a function component, and I want to force it to re-render.
How can I do so?
Since there's no instance this, I cannot call this.forceUpdate().
π You can now, using React hooks
Using react hooks, you can now call useState() in your function component.
useState() will return an array of 2 things:
A value, representing the current state.
Its setter. Use it to update the value.
Updating the value by its setter will force your function component to re-render,
just like forceUpdate does:
import React, { useState } from 'react';
//create your forceUpdate hook
function useForceUpdate(){
const [value, setValue] = useState(0); // integer state
return () => setValue(value => value + 1); // update state to force render
// A function that increment ππ» the previous state like here
// is better than directly setting `setValue(value + 1)`
}
function MyComponent() {
// call your hook here
const forceUpdate = useForceUpdate();
return (
<div>
{/*Clicking on the button will force to re-render like force update does */}
<button onClick={forceUpdate}>
Click to re-render
</button>
</div>
);
}
You can find a demo here.
The component above uses a custom hook function (useForceUpdate) which uses the react state hook useState. It increments the component's state's value and thus tells React to re-render the component.
EDIT
In an old version of this answer, the snippet used a boolean value, and toggled it in forceUpdate(). Now that I've edited my answer, the snippet use a number rather than a boolean.
Why ? (you would ask me)
Because once it happened to me that my forceUpdate() was called twice subsequently from 2 different events, and thus it was reseting the boolean value at its original state, and the component never rendered.
This is because in the useState's setter (setValue here), React compare the previous state with the new one, and render only if the state is different.
Update react v16.8 (16 Feb 2019 realease)
Since react 16.8 released with hooks, function components have the ability to hold persistent state. With that ability you can now mimic a forceUpdate:
function App() {
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
console.log("render");
return (
<div>
<button onClick={forceUpdate}>Force Render</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
Note that this approach should be re-considered and in most cases when you need to force an update you probably doing something wrong.
Before react 16.8.0
No you can't, State-Less function components are just normal functions that returns jsx, you don't have any access to the React life cycle methods as you are not extending from the React.Component.
Think of function-component as the render method part of the class components.
Official FAQ now recommends this way if you really need to do it:
const [ignored, forceUpdate] = useReducer(x => x + 1, 0);
function handleClick() {
forceUpdate();
}
Simplest way π
if you want to force a re-render, add a dummy state you can change to initiate a re-render.
const [rerender, setRerender] = useState(false);
...
setRerender(!rerender); //whenever you want to re-render
And this will ensure a re-render, And you can call setRerender(!rerender) anywhere, whenever you want :)
I used a third party library called
use-force-update
to force render my react functional components. Worked like charm.
Just use import the package in your project and use like this.
import useForceUpdate from 'use-force-update';
const MyButton = () => {
const forceUpdate = useForceUpdate();
const handleClick = () => {
alert('I will re-render now.');
forceUpdate();
};
return <button onClick={handleClick} />;
};
Best approach - no excess variables re-created on each render:
const forceUpdateReducer = (i) => i + 1
export const useForceUpdate = () => {
const [, forceUpdate] = useReducer(forceUpdateReducer, 0)
return forceUpdate
}
Usage:
const forceUpdate = useForceUpdate()
forceUpdate()
If you already have a state inside the function component and you don't want to alter it and requires a re-render you could fake a state update which will, in turn, re-render the component
const [items,setItems] = useState({
name:'Your Name',
status: 'Idle'
})
const reRender = () =>{
setItems((state) => [...state])
}
this will keep the state as it was and will make react into thinking the state has been updated
This can be done without explicitly using hooks provided you add a prop to your component and a state to the stateless component's parent component:
const ParentComponent = props => {
const [updateNow, setUpdateNow] = useState(true)
const updateFunc = () => {
setUpdateNow(!updateNow)
}
const MyComponent = props => {
return (<div> .... </div>)
}
const MyButtonComponent = props => {
return (<div> <input type="button" onClick={props.updateFunc} />.... </div>)
}
return (
<div>
<MyComponent updateMe={updateNow} />
<MyButtonComponent updateFunc={updateFunc}/>
</div>
)
}
The accepted answer is good.
Just to make it easier to understand.
Example component:
export default function MyComponent(props) {
const [updateView, setUpdateView] = useState(0);
return (
<>
<span style={{ display: "none" }}>{updateView}</span>
</>
);
}
To force re-rendering call the code below:
setUpdateView((updateView) => ++updateView);
None of these gave me a satisfactory answer so in the end I got what I wanted with the key prop, useRef and some random id generator like shortid.
Basically, I wanted some chat application to play itself out the first time someone opens the app. So, I needed full control over when and what the answers are updated with the ease of async await.
Example code:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// ... your JSX functional component, import shortid somewhere
const [render, rerender] = useState(shortid.generate())
const messageList = useRef([
new Message({id: 1, message: "Hi, let's get started!"})
])
useEffect(()=>{
async function _ () {
await sleep(500)
messageList.current.push(new Message({id: 1, message: "What's your name?"}))
// ... more stuff
// now trigger the update
rerender(shortid.generate())
}
_()
}, [])
// only the component with the right render key will update itself, the others will stay as is and won't rerender.
return <div key={render}>{messageList.current}</div>
In fact this also allowed me to roll something like a chat message with a rolling .
const waitChat = async (ms) => {
let text = "."
for (let i = 0; i < ms; i += 200) {
if (messageList.current[messageList.current.length - 1].id === 100) {
messageList.current = messageList.current.filter(({id}) => id !== 100)
}
messageList.current.push(new Message({
id: 100,
message: text
}))
if (text.length === 3) {
text = "."
} else {
text += "."
}
rerender(shortid.generate())
await sleep(200)
}
if (messageList.current[messageList.current.length - 1].id === 100) {
messageList.current = messageList.current.filter(({id}) => id !== 100)
}
}
If you are using functional components with version < 16.8. One workaround would be to directly call the same function like
import React from 'react';
function MyComponent() {
const forceUpdate = MyComponent();
return (
<div>
<button onClick={forceUpdate}>
Click to re-render
</button>
</div>
);
}
But this will break if you were passing some prop to it. In my case i just passed the same props which I received to rerender function.
For me just updating the state didn't work. I am using a library with components and it looks like I can't force the component to update.
My approach is extending the ones above with conditional rendering. In my case, I want to resize my component when a value is changed.
//hook to force updating the component on specific change
const useUpdateOnChange = (change: unknown): boolean => {
const [update, setUpdate] = useState(false);
useEffect(() => {
setUpdate(!update);
}, [change]);
useEffect(() => {
if (!update) setUpdate(true);
}, [update]);
return update;
};
const MyComponent = () => {
const [myState, setMyState] = useState();
const update = useUpdateOnChange(myState);
...
return (
<div>
... ...
{update && <LibraryComponent />}
</div>
);
};
You need to pass the value you want to track for change. The hook returns boolean which should be used for conditional rendering.
When the change value triggers the useEffect update goes to false which hides the component. After that the second useEffect is triggered and update goes true which makes the component visible again and this results in updating (resizing in my case).