I make find method on users array but selectedUserId = undefined
How can I execute this method necessarily after the async function so that the selectedUserId is not undefined?
function App() {
const [selectedUserId, setSelectedUserId] = useState(null)
const [users,setUsers] = useState([])
const [loading, setLoading] = useState(false)
console.log(users)
useAsyncEffect(async () => {
setLoading(true)
const res = await fetch('https://jsonplaceholder.typicode.com/users')
const data = await res.json()
setUsers(data)
setLoading(false)
}, [])
const selectedUser = users.find(u => u.id === selectedUserId)
console.log(selectedUser)
const onUserClick = (userId) => {
setSelectedUserId(userId)
}
if(loading) {
return <img src={preloader} alt="preloader"/>
}
return (
<div>
{ !selectedUser ? <ListUsers users={users} onUserClick={onUserClick} /> : <Profile user=
{selectedUser} />
}
</div>
)
}
useEffect() hook will be called after rendering / component update.
so, on First time the users value will be empty array.
so, the selectedUser value will be undefined.
Wrap it in another useEffect will work.
Related
In my project I have the component ExportSearchResultCSV. Inside this component the nested component CSVLink exports a CSV File.
const ExportSearchResultCSV = ({ ...props }) => {
const { results, filters, parseResults, justify = 'justify-end', fileName = "schede_sicurezza" } = props;
const [newResults, setNewResults] = useState();
const [newFilters, setNewFilters] = useState();
const [data, setData] = useState([]);
const [isLoading, setIsLoading] = useState(true)
const [headers, setHeaders] = useState([])
const prepareResults = () => {
let newResults = [];
if (results.length > 1) {
results.map(item => {
newResults.push(parseResults(item));
}); return newResults;
}
}
const createData = () => {
let final = [];
newResults && newResults?.map((result, index) => {
let _item = {};
newFilters.forEach(filter => {
_item[filter.filter] = result[filter.filter];
});
final.push(_item);
});
return final;
}
console.log(createData())
const createHeaders = () => {
let headers = [];
newFilters && newFilters.forEach(item => {
headers.push({ label: item.header, key: item.filter })
});
return headers;
}
React.useEffect(() => {
setNewFilters(filters);
setNewResults(prepareResults());
setData(createData());
setHeaders(createHeaders());
}, [results, filters])
return (
<div className={`flex ${justify} h-10`} title={"Esporta come CSV"}>
{results.length > 0 &&
<CSVLink data={createData()}
headers={headers}
filename={fileName}
separator={";"}
onClick={async () => {
await setNewFilters(filters);
await setNewResults(prepareResults());
await setData(createData());
await setHeaders(createHeaders());
}}>
<RoundButton icon={<FaFileCsv size={23} />} onClick={() => { }} />
</CSVLink>}
</div >
)
}
export default ExportSearchResultCSV;
The problem I am facing is the CSV file which is empty. When I log createData() function the result is initially and empty object and then it gets filled with the data. The CSV is properly exported when I edit this component and the page is refreshed. I tried passing createData() instead of data to the onClick event but it didn't fix the problem. Why is createData() returning an empty object first? What am I missing?
You call console.log(createData()) in your functional component upon the very first render. And I assume, upon the very first render, newFilters is not containing anything yet, because you initialize it like so const [newFilters, setNewFilters] = useState();.
That is why your first result of createData() is an empty object(?). When you execute the onClick(), you also call await setNewFilters(filters); which fills newFilters and createData() can work with something.
You might be missunderstanding useEffect(). Passing something to React.useEffect() like you do
React.useEffect(() => {
setNewFilters(filters);
setNewResults(prepareResults());
setData(createData());
setHeaders(createHeaders());
}, [results, filters]) <-- look here
means that useEffect() is only called, when results or filters change. Thus, it gets no executed upon initial render.
I am getting an object from the API ('https://api.covid19api.com/summary'). This object has a
key
Countries with an array of objects and this array of objects I need to filter.
const filteredData = data.Countries.filter(dat => {
return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
})
TypeError: Cannot read property 'Countries' of undefined.
Why an array of objects is not recognized and is not filtered?
In another file, the map method iterates over the same writing data.Countries without error.
const Home = () => {
const [data, setData] = useState();
const [searchfield, setSearchfield] = useState('')
useEffect(() => {
const fetch = async () => {
try{
const res = await axios.get('https://api.covid19api.com/summary');
setData(res.data);
}catch(error){
console.log(error);
}
};
fetch();
}, []);
const onSearchChange = (event) => {
setSearchfield(event.target.value)
}
const filteredData = data.Countries.filter(dat => {
return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
})
return (
<div className="main-container">
<Header searchChange={onSearchChange}/>
<div className="wrapper">
<Card data={data}/>
{/*<div className="graph">
<h1>Global Pandemic Crisis Graph</h1>
<img src={COVID.image} alt=""/>
</div>*/}
<div className="countries">
<Countries data={filteredData}/>
</div>
</div>
{/*<Footer />*/}
</div>
)
}
When you are fetching the data from an api, you need to use optional chaining ? when applying any higher order function to an array just incase the data haven't been loaded. for example
const filteredData = data?.Countries.filter(dat => {
return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
})
Issue
The initial data state is undefined, so data.Countries is undefined on the initial render.
const [data, setData] = useState();
Solution
Provide valid initial state and guard against later bad updates (if they happen).
const [data, setData] = useState({ Countries: [] });
...
const filteredData = data?.Countries?.filter(dat => {
return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
})
You need to filter your data in a callback of the axios response, or it will be "undefined" because it hasn't finished fetching it.
let filteredData = useRef(null);
useEffect(() => {
if (typeof data !== "undefined")
filteredData.current = data.Countries.filter((dat) => {
return dat.Country.toLowerCase().includes(searchfield.toLowerCase());
});
}, [data]);
const fetch = async () => {
const res = await axios
.get("https://api.covid19api.com/summary")
.then((response) => {
// response.data should not be "undefined" here.
setData(response.data);
})
.catch((error) => {
// Error fallback stuff
console.error(error);
});
};
if (!filteredData.current) fetch();
Later in your code you can check whether or not it has been defined,
return (
<div className="main-container">
<div className="wrapper">
{filteredData.current !== null &&
filteredData.current.map((CountryMap, i) =>
<div key={i}>{CountryMap.Country}</div>
)
}
</div>
</div>
);
I fetch data from goodreads API and save it in books array using hooks.
I need to get data and display in component according to search query. but there is an issue in console (Uncaught TypeError: setSearchText is not a function)
function App() {
const [books, setBooks] = useState([])
const [searchText, setSearchText] = ('')
const key = "xxx"
const url = `https://cors-anywhere.herokuapp.com/` +
`https://www.goodreads.com/search/index.xml?key=${key}&q=${searchText}`;
useEffect (()=> {
loadData()
}, [])
const loadData = async () => {
axios.get(url).then((res) => {
setBooks(res.data)
console.log(books)
})
}
const onTextChange = (e) => {
setSearchText(e.target.value)
}
return (
<>
<Container>
<h1>Bookstore</h1>
<input type='text'
placeholder='Search...'
name="searchText"
onChange={onTextChange}
value={searchText}
/>
</Container>
</>
);
}
You are missing a function call on the right side of the destructuring assignment.
const [books, setBooks] = useState([])
const [searchText, setSearchText] = ('') // This should be useState('')
So, i have this code that retrieves movies from api and im trying to implement live searching. I created an if statement to check the input but every time i put the first character in the input field I get the filter undefined error. How can I fix this?
import React, { useState, useEffect } from "react";
const SearchMovie = () => {
const [state, setState] = useState([]);
const [movie, setmovie] = useState("");
const [search, setSearch] = useState("");
const key = "xxxxxxxx";
const url = `http://www.omdbapi.com/?s=${movie}&apikey=${key}`;
useEffect(() => {
getData();
}, [movie]);
const getData = async () => {
const data = await fetch(url);
const response = await data.json();
setState(response.Search);
console.log(response.Search);
};
const updateSearch = (e) => {
setSearch(e.target.value);
};
const getSearch = (e) => {
e.preventDefault();
setmovie(search);
setSearch("");
}
if(search.length > 0) {
setState(state.filter((i)=>{
return i.Title.match(search)
}))
}
return (
<div>
<form onSubmit={getSearch} className="search-form">
<input
type="text"
className="search-bar"
value={search}
onChange={updateSearch}
/>
<button className="search-button" type="submit">
Search
</button>
</form>
{(state || []).map((details) => (
<>
<p>{details.Title}</p>
<p>{details.Year}</p>
<img src={details.Poster} alt=""/>
</>
))}
</div>
);
};
export default SearchMovie;
You have call filter on undefined. Because you have setState(response.Search). I think your result search is undefined. Let's check.
const getData = async () => {
const data = await fetch(url);
const response = await data.json();
setState(response.Search ? response.Search : []);
//or
//if (response.Search) setState(response.Search)
console.log(response.Search);
};
im using this Api to get json data.
const FetchEarthquakeData = url => {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(jsonData => setData(jsonData.features))
}, [url]);
return data;
};
The problem is when I use this function like this:
const jsonData = FetchEarthquakeData(url)
console.log(jsonData);
I get following console.logs:
null
Array(17)
So my function FetchEarthquakeData returns the null variable and! the desired api. However if I want to map() over the jsonData, the null value gets mapped. How can I refactor my code so I get only the Array?
I'm not quite sure what useState() and setData() do. But in order to fetch the json data from the API, you can make the function as followed, then you can perform operations on the fetched data.
const url = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson"
const FetchEarthquakeData = url => {
return new Promise(resolve => {
fetch(url)
.then(res => res.json())
.then(jsonData => resolve(jsonData.features))
})
}
FetchEarthquakeData(url).then(features => {
console.log(features)
// Do your map() here
})
as per the documentation of react hooks ,Only Call Hooks from React Functions Don’t call Hooks from regular JavaScript functions.
React Function Components -- which are basically just JavaScript Functions being React Components which return JSX (React's Syntax.)
for your requirement you can do as follows in your react component.
idea is to have state hook initialized with empty array then update it once json data available. the fetch logic can be moved inside useeffect .
const SampleComponent=()=>{
const [data,setData] = useState([])
useeffect (){
fetch(url).then((responsedata)=>setData(responseData) ,err=>console.log (err)
}
return (
<>
{
data.map((value,index)=>
<div key=index>{value}<div>
}
</>
)
}
if you find above fetching pattern repetitive in your app thou can use a custom hook for the same.
const useFetch = (url, options) => {
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
React.useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
fetchData();
}, []);
return { response, error };
};
in your React component you can use like
const {data,error} = useFetch(url , options)
You have to do it in an async fashion in order to achieve what you need.
I recommend you doing it separately. In case you need the data to load when the component mounts.
Another thing is that your function is a bit confusing.
Let's assume some things:
const Example = () => {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const result = await fetch(url);
setData(result.features);
};
fetchData();
}, []);
return (
<div>
{console.log(data)}
{data && <p>{JSON.stringify(data, null, 2)}</p>}
</div>
);
};
I am sure that the way you are doing it is storing the data properly on data, but you can not see it on your console.log. It is a bit tricky.
You can read a bit more here => https://medium.com/#wereHamster/beware-react-setstate-is-asynchronous-ce87ef1a9cf3
Adding a bit more of complexity in case you want to handle different states like loading and error.
const Example = () => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await fetch(url);
setData(result.features);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, []);
return (
<div>
{console.log(data)}
{data && <p>{JSON.stringify(data, null, 2)}</p>}
</div>
);
};