how to change state of 1 out of 3 components in react - javascript

I have the following layout.
<ParentComponent>
|ComponentA
|ComponentA
|ComponentA
Basically I call the same Component 3 times in the parent component because they are very similar but just have different values. I was told I need to put a button on the parent component to change the values of just one out of the 3 ComponentA. ComponentA is called 3 times to give me the same thing 3 times, if I make a button to set the state of one, it will go to all 3. How can I single out one component?
Thanks!

Make an array of props you want to send to your components, and then change the content of that array on some button/input events, as your application demands it.
For example:
const arrOfProps = [{someProp:'aaa'}, null, null]
const someFunc = (num)=>{
arrOfProps.forEach((x, index)=>{
if (index === num) {x = {someProp:'aaa'}}
else { x = null}
})
}
render(){
return(
<div>
<button onClick={()=>someFunc(0)}>One</button>
<button onClick={()=>someFunc(1)}>Two</button>
<button onClick={()=>someFunc(2)}>Three</button>
<ParentComponent>
<div>{arrOfComponents.map((comp, index)=>{
return <ComponentA key={index} prop={comp}/>
})}</div>
</ParentComponent>
</div>)
}

Related

Component's forwardRef children all point to the same ref in an array

I have a function component that takes a variable amount of child (forwardRef) function components. What I would like to achieve is having a ref to each of the child components for animations when a child component is clicked. I have a semi-working solution by creating an array of refs and then cloning all the children and passing an indexed ref to each of them. The only issue is that all of the refs in the index point to the same (last) child.
Here are my components:
const Navbar = ({children}) => {
const [activeLink, setActiveLink] = useState(0);
const linkRefs = Array(children.length).fill(React.createRef(null));
const handleActiveLinkChange = (index) => {
setActiveLink(index);
console.log(linkRefs[index].current);
}
return (
<nav>
{React.Children.map(children, (child, index) => React.cloneElement(child, {index: index, active: index === activeLink, handleActiveLinkChange, key: "child" + index, ref: linkRefs[index]}))}
</nav>
)
}
const Link = React.forwardRef(({children, active, index, handleActiveLinkChange}, ref) => {
return (
<a href="#" style={linkStyle} onClick={() => handleActiveLinkChange(index)} ref={ref}>
{active ? <b>{children}</b> : children}
</a>
)
});
And assuming I use the components in the following way:
<Navbar>
<Link>One</Link>
<Link>Two</Link>
<Link>Three</Link>
<Link>Four</Link>
<Link>Five</Link>
</Navbar>
I expect the refs to be:
Ref array index
Ref current
0
One
1
Two
2
Three
3
Four
4
Five
But the refs I get are:
Ref array index
Ref current
0
Five
1
Five
2
Five
3
Five
4
Five
I'm assuming it's something to do with variable scope but I just can't figure out the cause of the issue. I've tried many variations of loops and functions but I'd rather understand the cause than blindly try to find a solution.
The issue is with the following line. It creates only one ref and all the array indices refer to that single ref.
const linkRefs = Array(children.length).fill(React.createRef(null));
Instead of the above use the following line which creates new refs for each child as you expect.
const linkRefs = Array.from({ length: children.length }, () =>
React.createRef(null)
);

index with lodash times method

I need to replicate a component "n" times. To do that I used the lodash method times. The problem is that I need an index as a key for the components generated and It doesn't look like it has one.
I have the following code:
export const MyComponent: React.FC<{times: number}> = ({ times }) => {
return (
<>
{_.times(times, () => (
//I need a key={index} in this div
<div className="bg-white border-4 border-white md:rounded-md md:p-2 content-center my-4 shadow w-full">
</div>
))}
</>
);
};
This will return the component that is inside n times.
I tried to do a method that returns the component and set an index with useState, but it goes in an infinite loop. I thought to put a big random number as a key so it is extremely difficult to get the same, but I don't like that solution. I'd like to use this method because it is clean.
So what do you think I could do to give a to the component?
It's passed to you as a function parameter:
_.times(times, (index) => (blabla))

React dynamic components from JSON object

I'm searching for a shorter version to iterate through components from a JSON object
["Component1", "Component2", "Component3"]
The Index should be the step number and the component should be outputted dynamically. Right now I have a static way, which will become very uncomfortable with more elements:
<div">
{step === 1 && <Component1 />}
{step === 2 && <Component2 />}
{step === 3 && <Component3 />}
</div>
Does anyone knows a solution to this one?
Best regards!
You can use an array or an object to map key to its value (the indexes are keys here):
const components = [<Component1/>,<Component2/>,<Component3/>]
<div>{components[step]}</div>
The above components invoked in the array (meaning, although only a single component used, all elements called React.createElement) , to resemble the conditional rendering save a function component instead:
const functionComponents = [() => <Component1/>, () => <Component2/>,() => <Component3/>]
const Component = functionComponents[step];
<div><Component/></div>

Unable to render an array of values (React component)

I am building a test app to learn more about React and I have made an API call which gets a huge JSON object.
I was able to break this json into the parts that I need and now I have 10 arrays of 3 props each. I am able to send these 10 arrays in 3 props to another component, which needs to use these 3 props 10 times and render a div class Card each.
I can console.log(this.props) and it shows 10 different arrays with 3 props each,however, I cannot produce a same element 10 times.. I tried using map() but since my array is initially undefined, map() is not able to function properly either. Is there any thing in react like *ngFor in Angular ?
What is the best way to go about this?
*EDIT
Here's more code guys. Sorry still noobie here..
ERROR : this.props.map is not a function
return(
<div>
{this.props.map((data,i)=>{
return(
<li key={i}>{data.likes}</li>
);
*EDIT 2
Soo I tried running map function with an if condition but the code still breaks the very moment the condition gets true..
render() {
if(this.props.url !== undefined){
this.props.map((data,i) =>{
return <li key={i}>{data.likes}</li>
})
}
My state method is :
state = {
userId: undefined,
likes: undefined,
url: undefined
}
and im setting my values on each data stream as follows :
const pics = await fetch(`${base_url}?key=${api_key}&q=${query}
&img_type=photo&per_page=12`).then(response => {
return response.json();
})
pics.hits.map((data) =>{
return this.setState({
userId: data.user_id,
likes: data.likes,
url: data.webformatURL
})
})
this.props won't have map, it's not an array. It's an object with a property for each property passed to your component. For instance:
<YourComponent foo="bar"/>
...will have this.props.foo with the value "bar".
So if you're passing an array to your component, like this:
<YourComponent theArrayProperty={[{likes: 42},{likes:27}]} />
...then you need the name of that property:
return (
<div>
{this.props.theArrayProperty.map((data,i) => {
return (
<li key={i}>{data.likes}</li>
);
})}
</div>
);
Side note: You can use a concise arrow function for the map callback instead:
return (
<div>
{this.props.theArrayProperty.map((data,i) => <li key={i}>{data.likes}</li>)}
</div>
);
...and no need for the () if you put the opening tag on the line with return (you can't leave off the ( if it's on the next line, but you probably knew that):
return <div>
{this.props.theArrayProperty.map((data,i) => <li key={i}>{data.likes}</li>)}
</div>;
...but that's a matter of style.
With little information that you have provided, my guess is that code fails at map() when you try to use it with undefined value.
Try adding a conditional check to render
{props && props.map([RENDER CODE HERE])}
You can just make simple if statement to check if the array is not undefined, and then pass it to map function.
Another option is to set a defaultProps for an empty array.
MyComponent.defaultProps = {
arrProp: []
};

What are the benefits of immutability?

I'm using React to render long scrollable list of items (+1000). I found React Virtualized to help me with this.
So looking at the example here I should pass down the list as a prop to my item list component. What's tripping me up is that in the example the list is immutable (using Immutable.js) which I guess makes sense since that's how the props are supposed to work - but if I want to make a change to a row item I cannot change its state since the row will be rerendered using the list, thus throwing out the state.
What I'm trying to do is to highlight a row when I click it and have it still be highlighted if I scroll out of the view and back into it again. Now if the list is not immutable I can change the object representing the row and the highlighted row will stay highlighted, but I'm not sure that's the correct way to do it. Is there a solution to this other than mutating the props?
class ItemsList extends React.Component {
(...)
render() {
(...)
return(
<div>
<VirtualScroll
ref='VirtualScroll'
className={styles.VirtualScroll}
height={virtualScrollHeight}
overscanRowCount={overscanRowCount}
noRowsRenderer={this._noRowsRenderer}
rowCount={rowCount}
rowHeight={useDynamicRowHeight ? this._getRowHeight : virtualScrollRowHeight}
rowRenderer={this._rowRenderer}
scrollToIndex={scrollToIndex}
width={300}
/>
</div>
)
}
_rowRenderer ({ index }) {
const { list } = this.props;
const row = list[index];
return (
<Row index={index} />
)
}
}
class Row extends React.Component {
constructor(props) {
super(props);
this.state = {
highlighted: false
};
}
handleClick() {
this.setState({ highlighted: true });
list[this.props.index].color = 'yellow';
}
render() {
let color = list[this.props.index].color;
return (
<div
key={this.props.index}
style={{ height: 20, backgroundColor: color }}
onClick={this.handleClick.bind(this)}
>
This is row {this.props.index}
</div>
)
}
}
const list = [array of 1000+ objects];
ReactDOM.render(
<ItemsList
list={list}
/>,
document.getElementById('app')
);
If you only render let's say 10 out of your list of a 1000 at a time, then the only way to remember highlighted-flag, is to store it in the parent state, which is the list of 1000.
Without immutability, this would be something like:
// make a copy of the list - NB: this will not copy objects in the list
var list = this.state.list.slice();
// so when changing object, you are directly mutating state
list[itemToChange].highlighted = true;
// setting state will trigger re-render
this.setState({ list: list });
// but the damage is already done:
// e.g. shouldComponentUpdate lifecycle method will fail
// will always return false, even if state did change.
With immutability, you would be doing something quite similar:
// make a copy of the list
var list = this.state.list.slice();
// make a copy of the object to update
var newObject = Object.assign({}, list[itemToChange]);
// update the object copy
newObject.highlighted = true;
// insert the new object into list copy
list[itemToChange] = newObject;
// update state with the new list
this.setState({ list : list );
The above only works if the object does not contain more nested objects.
I am not familiar with immutable.js, but I'm sure they have excellent methods to deal with this more appropriately.
The argument for immutability in react is that you can reliably and transparently work with state changes (also react's life-cycle methods expect them). There are numerous questions on SO with a variant of "why is nextState == this.state", with answers coming down to "not keeping state and props immutable screwed things up"

Categories