Eu gostaria que o usuário pudesse classificar uma lista de itens de tarefas. Quando os usuários selecionam um item em uma lista suspensa, ele define o sortKey
que criará uma nova versão do setSortedTodos
e acionará a useEffect
chamada e setSortedTodos
.
O exemplo abaixo funciona exatamente como eu quero, no entanto, o eslint está me pedindo para adicionar todos
à useEffect
matriz de dependência e, se o fizer, causa um loop infinito (como seria de esperar).
const [todos, setTodos] = useState([]);
const [sortKey, setSortKey] = useState('title');
const setSortedTodos = useCallback((data) => {
const cloned = data.slice(0);
const sorted = cloned.sort((a, b) => {
const v1 = a[sortKey].toLowerCase();
const v2 = b[sortKey].toLowerCase();
if (v1 < v2) {
return -1;
}
if (v1 > v2) {
return 1;
}
return 0;
});
setTodos(sorted);
}, [sortKey]);
useEffect(() => {
setSortedTodos(todos);
}, [setSortedTodos]);
Exemplo ao vivo:
const {useState, useCallback, useEffect} = React;
const exampleToDos = [
{title: "This", priority: "1 - high", text: "Do this"},
{title: "That", priority: "1 - high", text: "Do that"},
{title: "The Other", priority: "2 - medium", text: "Do the other"},
];
function Example() {
const [todos, setTodos] = useState(exampleToDos);
const [sortKey, setSortKey] = useState('title');
const setSortedTodos = useCallback((data) => {
const cloned = data.slice(0);
const sorted = cloned.sort((a, b) => {
const v1 = a[sortKey].toLowerCase();
const v2 = b[sortKey].toLowerCase();
if (v1 < v2) {
return -1;
}
if (v1 > v2) {
return 1;
}
return 0;
});
setTodos(sorted);
}, [sortKey]);
useEffect(() => {
setSortedTodos(todos);
}, [setSortedTodos]);
const sortByChange = useCallback(e => {
setSortKey(e.target.value);
});
return (
<div>
Sort by:
<select onChange={sortByChange}>
<option selected={sortKey === "title"} value="title">Title</option>
<option selected={sortKey === "priority"} value="priority">Priority</option>
</select>
{todos.map(({text, title, priority}) => (
<div className="todo">
<h4>{title} <span className="priority">{priority}</span></h4>
<div>{text}</div>
</div>
))}
</div>
);
}
ReactDOM.render(<Example />, document.getElementById("root"));
body {
font-family: sans-serif;
}
.todo {
border: 1px solid #eee;
padding: 2px;
margin: 4px;
}
.todo h4 {
margin: 2px;
}
.priority {
float: right;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
Eu estou pensando que tem que haver uma maneira melhor de fazer isso que mantém o sorriso feliz.
sort
retorno de chamada pode ser justo: oreturn a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());
que também tem a vantagem de comparar o código de idioma se o ambiente tiver informações razoáveis de código de idioma. Se você preferir, também pode desestruturar: pastebin.com/7X4M1XTHeslint
lançando?[<>]
botão da barra de ferramentas)? Os snippets de pilha suportam React, incluindo JSX; aqui está como fazer um . Dessa forma, as pessoas podem verificar se as suas soluções propostas não têm o problema de loop infinito ...todos
à matriz de dependênciauseEffect
e ver por que não deveria. :-)Respostas:
Eu diria que isso significa que não é o ideal. A função é realmente dependente
todos
. SesetTodos
for chamada em outro lugar, a função de retorno de chamada deverá ser recalculada, caso contrário, ela opera com dados antigos.Por que você armazena a matriz classificada no estado de qualquer maneira? Você pode usar
useMemo
para classificar os valores quando a chave ou a matriz são alteradas:Depois faça referência a
sortedTodos
todos os lugares.Exemplo ao vivo:
Mostrar snippet de código
Não há necessidade de armazenar os valores classificados no estado, pois você sempre pode derivar / calcular a matriz classificada da matriz "base" e da chave de classificação. Eu diria que também torna seu código mais fácil de entender, pois é menos complexo.
fonte
useMemo
. Apenas uma pergunta secundária, por que não usar.localCompare
no tipo?O motivo do loop infinito é porque todos não correspondem à referência anterior e o efeito será executado novamente.
Por que usar um efeito para uma ação de clique? Você pode executá-lo em uma função como esta:
e no seu menu suspenso, faça um
onChange
.A propósito, sobre a dependência, o ESLint está certo! Seus Todos, no caso descrito acima, são uma dependência e devem estar na lista. A abordagem na seleção de um item está errada e, portanto, é seu problema.
fonte
data.slice(0)
cria a cópia.setState
pois ele não edita o objeto existente e, portanto, o clona internamente. Palavras erradas na minha resposta, é verdade. Eu vou editar isso.setState
não clona dados. Por que você pensa isso?setState
não clona nada. Felix e o OP estão corretos, você precisa copiar a matriz antes de classificá-la.state
.O que você precisa fazer aqui é usar a forma funcional de
setState
:Codesandbox de trabalho
Mesmo se você estiver copiando o estado para não alterar o original, ainda não há garantia de que você obterá o valor mais recente, devido à definição de estado como assíncrono. Além disso, a maioria dos métodos retornará uma cópia superficial, portanto você pode acabar alterando o estado original de qualquer maneira.
O uso do funcional
setState
garante que você obtenha o valor mais recente do estado e não mude o valor do estado original.fonte
.sort
altera a matriz no local, para que você ainda precise copiá-la.