I have o idea why I am getting TypeError: sheltersList.map is not a function when it should map the array instead? What is wrong with my code? I dont understand it..
import React from 'react';
const SheltersList = (sheltersList) => {
console.log(sheltersList);
if (sheltersList.lenght != 0) {
return (
<div>
{ sheltersList.map(function(shelter) {
<option id={shelter.id}>{shelter.name}</option>
})}
</div>
)
} else { return (
<option> loading... </option>
)}
}
export default SheltersList;
map is a function of the Array.prototype. It seems like the parameter sheltersList is not an Array.
Sine this is a React functional component, you might find your sheltersList Array from sheltersList.sheltersList in this case.
When you passing props to components by their name you should use destructuring syntax.
const SheltersList = ({ sheltersList }) => { // sheltersList is your list
Now your list is inside prop called "sheltersList". When you passed it like before your list is rather inside object named "sheltersList" which in this case should be rather named "props"
const SheltersList = ( sheltersList ) => { // sheltersList contains all props and your list is probably inside sheltersList.sheltersList
When you use destructuring you can also set default value to be sure if everything works fine when you forgot to pass sheltersList.
const SheltersList = ({ sheltersList = []}) => {
Related
I'm new to React and I'm stuck trying to get this onClick function to work properly.
I have a component "Row" that contains a dynamic list of divs that it gets from a function and returns them:
export function Row({parentState, setParentState}) {
let divList = getDivList(parentState, setParentState);
return (
<div>
{divList}
</div>
)
}
Say parentState could just be:
[["Name", "info"],
["Name2", "info2"]]
The function returns a list of divs, each with their own className determined based on data in the parentState. Each one needs to be able to update its own info in parentState with an onClick function, which must in turn update the className so that the appearance of the div can change. My code so far seems to update the parentState properly (React Devtools shows the changes, at least when I navigate away from the component and then navigate back, for some reason), but won't update the className until a later event. Right now it looks like this:
export function getDivList(parentState, setParentState) {
//parentState is an array of two-element arrays
const divList = parentState.map((ele, i) => {
let divClass = "class" + ele[1];
return (
<div
key={ele, i}
className={divClass}
onClick={() => {
let newParentState =
JSON.parse(JSON.stringify(parentState);
newParentState[i][1] = "newInfo";
setParentState(newParentState);}}>
{ele[0]}
</div>
)
}
return divList;
}
I have tried to use useEffect, probably wrong, but no luck. How should I do this?
Since your Row component has parentState as a prop, I assume it is a direct child of this parent component that contains parentState. You are trying to access getDivList in Row component without passing it as a prop, it won't work if you write your code this way.
You could use the children prop provided by React that allow you to write a component with an opening and closing tag: <Component>...</Component>. Everything inside will be in the children. For your code it would looks like this :
import React from 'react';
import { render } from 'react-dom';
import './style.css';
const App = () => {
const [parentState, setParentState] = React.useState([
['I am a div', 'bg-red'],
['I am another div', 'bg-red'],
]);
React.useEffect(
() => console.log('render on ParentState changes'),
[parentState]
);
const getDivList = () => {
return parentState.map((ele, i) => {
return (
<div
key={(ele, i)}
className={ele[1]}
onClick={() => {
// Copy of your state with the spread operator (...)
let newParentState = [...parentState];
// We don't know the new value here, I just invented it for the example
newParentState[i][1] = [newParentState[i][1], 'bg-blue'];
setParentState(newParentState);
}}
>
{ele[0]}
</div>
);
});
};
return <Row>{getDivList()}</Row>;
};
const Row = ({ children }) => {
return <>{children}</>;
};
render(<App />, document.getElementById('root'));
And a bit of css for the example :
.bg-red {
background-color: darkred;
color: white;
}
.bg-blue {
background-color:aliceblue;
}
Here is a repro on StackBlitz so you can play with it.
I assumed the shape of the parentState, yu will have to adapt by your needs but it should be something like that.
Now, if your data needs to be shared across multiple components, I highly recommand using a context. Here is my answer to another post where you'll find a simple example on how to implement a context Api.
I'm working on something in react and have encountered a challenge I'm not being able to solve myself. I've searched here and others places and I found topics with similar titles but didn't have anything to do with the problem I'm having, so here we go:
So I have an array which will be mapped into React, components, normally like so:
export default ParentComponent = () => {
//bunch of stuff here and there is an array called arr
return (<>
{arr.map((item, id) => {<ChildComponent props={item} key={id}>})}
</>)
}
but the thing is, there's a state in the parent element which stores the id of one of the ChildComponents that is currently selected (I'm doing this by setting up a context and setting this state inside the ChildComponent), and then the problem is that I have to reference a node inside of the ChildComponent which is currently selected. I can forward a ref no problem, but I also want to assign the ref only on the currently selected ChildComponent, I would like to do this:
export default ParentComponent = () => {
//bunch of stuff here and there is an array called arr and there's a state which holds the id of a selected ChildComponent called selectedObjectId
const selectedRef = createRef();
return (<>
<someContextProvider>
{arr.map((item, id) => {
<ChildComponent
props={item}
key={id}
ref={selectedObjectId == id ? selectedRef : null}
>
})}
<someContextProvider />
</>)
}
But I have tried and we can't do that. So how can dynamically assign the ref to only one particular element of an array if a certain condition is true?
You can use the props spread operator {...props} to pass a conditional ref by building the props object first. E.g.
export default ParentComponent = () => {
const selectedRef = useRef(null);
return (
<SomeContextProvider>
{arr.map((item, id) => {
const itemProps = selectedObjectId == id ? { ref: selectedRef } : {};
return (
<ChildComponent
props={item}
key={id}
{...itemProps}
/>
);
})}
<SomeContextProvider />
)
}
You cannot dynamically assign ref, but you can store all of them, and access by id
export default ParentComponent = () => {
//bunch of stuff here and there is an array called arr and theres a state wich holds the id of a selected ChildComponent called selectedObjectId
let refs = {}
// example of accessing current selected ref
const handleClick = () => {
if (refs[selectedObjectId])
refs[selectedObjectId].current.click() // call some method
}
return (<>
<someContextProvider>
{arr.map((item, id) => {
<ChildComponent
props={item}
key={id}
ref={refs[id]}
>
})}
<someContextProvider />
</>)
}
Solution
Like Drew commented in Medets answer, the only solution is to create an array of refs and access the desired one by simply matching the index of the ChildElement with the index of the ref array, as we can see here. There's no way we found to actually move a ref between objects, but performance cost for doing this should not be relevant.
I have a component that grabs an array out of a prop from the parent and then sets it to a state. I then modify this array with the intent on sending a modified version of the prop back up to the parent.
I'm confused because as I modify the state in the app, I console log out the prop object and it's being modified simultaneously despite never being touched by the function.
Here's a simplified version of the code:
import React, { useEffect, useState } from 'react';
const ExampleComponent = ({ propObj }) => {
const [stateArr, setStateArr] = useState([{}]);
useEffect(() => {
setStateArr(propObj.arr);
}, [propObj]);
const handleStateArrChange = (e) => {
const updatedStateArr = [...stateArr];
updatedStateArr[e.target.dataset.index].keyValue = parseInt(e.target.value);
setStateArr(updatedStateArr);
}
console.log(stateArr, propObj.arr);
return (
<ul>
{stateArr.map((stateArrItem, index) => {
return (
<li key={`${stateArrItem._id}~${index}`}>
<label htmlFor={`${stateArrItem.name}~name`}>{stateArrItem.name}</label>
<input
name={`${stateArrItem.name}~name`}
id={`${stateArrItem._id}~input`}
type="number"
value={stateArrItem.keyValue}
data-index={index}
onChange={handleStateArrChange} />
</li>
)
})}
</ul>
);
};
export default ExampleComponent;
As far as I understand, propObj should never change based on this code. Somehow though, it's mirroring the component's stateArr updates. Feel like I've gone crazy.
propObj|stateArr in state is updated correctly and returns new array references, but you have neglected to also copy the elements you are updating. updatedStateArr[e.target.dataset.index].keyValue = parseInt(e.target.value); is a state mutation. Remember, each element is also a reference back to the original elements.
Use a functional state update and map the current state to the next state. When the index matches, also copy the element into a new object and update the property desired.
const handleStateArrChange = (e) => {
const { dataset: { index }, value } = e.target;
setStateArr(stateArr => stateArr.map((el, i) => index === i ? {
...el,
keyValue: value,
} : el));
}
I created a react functional component to wrap sections of code based on a predicate (instead of using ternary within JSX).
So, the component looks like this:
const PredicateWrapper = ({ children, iff, otherwise }) => (
iff ? children : (otherwise || null)
);
And can be used like this:
<PredicateWrapper iff={students}>
{students.map(student => <Student />)}
</PredicateWrapper>
The issue is that this throws an error "cannot read property map of null" if students is null. If I replace the students.map with some text, the text does not render, proving the wrapper is working, however, the point of the wrapper is to deal with cases where students is null, and I would expect it not to "enter" inside the wrapper.
How can I achieve something like this that allows for the inner code to not evaluate and throw runtime errors if iff is falsy?
Really interesting question!
I could be woefully wrong here but I think it's because students.map(student => <Student /> is still part of your parent component's render tree. So when React tries to build the parent component's tree, it's trying to execute that line which throws an error.
PredicateWrapper is mounted and resolved when the child components are recursed upon, which is when the iff would kick in - but that's on the second iteration.
For eg, if I do this, I don't get the error
const Child = ({ students }) => {
return students.map(student => <Student />);
};
<PredicateWrapper iff={students}>
<Child students={students} />
</PredicateWrapper>
Let's have a look at how the JSX will be compiled to JavaScript with the online Babel compiler:
const PredicateWrapper = ({ children, iff, otherwise }) =>
iff ? children : otherwise || null;
const students = null;
const App = () =>
React.createElement(
PredicateWrapper,
{
iff: students,
},
students.map((student) => React.createElement(Student, null))
);
Now you can see students.map will be executed and cause the error "Cannot read property map of null"
One possible implementation to achieve your goal is to use render props:
The term “render prop” refers to a technique for sharing code between
React components using a prop whose value is a function.
const PredicateWrapper = ({ render, iff, otherwise = () => null }) =>
iff ? render() : otherwise();
const StudentA = () => <span>😎</span>;
const StudentB = () => <span>😂</span>;
const students = [StudentA, StudentB];
//const students = null;
function App() {
return (
<PredicateWrapper
iff={students}
render={() => students.map(Student => <Student />)}
/>
);
}
Currently learning React and need to get a prop of a clicked element. I managed to get it, but it does not feel like the "right" react way.
This is what I have (it works):
filterProjects = (event) => {
const value = event.currentTarget.getAttribute("filterTarget")
console.log(value)
}
Initially I tried multiple things, e.g.:
const value = this.props.filterTarget or const value = event.currentTarget.this.props.filterTarget or even using ref but all returned undefined when console logging the value.
(Using this.props as it's part of a class Component.)
This is what my target element looks like:
const categories = data.allPrismicProjectCategory.edges.map((cat, index) => {
return (
<a
key={index}
onClick={this.filterProjects}
filterTarget={cat.node.uid}
>
{cat.node.data.category.text}
</a>
)
})
One simple way is passing the value itself,
onClick={() => this.filterProjects(cat.node.uid)}
And the function,
filterProjects = (value) => {
console.log(value)
}