It might help to think in terms of controlled vs uncontrolled components. You may be familiar with this from core elements like <input>
s, where you can either pass in a defaultValue
prop, and let the input handle everything ("uncontrolled"), or you can pass in value
and onChange
and handle things yourself ("controlled"). You can design your table component as either a controlled component or uncontrolled component too.
Doing it as an uncontrolled component, you might pass in a prop that sets the initial sorting, but afterwards everything is handled by the table. The parent won't get notified and won't update its state:
const Parent = () => {
const [items, setItems] = useState(/* some array */);
return <MyTable items={items} defaultSort="asc" />
}
const MyTable = ({ items, defaultSort }) => {
const [sort, setSort] = useState(defaultSort ?? 'asc');
const sortedItems = useMemo(() => {
if (sort === 'asc') {
return [...items].sort(/* insert sort function here */)
} else {
return [...items].sort(/* insert sort function here */)
}
}, [items, sort]);
return (
<>
<button onClick={() => setSort(sort === 'asc' ? 'dsc' : 'asc')}>
Change Sort
</button>
{sortedItems.map(() => /* etc */)}
</>
)
}
If instead you do a controlled component, then the parent is in charge of the state, and the child just notifies the parent of relevant changes
const Parent = () => {
const [items, setItems] = useState(/* some array */);
const [sort, setSort] = useState('asc');
const sortedItems = useMemo(() => {
if (sort === 'asc') {
return [...items].sort(/* insert sort function here */)
} else {
return [...items].sort(/* insert sort function here */)
}
}, [items, sort]);
return <MyTable items={sortedItems} onSortToggled={() => setSort(sort === 'asc' ? 'dsc' : 'asc')} />
}
const MyTable = ({ items, onSortToggled}) => {
return (
<>
<button onClick={onSortToggled}>
Change Sort
</button>
{items.map(() => /* etc */)}
</>
)
}
If you add in some extra code to check for undefineds, it's possible to make your table support both controlled and uncontrolled modes, based on which set of props it is passed. But it should just be one or the other; you shouldn't try to have both components be managing state simultaneously, as this just adds opportunities for the states to get out of sync and bugs to be introduced.
the state that stores the items must be changed which is outside of my table component
If this is one of your requirements, then you're basically doing the controlled component version, and thus you must accept a function from the parent component which describes how to do so. The parent component is the only one who knows what state they have and how to update it.