How do you pass index into rowRenderer? - reactjs

https://codesandbox.io/s/qYEvQEl0
I try to render a list of draggables, everything seems fine only that I can't figure out how to pass 'index' into rowRenderer
If I do rowRenderer=props => <Row {...props}/>, index is passed in sucessfully.
But if I do:
const SortableRow = SortableElement(Row)
rowRenderer=props => <SortableRow {...props}/> ,
index is blocked somehow, failed to pass into <Row/>
Basically, I don't understand what can go wrong when you wrap your <Row/> component with a HOC? Why some props get to pass in, others not?

Copy the index into a different, custom prop...
rowRenderer = props => {
console.log(props.index);
return <SortableRow {...props} indexCopy={props.index} />;
};
Then, inside the child component, refer to this custom prop instead.
const Row = ({ indexCopy , style }) => {
console.log(indexCopy);
return (
<div style={style}>
<span>drag</span>
<input placeholder={'haha'} />
<span>index={indexCopy || 'undefined'}</span>
</div>
);
};
I'm not too familiar with HOCs, but I suspect that the react-sortable-hoc library is stripping out the implicit index and key values. However, as long as you copy them over into their own custom props, you should be fine.

Related

Registering nested objects with React Hook Form

I have been working with RHF for a while and it helps a lot actually, but I have been trying to do something for what I have not enough knowledge maybe.
So the thing its that I have a nested object that I bring to my componen throw props
const Child = ({ globalObject, register }) => {
const renderNested = Object.entries(globalObject.nestedObject);
return (
<span>
{renderNested.map(([key, value], index) => {
return (
<div key={index}>
<Field
type="text"
label={key}
name{`nestedObject.${key}`}
defaultValue={value}
ref={register}
/>
</div>
);
})}
</span>
);
};
All good, now, one of the keys inside this nestedObject has another object as a value, for which when I map over them and display, the field will show: [object Object]
I know how I would solve that issue if I am using a useState, for example.
Not sure if its a good practice but I would go with something like:
defaultValue={typeof value === 'someKey' ? value[key] : value}
but in this case using register (which I want to use since it saved me from other nightmares) I am not sure how to solve this.
Should I filter the array outside and then render for one side the keys that dont have an object as a value and then the rest?
It looks to me like it has to be something better than that.
Can anyone give advices?
just to clarify, nestedObject looks like:
nestedObject: {
key: value
key: {key:value}
}
You can access the fields using . dot notation, as per documentation for the register method. So register("name.key") works to retrieve the nested object as well as arrays, however note that in React Hook Form v7 the syntax to retrieve nested array items changed from arrayName[0] to arrayName.0.
Your component would look similar to the following:
const Child = ({ globalObject, register }) => {
const nestedKeys = Object.keys(globalObject.nestedObject);
return (
<>
{nestedKeys.map((key) => (
<Field key={key} {...register(`nestedObject.${key}`)} />
))}
</>
);
};
You should not use index as key prop in this case, as you have another stable identifier for each array element which comes from unique nested object keys.

React - Alternatives to triggering rerender of specific components after child mounting finishes

This is more of a conceptual question than anything else.
I'm trying to draw a SVG line between two elements in my application. The way I'm currently doing this is by having a top level ref, inside of which I store a ref for each of the child elements indexed by an arbitrary key. I then draw the arrows, deduced from a 2 dimensional array of pairs of these keys, look up the ref, and use the positioning of those elements to create the SVG's coordinates.
The problem with this method is, besides initial render, every render update afterwards uses outdated ref data as the children get rendered (and therefore positioned) after the parent, inside which the SVG layer is contained.
I've already thought of the obvious answers, useEffects, setStates, etc., but there doesn't seem to be a good solution here that I can think of. A lot of the obvious answers don't necessarily work in this case since they cause render loops. Another solution that I initially thought of was breaking down the arrow rendering to within each of the children, but this had problems of its own that initially caused the transition to having all of the refs live at the parent level.
My current solution is to key the SVG Layer component to a state variable, and then change the value of this variable in a useEffect once the final item renders, but this solution feels messy and improper.
Not exact but here's some code of the issue
Parent Component:
export default ({ items }) => {
const [svgKey, setSvgKey] = useState(0);
const pairs = items.reduce((arr, item) => {
for (const subItem of item.subItems) {
arr.push([subItem, item]);
}
}, [])
const itemRefs = useRef({});
return (
<>
{items.map(item => <Row items={items} setSvgKey={setSvgKey} item={item} refs={refs} key={item.key} />}
<SVGLayer key={svgKey} pairs={pairs} refs={refs} />
</>
);
}
SVG Layer
export default ({ pairs, refs }) => (
<svg>
{pairs.map(([a, b], i) => (
<Arrow key={i} a={refs.current[a.key]} b={refs.current[b.key]} />
)}
</svg>
);
Arrow
export default ({ a, b }) => <path d={[math for line coords here]} />;
Row
export default ({ refs, item, items, setSvgKey }) => {
useEffect(() => {
if (item.key === items[items.length - 1].key) {
setSvgKey(key => ++key);
}
});
return (
<div ref={el => (refs.current[item.key] = el)} />
);
}

Conditionally assign ref in react

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.

React hooks - Dispatching a reset state from parent component

This is my application with the scenario reproduced, here the demo in codesandbox
I have two components, Leagues ( parent ) and Details ( Child ).
I have a implemented reset button example in the Details Component button which does
const cleanArray = () => {
setDataHomeTeam([]);
};
<button onClick={cleanValue} type="button">Reset</button>
You can see in the demo that is emptying out an array of a team stat
My question is, can i implement the same button but out from Details component and from the parent component Leagues for example? Whats the way to achieve it?
I thought to go this way but i can not get it done.
So in my Details.js
let Details = forwardRef(({ ....
const cleanArray = () => {
setDataHomeTeam([]);
};
useImperativeHandle(ref, () => {
return {
cleanValue: cleanValue
}
});
in App.js
<Leagues/>
<button onClick={cleanValue} type="button">Reset</button>
<Details ref={ref} />
I get this error : 'cleanValue' is not defined no-undef
is it something that i can not do with react? How can i achieve it?
I think your approach sounds correct except for lacking the way of calling the api cleanValue you exposed. Basically you have to call it via a ref you pass to it as following:
function App() {
const ref = useRef();
return (
<>
<Leagues />
{/* call it via ref */}
<button onClick={() => ref.current.cleanValue()} type="button">Reset</button>
<Details ref={ref} />
</>
)
}
Codesandbox link: https://codesandbox.io/s/nifty-raman-c0zff?file=/src/components/Details.js
I don't completely understand what you are trying to do, but here is a solution I think is going to work for your problem
let's say you wanna filter that array with the selected team which is liverpool, first if you have control over the incoming data I recommend changing the obj in the array likethis
{day : 16 , teamName:"liverpool"}, this is going to help you filter that array later,
then you useEffect & useState to update that array
[teams, setTeams] = useState([]);
// the array changes here {day: 1 , teamName : "sao paulo"} , {day:2 ,teamname:"liverpool"}]
useEffect(()=>{
setTeams((T)=>{
return T.filter((oneTeam)=>{
return oneTeam.teamName == selectedTeam;
});
})
},[teams,selectedTeam]);

functions vs class components to handle events and attach arguments

I am trying to choose which is the most optimized way to write a React component that has to handle an event and send some data.
Hi I am tinkering and trying to get comfortable writing react components.
This piece of code fetches some array and makes buttons out of it.
https://codepen.io/daifuco/pen/OdxWYZ
App is just the main component, nothing special about it.
My question: As you can see in the working code, Header + GenreButton:
class Header extends Component {
render() {
return (
<div clasName="footer">
{this.props.data.map((genre)=>
<GenreButton clicky={this.props.clickytoTop}genre={genre}/>
)}
</div>
)
}
}
class GenreButton extends Component {
handleClick = () => {
this.props.clicky(this.props.genre)
}
render() {
return (
<div className="genreButton" onClick={this.handleClick}>{this.props.genre}</div>
)
}
}
has the same result as Header2, which is just a functional component, but as far as I know , Header2 creates a callback every time it renders each div.
function Header2(props) {
return (
<div clasName="footer">
{props.data.map((genre)=>
<div className="genreButton" onClick={()=>props.clickytoTop(genre)}>
{genre}
</div>)}</div>
)
}
So, I understand that Header2 is not the optimal way to design it?
are Header + GenreButton more optimized? Is there any way to improve it?
If you ever have the choice, it is usually preferable to use a stateless component, as they do not have to manage a state which would slow down your application.
To choose between one or the other, if your component needs to re-render itself, use a class, if not, use a function.
Your GenreButton does not need to be a class component either in this case :
const Header = ({ data, clickytoTop }) => ( //Deconstructs your props
<div clasName="footer">
{data.map((genre) =>
<GenreButton clicky={clickytoTop} genre={genre} />
)}
</div>
)
To avoid creating a new function everytime in your render, you could use a decorated arrow function, that you will bind this way :
const GenreButton = ({ clicky, genre }) => <div className="genreButton" onClick={clicky(genre)}>{genre}</div>
The function passed in the header props will now have the following declaration, to handle both sets of parameters :
clickFunction = genre => ev => {
}
When calling clicky(genre) it will return another function capable of accepting your click event.
<Header clickytoTop={this.clickFunction}/>
Second version (same result) :
const Header2 = ({ data, clickytoTop }) => ( //Deconstructs your props
<div clasName="footer">
{data.map(genre =>
<div className="genreButton" onClick={clickytoTop(genre)}>{genre}</div>
)}
</div>
)

Resources