props value is undefined in React js Hooks - javascript

I am writing a notepad web application. I am using React Hooks to use state variables. I am fetching data from an api using axios. Data contains a list of objects containing _id, title, status and detail. I am passing three values to update button as attributes and in onClick() method I am setting the values of my state variables using these attributes. Then these values are sent as props to a UpdateTask component. The probles is, two (_id and title) of those three variables are getting the correct value but one variable (detail) is getting undefined value. following is my code.
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import UpdateTask from './UpdateTask.jsx';
import DeleteTask from './DeleteTask.jsx';
function Tasks()
{
useEffect(()=>
{
async function fetchingData()
{
const tasks = await axios('http://127.0.0.1:8000/tasks');
setTasks(tasks.data)
};
fetchingData();
})
function handleUpdateClick(e)
{
setViewUpdate(!viewUpdate);
setUpdateId(e.target.id);
setUpdateTitle(e.target.title);
setUpdateDetail(e.target.detail);
console.log(e.target)
}
function handleDeleteClick(e)
{
setViewDelete(!viewDelete);
setDeleteId(e.target.id)
}
const [tasks, setTasks] = useState([]);
const [viewUpdate, setViewUpdate] = useState(false);
const [updateId, setUpdateId] = useState(null);
const [updateTitle, setUpdateTitle] = useState('');
const [updateDetail, setUpdateDetail] = useState('');
const [viewDelete, setViewDelete] = useState(false);
const [deleteId, setDeleteId] = useState(null);
var listTasks = tasks.map((task)=>
{
return(
<li className="main-task-list-items task-main" key={task._id} id={task._id}>
<h1>{task.title}</h1>
<p>{task.detail}</p>
<p>Status {task.status.toString()}</p>
<button
className="task-main-btn btn btn-primary"
id={task._id}
detail={task.detail}
title={task.title}
onClick={handleUpdateClick}
>
Update Task
</button>
<button
className="task-main-btn btn btn-danger"
id={task._id}
onClick={handleDeleteClick}
>
Delete Task
</button>
</li>
);
})
return(
<div>
<ul>{listTasks}</ul>
{viewUpdate ? <UpdateTask title={updateTitle} detail={updateDetail} id={updateId} handleCancel={handleUpdateClick} /> : null }
{viewDelete ? <DeleteTask id={deleteId} handleNo={handleDeleteClick}/> : null }
</div>
)
}
export default Tasks;
can anyone help me to solve this?

Try adding onClick by wrapping up with function and pass task -
onClick={ () => handleUpdateClick(task)}
function handleUpdateClick(task) {
setViewUpdate(!viewUpdate);
setUpdateId(task._id);
setUpdateTitle(task.title);
setUpdateDetail(task.detail);
}
Update this in your function call.!
Try this
You are able to get id and title because it comes under eventtarget. Detail is not the property of eventTarget. that might be the issue.

Related

Passing API call results to trigger render in react

I am fairly new to react and am stuck on how i pass the results of my api call to another component file to then trigger a render. Can anyone give me a simple explanation of what I need to to please?
this is my code that calls to the API and then i need the weatherDescription state to be used in and IF statement to conditionally render a local GLTF file to the canvas
import { useEffect } from "react";
import { useState } from "react";
import "dotenv";
export default function WeatherLogic() {
const [userLocation, setUserLocation] = useState("");
const [temperature, setTemp] = useState("");
const [weatherDescript, setWeatherDescript] = useState("");
const handleInput = (event) => {
setUserLocation(event.target.value);
};
const getWeather = async (userLocation) => {
const apiKey = import.meta.env.VITE_APP_API_KEY;
const res = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${userLocation}&appid=${apiKey}&units=metric`
);
const data = await res.json();
setTemp(data.main.temp);
setWeatherDescript(data.weather[0].description);
};
useEffect(() => {
getWeather(userLocation);
}, [userLocation]);
return (
<div className="formcontainerparent">
<div className="formcontainerchild">
<form className="weatherform">
<label>Location
<input
type="text"
name="name"
value={userLocation}
onChange={handleInput}
/>
</label>
</form>
<h1>{userLocation}</h1>
<h2>{temperature}°C</h2>
<h3>{weatherDescript}</h3>
</div>
</div>
);
}
One of the approaches will be to make a state for local GLTF file like:
const [file,setFile] = useState(null)
then make another useEffect hook and set this file on discription changes, i.e, add weatherDescript in it's dependency array:
useEffect(() => {
if(condition)
setFile(...datawhichyouneedtorender)
}, [weatherDescript]);
And lastly, in your jsx render the file variable in convas.

react fetch data on button click

I'm trying to fetch data in react. The problem is i have to click on button twice to get that data.
Although i don't get data on first click it somehow renders if I add JSON.stringify to it. If I don't add JSON.stringify it returns undefined. If anyone know what this is please help me
without clicking
on first click
on second click
import React, {useState,useEffect} from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios'
function Example() {
const [students,setStudents] = useState('')
const [name,setName] = useState('')
const handleClick = async() => {
const data = await axios.get('api/foo')
setStudents(data)
console.log(students)
}
return (
<div className="container">
<h2>Example component</h2>
<button onClick = {handleClick}>Get students</button>
<div>
{JSON.stringify(students.data)}
</div>
</div>
);
}
export default Example;
if (document.getElementById('root')) {
ReactDOM.render(<Example />, document.getElementById('root'));
}
The problem was that setStudents is an asynchronous function, so I just made student object and added to it loading property
const [students,setStudents] = useState({
data: '',
loading: true
})
const [name,setName] = useState('')
const handleClick = async() => {
const data = await axios.get('api/foo')
setStudents({
data: data,
loading: false
})
}
return (
<div className="container">
<h2>Example component</h2>
<button onClick = {handleClick}>Get students</button>
<div>
{students.loading?'':
students.data.data[0].name}
</div>
</div>
);
}
setStudent is an asynchronous function. This means the value of students won't change immediately after you call setStudents.
Try shifting the console.log outside the handleClick function. Like this -
import React, {useState,useEffect} from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios'
function Example() {
const [students,setStudents] = useState('')
const [name,setName] = useState('')
const handleClick = async() => {
const data = await axios.get('api/foo')
setStudents(data)
}
console.log(students)
return (
<div className="container">
<h2>Example component</h2>
<button onClick = {handleClick}>Get students</button>
<div>
{JSON.stringify(students.data)}
</div>
</div>
);
}
export default Example;
if (document.getElementById('root')) {
ReactDOM.render(<Example />, document.getElementById('root'));
}
Initially, the value will be an empty string, then it will change to the value from api/foo
React hooks are async so when you are running console.log(students) right after running setStudents(data) it is still not populated, however the 2nd time you click the button it is already populated from the first time you clicked it.
If you want to console the result right after the state setter runs you can see this answer on another question.

Rest API response not updating until page is refreshed

I've got component that displays contact information from a dealer as chosen by a user. To be more specific, a user selects their location, setting a cookie which then is used to define the API call. I pull in the contact information of the dealer in that location using Axios, store it in a context, and then display the information as necessary through several components: the header, a "current location" component etc.
The problem that I'm currently running into is that the contact information, as displayed in the Header for example, doesn't update until a user performs a hard refresh of the page, so, assuming the default text of the button is something like "Find A Dealer", once a dealer is selected, the button label should say the name of the dealer the user has selected. At present, it isn't working that way. Below is the code for the Header component, and my ApiContext.
ApiContext.tsx
import React, { createContext } from 'react';
import axios from 'axios';
import { makeUseAxios } from 'axios-hooks';
import { useCookie } from 'hooks/use-cookie';
const contextObject = {} as any;
export const context = createContext(contextObject);
const useAxios = makeUseAxios({
axios: axios.create({ baseURL: process.env.GATSBY_API_ENDPOINT }),
});
export const ApiContext = ({ children }: any) => {
const [cookie] = useCookie('one-day-location', '1');
const [{ data }] = useAxios(`${cookie}`);
const { Provider } = context;
return <Provider value={data}>{children}</Provider>;
};
Header.tsx
import React, { ReactNode, useContext, useEffect, useState } from 'react';
import Logo from 'assets/svg/logo.svg';
import css from 'classnames';
import { Button } from 'components/button/Button';
import { Link } from 'components/link/Link';
import { MenuIcon } from 'components/menu-icon/MenuIcon';
import { context } from 'contexts/ApiContext';
import { NotificationBar } from '../notification-bar/NotificationBar';
import s from './Header.scss';
import { MainNav } from './navigation/MainNav';
interface HeaderProps {
navigationContent: ReactNode;
}
export const Header = ({ navigationContent }: HeaderProps) => {
const [scrolled, setScrolled] = useState(false);
const [open, setOpen] = useState(false);
const data = useContext(context);
const buttonLabel = data ? data.name : 'Find a Dealer';
const buttonLink = data ? `tel:${data.phone}` : '/find-a-dealer';
useEffect(() => {
const handleScroll = () => {
const isScrolled = window.scrollY > 10;
if (isScrolled !== scrolled) {
setScrolled(!scrolled);
}
};
document.addEventListener('scroll', handleScroll, { passive: true });
return () => {
document.removeEventListener('scroll', handleScroll);
};
}, [scrolled]);
return (
<>
<NotificationBar notificationContent={navigationContent} />
<header className={scrolled ? css(s.header, s.header__scrolled) : s.header}>
<nav className={s.header__navigation}>
<ul className={s.header__container}>
<li className={s.header__logo}>
<Link to="/" className={s.header__link}>
<Logo />
</Link>
</li>
<li className={s.header__primary}>
<MainNav navigationItems={navigationContent} />
</li>
<li className={s.header__utility}>
<Button href={buttonLink}>{buttonLabel}</Button>
</li>
<li className={s.header__icon}>
<MenuIcon onClick={() => setOpen(!open)} />
</li>
</ul>
</nav>
</header>
</>
);
};
Here is a screenshot of my console logs, where I'm logging what is returned from data in the ApiContext.
Any suggestions on this would be greatly appreciated, even if it means completely refactoring the way that I'm using this. Thanks!
You are almost there, your ApiContext looks good, it retrieves the information and populates the context, however, what you are missing is a useState to trigger an update to force the re-hydration of your buttons.
What is happening is that your context never updates the data constant. At the first rendering is empty, once your request is done and the context is full but your button is never being updated. Something like this may work for you:
const data = useContext(context);
const [newData, setNewData] = useState(data);
const buttonLabel = newData? newData.name : 'Find a Dealer';
const buttonLink = newData? `tel:${newData.phone}` : '/find-a-dealer';
You may need to adapt the code a bit to fit your requirements, nevertheless, you may keep the idea, which is creating a state with your retrieved data.
You can create a useEffect to control when the data changes and populate the state if you wish:
useEffect(()=>{
setNewData(data)
}, [data])
After a lot of digging, I was able to figure this out myself.
Using the recommendations from Ferran as a base, I decided that it would be best to rehydrate the components displaying the contact info from a state, but as I'm using this context in multiple components, I needed to have the state update globally. I moved away from makeUseAxios, to a traditional axios call. The dealer ID is then stored in the state and used in the call. I also created the changeDealer const, which I can pass through the context, and which updates the state:
ApiContext.tsx
import React, { createContext, useEffect, useState } from 'react';
import axios from 'axios';
const contextObject = {} as any;
export const context = createContext(contextObject);
export const ApiContext = ({ children }: any) => {
const [dealerId, setDealerId] = useState(`1`);
useEffect(() => {
axios.get(`${process.env.GATSBY_API_ENDPOINT}/${dealerId}`).then((res) => setDealerId(res.data));
}, [dealerId]);
const changeDealer = (value: any) => {
setDealerId(value);
};
const { Provider } = context;
return <Provider value={{ data: dealerId, changeDealer: changeDealer }}>{children}</Provider>;
};
Then if, for example, I have a button that updates the dealer info, I import the context to the component and pass changeDealer through the it:
import { context } from 'contexts/ApiContext';
const { changeDealer } = useContext(context);
I can then attach it to a button like so:
<Link to="/" onClick={() => changeDealer(dealer.id)}>
Set Location
</Link>
This updates the state globally, changing the contact information across all the components that display it. I will be storing the data in a localStorage item, allowing the data to persist after a page refresh.

getting /src/components/App.jsx: Unexpected token, expected "}" error with onClick event

I have the following code for a button that is in a small React form / project.
<button onClick={()=>resetName();resetSurname();resetEmail()}>Submit</button>
The reset function is based on a hook that I created which is the following:
import { useState } from "react";
export default initialValue => {
const [name, setValue] = useState(initialValue);
const handleChange = e => {
setValue(e.target.value);
};
const reset = () => {
setValue("");
};
return [name, reset, handleChange];
};
I have imported this into my App.js file. Which reads as follows:
import React from "react";
import useInputHook from "../Hooks/useFormState";
function App() {
const [name, resetName, setName] = useInputHook("");
const [surname, resetSurname, setSurname] = useInputHook("");
const [email, resetEmail, setEmail] = useInputHook("");
Basically the ultimate goal is to reset the fields of the form. Should this be done this way or am I going about this the wrong way? Thanks for any help.
You should use brackets if you are calling more than one function.
Replace:
<button onClick={()=>resetName();resetSurname();resetEmail()}>Submit</button>
With:
<button
onClick={()=>{
resetName();
resetSurname();
resetEmail();
}}
>
Submit
</button>
Unexpected token, expected “}” error with onClick event
Now React expects } after resetName();.

How can I use get values from array of objects from JSON response

I am trying to learn how to use API's in react. I am making a search input for country names using the Rest countires API. I am getting data from https://restcountries.eu/rest/v2/all but I do not know how to handle this data as I can not use map on an object.
import axios from "axios";
import React, { useEffect, useState } from "react";
const App = () => {
const [countries, setCountries] = useState([]);
const [searchName, setSearchName] = useState("");
useEffect(() => {
axios.get("https://restcountries.eu/rest/v2/all").then(response => {
setCountries(response.data);
});
}, []);
const handleSearch = event => {
setSearchName(event.target.value);
};
return (
<div>
<div>
find countries <input onChange={handleSearch} />
</div>
<div></div>
</div>
);
};
export default App;
Expected to list countries after typing such as : sw = Botswana, Swaziland, Sweden ...
From the question it seems like, these are requirements of your app -
1
you need to search by country name
As you type in, list of countries matching the search should be displayed.
I created this sandbox with the code you provided - https://codesandbox.io/embed/58115762-rest-countries-o638k.
It shows a pair of country name and its capital as you enter input in the search box.
This is how I changed your code:
You need to search countries? - Use search API with country name as value of text input - searchName
https://restcountries.eu/rest/v2/name/${searchName}
To display the output with countries matching your search keyword - map over countries and get appropriate keys. Pass those keys as props to your newly created Country component.
Note, I did not need to change how you handled the JSON response. The searchName and countries are the only two state variables used to render the UI.
you will need to render countries after fetching from ajax request as like :
import axios from "axios";
import React, { useEffect, useState } from "react";
const App = () => {
const [countries, setCountries] = useState([]);
const [searchName, setSearchName] = useState("");
useEffect(() => {
axios.get("https://restcountries.eu/rest/v2/all").then(response => {
setCountries(response.data);
});
}, []);
const handleSearch = event => {
setSearchName(event.target.value);
};
return (
<div>
<div>
find countries <input onChange={handleSearch} />
</div>
<ul>
{(countries.length<=0)?"":
countries.map(country=> <li>country.name</li> )
}
</ul>
</div>
);
};
export default App;
I think this is what you are looking for.
If you have got questions, dont hesitate to ask :)
import axios from "axios";
import React, { useEffect, useState } from "react";
const App = () => {
const [countries, setCountries] = useState([]);
const [searchName, setSearchName] = useState("");
useEffect(() => {
axios.get("https://restcountries.eu/rest/v2/all").then(response => {
setCountries(response.data);
});
}, []);
const handleSearch = event => {
let str = event.target.value;
let filteredCountries = countries.filter((country) => country.name.toLowerCase().includes(str.toLowerCase()));
setCountries(filteredCountries);
setSearchName(str);
};
return (
<div>
<div>
find countries <input onChange={handleSearch} />
</div>
<ul> {(countries.length <= 0) ? "" : countries.map(country => <li>{country.name}</li>) } </ul>
</div>
);
};
export default App;
data =[your array];
countryList = data.map(data=>data.name)
console.log(countryList)

Categories