How to map an array inside an array in UseEffect - javascript

Below image will be the response I get from useEffect
My useEffect will look like this:
useEffect(() => {
const getStatesData = async () => {
await fetch ('http://localhost:3001/covid19')
.then((response) => response.json())
.then((data) => {
const dataStates = data.states
const states = dataStates.map((state2) => (
{
name: state2.name,
total: state2.total
}
));
setStates(states)
})
}
getStatesData();
}, []);
I'm trying to get name and total inside districts, how can I do that?
I've tried using adding districts: state2.districts in useEffect, and adding {state.districts.map(({name, total}) => ({name} ))} directly in the <div> but it's returning an error: Error: Objects are not valid as a React child (found: object with keys {name}). If you meant to render a collection of children, use an array instead.
I'm currently using material-ui to help me get thigs easier, here is my full code excluding the useEffect:
<div className="App">
{states.map(state => (
<Accordion expanded={expanded === state.name}
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1bh-content"
id="panel1bh-header"
>
<Typography className={classes.heading}>{state.name} ({state.total}) </Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
!! DISTRICTS NAME HERE (DISTRICTS TOTAL HERE) !!
</Typography>
</AccordionDetails>
</Accordion>
))}
</div>

Instead of mapping districts like this:
{state.districts.map(({name, total}) => ({name} ))}
do it like this:
{state.districts.map(({name, total}) => `${name} (${total})`)}
or even better to a component like this:
{state.districts.map(({name, total}) => <div>{name} ({total})<div>)}
within the Typography component.
The reason being, the mapping state.districts.map(({name, total}) => ({name} )) returns an object for each district, and React doesn't recognizes objects as renderable components.

Related

How to filter array to match params value with react

I wanted to create a e-commerce web application using react-bootstrap. I want the page to show different item based on category so if the URL is product/men'sclothing i want to filter my array and show only the product that have same category which is men's clothing (my path: product/:category). I already tried to filter my array using .filter method but it didn't work, it still show all product from various category, How can I fix it ?
Categorized product page:
const ProductList = () => {
const { category } = useParams()
const[productList, setProductList]= useState();
useEffect(() =>{
axios.get(`https://fakestoreapi.com/products`).then(res => {
const products = res.data;
setProductList(products);
var filteredCategory =
productList.filter((productList) =>productList.category === {category})
})
}, []);
console.log(productList)
return (
<>
<Row>
<h1> This is {category} paged</h1>
{productList && productList.map(product =>{
const {id, title, price, category,description,image} = product;
return(
<Col lg={3} className="d-flex">
<Card key={id} className="flex-fill productlist">
<Card.Img variant="top" src={image} />
<Card.Body>
<Card.Title>{title}</Card.Title>
<Card.Text>{category}</Card.Text>
<Card.Text>
Current Price: {price}
</Card.Text>
<Button variant="primary">Add to cart</Button>
</Card.Body>
</Card>
</Col>
)
})}
</Row>
</>
)
}
export default ProductList
In the filter function that you have used, try writing it as
productList.filter((product) => product.category === category)
When you write it as {category}, a object is created with key category and the value as the actual value. For example if value of category is shoes, it will create a object, { category: "shoes" }.
You also need to add category in useEffect dependency, to re-fetch products every time category is updated.
First, add a dependency to your UseEffect then remove the bracket inside the filter.
useEffect(() => {
async function getByCategory(){
const req = await fetch(URL):
const res = await req.json();
const filter = res.filter((item) => item.category === category);
setProductList(filter);
}
// check if params exits
if(category){
getByCategory();
}
}, [category]);
Try getting rid of the {} around the category variable in the filter function. The filter function is not inside the return statement and thus plain js (not jsx).
Also, you're never using the array containing the filtered products. I'd suggest to filter the products you get from axios, take the filtered products and put THEM into state with setProductList.
Was not able to test this since I'm on mobile, but give it a try.
Remove the curly braces when comparing the element.
__YOUR CODE
productList.filter((productList) =>productList.category === {category})
__NEW
productList.filter((productList) =>productList.category === category)
You are still listing all products because in your code you are looping through the productList state instead of the new value which come from the filtered data.
{productList && productList.map(product =>{
// this is the way you have defined your map code
}) }
It should be like this
const ProductList = () => {
const { category } = useParams()
const[productList, setProductList]= useState();
useEffect(() =>{
axios.get(`https://fakestoreapi.com/products`).then(res => {
const products = res.data;
setProductList(products);
})
}, []);
let filteredProducts = null;
if(category) {
filteredProducts = productList.filter((productList) => productList.category === category);
} else {
filteredProducts = products;
}
return (
<>
<Row>
<h1> This is {category} paged</h1>
{filteredProducts && filteredProducts.map(product =>{
// some code
})}
</Row>
</>
)
}
export default ProductList
As you can see I define a variable filter Products which contains products related to the category get from the url when It's present otherwise it will use the entire list of products

Passing props inorder to use the string value

I have the following functional component and de-structuring the parameter props:
const MyTimes = ({ myWindowGroup, name, fieldArrayName }) => (
<FieldArray
name={name}
render={(arrayHelpers) => (
<React.Fragment>
{myWindowGroup.fieldArrayName.map((myTime, index) => (
and I am calling the component with the following props:
<MyTimes
myWindowGroup={myWindowGroup}
fieldArrayName={"myTimes"}
name={`myWindowGroups.${index}.myTimes`}
/>
My question is as I am new to React and that is, how can I pass/use the fieldArrayName={"myTimes"} string value of myTimes into the MyTime component above so that I can replace the value of fieldArrayName.map to be myTimes.map ?
I've tried it the way it is and not working.
Use dynamic keys. See Bracket Notation.
myWindowGroup[fieldArrayName].map(....
const myWindowGroup = {
myTimes: ['This is the value array you want'],
foo: [],
};
console.log(myWindowGroup['myTimes']);
console.log(myWindowGroup['foo']);
You can just do
{myWindowGroup[fieldArrayName].map((myTime, index) => (
As I understand your question correctly, you can achieve desired output by following
const MyTimes = ({ myWindowGroup, name, fieldArrayName }) => (
<FieldArray
name={name}
render={(arrayHelpers) => (
<React.Fragment>
{myWindowGroup[fieldArrayName].map((myTime, index) => (
// your logic here
))}

Material UI React - Autocomplete - How to reset the internal state

My goal is to reset the internal state of Autocomplete Material-UI's component.
My custom component is rendered N times in my cycle
{branches.map((branch, index) => {
return (
<BranchSetting
key={index}
index={index}
data={branch}
removeBranch={removeBranch}
/>
)
})}
branch is my hook state.
This is my removeBranch function:
const removeBranch = (index) => {
let listBranch = branches;
listBranch.splice(index, 1);
setBranches([...listBranch]);
}
Every time I delete an item to my array branch, everything works fine except the Autocomplete.
This is my BranchSetting component:
import React, { useState, useEffect } from "react";
import Autocomplete from '#material-ui/lab/Autocomplete';
const BranchSettings = ({ removeBranch, index, branchModify, data }) => {
const [ brands, setBrands ] = useState(data.brands);
const handleBrandSelected = (event, payload) => {
const values = payload.map(item => item.value);
setBrands(values);
}
useEffect(() => {
setBrands(data.brands);
}, [data])
return (
<>
<Autocomplete
id={'branch-brand'}
multiple
disableCloseOnSelect
options={carsBrand}
getOptionLabel={(carsBrand) => carsBrand.label}
onChange={handleBrandSelected}
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip
variant="outlined"
label={option.value}
size="small"
{...getTagProps({ index })}
/>
))
}
renderInput={(params) => {
return (
<TextField
{...params}
variant="filled"
fullWidth
label={'brand'}
/>
)
}}
/>
</>
)
}
export default BranchSettings
carsBrand it is my data source that in the example I avoided writing the population. It's an array
Everytime I try to delete an item, Autocomplete keep the state of the component ad the prev position.
I'm looking a way to reset all the internal state of Autocomplete component.
The status I refer to can be seen with the devToolBar
I'm looking a good way to keep the items selected properly or that every time the component has changed, rerender the Autocomplete component.
I resolved the problem.
The problem was that Autocomplete component need to input an array of objects with label and value keys.
In the function handleBrandSelected I saved into my brands status just the value. I should have saved the whole object because then it must be sent as input in Autocomplete with the props value.
And to handle the object I should have also used props getOptionSelected.
No problems with the remove function, and no problems with indexes. Only the values selected in inputs and compliant with the documentation were missing.
So this is the new code
import React, { useState, useEffect } from "react";
import Autocomplete from '#material-ui/lab/Autocomplete';
const BranchSettings = ({ removeBranch, index, branchModify, data }) => {
const [ brands, setBrands ] = useState(data.brands);
const handleBrandSelected = (event, payload) => setBrands(payload);
useEffect(() => {
setBrands(data.brands);
}, [data])
return (
<>
<Autocomplete
id={'branch-brand'}
multiple
disableCloseOnSelect
options={carsBrand}
getOptionLabel={(carsBrand) => carsBrand.label}
onChange={handleBrandSelected}
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip
variant="outlined"
label={option.value}
size="small"
{...getTagProps({ index })}
/>
))
}
renderInput={(params) => {
return (
<TextField
{...params}
variant="filled"
fullWidth
label={'brand'}
/>
)
}}
getOptionSelected={(option, value) => option.value === value.value}
value={brands}
/>
</>
)
}
export default BranchSettings
This problem is probably caused by using the array index as a key in <BranchSetting key={index}. I recommend that you add a unique id when creating the branch object, and use that id as a key instead. You can use performance.now() or a small lib like nanoid.
You can read more about the negative impacts of using an index as a key here.

Rendering part of a React function component after async call gets completed

I'm using material-ui with a React function component and using its Autocomplete component. I customized it and whenever I change the text in input field, I expect the component to render new search result.
callAPI("xyz")
I'm calling the API in the action and using the xyz parameter, I'm calling the dispatch method from this function component.
Problem here is, when the component makes the call, it is supposed to wait for the API response and then render the result, but it gets an unresolved promise, so it fails rendering.
<Paper square>
{callAPI("xyz").results.map(
result => console.log(result);
)}
</Paper>
as results are a unresolved promise, it will fail to map.
I need some way to call the map only once data is available, or show some text before data is there and then change once data is fetched.
Any suggestions to correct this code will be very helpful.
EDIT:
function IntegrationDownshift() {
return (
<div>
<Downshift id="downshift-simple">
{({
getInputProps,
getItemProps,
getMenuProps,
highlightedIndex,
inputValue,
isOpen,
selectedItem
}) => (
<div>
{renderInput({
fullWidth: true,
InputProps: getInputProps({
placeholder: "Search users with id"
})
})}
<div {...getMenuProps()}>
{isOpen ?
<Paper square>
{callAPI(inputValue).users.map(
(suggestion, index) =>
renderSuggestion({
suggestion,
index,
itemProps: getItemProps({
item:
suggestion.userName
}),
highlightedIndex,
selectedItem
})
)}
</Paper>
: null}
</div>
</div>
)}
</Downshift>
</div>
);
}
React 16.8 introduces Hooks:
Hooks are functions that let you “hook into” React state and lifecycle
features from function components.
so you have useState() which you can declare a state variable with an empty array and call your API in the useEffect() to populate the state when you get the response from the API:
function App() {
const [data, setData] = useState([]);
useEffect(() => {
callAPI("xyz").then(result => {
setData(result);
})
}, []);
if(!data.length) return (<span>loading...</span>);
return (
<Paper square>
{data.map(
result => console.log(result);
)}
</Paper>
);
}
More about hooks: https://reactjs.org/docs/hooks-intro.html.
Easiest way to handle this is with a ternanry expression
And also its best practice to call your API request in a lifecycle method and then save the result in local state.
componentDidMount() {
callAPI("xyz").results.map(
result => this.setState(result);
}
<Paper square>
{this.state.results ?
this.state.results.map(
result => console.log(result);
: <p> Loading... </p>
)}
</Paper>

alternative to innerRef when using hooks

I am converting class based components to react hooks. I got confused on using the ref parts. Because, the way I am using it complains me that The "innerRef" API has been removed in styled-components v4 in favor of React 16 ref forwarding, use "ref" instead like a typical component..
How do i make it work when using hooks?
const Tabs = ({activeTab, children}) => {
const [tabsElements, setTabsElements] = useState([])
return (
<TabsContext.TabProvider activeTab={activeTab}>
<TabsContext.TabConsumer>
{value => (
<ReactTabs>
<TabsContainer>
<ListTabs>
{value.context.tabs.map(tab => (
<TabTitleItem
key={tab.id}
onClick={value.context.onClick(tab)}
id={tab.id}
innerRef={tabElement => {
if (!tabsElements[tab.id]) {
setTabsElements(tabElements => ({
...tabElements,
[tab.id]: tabElement,
}))
}
}}
isActiveTab={value.context.activeTab.id === tab.id}
>
<TabAnchorItem>{tab.title}</TabAnchorItem>
</TabTitleItem>
))}
</ListTabs>
<ActiveTabBorder
activeTabElement={tabsElements[value.context.activeTab.id]}
/>
</TabsContainer>
{children}
</ReactTabs>
)}
</TabsContext.TabConsumer>
</TabsContext.TabProvider>
)
}
Here is the demo
https://codesandbox.io/s/z3moq8662p
First of all you cannot update state within the ref callback method. Secondly you simply need to pass ref instead of innerRef to the TabTitleItem component since it internally handles ref using forwardRef
const Tabs = ({ activeTab, children }) => {
const [tabsElements, setTabsElements] = useState([]);
const tabElements = useRef({});
return (
<TabsContext.TabProvider activeTab={activeTab}>
<TabsContext.TabConsumer>
{value => (
<ReactTabs>
<TabsContainer>
<ListTabs>
{console.log("value", value.context)}
{value.context.tabs.map(tab => (
<TabTitleItem
key={tab.id}
onClick={value.context.onClick(tab)}
id={tab.id}
ref={tabElement => {
tabElements.current[tab.id] = tabElement;
}}
isActiveTab={value.context.activeTab.id === tab.id}
>
<TabAnchorItem>{tab.title}</TabAnchorItem>
</TabTitleItem>
))}
</ListTabs>
<ActiveTabBorder
activeTabElement={tabsElements[value.context.activeTab.id]}
/>
</TabsContainer>
{children}
</ReactTabs>
)}
</TabsContext.TabConsumer>
</TabsContext.TabProvider>
);
};
Working demo

Categories