#javascript #reactjs #material-ui #frontend
#javascript #reactjs #материал-пользовательский интерфейс #интерфейс
Вопрос:
У меня есть NavBar
компонент, который имеет список динамически генерируемых ссылок (эти ссылки генерируются после запроса моего бэкэнда для некоторых categories
). Эти ссылки хранятся внутри дочернего компонента панели навигации, который называется DrawerMenu
.
NavBar
Является дочерним элементом основного App.js
компонента.
В моем Category
компоненте у меня есть функция «удалить», которая удаляет категорию. Как только я удаляю категорию, я хочу удалить ссылку на нее в разделе NavBar
. Как бы я это сделал?
Для дальнейшего контекста мои компоненты приведены ниже:
Компонент DrawerMenu
class DrawerMenu extends Component {
state = {
menuItems: [] // Takes a series of objects of the shape { name: "", link: "" }
}
getData = (query) => {
// Query backend for category data and set it to this.state.menuItems
}
componentDidMount() {
this.getData(menuItemsQuery)
}
render() {
const { classes, handleDrawerClose, open } = this.props
const { menuItems } = this.state
const drawer = (classes, handleDrawerClose) => (
<div>
...
{
menuItems.map((menuItem, index) => (
<Link color="inherit" key={index} to={menuItem.link} className={classes.drawerLink} component={RouterLink}>
<ListItem button className={classes.drawerListItem} onClick={handleDrawerClose}>
<ListItemText primary={menuItem.name} />
</ListItem>
</Link>
))
}
...
</div>
)
...
return (
<div>
<Drawer
variant="temporary"
anchor='left'
open={open}
onClose={handleDrawerClose}
classes={{
paper: `${open ? classes.drawerOpen : null} ${!open ? classes.drawerClose : null}`,
}}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
>
{drawer(classes, handleDrawerClose)}
</Drawer>
</div>
)
}
}
Компонент панели навигации
function PrimarySearchAppBar(props) {
return (
<div className={classes.grow}>
...
<DrawerMenu
classes={classes}
handleDrawerClose={handleDrawerClose}
open={open}
/>
...
</div>
)
}
Компонент категории
class Category extends Component {
...
deleteCategory = async () => {
// Code to request backend to delete category
this.props.history.push(`/`)
}
...
}
Комментарии:
1. Вы можете использовать Content API reactjs.org/docs/context.html
2. Вы можете добавить другое состояние для пометки о том, что сделан запрос на сервер, и вы можете извлечь новые данные после удаления
Ответ №1:
Есть два распространенных способа сделать это: вы можете либо использовать инструмент управления состоянием, например Redux
, либо передать свое состояние вниз по дереву компонентов в качестве реквизита.
Redux часто используется, когда несколько компонентов зависят от одного и того же состояния или когда компонент, зависящий от состояния, имеет глубину в несколько слоев, поэтому было бы неудобно передавать его в качестве реквизита.
Я предполагаю, что ваше дерево компонентов не очень большое, поэтому я создам простой пример передачи реквизитов по дереву.
class DrawerMenu extends Component {
// We're gonna manage the state here, so the deletion
// will actually be handled by this component
state = {
menuItems: [] // Takes a series of objects of the shape { name: "", link: "" }
}
handleDelete = (id) => {
let updatedMenuItem = [...this.state.menuItems]; //Create a copy
updatedMenuItem = updatedMenuItem(item => item.id !== id) // Remove the
deleted item
this.setState({
menuItems: updatedMenuItem
})
}
...
// Then wherever you render the category component
<Category handleDelete = {handleDelete}/> //Pass a reference to the delete method
}
Компонент категории
class Category extends Component {
...
deleteCategory = async () => {
// Code to request backend to delete category
this.props.handleDelete(categoryId) //Pass the id of the category
this.props.history.push(`/`)
}
...
}
Я бы посоветовал прочитать об управлении состоянием, это основная концепция в React, и вы будете использовать ее везде. Например, Redux и Context API.
Ответ №2:
Не уверен, почему Деннис Ваш удалил свой ответ, они правильные, но, возможно, недостаточно описательные в решении.
Способ удаления категории заключается не в том, чтобы вызвать саму серверную часть изнутри компонента категории, потому что тогда панель навигации не знает, что вы совершили вызов, а в том, чтобы вызвать обратный вызов, который находится в предке, общем для компонента категории и панели навигации, для удаления категории, а затем повторно запросить категориюсписок категорий с сервера. В приведенном ниже примере этот общий предок MyCategoriesProvider
Поскольку компонент категории, вероятно, будет находиться в другом месте (или в нескольких местах) в дереве, чем панель навигации, лучше всего использовать context .
Честно говоря, это отличное место для redux, но я не собираюсь навязывать вам redux и вместо этого просто продемонстрирую контекстное решение.
// We're going to create a context that will manage your categories
// The only job of this context is to hold the current categories,
// and supply the updating functions. For brevity, I'll just give
// it a handleDelete function.
// Ideally, you'd also store the status of the request in this context
// as well so you could show loaders in the app, etc
import { createContext } from 'react';
// export this, we'll be using it later
export const CategoriesContext = createContext();
// export this, we'll render it high up in the app
// it will only accept children
export const MyCategoriesProvider = ({children}) => {
// here we can add a status flag in case we wanted to show a spinner
// somewhere down in your app
const [isRequestingCategories,setIsRequestingCategories] = useState(false);
// this is your list of categories that you got from the server
// we'll start with an empty array
const [categories,setCategories] = useState([]);
const fetch = async () => {
setIsRequestingCategories(true);
setCategories(await apiCallToFetchCategories());
setIsRequestingCategories(false);
}
const handleDelete = async category => {
await apiCallToDeleteCategory(category);
// we deleted a category, so we should re-request the list from the server
fetch();
}
useEffect(() => {
// when this component mounts, fetch the categories immediately
fetch();
// feel free to ignore any warnings if you're using a linter about rules of hooks here - this is 100% a "componentDidMount" hook and doesn't have any dependencies
},[]);
return <CategoriesContext.Provider value={{categories,isRequestingCategories,handleDelete}}>{children}</CategoriesContext.Provider>
}
// And you use it like this:
const App = () => {
return (
<MyCategoriesProvider>
<SomeOtherComponent>
<SomeOtherComponent> <- let's say your PrimarySearchBar is in here somewhere
<SomeOtherComponent>
</MyCategoriesProvider>
)
}
// in PrimarySearchBar you'd do this:
function PrimarySearchBar(props) => {
const {categories} = useContext(CategoriesContext); // you exported this above, remember?
// pass it as a prop to navbar, you could easily put the useContext hook inside of any component
return <NavBar categories={categories}/>
}
// in your category component you could do this:
class Category extends Component {
render() {
// Don't forget, categoriesContext is the thing you exported way up at the top
<CategoriesContext.Consumer>
{({handleDelete}) => {
return <button onClick={() => handleDelete(this.props.category)}>
}}
</CategoriesContext.Consumer>
}
}
Редактировать:
Я вижу, что вы смешиваете классовые и функциональные компоненты, и это нормально. Вы должны ознакомиться с этой статьей о том, как использовать контекстный API в любом из них — в функциональных компонентах вы обычно используете useContext
перехват, а в компонентах класса вы будете использовать потребитель.
Ответ №3:
Я бы просто обновил список категорий, поступающих с сервера, после выполнения запроса на удаление.
Я бы сделал это следующим образом:
- Я бы сделал компонент ящика не таким умным, заставив его получать список элементов меню.
<DrawerMenu
classes={classes}
handleDrawerClose={handleDrawerClose}
open={open}
items={/* ... */}
/>
Это важный шаг, потому что теперь, чтобы обновить список отображаемых элементов, вы просто передаете другой список. Таким образом, логика на стороне сервера остается отключенной от этого компонента.
- Я не уверен, где вы отображаете
Category
компоненты, но предположим, что он отображается внеPrimarySearchAppBar
, кажется, что эти элементы меню могут потребоваться для передачи компонентам с верхнего уровня. Я вижу 2 решения:- Я бы сделал запрос для элементов меню из того же места, где я делаю запрос для категорий:
const App = props => {
const [categories, setCategories] = React.useState([])
const [menuItems, setMenuItems] = React.useState([])
const fetchCategories = useCallback(()=> {
yourApi.getCategories().then(categories => setCategories(categories))
})
const fetchMenuItems = useCallback(() => {
yourApi.getMenuItems().then(menuItems => setMenuItems(menuItems))
})
useEffect(() => {
fetchCategories()
}, [])
useEffect(() => {
fetchMenuItems()
}, [categories])
const handleDeleteCategory = useCallback(idToDelete => {
yourApi.deleteCategory(idToDelete).then(fetchCategories)
})
return (
<div>
<PrimarySearchAppBar menuItems={menuItems}/>
<Categories categories={categories} onDeleteClick={handleDeleteCategory} />
</div>
)
}
- вы можете сделать то же самое, но сделать это с провайдером и с помощью content API, если вы не хотите, чтобы здесь была вся логика. Хорошо иметь логику smart / fetches / на стороне сервера в компоненте верхнего уровня, а затем передавать реквизиты немым компонентам.
PS. Существует также хороший хук, упрощающий выборку: https://github.com/doasync/use-promise
В настоящее время я использую пользовательскую версию хука usePromise, который я нашел, потому что я добавил некоторые интересные функции. Я могу поделиться этим, если вы хотите, но я не хочу добавлять шума к ответу.