I'm able to fetch the data but not sure how to map nested items
data looks like that
{"id":3,
"brands":[{"id":6,"products":[],
"title":"Lenovo",
"image":"/img.png",
"timestamp":"2022-02-07T15:11:36.059719+05:30",
"updated":"2022-02-07T15:11:36.059719+05:30",
"category":3}],
"title":"Laptop",
"image":"/img.png",
"timestamp":"2022-01-30T23:18:14.124583+05:30",
"updated":"2022-01-30T23:18:14.124583+05:30"}
I'm using following method to do this.
const CategoriesToBrands = ({ match }) => {
const [hasError, setErrors] = useState(false);
const [items, setItems] = useState({});
const { id } = useParams();
async function fetchData() {
const data = await fetch("/api/v1/categories/nested/" + id);
data.json()
.then(data => setItems(data))
.catch(data => setErrors(data));
}
useEffect(() => {
fetchData()
}, [])
return (
<div>
<div>{JSON.stringify(items)}</div>
<hr />
{items.title}
<hr />
<div>Has error: {JSON.stringify(hasError)}</div>
</div>
);
};
export default CategoriesToBrands;
I also tried multiple method suggested on stack overflow to map the items but no one seems to be working . or they are using another method . there can be multiple number of items in brands [] and i want to display each one of them for now there is one.. So what would be the syntax or method to map them properly.
You have an brands array inside your main object fetched by async request. It means you should to get brands by items.brands. Something like that:
const CategoriesToBrands = ({ match }) => {
const [hasError, setErrors] = useState(false);
const [items, setItems] = useState({});
const { id } = useParams();
async function fetchData() {
const data = await fetch("/api/v1/categories/nested/" + id);
data.json()
.then(data => setItems(data))
.catch(data => setErrors(data));
}
useEffect(() => {
fetchData()
}, [])
return (
<div>
<div>{JSON.stringify(items)}</div>
<hr />
{items.title}
<hr />
{items.brands ? items.brands.map(brand => (
<div key={brand.id}>brand id: {brand.id}</div>
)) : null}
<div>Has error: {JSON.stringify(hasError)}</div>
</div>
);
};
export default CategoriesToBrands;
The good idea is also changing the name of your const [items, setItems] = useState({});. In your case you are fetching one item, not multiple (I assume this fact by your id variable). So the name like item, setItem sounds better.
Related
I am trying to render listed property information from an array of objects. I used this method in another part of my project with success, but in this instance, I am not getting anything at all.
here is the code I have
import { database } from "../../components/firebase";
import { ref, child, get } from "firebase/database";
import { useState, useEffect } from "react";
export default function Dashboard() {
const dbRef = ref(database);
const [users, setUsers] = useState([]);
const array = [];
const getData = () => {
get(child(dbRef, "users/"))
.then((snapshot) => {
const data = snapshot.val();
setUsers(data);
})
.catch((err) => {
console.log(err);
});
};
const getProperties = () => {
Object.values(users).forEach((user) => {
Object.values(user?.properties).forEach((property) => {
array.push(property);
console.log(property);
});
});
console.log(array);
};
useEffect(() => {
getData();
getProperties();
}, [dbRef]);
return (
<>
<div>Properties </div>
<div>
{array.map((property) => (
<div key={property.property_id}>
<h1>{property?.property_name}</h1>
<p>{property?.description}</p>
<p>{property?.rooms}</p>
<p>{property?.phone}</p>
</div>
))}
</div>
<p>oi</p>
</>
);
}
Nothing happens, it only prints "properties" and "oi"
getData is asynchronous. When you execute getProperties, your users state will still be its initial, empty array value.
You don't appear to be using users for anything else but assuming you want to keep it, the easiest way to drive some piece of state (array) from another (users) is to use a memo hook.
// this is all better defined outside your component
const usersRef = ref(database, "users");
const getUsers = async () => (await get(usersRef)).val();
export default function Dashboard() {
const [users, setUsers] = useState({}); // initialise with the correct type
// Compute all `properties` based on `users`
const allProperties = useMemo(
() =>
Object.values(users).flatMap(({ properties }) =>
Object.values(properties)
),
[users]
);
// Load user data on component mount
useEffect(() => {
getUsers().then(setUsers);
}, []);
return (
<>
<div>Properties </div>
<div>
{allProperties.map((property) => (
<div key={property.property_id}>
<h1>{property.property_name}</h1>
<p>{property.description}</p>
<p>{property.rooms}</p>
<p>{property.phone}</p>
</div>
))}
</div>
<p>oi</p>
</>
);
}
The memo hook will recompute allProperties any time users is changed.
If you don't need the users state, then there's not much need for the memo hook. Instead, just maintain the state you do need
const [allProperties, setAllProperties] = useState([]); // init with empty array
useEffect(() => {
getUsers().then((users) => {
setAllProperties(
Object.values(users).flatMap(({ properties }) =>
Object.values(properties)
)
);
});
}, []);
I have an api (an arr of objects) which I need to pass into a state, so that I can then pass that data inside a component to show it on the website.
1st approach:
// pulls the api data
const newData = axios.get(url).then((resp) => {
const apiData = resp.data;
apiData.map((video) => {
return video;
});
});
// sets the state for the video
const [selectedVideo, setSelectedVideo] = useState(newData[0]);
const [videos] = useState(videoDetailsData);
...
return (
<>
<FeaturedVideoDescription selectedVideo={selectedVideo} />
</>
)
2nd approach:
const useAxiosUrl = () => {
const [selectedVideo, setSelectedVideo] = useState(null);
useEffect(() => {
axios
.get(url)
.then((resp) => setSelectedVideo(resp.data))
});
return selectedVideo;
}
...
return (
<>
<FeaturedVideoDescription selectedVideo={selectedVideo} />
</>
)
both of these approaches don't seem to work. What am I missing here?
The correct way is to call your axios method inside the useEffect function.
const fetchData = axios.get(url).then((resp) => setSelectedVideo(resp.data)));
useEffect(() => {
fetchData();
}, [])
or if you need async/await
useEffect(() => {
const fetchData = async () => {
const response = await axios.get(url);
setSelectedVideo(resp.data);
}
fetchData();
}, [])
Whenever I dispatch a search action using context and useReducer for an object in an array stored in local storage, it returns the object, but when I delete the search query from the input box, the list is not returned and the page is blank, can anyone help please?
This is my context:
const NotesContext = createContext(null);
const NotesDispatchContext = createContext(null);
const getStoredNotes = (initialNotes = InitialNotes) => {
return JSON.parse(localStorage.getItem("storedNotes")) || initialNotes;
};
export const NotesProvider = ({ children }) => {
const [NOTES, dispatch] = useReducer(NotesReducer, getStoredNotes());
useEffect(() => {
localStorage.setItem("storedNotes", JSON.stringify(NOTES));
}, [NOTES]);
return (
<NotesContext.Provider value={NOTES}>
<NotesDispatchContext.Provider value={dispatch}>
{children}
</NotesDispatchContext.Provider>
</NotesContext.Provider>
);
};
export const useNotesContext = () => {
return useContext(NotesContext);
};
export const useNotesDispatchContext = () => {
return useContext(NotesDispatchContext);
};
const App = () => {
const [query, setQuery] = useState("");
const dispatch = useNotesDispatchContext();
useEffect(() => {
if (query.length !== 0) {
dispatch({
type: "searchNotes",
query: query,
});
}
}, [query]);
return (
<div className="container">
<header>
<Title title={"Notes"} className={"app_title"} />
<form className="search_container">
<span class="material-symbols-outlined">search</span>
<input
type="search"
placeholder="search notes"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
</form>
</header>
This is my reducer function
case "searchNotes": {
[...NOTES].filter((note) =>
note.title.toLowerCase().includes(action.query)
);
}
The function seems to actually remove the all data from the local storage instead of filtering based on the query string.
Issue
When you dispatch searchNotes you are changing NOTES and the blow useEffect runs. So if the filter resulted to an empty array, there would be nothing in localStorage.
useEffect(() => {
localStorage.setItem("storedNotes", JSON.stringify(NOTES));
}, [NOTES]);
Solution
What you can do is to remove that useEffect in App that has query as dependency and dispatching searchNotes. And filter directly while rendering, something like this:
{
NOTES.filter((note) => note.title.toLowerCase().includes(query)).map((note, index) => (
<div key={index}>{note.title}</div>
))
}
And at this point you can remove searchNotes case from your reducer.
I am new to React (and still new to JS too), and i am trying to build my first React project. I am fetching an API , rendering some items, and building a Search Bar that filters out the items rendered.
My filtering function is more or less working, and inside of it, i store the filtered results in let result , but How i should access those results from the return part (JSX area, i think) to loop over them?
This is my code :
import React, { useState, useEffect } from "react";
import ListItem from "./ListItem";
const List = () => {
const [data, setData] = useState();
const [input, setInput] = useState("");
const onInputChange = (event) => {
setInput(event.target.value);
const value = event.target.value.toLowerCase();
let result = [];
result = data.filter((item) =>
item.name.toLowerCase().includes(value.toLowerCase())
);
setInput(result);
};
useEffect(() => {
const getData = async () => {
const response = await fetch(
"https://rickandmortyapi.com/api/character/"
);
const obj = await response.json();
setData(obj.results);
};
getData();
}, []);
return (
<div>
<input type="text" name={input} onChange={onInputChange}></input>
{data &&
data.map((item) => {
return <ListItem key={item.id} character={item} />;
})}
</div>
);
};
export default List;
So far, I can only loop over input which contains the results, like this input && input.map((item) , but that gives me an empty array when the page is loaded , until i make a search.
You just initialise input as a string so just keep input for keeping input value not result data. You can create another state for keeping result OR put result data back on Data variable.
Here I am showing you to keep result data separate.
import React, { useState, useEffect } from "react";
import ListItem from "./ListItem";
const List = () => {
const [data, setData] = useState();
const [searchResult, setSearchResult] = useState();
const [input, setInput] = useState("");
const onInputChange = (event) => {
setInput(event.target.value);
const value = event.target.value.toLowerCase();
let result = [];
result = data.filter((item) =>
item.name.toLowerCase().includes(value.toLowerCase())
);
setSearchResult(result);
};
useEffect(() => {
const getData = async () => {
const response = await fetch(
"https://rickandmortyapi.com/api/character/"
);
const obj = await response.json();
setData(obj.results);
};
getData();
}, []);
return (
<div>
<input type="text" name={input} onChange={onInputChange}></input>
{input===""? data &&
data.map((item) => {
return <ListItem key={item.id} character={item} />;
}):
searchResult &&
searchResult.map((item) => {
return <ListItem key={item.id} character={item} />;
})
}
</div>
);
};
export default List;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This is separating your original data and search result different.
You need to use a variable to store data after filter:
const [data, setData] = useState([]);
const onInputChange = (event) => {
setInput(event.target.value);
};
const result = data.filter((item) =>
item.name.toLowerCase().includes(input.toLowerCase())
);
return (
...
{result?.map((item) => {
<ListItem key={item.id} character={item} />;
})}
...
)
One possible solution would be to filter while rendering,
In this scenario you would only need to save the the input value (onInputChange):
const onInputChange = (event) => {
setInput(event.target.value);
};
Then while rendering you would need to add the filtering logic:
{ // if input is not empty
data
.filter(item => item.name.includes(input.toLowerCase()))
.map((item) => {
return <ListItem key={item.id} character={item} />;
})
I am making dummy app to test server side API.
First request returns nested JSON object with Product names and number of variants that it has. From there I extract Product name so I can send second request to fetch list of variants with product images, sizes etc.
Sometimes it will load and display variants from only one product but most of the times it will work correctly and load all variants from both dummy products.
Is there a better way of doing this to ensure it works consistently good. Also I would like to know if there is a better overall approach to write something like this.
Here is the code:
import React, { useEffect, useState } from "react";
import axios from "axios";
import ShirtList from "../components/ShirtList";
const recipeId = "15f09b5f-7a5c-458e-9c41-f09d6485940e";
const HomePage = props => {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
axios
.get(
`https://api.print.io/api/v/5/source/api/prpproducts/?recipeid=${recipeId}&page=1`
)
.then(response => {
let shirtList = [];
const itemsLength = response.data.Products.length;
response.data.Products.forEach((element, index) => {
axios
.get(
`https://api.print.io/api/v/5/source/api/prpvariants/?recipeid=${recipeId}&page=1&productName=${element.ProductName}`
)
.then(response => {
shirtList.push(response.data.Variants);
if (index === itemsLength - 1) {
setLoaded(shirtList);
}
});
});
});
}, []);
const ListItems = props => {
if (props.loaded) {
return loaded.map(item => <ShirtList items={item} />);
} else {
return null;
}
};
return (
<div>
<ListItems loaded={loaded} />
</div>
);
};
export default HomePage;
You are setting the loaded shirts after each iteration so you will only get the last resolved promise data, instead fetch all the data and then update the state.
Also, separate your state, one for the loading state and one for the data.
Option 1 using async/await
const recipeId = '15f09b5f-7a5c-458e-9c41-f09d6485940e'
const BASE_URL = 'https://api.print.io/api/v/5/source/api'
const fetchProducts = async () => {
const { data } = await axios.get(`${BASE_URL}/prpproducts/?recipeid=${recipeId}&page=1`)
return data.Products
}
const fetchShirts = async productName => {
const { data } = await axios.get(
`${BASE_URL}/prpvariants/?recipeid=${recipeId}&page=1&productName=${productName}`,
)
return data.Variants
}
const HomePage = props => {
const [isLoading, setIsLoading] = useState(false)
const [shirtList, setShirtList] = useState([])
useEffect(() => {
setIsLoading(true)
const fetchProductShirts = async () => {
const products = await fetchProducts()
const shirts = await Promise.all(
products.map(({ productName }) => fetchShirts(productName)),
)
setShirtList(shirts)
setIsLoading(false)
}
fetchProductShirts().catch(console.log)
}, [])
}
Option 2 using raw promises
const recipeId = '15f09b5f-7a5c-458e-9c41-f09d6485940e'
const BASE_URL = 'https://api.print.io/api/v/5/source/api'
const fetchProducts = () =>
axios.get(`${BASE_URL}/prpproducts/?recipeid=${recipeId}&page=1`)
.then(({ data }) => data.Products)
const fetchShirts = productName =>
axios
.get(
`${BASE_URL}/prpvariants/?recipeid=${recipeId}&page=1&productName=${productName}`,
)
.then(({ data }) => data.Variants)
const HomePage = props => {
const [isLoading, setIsLoading] = useState(false)
const [shirtList, setShirtList] = useState([])
useEffect(() => {
setIsLoading(true)
fetchProducts
.then(products) =>
Promise.all(products.map(({ productName }) => fetchShirts(productName))),
)
.then(setShirtList)
.catch(console.log)
.finally(() => setIsLoading(false)
}, [])
}
Now you have isLoading state for the loading state and shirtList for the data, you can render based on that like this
return (
<div>
{isLoading ? (
<span>loading...</span>
) : (
// always set a unique key when rendering a list.
// also rethink the prop names
shirtList.map(shirt => <ShirtList key={shirt.id} items={shirt} />)
)}
</div>
)
Refferences
Promise.all
Promise.prototype.finally
React key prop
The following should pass a flat array of all variants (for all products ) into setLoaded. I think this is what you want.
Once all the products have been retrieved, we map them to an array of promises for fetching the variants.
We use Promise.allSettled to wait for all the variants to be retrieved, and then we flatten the result into a single array.
useEffect(()=>(async()=>{
const ps = await getProducts(recipeId)
const variants = takeSuccessful(
await Promise.allSettled(
ps.map(({ProductName})=>getVariants({ recipeId, ProductName }))))
setLoaded(variants.flat())
})())
...and you will need utility functions something like these:
const takeSuccessful = (settledResponses)=>settledResponses.map(({status, value})=>status === 'fulfilled' && value)
const productURL = (recipeId)=>`https://api.print.io/api/v/5/source/api/prpproducts/?recipeid=${recipeId}&page=1`
const variantsURL = ({recipeId, productName})=>`https://api.print.io/api/v/5/source/api/prpvariants/?recipeid=${recipeId}&page=1&productName=${productName}`
const getProducts = async(recipeId)=>
(await axios.get(productURL(recipeId)))?.data?.Products
const getVariants = async({recipeId, productName})=>
(await axios.get(variantsURL({recipeId,productName})))?.data?.Variants