I have app writen by react:
the main App is:
function App({articles}) {
...
const SortVote=()=>{
console.log(childRef.current);
console.log(Object.getOwnPropertyNames(childRef.current))
childRef.current.SortVote();
}
const [art, setArt]= useState(articles);
const childRef = useRef();
return (
<div className="App">
<h8k-navbar header={title}></h8k-navbar>
<div className="layout-row align-items-center justify-content-center my-20 navigation">
<label className="form-hint mb-0 text-uppercase font-weight-light">Sort By</label>
<button data-testid="most-upvoted-link" className="small" onClick={SortVote}>Most Upvoted</button>
</div>
<Articles articles={art} cRef={childRef}/>
</div>
);
}
the children componenet is:
function Articles({ articles, cRef }) {
const [arts, setArts] = useState(articles);
const sortVote = () => {
const result = articles.sort((a, b) => {
if (a.upvotes > b.upvotes) {
return -1;
}
if (a.upvotes < b.upvotes) {
return 1;
}
return 0;
});
setArts(result);
};
const sortDate = () => {
const result = articles.sort((a, b) => {
if (a.date > b.date) {
return -1;
}
if (a.date < b.date) {
return 1;
}
return 0;
});
setArts(result);
};
useImperativeHandle(cRef, () => ({
sortVote,
sortDate
}));
if (articles)
return (
<div className="card w-50 mx-auto">
<table>
<thead>
<tr>
<th>Title</th>
<th>Upvotes</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{arts.map((item, index) => (
<tr data-testid="article" key={index}>
<td data-testid="article-title">{item.title}</td>
<td data-testid="article-upvotes">{item.upvotes}</td>
<td data-testid="article-date">{item.date}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
I use useImperativeHandle in children Component and Try to use 'childRef.current.SortVote()' in parent Component to invoken children Component function. But the browser has error:
childRef.current.SortVote is not a function
The full code is on: https://stackblitz.com/edit/react-vy7df9
At useImpertiveHandle hook, the SortVote and sortDate is already defined function, but why here still error SortVote is not function?
The useImperativeHandle
is used with passed React refs, not props holding ref values.
useImperativeHandle
customizes the instance value that is exposed to parent components when usingref
. As always, imperative code using refs should be avoided in most cases.useImperativeHandle
should be used withforwardRef
:function FancyInput(props, ref) { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input ref={inputRef} ... />; } FancyInput = forwardRef(FancyInput);
In your code you are not passing a ref to the child component, you are passing a prop that has a ref value.
<Articles cRef={childRef} articles={art} />
vs
<Articles ref={childRef} articles={art} />
First, update the function signature of Articles
component to consume a React ref. Note that this is a second argument passed to the function, props
object being the first.
function Articles({ articles }, ref) {
...
useImperativeHandle(ref, () => ({
sortVote,
sortDate
}));
if (articles) {
return (
...
);
}
return null;
}
Next, wrap the Articles
component in React.forwardRef.
Articles = forwardRef(Articles);
Finally, pass a ref to the Articles
component. Note, it is sortVote
, not SortVote
.
function App({articles}) {
const [art, setArt]= useState(articles);
const childRef = useRef();
...
const SortVote=()=>{
console.log(childRef.current);
console.log(Object.getOwnPropertyNames(childRef.current))
childRef.current.sortVote();
}
return (
<div className="App">
...
<Articles
ref={childRef}
articles={art}
/>
</div>
);
}
You've also a bug in the child Articles
component. Array.prototype.sort
does an in-place sorting, which means it mutates the arts
state. You first need to copy the array before sorting it. Array.prototype.slice
is a simple functional programming way to copy the array inline before sorting it.
function Articles({ articles }, ref) {
const [arts, setArts] = useState(articles);
const sortVote = () => {
const result = articles.slice().sort((a, b) => {
if (a.upvotes > b.upvotes) {
return -1;
}
if (a.upvotes < b.upvotes) {
return 1;
}
return 0;
});
setArts(result);
};
const sortDate = () => {
const result = articles.slice().sort((a, b) => {
if (a.date > b.date) {
return -1;
}
if (a.date < b.date) {
return 1;
}
return 0;
});
setArts(result);
};
...