I have a pretty simple useEffect hook
const [tagsWithData, setTagsWithData] = useState([]);
useEffect(() => {
....
const finalsTags = temp.map((item) => item.name);
setTagsWithData(finalsTags);
}, []);
Inside of return, I have condition to render the input tag
{tagsWithData.length !== 0 ? (
<TagsInput
selectedTags={selectedTags}
tags={tagsWithData}
/>
) : (
<TagsInput
selectedTags={selectedTags}
tags={tags}
/>
)}
The above code always stays on 0 and it does not move to the else condition.
What am I making wrong here.
Thank you
Your useEffect is not being told to update. useEffect needs to be passed the value/dependencies that it needs to (trigger the) update on. Without it, the effect will only run once on (initial) component render
const [tagsWithData, setTagsWithData] = useState([]);
useEffect(() => {
....
const finalsTags = temp.map((item) => item.name);
setTagsWithData(finalsTags);
}, [temp]); // <--- add this
Below is a small example illustrating the differences. Click on the button, and check out the output of both effectWithDep and effectWithoutDep. You'll notice only effectWithDep will update.
// Get a hook function
const { useState, useEffect } = React;
const Example = ({title}) => {
const [count, setCount] = useState(0);
const [effectWithDep, setEffectWithDep] = useState(0);
const [effectWithoutDep, setEffectWithoutDep] = useState(0);
useEffect(() => {
setEffectWithDep(count)
}, [count])
useEffect(() => {
setEffectWithoutDep(count)
}, [])
return (
<div>
<p>{title}</p>
<p>effectWithDep: {effectWithDep}</p>
<p>effectWithoutDep: {effectWithoutDep}</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
};
// Render it
ReactDOM.render(
<Example title="Example using Hooks:" />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Related
Is there any api that allow us to write code something like this:
const MyComponents = () => {
const [number, setNumber] = useState(0);
return {
Btn: <Button onPress={() => setNumber(number + 1)}>
{number}
</Button>,
Log: <p>{number}</p>
}
}
const Perent = () => <>
<div ...>
<MyComponents.Btn/>
...
...
</div>
<MyComponents.Log/>
</>
Some kind of ability to group some Component.And render them in different places...
Seems like this would be better achieved by using a Context.
E.g.
const { createContext, useState, useContext } = React;
const CountContext = createContext();
const CountContainer = ({ children }) => {
const [number, setNumber] = useState(0);
return <CountContext.Provider value={{ number, setNumber }}>
{children}
</CountContext.Provider>
};
const CountButton = () => {
const { number, setNumber } = useContext(CountContext);
return <button onClick={() => setNumber((c) => c + 1)}>
{number}
</button>;
};
const CountLog = () => {
const { number } = useContext(CountContext);
return <p>{number}</p>;
};
const SomeCountButtons = () => <div><CountButton /><CountButton /></div>;
const App = () => (<div>
<CountContainer>
<CountButton />
<CountLog />
</CountContainer>
<CountContainer>
<SomeCountButtons />
<CountLog />
</CountContainer>
</div>);
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Then any <CountButton>s or <CountLog>s that occur anywhere within the same <CountContainer> will be able to share their state.
How to fetch time when submit button is clicked from app component. I have two separate component a Timer component and a App component.
when i click on submit button from app component it should take current time snapshot from timer component and send it to app component
I don't want to use Submit in timer component
Is it possible to do in React
?
Here is app component
export default function App() {
return (
<div className="App">
<h2>Start editing to see some magic happen!</h2>
<h3>Time: </h3>
<button >Submit</button>
<Timer />
</div>
);
}
Here is Timer Component
export default function Time() {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
let countersystem;
countersystem = setTimeout(() => setCounter(counter + 1), 1000);
return () => {
clearTimeout(countersystem);
};
}, [counter]);
return (
<div className="App">
<div>Countdown: {counter}</div>
</div>
);
}
Here you go :
You have to take 2 state on App level
1 : snapshotTrigger to trigger to timer component get current snapshot via useEffect
2 : snapshot maintain last/current snapshot
You can run the below code snippet, hope that will clear your doubts :
const { useState , useEffect } = React;
function Timer({snapshotTrigger , getSnapshot }) {
const [counter, setCounter] = useState(0);
// ------------ START : ADDED -----------
useEffect(() => {
if (snapshotTrigger) {
getSnapshot(counter);
}
}, [snapshotTrigger]);
// ------------ END : ADDED -----------
useEffect(() => {
let countersystem;
countersystem = setTimeout(() => setCounter(counter + 1), 1000);
return () => {
clearTimeout(countersystem);
};
}, [counter]);
return (
<div className="App">
<div>Countdown: {counter}</div>
</div>
);
}
function App() {
// ------------ START : ADDED -----------
const [snapshotTrigger, setSnapshotTrigger] = useState(0);
const [snapshot, setSnapshot] = useState(0);
// ------------ START : END -----------
return (
<div className="App">
<h2>Start editing to see some magic happen!</h2>
<h3>Time: {snapshot}</h3>
<button onClick={() => setSnapshotTrigger(snapshotTrigger + 1)}>Submit</button>
<Timer snapshotTrigger={snapshotTrigger} getSnapshot={setSnapshot} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('react-root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react-root"></div>
To achieve what you want I added two states to the <App> component (counter and trigger) and sent the counter set state function of the <App> component to the <Timer>, and used trigger state variable to check if the timer should continue or not, here is a snippet of what I did and also you can check this snippet for working sample:
import React from "react";
import "./styles.css";
function Timer({ onTick, active }) {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
let countersystem;
countersystem = setTimeout(() => setCounter(counter + 1), 1000);
return () => {
clearTimeout(countersystem);
};
}, [counter]);
React.useEffect(() => {
if (active) {
onTick(counter);
}
}, [active]);
return (
<div className="App">
<div>Countdown: {counter}</div>
</div>
);
}
export default function App() {
const [counter, setCounter] = React.useState(0);
const [tigger, setTrigger] = React.useState(0);
return (
<div className="App">
<h2>Start editing to see some magic happen!</h2>
<h3>Time: {counter}</h3>
<button
onClick={() => {
setTrigger(tigger + 1);
}}
>
Submit
</button>
<Timer onTick={setCounter} active={tigger} />
</div>
);
}
I'm in the process of refactoring some of our components so I'm trying to incorporate memoization as some components may re-render with the same values (for example, hotlinked image URLs unless they are the same).
I have a simple component:
const CardHeader = props => {
// img is a stringand showAvatar is a boolean but it's always true
const { ..., showAvatar, img } = props;
return (
<CardHeader>
<ListItem>
// AvatarImage shouldn't re-render if img is the same as previous
{showAvatar && <AvatarImage img={img} />
</ListItem>
</CardHeader>
);
}
And then the AvatarImage:
const AvatarImage = React.memo(props => {
console.log("why is this still re-rendering when the img value hasn't changed?");
const { img } = props;
return (
<ListItemAvatar>
{img ?
<Avatar src={img} />
:
<Avatar>
Some initials
</Avatar>
}
</ListItemAvatar>
);
});
I have also tried passing in second argument of memo:
(prevProps, nextProps) => {
return true; // Don't re-render!
}
But the console.log still shows every time. I'm obviously missing something here or don't quite understand how this works. This component is a few levels down, but it passes in the img if it's available every time so I'd expect it to know that if the img was passed in the previous render and it's the same it knows not to re-render it again but for some reason it does?
Thanks all. It's much appreciated.
Well it is either showAvatar is not always true or CardHeader ListItem component magically decides whether show children or not
Example
const { useState, useEffect, memo, createContext, useContext } = React;
const getAvatars = () => Promise.resolve([
{
src: 'https://i.picsum.photos/id/614/50/50.jpg'
},
{
src: 'https://i.picsum.photos/id/613/50/50.jpg'
}
])
const Avatar = ({src}) => {
console.log('avatar render');
return <img src={src} alt="avatar"/>
}
const MemoAvatarToggle = memo(({src}) => {
console.log('memo avatar with \'expression &&\' render');
return <div>
{src ? <img src={src} alt="avatar"/> : <div>Test </div>}
</div>
})
const CardHeader = ({children}) => {
const luck = Boolean(Math.floor(Math.random() * 1.7));
return <div>
{luck && children}
</div>
}
const ListItem = ({children}) => {
return <div>
{children}
</div>
}
const ShowAvatarContext = createContext()
const App = (props) => {
const [avatars, setAvatars] = useState([]);
const [toggle, setToggle] = useState(false);
const [showAvatar, setShowAvatar] = useContext(ShowAvatarContext);
useEffect(() => {
let isUnmounted = false;
let handle = null;
setTimeout(() => {
if(isUnmounted) {
return;
}
setShowAvatar(true);
}, 500);
getAvatars()
.then(avatars => {
if(isUnmounted) {
return;
}
setAvatars(avatars)
})
const toggle = () => {
setToggle(prev => !prev);
handle = setTimeout(toggle, 1000);
//setShowAvatar(prev => !prev);
}
handle = setTimeout(toggle, 1000);
return () => {
isUnmounted = true;
clearTimeout(handle);
}
}, []);
return <div>
<CardHeader>
<ListItem>
{showAvatar && avatars.map((avatar, index) => <MemoAvatarToggle key={index} src={avatar.src}/>)}
</ListItem>
</CardHeader>
{toggle ? 1 : 0}
</div>
}
const ShowAvatarProvider = ({children}) => {
const state = useState(false);
return <ShowAvatarContext.Provider value={state}>
{children}
</ShowAvatarContext.Provider>
}
ReactDOM.render(
<ShowAvatarProvider>
<App/>
</ShowAvatarProvider>,
document.getElementById('root')
);
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>
Do you have StrictMode enabled? That will cause a component memoized with React.memo to render twice.
More information:
https://reactjs.org/docs/strict-mode.html
My React Component is rendering twice because of Strict Mode
memo will not block re-render if the component is actually referenced the changing props or functions.
In your scenario your AvatarImage referenced img, in this case if parent's state's img is changed, then your component will be re-rendered.
Alternatively, if your parent is just changed other props instead of img, then the AvatarImage will NOT be re-rendered.
Alternatively, if any props but you didn't add memo to AvatarImage, then AvatarImage will be re-rendered for each of parent's state updated.
You need to memorized img props too.
const CardHeader = props => {
const { showAvatar, img } = props;
const updatedIMG = React.useMemo(() => img, []);
return (
<CardHeader>
<ListItem>
{showAvatar && <AvatarImage img={updatedIMG} />
</ListItem>
</CardHeader>
);
}
Above one would work
I'm trying to learn how to use intervalRef where I increment the state ever 100 ms, but for some reason it does not work.
const {useState,useEffect,useRef} = React;
function Timer({active}) {
const intervalRef = useRef(null)
const [count, setCount] = useState(0)
useEffect(()=>{
if(active){
intervalRef.current = setInterval(()=>{
console.log(count);
setCount(count + 1);
},100)
} else {
clearInterval(intervalRef.current)
}
},[active])
return (
<p>{count}</p>
)
}
function Main() {
const [active, setActive] = useState(false)
return (
<div>
<Timer active={active}/>
<button onClick={()=>{setActive(!active)}}>Toggle</button>
</div>
)
}
ReactDOM.render(<Main />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="app"></div>
The interval works completely fine since the console.log(count) prints ok, but why doesn't setCount work?
Since the useEffect is not dependant on count, the count inside the closure is always 0, and 0 + 1 -> 1. Use an updater function when you call setState. The update function is called with the current state.
Note: you should also return a cleanup function from useEffect, that will clear the interval if the component is unmounted.
const { useState, useEffect, useRef } = React;
function Timer({ active }) {
const intervalRef = useRef(null);
const [count, setCount] = useState(0);
useEffect(
() => {
if (active) {
intervalRef.current = setInterval(() => {
setCount(count => count + 1);
}, 100);
} else {
clearInterval(intervalRef.current);
}
return () => clearInterval(intervalRef.current); // cleanup function
},
[active]
);
return <p>{count}</p>;
}
function Main() {
const [active, setActive] = useState(false);
return (
<div>
<Timer active={active} />
<button onClick={() => { setActive(!active); }}>Toggle</button>
</div>
);
}
ReactDOM.render(<Main />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Im trying to display a very long list from .json file (2k+ nodes with multiple lines of text). Is there a way to set useState variable after list finishes rendering itself cause useEffect refused to work
import React from 'react';
import LongList from './LongList.json';
const LongList = () => {
const [isLoaded,setIsLoaded] = React.useState(false);
React.useEffect(() => {
setIsLoaded(true);
}, [setIsLoaded]);
return (
<div>
{LongList.map(element => (
<div key={element.text}>{element.text}</div>
))}
</div>
);
};
You can do something like that by checking the index of the current item:
{LongList.map((element, index) => (
<div key={element.text}>{element.text}</div>
if(index === LongList.length - 1) {
// it is loaded
}
))}
You're on the right track with useEffect. I believe part of the issue you're having is due to using setIsLoaded as the second argument to useEffect. Instead, use [], which tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. More info in the React docs.
Here's an example, with a console log in the useEffect callback showing it's only run once.
const data = Array.from(Array(10001).keys());
const LongList = ({data}) => {
const containerRef = React.useRef(null);
const [height, setHeight] = React.useState(0);
React.useEffect(() => {
console.log('Height: ', containerRef.current.clientHeight);
setHeight(containerRef.current.clientHeight);
}, []);
return (
<div>
<div>Height: {height}</div>
<div ref={containerRef}>
{data.map(element => (
<div key={element}>{element}</div>
))}
</div>
</div>
);
};
ReactDOM.render(<LongList data={data} />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>