I have 2 states:
const [formData, setFormData] = useState({ input: null });
const [outputData, setOutputData] = useState({ output: null });
formData is coming from my < input />. So, I am writing the input data by writing. outputData is coming from my GET api call.
I need to separate input data and output data. How can I tell the function if the message is from me?
function renderMessage(message) {
//const here
if (messageFromMe) {
return <Me />;
} else {
return <You />;
}
}
function Me(props) {
return (
<li className="Messages-message currentMember">
<div className="Message-content">
Hey its me
</div>
</li>
);
}
function You(props) {
return (
<li className="Messages-message">
<div className="Message-content">
Test
</div>
</li>
);
}
This might be a good application for useEffect hook. The useEffect hook takes 2 arguments, a callback function (this could be your renderMessage) and an array of values. Whenever a value in the array changes, useEffect will call your function passed into the first argument. The hook itself would be inside of a higher-level component that would effectively parent your You and Me components when they are rendered by the callback of the hook.
Now we can do this:
const [formData, setFormData] = useState(null);
const [outputData, setOutputData] = useState(null);
const [lastMessageSender, setLastMessageSender] = useState("");
let messageFromMe = (lastMessageSender === "me");
useEffect(()=>{renderMessage(message)}, [lastMessageSender]);
The last piece is this - whenever you call setFormData, also call setLastMessageSender and pass in the value "me" and whenever you call setOutputData, also call setLastMessageSender and pass in the value "you" - they could be other values eventually, such as the name of the user.
You may want to have convenience functions to help you code stay DRY...
const updateMessageFromOutput = payload => {
setOutputData(payload);
setLastMessageSender("you");
}
const updateMessageFromForm = payload => {
setFormData(payload);
setLastMessageSender("me");
}
Related
I am still new to React and am trying to update my api with a fetch request that takes the values provided by my "form". I'm trying to pass in a variable as a parameter to a function but its undefined.
I'm thinking it may be something to do with it being asynchronous but I'm not entirely sure.
import './MakeRequest.css';
import React, { useState , useEffect } from 'react'
import { useLocation } from "react-router-dom";
const MakeRequest = () => {
const [partySize, setPartySize] = useState(0);
const [time, setTime] = useState("");
const [expiration_date, setExpirationDate] = useState("");
const addRequests = async ({restaurant,partySize,time,expiration_date}) => {
//values are undefined expect restaurant_name
console.log(partySize)
console.log(time)
console.log(expiration_date)
let item={"restaurant":restaurant.restaurant_name,"party_size":partySize,"time": time,"expiration_date":expiration_date}
let result = await fetch("api thingy", {
method: 'POST',
headers:{
"Content-Type":"application/json",
"Accept":'application/json'
},
body:JSON.stringify(item)
})
result = await result.json();
};
const location = useLocation();
const restaurant = location.state;
return (
<div className = "makeRequest">
<img src={restaurant.picture} alt="" className="requestpicture"/>
<h1> Reservation at {restaurant.restaurant_name} </h1>
<h2> {restaurant.address} </h2>
<input type = "number" placeholder = "Party Size" id = "party-size"
onChange = {(e)=>setPartySize(e.target.value)}
className = "form-control"/>
<input type = "time" placeholder = "Time" id = "time"
onChange = {(e)=>setTime(e.target.value)}
className = "form-control"/>
<input type = "date" placeholder = "Expiration Date" id = "expiration-date"
onChange = {(e)=>setExpirationDate(e.target.value)}
className = "form-control"/>
{/*
<button onClick={() => console.log(partySize)}> Make Request </button>
*/}
<button onClick={() => addRequests(restaurant,partySize,time,expiration_date)}> Make Request </button>
</div>
)}
export default MakeRequest;
I tried checking to see if for some reason the values are never set in the first place. but when I run the commented code:
<button onClick={() => console.log(partySize)}> Make Request </button>
I actually receive the right values. Why is this so?
You're calling addRequests with 5 arguments, but your function expects a single object.
You could either remove the curlies from the function declaration:
const addRequests = async (restaurant,partySize,time,expiration_date) => {
Or add them to the place where you call it:
onClick={() => addRequests({restaurant,partySize,time,expiration_date})}
Additional Info
Functions that take many arguments can be annoying to use because you have to remember what order to pass them in:
// args must be passed in this order
function addRequests (restaurant, partySize, time, expiration_date) { ... }
To avoid this problem it's common to declare functions that take a single argument, an object with named properties:
function addRequests(options) {}
Declared this way the function can be invoked without having to think about argument order:
const request = {
partySize: 5,
expiration_date: new Date(),
time: '7:00',
restaurant: {
name: 'Pizza Chunks',
address: '999 Chonky Chonk Way'
}
}
addRequests(request);
The function can then extract the info it needs by name:
function addRequests(options) {
const time = options.time; // '7:00'
const partySize = options.partySize; // 5
...
}
It can extract a bunch of options at once via destructuring. This is equivalent to the previous snippet:
function addRequests(options) {
const { time, partySize } = options;
...
}
To tighten up the code even further, you can do the destructuring on incoming argument. The function still takes a single argument. We're just extracting properties from that argument. Again, this is equivalent to the examples above.
function addRequests({ time, partySize }) {
...
}
shorthand property names
One other thing crucial to understanding the effect the curly braces have when invoking your function is "shorthand property names". When declaring an object literal you can omit the "value" for properties where you'd just be repeating the name:
const partySize = 5;
const time = '7:00';
const options = {
partySize,
time,
}
// same as
const options = {
partySize: partySize,
time: time,
}
So when you do this (with the curly braces):
addRequests({ restaurant, partySize, time, expiration_date })
You're passing a single argument, an object, with those fields.
Without the curlies you're passing 4 arguments, in a specific order:
addRequests( restaurant, partySize, time, expiration_date )
You can make it work either way, but you have to decide whether the function takes multiple positional arguments or a single options object argument.
You can of course do both if you want:
function someFunc(foo, options) { ... }
someFunc(24, { bing, bang, boom })
In a custom alert system I have created in react I have 2 main functions, a custom hook:
function useAlerts(){
const [alerts, setAlerts] = useState([]);
const [count, setCount] = useState(0);
let add = function(content){
let remove = function(){
console.log(`alerts was set to ${alerts} before remove`);
setAlerts(alerts.slice(1));
};
let newAlert =
<div className = 'alert' onAnimationEnd = {remove} key = {count}>
<Warning/>
<span>
{content}
</span>
</div>
setAlerts([...alerts, newAlert]);
setCount(count + 1);
}
return [alerts,add];
}
and another element to display the data within the custom hook.
function Alerts(){
let [alerts,add] = useAlerts();
useEffect(() => {
let handler = function(){
add('test');
};
window.addEventListener('keyup',handler);
return function(){
window.removeEventListener('keyup',handler);
}
});
return (
<div className = 'alerts'>
{alerts}
</div>
)
}
the current issue I have is with the remove callback function, the console will look something like this.
let remove = function(){
console.log(`alerts was set to ${alerts} before remove`);
/// expected output: alerts was set to [<div>,<div>,<div>,<div>] before remove
/// given output: alerts was set to [] before remove
setAlerts(alerts.slice(1));
};
I understand this is because the remove function is taking the initial value of the alert state, but how do I make sure it keeps an up to date value? React doesn't seem to allow useEffect in a callback so I seem to be a bit stuck. Is passing the value in an object the way to go or something?
The issue I believe I see here is that of stale enclosures over the local React state. Use functional state updates to correctly update from the previous state instead of whatever if closed over in callback scope. In fact, use functional state updates any time the next state depends on the previous state's value. Incrementing counts and mutating arrays are two prime examples for needing functional state updates.
function useAlerts() {
const [alerts, setAlerts] = useState([]);
const [count, setCount] = useState(0);
const add = (content) => {
const remove = () => {
console.log(`alerts was set to ${alerts} before remove`);
setAlerts(alerts => alerts.slice(1));
};
const newAlert = (
<div className='alert' onAnimationEnd={remove} key={count}>
<Warning/>
<span>
{content}
</span>
</div>
);
setAlerts(alerts => [...alerts, newAlert]);
setCount(count => count + 1);
}
return [alerts, add];
}
I wanted to add the data of a Axios response into my useState Array. The problem is that I don't know how to get the data out of the then() function. Basically what I want to do is save the Axios response so that I can use it in my Array. (I'm trying React for the first time)
for (var i = 1; i <= props.max; i++) {
const response = Axios.get("http://localhost:3001/content", {id: 1});
response.then((res) => {
console.log(res.data[0].title)
})
blogs.push({title: "TITLE HERE", text: "text", author: "author", date: "date"}); //I want to insert the title here
}
I already tried:
const [title, setTitle] = useState();
for (var i = 1; i <= props.max; i++) {
const response = Axios.get("http://localhost:3001/content", {id: 1});
response.then((res) => {
setTitle(res.data[0].title)
})
}
Heres the complete function:
import React, { useEffect, useState, express, memo } from "react";
import './network.css';
import Axios from 'axios';
function Content(props) {
const [blogs, setBlogs] = useState([]);
const [title, setTitle] = useState();
/**const [text, setText] = useState();
const [author, setAuthor] = useState();
const [date, setDate] = useState();*/
for (var i = 1; i <= props.max; i++) {
const response = Axios.get("http://localhost:3001/content", {id: 1});
response.then((res) => {
console.log(res.data[0].title)
})
blogs.push({title: "TITLE", text: "text", author: "author", date: "date"}); //I want to insert the title here
}
return (
<div>
{blogs.map((blog) => (
<div class="card">
<div class="card-body">
<h4>{blog.title}</h4>
<p>{blog.text}</p>
<div class="user">
<img alt="user" id="image"/>
<div class="user-info">
<h5>{blog.author}</h5>
<small>{blog.date}</small>
</div>
</div>
</div>
</div>
))}
</div>
);
}
export default Content;
Please add your fetch logic on useEffect hook. Otherwise, fetch logic will be executed in every re-render.
Your app may get frozen.
And you should not change state variable blogs by blogs.push....
use setBlogs function.
And please use className instead of class in DOM.
I see many problems in the code and strongly to read react help before writing any code.
Anyway updated code is here.
function Content(props) {
const [blogs, setBlogs] = useState([]);
const [title, setTitle] = useState();
/**const [text, setText] = useState();
const [author, setAuthor] = useState();
const [date, setDate] = useState();*/
useEffect(() => {
for (var i = 1; i <= props.max; i++) {
const response = Axios.get("http://localhost:3001/content", {id: 1});
response.then((res) => {
setBlogs(res.data);
})
}
}, []);
return (
<div>
{blogs.map((blog, key) => (
<div className="card" index={key}>
<div className="card-body">
<h4>{blog.title}</h4>
<p>{blog.text}</p>
<div className="user">
<img alt="user" id="image"/>
<div className="user-info">
<h5>{blog.author}</h5>
<small>{blog.date}</small>
</div>
</div>
</div>
</div>
))}
</div>
);
}
export default Content;
You should useEffect hook for fetching data and .then should set it to the state. UseEffect will fetch data when the component is rendered, and store it in the state. Then you can display it in jsx. I recommend reading this article (which shows how to do it with Axios).
https://www.freecodecamp.org/news/how-to-use-axios-with-react/
Since Axios is a promise-based HTTP client library you will have to deal with the data only after the response is resolved. This means you have two options . 1) use setBlog inside then function completely (which is mentioned by other answers already)
2) Use async-await syntax(which is just syntactic sugar to the Promise.then() and Promise.catch())
Another thing you need to keep in mind is to try and treat your react state arrays as immutable. So you need to use the spread operator instead of the traditional push method because the push method directly mutates your state array and can lead to some dangerous and error-prone code.
It is recommended that you make you react 'state' changes in the useEffect hook or inside a function in the functional component. triggering state changes in the manner you have done can lead to infinite rendering and cause a Runtime Error.
When creating a list in the UI from an array with JSX, you should add a key prop to each child and to any of itsβ children. (the key is recommended 'NOT 'to be the index of the array) Below is the code sample to set your array into the state
useEffect(()=>{
(async()=>{
for(let i=0;i<=props.max;i++){
let response = await Axios.get("http://localhost:3001/content",{id:1});
setBlogs((blog)=>[...blog,response.data]);
}
})();
});
I am building Weather App, my idea is to save city name in database/localhost, place cities in useState(right now it's hard coded), iterate using map in first child component and display in second child component.
The problem is that 2nd child component outputs only one element (event though console.log prints both)
BTW when I change code in my editor and save, then another 'li' element appears
main component
const App = () => {
const [cities, setCities] = useState(['London', 'Berlin']);
return (
<div>
<DisplayWeather displayWeather={cities}/>
</div>
)
}
export default App
first child component
const DisplayWeather = ({displayWeather}) => {
const [fetchData, setFetchData] = useState([]);
const apiKey = '4c97ef52cb86a6fa1cff027ac4a37671';
useEffect(() => {
displayWeather.map(async city=>{
const res =await fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`)
const data = await res.json();
setFetchData([...fetchData , data]);
})
}, [])
return (
<>
{fetchData.map(data=>(
<ul>
<Weather
data={data}/>
</ul>
))}
</>
)
}
export default DisplayWeather
second child component
const Weather = ({data}) => {
console.log(data) // it prints correctly both data
return (
<li>
{data.name} //display only one data
</li>
)
}
export default Weather
The Problem
The setFetchData hooks setter method is asynchronous by default, it doesn't give you the updated value of the state immediately after it is set.
When the weather result for the second city is returned and set to state, the current value fetchData at the time is still an empty array, so you're essentially spreading an empty array with the second weather result
Solution
Pass a callback to your setFetchData and get the current previous value of the state and then continue with your spread accordingly.
Like this ππ½
setFetchData((previousData) => [...previousData, data]);
I'm trying to understand if passing the setter from useState is an issue or not.
In this example, my child component receives both the state and the setter to change it.
export const Search = () => {
const [keywords, setKeywords] = useState('');
return (
<Fragment>
<KeywordFilter
keywords={keywords}
setKeywords={setKeywords}
/>
</Fragment>
);
};
then on the child I have something like:
export const KeywordFilter: ({ keywords, setKeywords }) => {
const handleSearch = (newKeywords) => {
setKeywords(newKeywords)
};
return (
<div>
<span>{keywords}</span>
<input value={keywords} onChange={handleSearch} />
</div>
);
};
My question is, should I have a callback function on the parent to setKeywords or is it ok to pass setKeywords and call it from the child?
There's no need to create an addition function just to forward values to setKeywords, unless you want to do something with those values before hand. For example, maybe you're paranoid that the child components might send you bad data, you could do:
const [keywords, setKeywords] = useState('');
const gatedSetKeywords = useCallback((value) => {
if (typeof value !== 'string') {
console.error('Alex, you wrote another bug!');
return;
}
setKeywords(value);
}, []);
// ...
<KeywordFilter
keywords={keywords}
setKeywords={gatedSetKeywords}
/>
But most of the time you won't need to do anything like that, so passing setKeywords itself is fine.
why not?
A setter of state is just a function value from prop's view. And the call time can be anytime as long as the relative component is live.