How do you update the LatestTweetsComponent with the data returned from a fetch call that happens in handleRequest? tweets is updating correctly onClick however the LatestTweetsComponent does not render or update. I may be approaching this incorrectly.
const LatestTweets = () => {
const [tweets, setTweets] = useState(null)
const handleRequest = async () => {
// this hits an api to get tweets and sets the tweets
// is this where the LatestTweetsComponent render is called?
}
return (
<div>
<button onClick={() => handleRequest()}>View Latest Tweets</button>
<LatestTweetsComponent tweets={tweets} />
</div>
)
}
const LatestTweetsComponent = props => {
return (
<div>
{props.map((tweet, index) => {
return <p key={index}>{tweet}</p>
})}
</div>
)
}
export default LatestTweets
i think this is because you are trying to map over "props", while you should be mapping over "props.tweets"
try this :
const LatestTweetsComponent = props => {
return (
<div>
{props.tweets.map((tweet, index) => {
return <p key={index}>{tweet}</p>
})}
</div>
)
}
Use that handleFetch in useEffect. So, whenever your state gets change useeffect will rerender that change.
And use tweets as argument in useEffect
Related
I have a ReactTS-App and I pass a prop via Router-Dom-Props to another component. The problem here is that I can use meal.name alone, but if I use meal.food with it or meal.food alone it doesnt work anymore.
Uncaught TypeError: meal.food is undefined
I checked TypeScript- & useEffect-errors, but I didnt find a solution yet.
And are the props loaded before the first render?
UPDATE I can print the meal.food when I use it like that location.state.meal.food but I cannot use it from the useState after I set it - I console.log it in the useEffect.
Meal.tsx
return (
<Link className="Meal Link" to={`/MealDetails/${meal.id}`} state={{ meal: meal }}>
<div className="MealIconMealName">
<div className="MealName">{meal.name}</div>
</div>
</Link>
);
};
MealDetails.tsx
const MealDetails = () => {
const location = useLocation();
const navigate = useNavigate();
const [meal, setMeal] = useState(Object);
useEffect(() => {
if (location.state) {
if (location.state.meal) {
setMeal(location.state.meal);
console.log("useEffect" + meal);
}
}
return () => {};
}, [location]);
return (
<div className="MealDetails">
<header>
<div className="HeaderText">{meal.name}</div>
</header>
<div className="MealDetailsCalorieCounter"></div>
{meal.food.map((meal : any) => (
<Food
key={meal.id}
/>
))}
</div>
);
};
Example meal-Object out of the .json file
{
"id": 1,
"name": "Breakfast",
"food": [
{
"name": "Waffeln",
"kcal" : 505
}
],
"calories": 505
}
Issue
The issue here is it seems that the meal.food is undefined on the initial render prior to the useEffect hook updating the local component state from the route state (i.e. from location.state).
Solution
It's considered a React anti-pattern to store passed data and/or derived "state" in local component state. Just consume the passed location.state.meal route state directly. You should also use defensive programming patterns in case a user navigates to the route rendering MealDetails from anywhere else other than the link that passes the route state.
Example:
const MealDetails = () => {
const { state } = useLocation(); // <-- access state
const navigate = useNavigate();
const meal = state || {}; // <-- provide fallback value
return (
<div className="MealDetails">
<header>
<div className="HeaderText">{meal.name}</div>
</header>
<div className="MealDetailsCalorieCounter"></div>
{(meal.food }} []).map((meal: any) => ( // <-- provide fallback array
<Food key={meal.id} />
))}
</div>
);
};
I don't think useEffect here is necessary. You can get the meal directly from the location object.
const MealDetails = () => {
const location = useLocation();
const { meal } = location.state;
return (
<div className="MealDetails">
<header>
<div className="HeaderText">{meal.name}</div>
</header>
<div className="MealDetailsCalorieCounter"></div>
{meal.food.map((meal : any) => (
<Food
key={meal.id}
/>
))}
</div>
);
};
interface MyValue {
//interface declaration
}
export function MyComponent {
const [myvalue, setMyvalue] = useState<MyValue>()
useEffect(() => {
setMyvalue(passedData)
}, [passedData])
function getAutofocus() {
// return true/false based on myvalue value
}
render() {
return (
<div>
<input
autofocus={getAutofocus()}
ref={c => (this._input = c)}
/>
</div>
);
}
}
}
passedData is passed as a prop from parent and this is populated in the parent via a server GET call, which takes some time to resolve.
PROBLEM - getAutofocus() rendered before passedData is properly loaded.
my requirement here is to wait until passedData is properly resolved before invoking the
getAutofocus() method.
If we can stop the rendering of the UI/ or the input field until passedData is completely resolved, that will allow getAutofocus() to properly execute.
what's the best way to do this? can react suspense be used here?
Sounds like conditional rendering would be enough to get what you need:
render() {
// if myvalue is not populated yet, do not render anything
return !myvalue ? null : (
<div>
<input
autofocus={getAutofocus()}
ref={c => (this._input = c)}
/>
</div>
);
}
}
The proper way of doing this is with a ref
const MyCom = props => {
const inputRef = React.useRef();
React.useEffect(()=>{
if (inputRef.current) {
inputRef.current.focus();
}
},[inputRef]);
return (
<div>
<input
ref={inputRef}
/>
</div>
);
}
Remove render method only class components have render
I have 2 components. In the parent component I have this:
const Demo = () => {
const [state, setState] = useState(true);
const onChange = useCallback(
(value: string) => {
console.log(value);
},
[],
);
return (
<div className="a">
<button onClick={() => setState(!state)}>sds</button>
<div className="123">
<Bar searchHandler={onChangeSearchHandler} />
</div>
</div>
);
};
In the Bar component I have this:
const Bar = ({ searchHandler }) => {
console.log('bar');
return (
<div>
<input type="text" onChange={(value) => searchHandler(value.target.value)} />
</div>
);
};
Wrapping onChange with useCallback I expect to cache the function and when I click on <button onClick={() => setState(false)}>sds</button> I don't want to render Bar component, but it is triggered. Why Bar component is triggered and how to prevent this with useCallback?
This has nothing to do with the onChange function you're wrapping with useCallback. Bar gets re-rendered because you're changing the state through setState in its parent component. When you change the state in a component all its child components get re-rendered.
You can verify it yourself by trying this:
const Demo = () => {
const [state, setState] = useState(true);
return (
<div className="a">
<button onClick={() => setState(!state)}>sds</button>
<div className="123">
<Bar />
</div>
</div>
);
};
const Bar = ({ searchHandler }) => {
console.log('bar');
return (
<div></div>
);
};
You'll see that the Bar gets re-rerender anyway.
If you want to skip re-rerendring any of the child components, you should memoize them using React.memo when applicable.
Also, you should familiarize yourself with how state in react works and how does it affect the nested components as this is a main concept.
The issue is that you haven't used React.memo on Bar component. The function useCallback works only if you use HOC from memo.
try this, in Bar component create this wrapped component:
const WrappedBar = React.memo(Bar);
and in parent component use this wrapped bar:
const Demo = () => {
const [state, setState] = useState(true);
const onChange = useCallback(
(value: string) => {
console.log(value);
},
[],
);
return (
<div className="a">
<button onClick={() => setState(!state)}>sds</button>
<div className="123">
<WrappedBar searchHandler={onChangeSearchHandler} />
</div>
</div>
);
};
This is my parent Component having state ( value and item ). I am trying to pass value state as a props to child component. The code executed in render method is Performing toggle when i click on button. But when i call the list function inside componentDidMount, Toggle is not working but click event is performed.
import React, { Component } from 'react'
import Card from './Components/Card/Card'
export class App extends Component {
state = {
values : new Array(4).fill(false),
item : [],
}
toggleHandler = (index) => {
console.log("CLICKED");
let stateObject = this.state.values;
stateObject.splice(index,1,!this.state.values[index]);
this.setState({ values: stateObject });
}
list = () => {
const listItem = this.state.values.map((data, index) => {
return <Card key = {index}
show = {this.state.values[index]}
toggleHandler = {() => this.toggleHandler(index)} />
})
this.setState({ item : listItem });
}
componentDidMount(){
// if this is not executed as the JSX is render method is executed everything is working fine. as props are getting update in child component.
this.list();
}
render() {
return (
<div>
{/* {this.state.values.map((data, index) => {
return <Card key = {index}
show = {this.state.values[index]}
toggleHandler = {() => this.toggleHandler(index)} />
})
} */}
{this.state.item}
</div>
)
}
}
export default App
This is my child Component where the state is passed as props
import React from 'react'
const Card = (props) => {
return (
<div>
<section>
<h1>Name : John Doe</h1>
<h3>Age : 20 </h3>
</section>
{props.show ?
<section>Skills : good at nothing</section> : null
}
<button onClick={props.toggleHandler} >Toggle</button>
</div>
)
}
export default Card
I know the componentDidMount is executed only once. but how to make it work except writing the JSX directly inside render method
make a copy of the state instead of mutating it directly. By using [...this.state.values] or this.state.values.slice()
toggleHandler = (index) => {
console.log("CLICKED");
let stateObject = [...this.state.values]
stateObject = stateObject.filter((_, i) => i !== index);
this.setState({ values: stateObject });
}
Also in your render method, this.state.item is an array so you need to loop it
{this.state.item.map(Element => <Element />}
Also directly in your Render method you can just do
{this.state.values.map((data, index) => {
return <Card key = {index}
show = {this.state.values[index]}
toggleHandler = {() => this.toggleHandler(index)} />
})}
In your card component try using
<button onClick={() => props.toggleHandler()}} >Toggle</button>
Value should be mapped inside render() of the class component in order to work
like this:
render() {
const { values } = this.state;
return (
<div>
{values.map((data, index) => {
return (
<Card
key={index}
show={values[index]}
toggleHandler={() => this.toggleHandler(index)}
/>
);
})}
</div>
);
}
check sandbox for demo
https://codesandbox.io/s/stupefied-spence-67p4f?file=/src/App.js
I'd like to react rerender component after every state edit.
App component:
let [cur1, setCur1] = useState('USD')
let [cur2, setCur2] = useState('EUR')
let [result, setResult] = useState(0)
let currenciesArr = [cur1, cur2]
async function getRate(e) {
e.preventDefault()
setCur1(cur1 = e.target.cur1.value)
setCur2(cur2 = e.target.cur2.value)
let amount = e.target.amount.value
const api_url = await fetch(`https://free.currconv.com/api/v7/convert?q=${cur1}_${cur2}&compact=ultra&apiKey=${API_KEY}`)
const data = await api_url.json()
await setResult(convert(amount, data))
}
I have used Context.Provider for rerender, but it doesn't work.
return (
<Context.Provider value={{currenciesArr}}>
<div>
<Choose getRate={getRate} chooseCur={chooseCur} chooseCur2={chooseCur2}/>
<ShowRate currencies={currenciesArr} result={result}/>
</div>
</Context.Provider>
)
Component that need to rerender
function Choose(props) {
const cProps = useContext(Context)
console.log(cProps.currenciesArr);
return(
<div>
<div>
<button onClick={ props.chooseCur } name='RUB'>RUB</button>
<button onClick={ props.chooseCur } name='AUD'>AUD</button>
</div>
<div>
<button onClick={ props.chooseCur2 } name='EUR'>EUR</button>
<button onClick={ props.chooseCur2 } name='GBP'>GBP</button>
</div>
<form onSubmit={props.getRate}>
{cProps.currenciesArr.map((item,i) => {
return(
<input type='text' key={i} name={'cur'+(i+1)} defaultValue={item}></input>
)
})
}
<input type='text' name='amount' defaultValue='1'></input>
<button onClick={(e)=>{console.log(e.target)}} ></button>
</form>
</div>
)
}
Button with prop props.chooseCur setting state in App component
function chooseCur(e) {
e.preventDefault()
setCur1(e.target.name)
}
function chooseCur2(e) {
e.preventDefault()
setCur2(e.target.name)
}
and i'd like to "choose" component will rerender after setState.
First currenciesArr should be part of the state as const [currenciesArr, setCurrenciesArr] = useState([cur1, cur2])
Next, you need to call setCurrenciesArr in your chooseCur2 functions. I used a restructuring assignment to get the value of name inside the function. Hooks are called when the event loop is complete. See Capbase Medium post for more information on hooks and the event loop.
In choose.js
You need to use value in your input instead of defaultValue and set it as readonly to prevent receiving a warning about setting the value.
Default value provides the value if none is present.
See the following codesandbox for a working version.
https://codesandbox.io/s/long-rain-8vyuh