I am having an issue with my app in that it re renders a new joke twice when I click the new button function. Here is my code:
import React, { useState, useEffect } from "react";
import { Typography, Button } from "#material-ui/core";
import Navigation from "../Navigation";
export default function RandomJoke() {
const [isLoaded, setLoaded] = useState(false);
const [jokeData, setJokeData] = useState({});
const [loadNewJoke, setLoadNewJoke] = useState(false);
function useFetch() {
async function fetchMyAPI() {
let response = await fetch("https://icanhazdadjoke.com/slack");
response = await response.json();
setJokeData(response);
setLoaded(true);
}
useEffect(() => {
fetchMyAPI();
if (loadNewJoke) setLoadNewJoke(false);
}, [loadNewJoke]);
}
useFetch();
function reloadJoke() {
setLoaded(false);
setLoadNewJoke(true);
}
return (
<>
<Navigation mainpage="RandomJoke" />
<Typography variant="h6">Random Dad Joke</Typography>
{isLoaded && <div>{jokeData.attachments[0].text}</div>}
{!isLoaded && <div>loading...</div>}
{isLoaded && (
<Button variant="contained" onClick={() => reloadJoke()}>
New one
</Button>
)}
</>
);
}
I tried adding a newjoke state hook but still couldn't work it out. Thank you
That useEffect fires whenever the value of loadNewJoke changes, right? Not just when loadNewJoke is set to true. Look closely at the calls made after a button press, and how many times setLoadNewJoke is called.
Try to move:
if (loadNewJoke) setLoadNewJoke(false);
In your fetchMyApi function. I'm guessing when you hit the button, you trigger the effect cuz u change you deps value in this case to true. Then before effect is over you change it again to false which will triggeer re-run on your effect.
But why you dont just trigger fetchApi in your callback on button click, this way you can remove 1 state [loadNewJoke, setLoadNewJoke], will also remove the useEffect and make code cleaner over all
You're using useEffect wrong, i suggest u take a look at the Rules of Hooks
Don’t call Hooks inside loops, conditions, or nested functions.
I followed what Andom Miltev said about triggering the async function directly in my callback and it now works smoothly - thank you everyone for the help :)
import React, { useState, useEffect } from "react";
import { Typography, Button } from "#material-ui/core";
import Navigation from "../Navigation";
export default function RandomJoke() {
const [isLoaded, setLoaded] = useState(false);
const [jokeData, setJokeData] = useState({});
async function fetchMyAPI() {
setLoaded(false);
let response = await fetch("https://icanhazdadjoke.com/slack");
response = await response.json();
setJokeData(response);
setLoaded(true);
console.log("fired 1");
}
useEffect(() => {
fetchMyAPI();
}, []);
return (
<>
<Navigation mainpage="RandomJoke" />
<Typography variant="h6">Random Dad Joke</Typography>
{isLoaded && <div>{jokeData.attachments[0].text}</div>}
{!isLoaded && <div>loading...</div>}
{isLoaded && (
<Button variant="contained" onClick={() => fetchMyAPI()}>
New one
</Button>
)}
</>
);
}
Related
Consider I got a component called Test
import {useEffect, useState} from "react";
const Test = (props) => {
const [Amount, setAmount] = useState(1);
useEffect(()=>{
if(props.defaultAmount){
setAmount(props.defaultAmount)
}
props.getResult(Amount);
},[props, Amount])
return (
<>
<span>Amount is: {Amount}</span>
<input value={Amount} onChange={(e)=>setAmount(e.target.value)}/>
</>
)
}
export default Test;
I use this in two different components (actually my pages), one with defaultAmount another without.
Page 1:
<Test getResult={getAmountResult} defaultAmount={25}/>
But this not update result and it back to default one!
Page 2:
<Test getResult={getAmountResult} />
it works fine!
Working Demo
Is there any solution to avoid this?
try to change your code like this
import {useEffect, useState} from "react";
const Test = (props) => {
const [Amount, setAmount] = useState(1);
useEffect(() => {
props.getResult(Amount);
}, [Amount])
useEffect(()=>{
if(props.defaultAmount){
setAmount(props.defaultAmount)
}
},[props.defaultAmount])
return (
<>
<span>Amount is: {Amount}</span>
<input value={Amount} onChange={(e)=>setAmount(e.target.value)}/>
</>
)
}
export default Test;
in your current implementation you always overwrite the amount state with the default
Your useEffect function is the culprit. You're setting the Amount back to defaultAmount everytime Amount changes, thus overriding the user input.
Try updating the condition within useEffect before you set the value, to make sure you don't override the user input, something like:
useEffect(()=>{
if(props.defaultAmount && Amount === 1){ // Checking if the amount is still the initial value
setAmount(props.defaultAmount)
}
props.getResult(Amount);
},[props, Amount])
When input changes, setAmount called, it will update amount and trigger useEffect hook which will set amount to default value. Try this
import { useEffect, useState } from "react";
const Test = (props) => {
const [amount, setAmount] = useState(props.defaultAmount);
useEffect(() => {
if (amount) {
props.getResult(amount);
}
}, [amount, props]);
return (
<>
<span>Amount is: {amount}</span>
<input value={amount} onChange={(e) => setAmount(e.target.value)} />
</>
);
};
export default Test;
Hi guys I am trying to fetch data using get, and I want the data to be displayed after I click on the button, as a normal crud
I am new in programming if there is someone that can help me. I APPRECIATE THANKS
everything in my backend is ok, I try in postman and console.log is everything good. My problem is only in this part thanks
import { useState, useEffect } from "react";
import axios from "axios";
function Usuarios() {
const [users, setUsers] = useState([]);
useEffect(()=> {
const todosUsers = async () => {
const res= await axios.get("/users");
console.log(res)
setUsers(res.data);
}
todosUsers()
},[])
return (
<>
<button onClick=
{users.map((users) => (
<h1>{users.username}</h1>
))}></button>
</>
)
}
export default Usuarios;
one solution would be to keep a seperate variable to see if button is clicked as S.Singh mentioned
const [users, setUsers] = useState([]);
const [clicked, setClicked] = useState(false);
and in your component you can set the clicked variable to true on click
return (
<>
<button onClick={() => setClicked(true)}></button> //setting clicked true onclick
{clicked && users.map((users) => ( //only renders when click is true
<h1>{users.username}</h1>
))}
</>
)
if you want to hide and show alternatively on click just change the line to onClick={() => setClicked(!clicked)}
codesandbox demo
Move
{users.map((users) => (
<h1>{users.username}</h1>
))
}
from onClick definition and place it in an a markup where you want it to render. Define onClick hendler, which will be setting data from an API to a state
import { useState, useEffect } from "react";
import axios from "axios";
function Usuarios() {
const [users, setUsers] = useState([]);
const fetchOnClick = async () => {
await axios.get("/users");
console.log(res)
setUsers(res.data);
}
return (
<>
<button onClick={fetchOnClick}>
Fetch
</button>
<div>
{users.map((users) => (
<h1>{users.username}</h1>
))}
</div>
</>
)
}
OR
If you want to fetch the data inside useEffect hook, like you did in your example
import React, { useState, useEffect } from "react";
function Usuarios() {
const [users, setUsers] = useState([]);
const [isContainerShowed, setIsContainerShowed] = useState(false)
useEffect(() => {
const res= await axios.get("/users");
console.log(res)
setUsers(res.data);
}, [])
const displayContainer = () => setIsContainerShowed(true);
return (
<>
<button onClick={displayContainer}>
Fetch
</button>
<div style={{display: isContainerShowed ? "block" : "none"}}>
{users.map(users => <h1>{users.username}</h1>)}
</div>
</>
)
}
use Effect hook runs every time the component is rendered but as I understand, you want to display users when the button is clicked, so you can fetch users once the button is clicked and display them using the code below
import { useState, useEffect } from "react";
import axios from "axios";
function Usuarios() {
const [users, setUsers] = useState([]);
const fetchUsers= async () => {
const res= await axios.get("/users");
setUsers(res.data);
}
return (
<>
<button onClick={() => fetchUsers()}></button>
{
users.length && users.map(user => <h2>{user.username}</h2>)
}
</>
)
}
I have a React project where the parent component (functional) holds state to determine what formatting is applied to a list. When the user clicks a button, a modal is generated - I pass an anonymous function to that modal as props (onMainButtonClick), which when called flips the state and changes the formatting of the list (logic for this is in parent component).
When I use the button in my modal component (onClick={() => onMainButtonClick()}), the code works as expected. However, I would also like an enter press to trigger this. Therefore I have the following code implemented for this, but it doesn't function as expected. The modal closes and the function fires (I know this as I put a console log in there...) but the state that impacts the formatting is not changed.
EDIT: Having made a proposed change below (to memo'ize the onEnterPress function so it gets removed properly), here's the full code for the modal:
import { React, useRef, useEffect, useCallback } from "react";
import ReactDOM from "react-dom";
const Modal = ({
title,
description,
isOpen,
onClose,
onMainButtonClick,
mainButtonText,
secondaryButtonText,
closeOnly,
}) => {
const node = useRef();
const onEnterPress = useCallback(
(e) => {
if (e.key === "Enter") {
onMainButtonClick();
}
},
[onMainButtonClick]
);
useEffect(() => {
window.addEventListener("keydown", onEnterPress);
return () => {
window.removeEventListener("keydown", onEnterPress);
};
}, [onMainButtonClick]);
const handleClickOutside = (e) => {
if (node.current.contains(e.target)) {
return null;
}
onClose();
};
if (!isOpen) return null;
return ReactDOM.createPortal(
{// JSX here, removed for brevity},
document.body
);
};
export default Modal;
And the code for the parent (which shows the modal and passes down onMainButtonClick):
import { React, useState } from "react";
import { decode } from "html-entities";
import useList from "../../queries/useList";
import useBuyItem from "../../mutations/useBuyItem";
import Header from "../Shared/Header";
import Description from "../Shared/Description";
import ListItem from "./ListItem";
import BuyOverlay from "./BuyOverlay";
import Modal from "../Modal";
import ViewOperations from "./ViewOperations";
const View = (props) => {
const [buyOverlayOpen, setBuyOverlayOpen] = useState(false);
const [viewBuyersOverlayOpen, setBuyersOverlayOpen] = useState(false);
const [viewBuyers, setViewBuyers] = useState(false);
const [buyItemId, setBuyItemId] = useState();
const { isLoading, isError, data, error } = useList(props.match.params.id);
const buyItemMutation = useBuyItem(buyItemId, props.match.params.id, () =>
setBuyOverlayOpen(false)
);
if (isLoading) {
return <div>Loading the list...</div>;
}
if (isError) {
return <div>An error occured, please refresh and try again</div>;
}
return (
<div className="w-full">
<div className="mb-10 text-gray-600 font-light">
<Header text={data.name} />
<Description text={data.description} />
<ViewOperations
toggleViewBuyers={() => setViewBuyers(!viewBuyers)}
toggleBuyersOverlay={() =>
setBuyersOverlayOpen(!viewBuyersOverlayOpen)
}
viewBuyers={viewBuyers}
/>
<div id="list" className="container mt-10">
{data.items.length === 0
? "No items have been added to this list"
: ""}
<ul className="flex flex-col w-full text-white md:text-xl">
{data.items.map((item) => {
return (
<ListItem
item={decode(item.item)}
description={decode(item.description)}
itemId={item._id}
isBought={decode(item.bought)}
boughtBy={decode(item.boughtBy)}
boughtDate={decode(item.boughtDate)}
viewlink={item.link}
handleBuy={() => {
setBuyItemId(item._id);
setBuyOverlayOpen(true);
}}
key={item._id}
viewBuyers={viewBuyers}
/>
);
})}
</ul>
</div>
</div>
<BuyOverlay
isOpen={buyOverlayOpen}
message="Message"
onClose={() => setBuyOverlayOpen(false)}
onConfirmClick={(buyerName) => {
buyItemMutation.mutate(buyerName);
}}
/>
<Modal
title="Title"
description="Description"
isOpen={viewBuyersOverlayOpen}
onClose={() => {
setBuyersOverlayOpen(false);
}}
onMainButtonClick={() => {
setViewBuyers(true);
setBuyersOverlayOpen(false);
}}
mainButtonText="OK"
secondaryButtonText="Cancel"
/>
</div>
);
};
export default View;
Any ideas for why this is happening? I have a feeling it might be something to do with useEffect here, but I'm a bit lost otherwise...
onEnterPress is most probably directly defined in your functional component and that's why its reference changes every time your component re-renders. Your useEffect closes over this newly defined function on each render so your handler is not going to work as you expected.
You can wrap your onEnterPress with useCallback to memoize your handler onEnterPress so its definition stays the same throughout each render just like this:
const onEnterPress = useCallback(e => {
if (e.key === "Enter") {
onMainButtonClick();
}
}, [onMainButtonClick]); // Another function dep. You can also wrap it with useCallback or carry over its logic into the callback here
useEffect(() => {
window.addEventListener('keydown', onEnterPress);
return () => {
window.removeEventListener('keydown', onEnterPress);
};
}, [onEnterPress]);
I figured this out in the end - turns out it was as simple as my enterPress callback needing a preventDefault statement before the if block (e.preventDefault()).
There must have been something else on the page that was capturing the enter press and causing it to behave strangely.
Background
I'm working on a sidebar component that can be opened from many buttons in the UI. I want to 1) only render it once, 2) grant access to update its isVisible state to these buttons without drilling props down through a common ancestor.
Expectations vs Reality
I'd expect that I could create a context with its own api method to update an internal state. In my code sample I'm attempting to do this with an IIFE.
Questions
How does this break the rules of hooks?
How else can I provide an update function to this context?
export const SidebarContext = createContext((() => {
const [isVisible, setIsVisible] = useState(false)
return {
isVisible,
toggleVisibility: () => setIsVisible(!isVisible)
}
})())
createContext() receives the default value. So you're defining a function which is invoked immediately and the result of it will be used as default value for the context. That's where the useState breaks this rule:
Call Hooks from React function components.
In order to accomplish what you want you can do this:
import React, { createContext, useContext, useState } from "react";
const SidebarContext = createContext();
function Provider({ children }) {
let [isVisible, setIsVisible] = useState(false);
let toggle = useCallback(() => setIsVisible(s => !s), [setIsVisible])
// Pass the `state` and `functions` to the context value
return (
<SidebarContext.Provider value={{ isVisible, toggle }}>
{children}
</SidebarContext.Provider>
);
}
function YourButton() {
let { isVisible, toggle } = useContext(SidebarContext);
return (
<div>
<div>Sidebar is {isVisible : 'open': 'close'}</div>
<button onClick={toggle}>
Toggle
</button>
</div>
);
}
function App() {
return (
<Provider>
<YourButton />
</Provider>
);
}
I get a warning in my FlatList when using the useEffect hook to fetch data.
This is the complete component to reproduce the issue:
import React, { useState, useEffect } from "react";
import {
Text,
View,
FlatList,
ActivityIndicator,
SafeAreaView,
Button
} from "react-native";
const Test = props => {
const [people, setPeople] = useState([]);
const [loading, setLoading] = useState(false);
const [page, setPage] = useState(1);
useEffect(() => {
setLoading(true);
fetch(`http://jsonplaceholder.typicode.com/photos?_start=${page}&_limit=20`)
.then(res => res.json())
.then(res => {
//console.log(res);
setPeople(people => [...people, ...res]);
setLoading(false);
});
}, [page]);
const loadMore = () => {
setPage(page + 20);
};
return (
<SafeAreaView style={{ flex: 1 }}>
<FlatList
data={people}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<View>
<Text>{item.id}</Text>
<Text>{item.title}</Text>
</View>
)}
ListFooterComponent={
loading ? (
<ActivityIndicator />
) : (
<Button title="Load More" onPress={loadMore} />
)
}
/>
</SafeAreaView>
);
};
export default Test;
This is the warning I'm getting
VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc. Object {
"contentLength": 4418,
"dt": 705,
"prevDt": 669,
}
It basically tells me to use PureComponent or shouldComponentUpdate, though, but AFAIK both do not work with either a functional component or the useEffect hook, do they?
Although I did not notice a (huge) performance drop, I'm still wondering if there's a workaround to fix this issue. Any help would be appreciated. Thank you very much.
Edit: Using a PureComponent does not fix the issue:
Created PureComponentTest.js
import React from "react";
import { Text, View } from "react-native";
const PureComponentTest = props => {
return (
<View>
<Text>{props.id}</Text>
<Text>{props.title}</Text>
</View>
);
};
export default PureComponentTest;
And in my Component I updated renderItem={renderItems}:
const renderItems = itemData => {
return (
<PureComponentTest
id={itemData.item.id}
title={itemData.item.title}
/>
);
};
I really don’t see anything wrong with your component. It’s a very simple pure component. It may simply because the fetched data is too big. Try reduce the number of pages fetched each time. say 5 or 10 pages
maybe the warning is for fetch into useEffect, so review the next documentation:
Some rules to keep in mind about the useEffect hook:
You cannot call a hook within a conditional; Hooks must be called in
the exact same order. Putting the useEffect inside of a conditional
could break that cycle; The function you pass the hook cannot be an
async function, because async functions implicitly return promises,
and a useEffect function either returns nothing or a cleanup function.
Consider use it:
fetch(`http://jsonplaceholder.typicode.com/photos?_start=${page}&_limit=20`)
.then(res => res.json())
.then(res => {
//console.log(res);
setPeople(people => [...people, ...res]);
})
.catch(error=> console.error(error))
.finally(() => setLoading(false));