Im just wondering how i can stop the function from returning before the state has been updated - Here is my code so i can explain more clearly.
const mockData = {
current: {
temperature: 'loading'
}
}
export default function Weather({ city }) {
const [data, setData] = useState(mockData)
const url = `http://api.weatherstack.com/current?access_key=2cbe1b14f771abee0713f93317e1b107&query=${city}`
useEffect(() => {
axios.get(url).then(({ data }) => {
setData(data, () => { })
})
}, [])
return (
<div>
<h1>Weather</h1>
<p>temperature: {data.current.temperature}</p>
</div>
)
}
Right now i am using mockData because if i dont i will get an error because the .current.temperature properties do not exist (because set state hasnt been updated yet).
How can i stop the error and stop the div being returned before the set state has been updated or atleast stop the error and return an empy div or something.
What you can do is add a conditional within your return.
Try this:
return (
<div>
<h1>Weather</h1>
<p>Temperature: {(data && data.current) ? data.current.temperature : ""}</p>
</div>
)
You can also use optional chaining to achieve the same result.
return (
<div>
<h1>Weather</h1>
<p>Temperature: {data?.current?.temperature || ""}</p>
</div>
)
You need to check that data exists before you reference data.current, and you need to check that data.current exists before you reference data.current.temperature. If you access a property of undefined, your code will crash.
You need to have some additional state, such as isLoading
If you want a hack, you can do this:
export default function Weather({ city }) {
const [data, setData] = useState(mockData)
const url = `http://api.weatherstack.com/current?access_key=2cbe1b14f771abee0713f93317e1b107&query=${city}`
useEffect(() => {
axios.get(url).then(({ data }) => {
setData(data, () => { })
})
}, [])
function renderTemperature() {
if (!data) {
return null;
} else if (data && !data.current) {
return null;
} else if (data.current && data.current.temperature) {
return <p>temperature: {data.current.temperature}</p>
}
}
return (
<div>
<h1>Weather</h1>
{ renderTemperature() }
</div>
)
}
Much better solution:
export default function Weather({ city }) {
const [data, setData] = useState(null)
const [isLoaded, setIsLoaded] = useState(false);
const url = `http://api.weatherstack.com/current?access_key=2cbe1b14f771abee0713f93317e1b107&query=${city}`
useEffect(() => {
axios.get(url).then(({ data }) => {
setData(data, () => { })
setIsLoaded(true);
})
}, [])
function renderTemperature() {
}
return (
<div>
<h1>Weather</h1>
{ isLoaded ? <p>temperature: {data.current.temperature}</p> : null}
</div>
)
}
Related
I'm new to react and trying to connect firestore for my project.
I followed the example from the Internet and everything works, the data is deleted and written to the database, also when I change the data they change in the database, but I get errors in the console and a white screen.
Uncaught TypeError: data.map is not a function
If you need any more files or code, I will correct my question, please write which ones I need to add
Also, when loading the page, I get the following error in the console:
Uncaught (in promise) Error: Could not establish connection. Receiving end does not exist. at wrappedSendMessageCallback
Here is the code that throws the error:
export default function Saved({ data, setData }) {
function editData(id, newWord, newTranslate, newNote) {
const editedDataList = async (card) => {
if (id === card.id) {
return {
...card,
word: newWord,
translate: newTranslate,
note: newNote,
};
}
let newFields = {
word: newWord,
translate: newTranslate,
note: newNote,
}
await updateDoc(doc(db, "db-name", id), newFields);
console.log(newFields)
return card;
};
setData(editedDataList);
}
const deletePost = async (id) => {
await deleteDoc(doc(db, "db-name", id));
};
const dataList = data.map((card) => (
<SavedData
id={card.id}
key={card.id}
word={card.word}
translate={card.translate}
note={card.note}
editData={editData}
del={deletePost}
/>
));
return (
<div>
<div className="sec-menu"></div>
<div className="saved-inner">
{data.length >= 1 ? (
<div className="saved-list">{dataList}</div>
) : (
<Link className="main-btn" to="/addcard">
Add
</Link>
)}
</div>
</div>
);
}
Here Menu.js code:
function Menu() {
const [data, setData] = useState([]);
useEffect(() => {
const q = query(collection(db, "db-name"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
let wordsArr = [];
querySnapshot.forEach((doc) => {
wordsArr.push({ ...doc.data(), id: doc.id });
});
setData(wordsArr);
});
return () => unsubscribe();
}, []);
return (
<div className="content">
<AuthContextProvider>
<Routes>
<Route
path="saved"
element={<Saved data={data} setData={setData} />}
/>
</Route>
</Routes>
</AuthContextProvider>
</div>
);
}
export default Menu;
On second glance, the issue is where you call setData(editedDataList). You're passing in a function into this method which is in turn updating data to be a function instead of an array. Try changing, editData() to be something like this:
const editData = async (id, newWord, newTranslate, newNote) => {
const editedDataList = await Promise.all(data.map(async (card) => {
let newFields = {
word: newWord,
translate: newTranslate,
note: newNote,
};
if (id === card.id) {
return { ...card, ...newFields };
}
await updateDoc(doc(db, "db-name", id), newFields);
console.log(newFields);
return card;
}));
setData(editedDataList);
};
editedDataList will be an array of the modified cards in the original and setData() should work as expected.
maybe the error occurs because "data" object is not an array.
And check what are you setting on "setData(editedDataList);" instruction
I am having a data rendering issue in react. Somehow, data is not automatically updated after it's updated in the server side. I can't put all the code in here, cuz the code is kind of lengthy. so i pasted/renamed some variables. Even if some variables are missing, please understand. Basically, I have a button on the page and when the button is clicked, the status changes to 'UPLOADING' and the function checkIfDataExists is called to fetch data from the server side and data should be automatically updated without page refresh, but when I test this, data is successfully retrieved from the server side, but the updated data is not rendered. I see 'successful...' on the Console. Is there anything wrong?
const Settings: React.FC<IProps> = props => {
const { orgId } = props
const password = 'dummy'
const { data } = httpCall(`/${orgId}/${userId}/settings`)
return (
<div>
{data && <SettingsForm data={data} password={password} {...props} />}
</div>
)
}
const SettingsForm: React.FC<Settings & IProps> = ({
data,
password
}) => {
const [status, setStatus] = useState<'ERROR' | 'DONE' | 'UPLOADING'>()
const service = getServiceInstance(data.organizationId)
function checkIfDataExists(user: any) {
return () => {
httpCall
.getClient(user.id)
.then(value => {
console.log('successful...')
data.modeUsername = value.modeUsername
data.modePassword = value.modePassword
})
.catch(() => {
setStatus('ERROR')
})
}
}
useEffect(() => {
if (!status) return
switch (status) {
case 'UPLOADING': {
const timer = setInterval(
checkIfDataExists({ id: data.id }),
2000
)
return () => clearInterval(timer)
}
}
}, [status, client
])
<div className="info-section">
<p className="detail">Username</p>
<p>{data.modeUsername}</p>
</div>
<div className="info-section">
<p className="detail">Password</p>
<p>{data.modePassword}</p>
</div>
The problem I see is that after you setInterval an API you didn't set in the state to trigger the component to rerender. You don't need to be explicit to define resData to data because if you define data already useState already it types.
const SettingsForm: React.FC<Settings & IProps> = ({
data,
password
}) => {
const [resdata,setResData] = useState(data)
const [status, setStatus] = useState<'ERROR' | 'DONE' | 'UPLOADING'>()
const service = getServiceInstance(data.organizationId)
function checkIfDataExists(user: any) {
return () => {
httpCall
.getClient(user.id)
.then(value => {
console.log('successful...')
setResData({
modeUsername: value.modeUsername,
modePassword: value.modePassword,
})
// data.modeUsername = value.modeUsername
// data.modePassword = value.modePassword
})
.catch(() => {
setStatus('ERROR')
})
}
}
useEffect(() => {
if (!status) return
switch (status) {
case 'UPLOADING': {
const timer = setInterval(
checkIfDataExists({ id: data.id }),
2000
)
return () => clearInterval(timer)
}
}
}, [status, client
])
<div className="info-section">
<p className="detail">Username</p>
<p>{resdata.modeUsername}</p>
</div>
<div className="info-section">
<p className="detail">Password</p>
<p>{resdata.modePassword}</p>
</div>
I have a list and this list has several elements and I iterate over the list. For each list I display two buttons and an input field.
Now I have the following problem: as soon as I write something in a text field, the same value is also entered in the other text fields. However, I only want to change a value in one text field, so the others should not receive this value.
How can I make it so that one text field is for one element and when I write something in this text field, it is not for all the other elements as well?
import React, { useState, useEffect } from 'react'
import axios from 'axios'
function Training({ teamid }) {
const [isTrainingExisting, setIsTrainingExisting] = useState(false);
const [trainingData, setTrainingData] = useState([]);
const [addTraining, setAddTraining] = useState(false);
const [day, setDay] = useState('');
const [from, setFrom] = useState('');
const [until, setUntil] = useState('');
const getTrainingData = () => {
axios
.get(`${process.env.REACT_APP_API_URL}/team/team_training-${teamid}`,
)
.then((res) => {
if (res.status === 200) {
if (typeof res.data !== 'undefined' && res.data.length > 0) {
// the array is defined and has at least one element
setIsTrainingExisting(true)
setTrainingData(res.data)
}
else {
setIsTrainingExisting(false)
}
}
})
.catch((error) => {
//console.log(error);
});
}
useEffect(() => {
getTrainingData();
}, []);
const deleteTraining = (id) => {
axios
.delete(`${process.env.REACT_APP_API_URL}/team/delete/team_training-${teamid}`,
{ data: { trainingsid: `${id}` } })
.then((res) => {
if (res.status === 200) {
var myArray = trainingData.filter(function (obj) {
return obj.trainingsid !== id;
});
//console.log(myArray)
setTrainingData(() => [...myArray]);
}
})
.catch((error) => {
console.log(error);
});
}
const addNewTraining = () => {
setAddTraining(true);
}
const addTrainingNew = () => {
axios
.post(`${process.env.REACT_APP_API_URL}/team/add/team_training-${teamid}`,
{ von: `${from}`, bis: `${until}`, tag: `${day}` })
.then((res) => {
if (res.status === 200) {
setAddTraining(false)
const newTraining = {
trainingsid: res.data,
mannschaftsid: teamid,
von: `${from}`,
bis: `${until}`,
tag: `${day}`
}
setTrainingData(() => [...trainingData, newTraining]);
//console.log(trainingData)
}
})
.catch((error) => {
console.log(error);
});
}
const [editing, setEditing] = useState(null);
const editingTraining = (id) => {
//console.log(id)
setEditing(id);
};
const updateTraining = (trainingsid) => {
}
return (
<div>
{trainingData.map((d, i) => (
<div key={i}>
Trainingszeiten
<input class="input is-normal" type="text" key={ d.trainingsid } value={day} placeholder="Wochentag" onChange={event => setDay(event.target.value)} readOnly={false}></input>
{d.tag} - {d.von} bis {d.bis} Uhr
<button className="button is-danger" onClick={() => deleteTraining(d.trainingsid)}>Löschen</button>
{editing === d.trainingsid ? (
<button className="button is-success" onClick={() => { editingTraining(null); updateTraining(d.trainingsid); }}>Save</button>
) : (
<button className="button is-info" onClick={() => editingTraining(d.trainingsid)}>Edit</button>
)}
<br />
</div>
))}
)
}
export default Training
The reason you see all fields changing is because when you build the input elements while using .map you are probably assigning the same onChange event and using the same state value to provide the value for the input element.
You should correctly manage this information and isolate the elements from their handlers. There are several ways to efficiently manage this with help of either useReducer or some other paradigm of your choice. I will provide a simple example showing the issue vs no issue with a controlled approach,
This is what I suspect you are doing, and this will show the issue. AS you can see, here I use the val to set the value of <input/> and that happens repeatedly for both the items for which we are building the elements,
const dataSource = [{id: '1', value: 'val1'}, {id: '2', value: 'val2'}]
export default function App() {
const [val, setVal]= useState('');
const onTextChange = (event) => {
setVal(event.target.value);
}
return (
<div className="App">
{dataSource.map(x => {
return (
<div key={x.id}>
<input type="text" value={val} onChange={onTextChange}/>
</div>
)
})}
</div>
);
}
This is how you would go about it.
export default function App() {
const [data, setData]= useState(dataSource);
const onTextChange = (event) => {
const id = String(event.target.dataset.id);
const val = String(event.target.value);
const match = data.find(x => x.id === id);
const updatedItem = {...match, value: val};
if(match && val){
const updatedArrayData = [...data.filter(x => x.id !== id), updatedItem];
const sortedData = updatedArrayData.sort((a, b) => Number(a.id) - Number(b.id));
console.log(sortedData);
setData(sortedData); // sorting to retain order of elements or else they will jump around
}
}
return (
<div className="App">
{data.map(x => {
return (
<div key={x.id}>
<input data-id={x.id} type="text" value={x.value} onChange={onTextChange}/>
</div>
)
})}
</div>
);
}
What im doing here is, finding a way to map an element to its own with the help of an identifier. I have used the data-id attribute for it. I use this value again in the callback to identify the match, update it correctly and update the state again so the re render shows correct values.
I am having an issue where the null message (I dont have Partners) is flashing up for a second or so before the state is set and the message changes .. Is there any way to not show it until everything is resolved?
const Dashboard = () => {
const [partners, setPartners] = useState([]);
useEffect(() => {
async function getPartners() {
await axios.get('/api/partners').then(response => {
setPartners(response.data.data);
})
}
getPartners();
}, []);
return (
<>
{partners?.length ? <div><p>I have Partners</p></div> : <div><p>I dont have Partners</p></div>}
</>
)
}
export default Dashboard;
There are several approaches to achieve such a thing the simplest one is to use another state to indicate the HTTP request status. Here how it is can be implemented.
const Dashboard = () => {
const [partners, setPartners] = useState([]);
const [status, setStatus] = useState('idle');
useEffect(() => {
setStatus('pending');
async function getPartners() {
await axios.get('/api/partners').then(response => {
setPartners(response.data.data);
setStatus('resolved');
})
}
getPartners();
}, []);
return (
<>
{status === 'idle' || status === 'pending' ?
<LoadingComponent /> // A custom component to represent loading status
:
<>
{partners?.length ? <div><p>I have Partners</p></div> : <div><p>I dont have Partners</p></div>}
<>
}
</>
)
}
export default Dashboard;
I have a code similar to this one:
function Component1(...) {
...
function checkIfCtrlKey(event) {
return event.ctrlKey;
}
return (
{ checkIfCtrlKey() && (<Component2 .../>) }
);
}
The sense behind this is that the Component2 is just rendered if the Ctrl-key is being pressed. When running this code, I get following error message: TypeError: Cannot read property 'ctrlKey' of undefined
What is my mistake? Is there a solution or other possibility to implement my need?
You need to put an event listener on the window object and within that hander you can set some state to switch between a true and false
Something like this.
function Component1() {
const [ctrlActive, setCtrlActive] = useState(false)
useEffect(() => {
window.addEventListener('keydown', (e => {
if("Do a check here to see if it's CTRL key") {
setCtrlActive(!ctrlActive)
}
}), [])
})
return ctrlActive ? <Component2 /> : null
}
You can use Vanilla JS for that like this:
import React, { useEffect, useState } from "react";
export default function App() {
const [ctrlPressed, setCtrlPressed] = useState(false);
const handleKey = (e) => setCtrlPressed(e.ctrlKey);
useEffect(() => {
window.addEventListener("keydown", handleKey);
window.addEventListener("keyup", handleKey);
return function cleanup() {
window.removeEventListener("keydown", handleKey);
window.removeEventListener("keyup", handleKey);
};
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{ctrlPressed ? <h2>You're pressing CTRL Key</h2> : null}
</div>
);
}
Working example over here
Your making mistake here,
{ checkIfCtrlKey() && (<Component2 .../>) }
refer
function checkIfCtrlKey(event) {
return event.ctrlKey;
}
How u suppose that checkIfCtrlKey will be passed with event arg when your calling like this checkIfCtrlKey() ??
You might wanted to attach it to window,
function Component1() {
const [ctrlKeyPressed, setCKP] = useState(false)
const handleKey = ev => {
setCKP(ev.ctrlKey)
}
useEffect(() => {
window.addEventListener('keydown', handleKey);
window.addEventListener('keyup', handleKey);
return () => {
window.removeEventListener('keydown', handleKey);
window.removeEventListener('keyup', handleKey);
}
}, [])
return (
<>
...
{ctrlKeyPressed && <Component2 />}
...
</>
)
}
Shows Component2 as long as ctrlKey is pressed