TL/DR: Having trouble referencing items in array by index (using React), could use some guidance.
I am attempting to create a component on my SPA out of data coming from an API. Using React hook useState and useEffect I have created state, done an axios call, and then set the response.data.articles to state (.articles is the array of objects I am using to create the dynamic content).
function App() {
const [storyArray, setStoryArray] = useState();
useEffect(() => {
axios.get('http://newsapi.org/v2/everything?domains=wsj.com&apiKey=[redacted_key_value]')
.then((response) => {
// console.log(response);
setStoryArray(response.data.articles);
})
.catch((err) => {
console.log(err);
})
}, [])
console.log(storyArray)
return (
<div className="App">
<Directory />
<HeaderStory />
</div>
);
}
From here, my state is an array of objects. My goal is to pass THE FIRST object as props to the component <HeaderStory /> but any time I attempt to reference this array item with dot notation I am met with an undefined error. My attempt at circumventing this is problem was to set the item to a variable and then pass the variable as props to the component.
const firstStory = storyArray[0];
This also resulted in an undefined error. Looking for advice / assistance on referencing items in an array to be passed and used in React.
On the first render the storyArray will have no value/undefined, The useEffect hook will execute only after component mount.
So you have to render the component conditionally, if the storyArray has value then only render the HeaderStory.
Example:
function App() {
const [storyArray, setStoryArray] = useState();
useEffect(() => {
axios.get('http://newsapi.org/v2/everything?domains=wsj.com&apiKey=[redacted_key_value]')
.then((response) => {
// console.log(response);
setStoryArray(response.data.articles);
})
.catch((err) => {
console.log(err);
})
}, [])
return (
<div className="App" >
<Directory />
{storyArray && <HeaderStory firstStory={storyArray[0]} />}
</div>
);
}
You should init default value for storyArray.
Example code:
function App() {
const [storyArray, setStoryArray] = useState([]); //Init storyArray value
useEffect(() => {
axios.get('http://newsapi.org/v2/everything?domains=wsj.com&apiKey=[redacted_key_value]')
.then((response) => {
// console.log(response);
setStoryArray(response.data.articles);
})
.catch((err) => {
console.log(err);
})
}, [])
console.log(storyArray)
return (
<div className="App">
<Directory />
<HeaderStory firstStory={storyArray[0] || {}} />
</div>
);
}
I set props firstStory={storyArray[0] || {}} because if storyArray[0] is undefined then pass empty object "{}" for firstStory prop.
Related
Redux state is undefined in first render and I returned the initilizedState = {} in my reducer
store.js
const store = createStore(
rootReducer,
compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension() : (f) => f
)
)
export default store
rootReducer.js
const rootReducer = combineReducers({
search: searchReducer,
product: productReducer,
})
export default rootReducer
reducer.js
const initialState = {}
const productReducer = (state = initialState, action) => {
const { type, payload } = action
switch (type) {
case PRODUCTS_ALL:
console.log('reducer')
return { ...state, items: payload }
default:
return state
}
}
export default productReducer
action.js
const products = axios.create({
baseURL: 'http://localhost:8001/api/products',
})
export const allProducts = () => async (dispatch) => {
console.log('fetching')
await products.get('/').then((res) => {
dispatch({
type: PRODUCTS_ALL,
payload: res.data,
})
})
}
And although I used connect() in my feed container
Feed.js
function Feed({ allProducts, product }) {
const [productItems, setProductItems] = useState()
const [loading, setLoading] = useState(false)
React.useEffect(() => {
allProducts()
console.log(product)
}, [])
return (
<div className='feed__content'>
{loading ? (
<Loader
type='Oval'
color='#212121'
height={100}
width={100}
/>
) : (
<div className='feed__products'>
<div className='feed__productsList'>
{product.map((product) => {
return (
<Product
name={product.name}
image={product.image}
price={product.price}
/>
)
})}
</div>
</div>
)}
</div>
)
}
const mapStateToProps = (state) => ({
product: state.product.items,
})
const mapDispatchToProps = (dispatch) => {
return {
allProducts: () => dispatch(allProducts()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Feed)
if you look at the data on the console you will see undefined but if I add a useEffect dependency it will create a loop , and the first of them is undefined and the rest are the data that I want.
because of this problem when I want to render products with map , it throws an error that said can't map undefiend .
How can I solve this problem
Joel Jaimon code worked well .
and in my code I added
const initialState = {
items: []
}
according to #Andrew and product?.map so my code works well now.
In Feed.js, you aren't getting the entire slice of state. You are trying to access the key item inside.
const mapStateToProps = (state) => ({
product: state.product.items,
})
Change your initialState to include that key and your code should be fine
const initialState = {
items: []
}
A/c to your code you're trying to make an async action in redux. You should use redux-saga or redux-thunk for this.
But you can achieve your output in the following way, without using redux-saga or thunk.
Modify your code in the following way:
Feed.js
function Feed({ allProducts, product }) {
// Set loading to true inititally
const [loading, setLoading] = useState(true);
React.useEffect(() => {
// Make your api call here once its complete and you get a res,
// dispatch the referred action with response as payload.
(async () => {
const products = axios.create({
baseURL: "http://localhost:8001/api/products",
});
const {data} = await products.get("/");
allProducts(data);
})()
// This call is only made when you load it for the first time, if it
// depends
// on something add that dependencies in the dependency array of //
// useEffect.
}, []);
// once store is updated with the passed payload. Set loading to
// false.
React.useEffect(() => {
if(product){
setLoading(false);
}
}, [product])
return (
<div className="feed__content">
{loading ? (
<Loader type="Oval" color="#212121" height={100} width={100} />
) : (
<div className="feed__products">
<div className="feed__productsList">
{product.map((product) => {
return (
<Product
name={product.name}
image={product.image}
price={product.price}
/>
);
})}
</div>
</div>
)}
</div>
);
}
const mapStateToProps = ({product}) => ({
product: product?.items,
});
// here we have a payload to pass
const mapDispatchToProps = (dispatch) => {
return {
allProducts: (payload) => dispatch(allProducts(payload)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Feed);
Action
export const allProducts = (payload) => ({
type: "PRODUCTS_ALL",
payload,
});
Reducer
const productReducer = (state = initialState, action) => {
const { type, payload } = action
switch (type) {
case "PRODUCTS_ALL":
return { ...state, items: payload }
default:
return state
}
}
export default productReducer
Problem statement: I would be giving you three really best methods, in order to solve it, the problem appears when redux is transferring states to the components, so it seems that the render is faster than the response of props to the component.
So, when you have props undefined by their value your application crashes.
note: I will be taking a general example and will show you how to avoid this error.
For example, I have a component in which I want to render the data from the redux response (with mapPropsToState), what I need to do is only to check it for undefined and then if it is undefined we need to use the conditional (ternary) operator for if and else statements.
//suppose you will get an address object from api ,which will contain city ,area ,street ,pin code etc
// address={} intially address is a blank object
render(){
return(
(typeof address.area!='undefined')?
<div>
<div>{address.city}</div>
<div>{address.street}</div>
<div>{address.pin_code}</div>
<div>{address.area}</div>
</div>
:<div>loading....</div>
)
}
And another way is to simply use the package of loadash in loadash you can achieve the same by calling the function "isUndefined"
you can also use it in the inputFields likewise
<FloatingLabel label="Username">
<Form.Control
type="input"
placeholder=" "
name="username"
value={props.username}
defaultValue={_.isUndefined(props.userEditResponse)?'':props.userEditResponse.username}
required
onChange={(e)=>onInputchange(e)}
/>
</FloatingLabel>
//note: you can also use (typeof props.userEditResponse!='undefined')?'do somthing':'leave it'
loadash installation:
npm i lodash
then in your component import the the below line and then you can use it, the way i used in the above example.
import _ from 'lodash'
note: the answer was just to give you an idea and this was really a panic problem that is why I shared it with you guys, it might help many of you, you can use a similar way in different situations. thanks!
furtherMore your can work with ?.:
The ?. operator is like the . chaining operator, except that instead of causing an error if a reference is nullish (null or undefined), the expression short-circuits with a return value of undefined. When used with function calls, it returns undefined if the given function does not exist.
This results in shorter and simpler expressions when accessing chained properties when the possibility exists that a reference may be missing. It can also be helpful while exploring the content of an object when there's no known guarantee as to which properties are required.
{ props.rolesListSuccessResponse?.permissions.map((list,index) => (
<div className="mb-3 col-md-3" key={index}>
<Form.Check
type={'checkbox'}
id={list.name}
label={list.name.charAt(0).toUpperCase()+list.name.slice(1)}
/>
</div>
))}
I wrote a program that takes and displays contacts from an array, and we have an input for searching between contacts, which we type and display the result.
I used if in the search function to check if the searchKeyword changes, remember to do the filter else, it did not change, return contacts and no filter is done
I want to do this control with useEffect and I commented on the part I wrote with useEffect. Please help me to reach the solution of using useEffect. Thank you.
In fact, I want to use useEffect instead of if
I put my code in the link below
https://codesandbox.io/s/simple-child-parent-comp-forked-4qf39?file=/src/App.js:905-913
Issue
In the useEffect hook in your sandbox you aren't actually updating any state.
useEffect(()=>{
const handleFilterContact = () => {
return contacts.filter((contact) =>
contact.fullName.toLowerCase().includes(searchKeyword.toLowerCase())
);
};
return () => contacts;
},[searchKeyword]);
You are returning a value from the useEffect hook which is interpreted by React to be a hook cleanup function.
See Cleaning up an effect
Solution
Add state to MainContent to hold filtered contacts array. Pass the filtered state to the Contact component. You can use the same handleFilterContact function to compute the filtered state.
const MainContent = ({ contacts }) => {
const [searchKeyword, setSearchKeyword] = useState("");
const [filtered, setFiltered] = useState(contacts.slice());
const setValueSearch = (e) => setSearchKeyword(e.target.value);
useEffect(() => {
const handleFilterContact = () => {
if (searchKeyword.length >= 1) {
return contacts.filter((contact) =>
contact.fullName.toLowerCase().includes(searchKeyword.toLowerCase())
);
} else {
return contacts;
}
};
setFiltered(handleFilterContact());
}, [contacts, searchKeyword]);
return (
<div>
<input
placeholder="Enter a keyword to search"
onChange={setValueSearch}
/>
<Contact contacts={contacts} filter={filtered} />
</div>
);
};
Suggestion
I would recommend against storing a filtered contacts array in state since it is easily derived from the passed contacts prop and the local searchKeyword state. You can filter inline.
const MainContent = ({ contacts }) => {
const [searchKeyword, setSearchKeyword] = useState("");
const setValueSearch = (e) => setSearchKeyword(e.target.value);
const filterContact = (contact) => {
if (searchKeyword.length >= 1) {
return contact.fullName
.toLowerCase()
.includes(searchKeyword.toLowerCase());
}
return true;
};
return (
<div>
<input
placeholder="Enter a keyword to search"
onChange={setValueSearch}
/>
<Contact contacts={contacts.filter(filterContact)} />
</div>
);
};
I got two react components. The first component is managing a list of the second component after making a call to my api and I tried to put a delete button to remove one component from the list.
I got a weird behavior : when I click on the remove document button the document is set to an empty array. And I got a log of an empty array for the documents.
Parent component :
export const DocumentEnvelope: React.FC<DocumentEnvelopeProps> = (props) => {
const [documents, setDocuments] = useState([]);
useEffect(() => {
setDocuments([]);
axios.get("/myurl").then(response => {
response.data.forEach(document => {
setDocuments((doc) => [...doc, <Document name={document.name} removeDocument={removeDocument}/>]);});
});
}, []);
const removeDocument = (name) => {
console.log(documents);
setDocuments(documents.filter(item => item.name !== name));
};
return (
<>
{documents}
</>
);
};
Child component :
interface DocumentProps {
removeDocument,
name: string
}
export const Document: React.FC<DocumentProps> = (props) => {
return (
<div>
My document
<button onClick={() => props.removeDocument(props.name)}>
Remove document
</button>
</div>
);
};
<Document name=document.name removeDocument={removeDocument}/>
This part is missing curly braces around document.name, consider using a different variable name as document is used to refer to the html document in javascript. I can also recommend storing the data in the state but not the components themselves.
You're filtering on the documents array as if it consisted of document objects. But you have set the documents array to list of Document React Elements.
Let your documents array consist pure JS objects i.e. a document as you're getting it from response instead of React elements. Use map in JSX to loop over documents and return <Document.../> elements.
I mean like the following :-
export const DocumentEnvelope: React.FC<DocumentEnvelopeProps> = (props) => {
const [documents, setDocuments] = useState([]);
useEffect(() => {
axios.get("/myurl").then(response => {
setDocuments(response.data);
}, []);
const removeDocument = (name) => {
setDocuments(documents.filter(document => document.name !== name));
};
return (
<>
{documents.map((document)=>
<Document name={document.name} removeDocument={removeDocument}/>
)}
</>
);
};
In my application, I use hooks to set the values of a variable. I have a function that calls the API and sets the values of the "rooms" variable.
I used useEffect in order to do like componentDidMount lifecycle. However, even with the condition I put in my code, getRooms() is called up to 4 times in my application. When I render the values of rooms, I get 4 times the same room when it should be there only one time.
Here is my code:
export default function Rooms() {
const [rooms, setRooms] = useState({})
function getRooms() {
client.get('/rooms').then((s) => {setRooms(s.data.rooms)}, (e) => {console.log(e)});
}
useEffect(() => {
if(Object.values(rooms).length === 0) {
getRooms();
}
})
return (
<React.Fragment>
<InfoPanel
title="rooms"
description="skip this part if you don't have any room in your house"
instruction="select and name every room you have" />
<ActionPanel>
{Object.values(rooms).map((r) => {
return <p>{r.title}</p>
})}
</ActionPanel>
</React.Fragment>
)
}
How can I make sure that my code is called only ONCE when the component mounts?
N.B: "client" is axios.
I optimized your solution
export default function Rooms() {
const [rooms, setRooms] = useState({})
useEffect(() => {
function getRooms() {
client.get('/rooms').then((s) => {return s.data.rooms}, (e) => {console.log(e)});
}
if(Object.values(rooms).length === 0) {
const apiResult = getRooms();
setState(apiResult);
}
}, [])
return (
<React.Fragment>
<InfoPanel
title="rooms"
description="skip this part if you don't have any room in your house"
instruction="select and name every room you have" />
<ActionPanel>
{Object.values(rooms).map((r) => {
return <p>{r.title}</p>
})}
</ActionPanel>
</React.Fragment>
)
}
Add an empty array as a second argument for useEffect().
useEffect(() => {
doStuff();
}, []);
The second argument tells which changes should trigger the function. Empty array means that run it once, but don't react to furher changes.
See documentation right here.
On a custom react-admin page, I use the Query component to fetch and show data and the withDataProvider decorator to save a mutation:
export const Controller = ({data, dataProvider}: any) => {
...
dataProvider(UPDATE, "users", { id: data.id, data: {...data, newField: "foo" })
...
}
export const Container = ({ data, loading, loaderror, ...props }: any) => {
const ConnectedController = useMemo(() => withDataProvider(Controller), []);
if (loading) { return <p data-testid="loading">Loading</p>; }
if (loaderror) { return <p data-testid="error">Error</p>; }
return <ConnectedController {...props} data={data} />;
}
export const InjectInitialValues = ({ userid, ...props }: InjectProps) => {
return <Query type="GET_ONE" resource="users" payload={{ id: userid }}>
{({ data, loading, error }: { data: object, loading: boolean, error: string }) => (
<Container {...props} data={data} loading={loading} loaderror={error} />
)}
</Query>
};
However, after saving the changes, the query component does not trigger a rerender with the new values. Does anyone know how to plug it together such that query rerenders after a change?
Thank you very much.
Could you add your code? to run re-render you should update state or receive new data via props, also you can run force update, but i am not sure if it will help you. First we should see code
It seems to me that you have a problem in this line, so you set an empty list of dependencies [].
const ConnectedController = useMemo(() => withDataProvider(Controller), []);
When you specify a dependency list through the last argument of the useMemo hook, it should include all the values used,
which are involved in the React data stream, including the pros, state, and their derivatives.