Hi I do have to following simplyfied code. I use Formik as a Validation. Also Material Ui and Reactjs. The Form, Row and Col Tags come from Material. The FastField is same as InputField.
What I want is onClick in the Inputfield a dropdown appears and shows an array which I fetched with the axios-Request.
´´´
const url = 'http://localhost:3000';
const [searchValues, setSearchValues] = useState([]);
const getDropdownItems = (event) => {
console.log('event', event.target.getAttribute('id'));
axios
.get(`${url}/${event.target.getAttribute('id')}`)
.then(
(res) => setSearchValues(res),
console.log('restl', searchValues)
);
};
render(
<Form
onFocus={getDropdownItems}
onSubmit={formik.handleSubmit}
>
<Row>
<Col xs="auto" style={minWidth}>
<FastField
id="DatumEingabe"
name="DatumEingabe"
component={Autocomplete}
label="Datum-Eingabe"
type="text"
options={searchValues}
/>
</Col>
</Row>
</Form>
)
When I check my console I get from the first console.log the name of
the Inputfield. The second console.log says the array is empty,
despite the res is available and should be set. Why does it not work
this way.
setSearchValues(res) will not update searchValues until the next render. If you want to log it each time it changes, you should instead do
const [searchValues, setSearchValues] = useState([]);
useEffect(() => {
console.log(searchValues);
}, [searchValues]);
const getDropdownItems = (event) => {
console.log('event', event.target.getAttribute('id'));
axios
.get(`${url}/${event.target.getAttribute('id')}`)
.then(
(res) => setSearchValues(res)
);
};
I don't think the change is made inmediatly. Try logging searchValues after a second or something like that to see if that is the problem.
const getDropdownItems = (event) => {
console.log('event', event.target.getAttribute('id'));
axios
.get(`${url}/${event.target.getAttribute('id')}`)
.then(
(res) => {
setSearchValues(res);
setTimeout(() => {
console.log('restl', searchValues);
}, 1000)
}
);
};
Also, you have the useEffect hook, which fires an event when a variable is change, so if you want to log it the second it changes you should use:
useEffect(() => {
console.log(searchValues);
}, [searchValues])
To acomplish that, remember to import:
import { useEffect } from "react";
or use
React.useEffect(...)
Related
i am a React newbie (30h learning) and some basic Javascript background. Now i am learning with a course, and tried to "leave" the path. But i am curious how my intended goal could be achieved.
There is a Memegenerator who get all the images of memes from an API in the beginning of the rendering. This is solved with an useEffect-Hook. Now i want that the function getMemeImage() is running ONCE at the beginning AFTER the API-Call was made and the state was updated (this is not part of the course, but i want to try it anyway).
But its giving me an error: "Can't perform a React state update on an unmounted component"
While my research i found that things like didMount(tbh i not understand) and so on are not the "modern" way of react with primarily using hooks and you can simply use an second useEffect. But that is unfortunately not working for me.
How can i solve this and is there an "simple" way for a beginner or it is advanced stuff? I thought maybe to use a timeoutfunction, but seems to be very bad coding.
import React from "react"
export default function Meme() {
const [meme, setMeme] = React.useState({
topText: "",
bottomText: "",
randomImage: ""
})
const [allMemes, setAllMemes] = React.useState([])
React.useEffect(() => { /* first useEffect */
fetch("https://api.imgflip.com/get_memes")
.then(res => res.json())
.then(data => setAllMemes(data.data.memes))
}, [])
React.useEffect(() => { /* This should run after the setAllMemes in the first useEffect was complete */
getMemeImage()
}, [allMemes])
function getMemeImage() {
const randomNumber = Math.floor(Math.random() * allMemes.length)
const url = allMemes[randomNumber].url
setMeme(prevMeme => ({
...prevMeme,
randomImage: url
}))
}
function handleChange(event) {
const {name, value} = event.target
setMeme(prevMeme => ({
...prevMeme,
[name]: value
}))
}
return (
<main>
<div className="form">
<input
type="text"
placeholder="Top text"
className="form--input"
name="topText"
value={meme.topText}
onChange={handleChange}
/>
<input
type="text"
placeholder="Bottom text"
className="form--input"
name="bottomText"
value={meme.bottomText}
onChange={handleChange}
/>
<button
className="form--button"
onClick={getMemeImage}
>
Get a new meme image 🖼
</button>
</div>
<div className="meme">
<img src={meme.randomImage} className="meme--image" />
<h2 className="meme--text top">{meme.topText}</h2>
<h2 className="meme--text bottom">{meme.bottomText}</h2>
</div>
</main>
)
}
First of all, "Can't perform a React state update on an unmounted component" is a warning, not an error.
In order to call 'getMemeImage' only after the first useEffect hook execution, you can check the value of a binary flag that changes after the first useEffect was executed and the response of the async function was recieved.
import React from "react";
export default function Meme() {
const [meme, setMeme] = React.useState({
topText: "",
bottomText: "",
randomImage: ""
});
const [allMemes, setAllMemes] = React.useState([]);
const isInitialRender = React.useRef(true);
React.useEffect(() => {
fetch("https://api.imgflip.com/get_memes")
.then((res) => res.json())
.then((data) => {
isInitialRender.current = false;
setAllMemes(data.data.memes);
});
}, []);
React.useEffect(() => {
/* Check if it is called before the first useEffect data fetching was completed */
if (!isInitialRender.current) getMemeImage();
}, [allMemes]);
function getMemeImage() {
const randomNumber = Math.floor(Math.random() * allMemes.length);
const url = allMemes[randomNumber].url;
setMeme((prevMeme) => ({
...prevMeme,
randomImage: url
}));
}
}
Using the useRef hook persists the value it references between renders.
You can just add control to your second useEffect, it will solve your issue. This is working sample https://codesandbox.io/s/heuristic-matan-3kdbmv
React.useEffect(() => {
if (allMemes.length > 0) {
getMemeImage();
}
}, [allMemes]);
Beginner question. I know this is a simple question but I haven't been able to get this to work. I'm passing an object which holds an array of k:v pairs to a component. Eventually this props will contain multiple k:v pairs, but for now I'm just passing the one.
[{goal: 20000}]
In the component I'm trying to grab the value, 20000, so I can display it on screen. I can't seem to get just the number. If I look at props.goal I get the entire k:v.
[{goal: 20000}]
If I try props[0].goal I get 'TypeError: undefined is not an object (evaluating 'props[0].goal')'
What am I missing? Thanks for any help.
Update:
Here is the entire code for the component in question.
import { React, useState } from "react";
import Form from "react-bootstrap/Form";
import { Row, Col, Button } from "react-bootstrap";
import "./../css/Goal.css";
const Goal = (props) => {
// const [goal, setGoal] = useState("");
const [record, setRecord] = useState("");
const monthlyGoal = 2;
console.log("props[0]");
console.log(props[0]); //undefined
console.log("props");
console.log({ props }); //See below
props: Object
goal: Object
goals: [{goal: 20000}] (1)
const handleInput = (event) => {
console.log(event);
event.preventDefault();
setRecord(event.target.value);
console.log(record);
};
const defaultOptions = {
significantDigits: 2,
thousandsSeparator: ",",
decimalSeparator: ".",
symbol: "$",
};
const formattedMonthlyGoal = (value, options) => {
if (typeof value !== "number") value = 0.0;
options = { ...defaultOptions, ...options };
value = value.toFixed(options.significantDigits);
const [currency, decimal] = value.split(".");
return `${options.symbol} ${currency.replace(
/\B(?=(\d{3})+(?!\d))/g,
options.thousandsSeparator
)}${options.decimalSeparator}${decimal}`;
};
return (
<Form>
<Row className="align-items-center flex">
<Col sm={3} className="goal sm={3}">
<Form.Control
id="inlineFormInputGoal"
placeholder="Goal"
// onChange={(e) => setGoal(e.target.value)}
/>
<Button type="submit" className="submit btn-3" onSubmit={handleInput}>
Submit
</Button>
</Col>
<Col>
<h1 className="text-box">
Goal: {formattedMonthlyGoal(monthlyGoal)}
</h1>
</Col>
</Row>
</Form>
);
};
export default Goal;
Update 2:Here is the parent component:
import React, { useEffect, useState } from "react";
import Goal from "./Goal";
import axios from "axios";
const Dashboard = () => {
const [dashboardinfo, setdashboardinfo] = useState([]);
useEffect(() => {
async function fetchData() {
try {
const data = (await axios.get("/api/goals/getgoals")).data;
setdashboardinfo(data);
} catch (error) {
console.log(error);
}
}
fetchData();
}, []);
return (
<React.Fragment>
<Goal dashboardinfo={dashboardinfo} />
</React.Fragment>
);
};
export default Dashboard;
If you get an object like the following from console logging destructured props:
{
dashboardinfo: {goals: [{goal: 20000}]}
}
You need to use props.dashboardinfo.goals[0].goal to get the value.
Your props contains the object "dashboardinfo" so you need to do
props.dashboardinfo.goals[0].goal
or a better way is to destructure your props object like this
const Goal = ({dashboardinfo: { goals }}) => {
...
goals[0].goal
...
}
I believe I've resolved my issue. It wasn't so much a problem with accessing the key:value as I thought, because when the page was initialized I was able to grab the value and display it fine. However, when I refreshed the page I lost all of the props data and that resulted in an error. I tracked it down to the useState didn't seem to be updating the value before I was trying to read it. So I added a useEffect in the child component.
const Goal = (props) => {
const [goal, setgoal] = useState([]);
useEffect(() => {
setgoal(props.goal);
console.log("the goal", goal);
}, [props.goal, goal]);
...
This seems to have worked as I'm getting the information I want and not getting any errors when I refresh. This may not be the ideal way to go about this but it is working.
I have an API call set up with two search buttons from one input box. Each button adds something using state to the api call which the code below should demonstrate. The calls both work fine independently and display the correct information.
If a user has clicked the 'wild cards' button the results show but then on clicking the 'standard set' button the results don't re-render the correct results until the button is pressed a second time (vice versa for both buttons).
I have removed the un-related code as to condense the question
Any help appreciated
Home.js - with api call, state and functions passed down as props to the searchBar
export const Home = () => {
const [searchTerm, setSearchTerm] = useState('')
const [wild, setWild] = useState('')
let accessToken;
const getCards = async() => {
try {
await getToken()
const response = await fetch(`https://eu.api.blizzard.com/hearthstone/cards/?collectible=1${wild}&textFilter=${searchTerm}&locale=en-US$access_token=${accessToken}`, {
headers: {
Authorization: `Bearer ${accessToken}`
}})
const data = await response.json()
const {cards} = data
if (cards){
const newCards = cards.map((card) => { ** some data i want ** }
setCards(newCards)
}
} catch (error) {
console.log(error)
}
}
return (
<SearchBar getCards = {getCards}
setWild = {setWild}
setSearchTerm = {setSearchTerm} />
</div>
</div>
)
}
SearchBar Component - again condensed for the sake of this question
export const SearchBar = (props) => {
const searchBox = useRef('')
const handleClickWild = (e) => {
e.preventDefault()
props.setWild('')
props.getCards()
}
const handleClickStandard = (e) => {
e.preventDefault()
props.setWild('&set=standard')
props.getCards()
}
const handleChange = (e) => {
props.setSearchTerm(e.target.value)
}
return (
<form>
<input className = 'input-search'
type = 'text'
placeholder = 'Search for Cards by Name...'
ref = {searchBox}
onChange = {handleChange} />
<div className = 'search-buttons'>
<input type = 'submit' className = 'button-search' onClick = {handleClickWild} value = 'Wild Cards' />
<input type = 'submit' className = 'button-search' onClick = {handleClickStandard} value = 'Standard Set' />
</div>
</form>
)
}
You have to use useEffect hook here.
You can use wild in dependency array and whenever you change the value of searchTerm use effect will automatically call your getCards function.
As you mentioned in the comment you want to show changes when user search anything then keep the wild in simple variable and add search term in useEffect and if you want you can add both in the useEffect dependency array
useEffect(()=> {
getCards()
}, [searchTerm])
Just remove explicite calls of props.getCards after setting wild from SearchBar component.
I solved this with useEffect as suggested but I added an 'if (hasSearched)' as state value of is the user had searched previously to prevent the API auto calling on page load
In my app I have profile section with a form. When the component mounts I want to fetch user data from firebase, and display it in the form, with the current values of the user profile. Either using the "value" prop or the "placeholder" prop.
When the user makes changes in the form inputs and submit the changes, I want the database to update and the form to update with the new data.
Currently I can make the database value appear in the form input field, or I can make the form input field empty, but update the database. But not both.
The following code makes the database data render in the form input, but it cant be changed.
I know it could be something with the second useEffect() and the getUserData() function, that I cant seem to figure out.
const UserEdit = (props) => {
const [currentUser, setCurrentUser] = useState('');
const [forening, setForening] = useState('');
useEffect(() => {
firebase_app.auth().onAuthStateChanged(setCurrentUser);
}, [])
const getUserData = async () => {
await dbRef.ref('/' + currentUser.uid + '/profil/' ).once('value', snapshot => {
const value = snapshot.val();
setForening(value)
})
}
useEffect(() => {
getUserData()
},[] )
const handleInput = (event) => {
setForening(event.target.value)
}
const updateUserData = () => {
dbRef.ref('/' + currentUser.uid + '/profil/' ).set({foreningsnavn: forening}, function(error) {
if(error) {
console.log("update failed")
} else {
alert(forening)
}
})
}
const handleClick = () => {
updateUserData()
}
return (
<>
<div className="card-body">
<div className="row">
<div className="col-md-5">
<div className="form-group">
<label className="form-label">{Forening}</label>
<input className="form-control" type="text" value={forening} onChange={handleInput}/>
</div>
</div>
</div>
</div>
</>
)
}
Your second useEffect will run only one time because the second argument array [] of dependencies is empty:
useEffect(() => {
getUserData()
},[] )
You can add foreign dependency to make useEffect run with input change
useEffect(() => {
getUserData()
},[foreign] )
or you can use polling to sync database state
I am loading data on the initial load. When they user clicks the button to add a recognition, the api call adds it, and returns it. I add the new post to the array, but the new update doesn't render. The return object, and the array of objects are the same object type. When I reload the page, the new post is rendered, just not on the add function. Is there something that I am missing?
const [recognitions, setRecognitions] = useState([]);
useEffect(() => {
Api.GetRecognitions(params)
.then(response => {
const items = response || [];
setRecognitions(recognitions => [...recognitions, ...items]);
})
}, [setRecognitions]);
const handleAddPost = () => {
Api.AddRecognition(params)
.then(response => {
const newPost = response;
setRecognitions(recognitions=> [...recognitions, newPost])
});
}
<Form.Group>
<Form.Field>
<Button basic color='blue' onClick={handleAddPost}>Add</Button>
</Form.Field>
</Form.Group>
<Form.Group>
<Form.Field>
{recognitions.map(recognition => (
<RecogWallPost
key={recognition.recogStagingId}
recognition={recognition}
participantId={participantId}
/>
)
)}
</Form.Field>
</Form.Group>
Instead of passing [setRecognitions] as the second argument to useEffect, you want to pass [recognitions]. This tells the useEffect hook to run every time recognitions changes, which it does inside handleAddPost.
You have to create an async function, and then use it as follow:
useEffect(() => {
async function initData() {
Api.GetRecognitions(params)
.then(response => {
const items = response || [];
setRecognitions(recognitions => [...recognitions, ...items]);
})
}
initData()
}, [setRecognitions]);