Skip to content

Instantly share code, notes, and snippets.

@nicolasmelo1
Last active July 11, 2024 19:00
Show Gist options
  • Save nicolasmelo1/7a3af5192cede071456efcd0bf6bbd14 to your computer and use it in GitHub Desktop.
Save nicolasmelo1/7a3af5192cede071456efcd0bf6bbd14 to your computer and use it in GitHub Desktop.
/**
* Isso é uma factory function, basicamente é como se tivesse instanciando uma classe, mas faço isso com function,
* prefiro esse padrão do que usar classes. Eu tenho que chamar essa função globalmente antes de começar o app.
*
* IMPORTANTE: Não demorei nem 10 minutos fazendo essa porcaria, então sei lá, recomendo algo mais parrudão pra usar em prod.
* Principalmente algo com tipagem melhor que vc consiga listar os eventos disponiveis e os parâmetros que cada callback recebe.
*/
function simplePubSubFactory() {
const subscribed = new Map<string, Map<symbol, (...args: any[]) => void>>
/**
* Sobrescreve um callback a um evento, idealmente o nome dos eventos tem nomes únicos.
*
* @param event - Nome do evento
* @param callback - O que vai ser executado quando o evento for publicado
*
* @returns - Retorna uma função que remove o callback do evento
*/
function subscribe(event: string, callback: (...args: any[]) => void) {
if (!subscribed.has(event)) {
subscribed.set(event, new Map())
}
const id = Symbol()
subscribed.get(event).set(id, callback)
return () => {
subscribed.get(event).delete(id)
}
}
/**
* Publica um evento, todos os callbacks registrados para esse evento serão executados.
*
* @param event - Nome do evento
* @param args - Argumentos que serão passados para os callbacks
*/
function publish(event: string, ...args: any[]) {
if (!subscribed.has(event)) {
return
}
for (const callback of subscribed.get(event).values()) {
callback(...args)
}
}
return { subscribe, publish }
}
// O que vc usa idealmente
const pubsub = simplePubSubFactory()
function App() {
// Faz o fetch dos usuários globalmente e publica para todos os callbacks registrados que o evento aconteceu
// Idealmente vc faria isso dentro de um contexto global, mas aqui é só um exemplo.
useEffect(() => {
fetch('/users').then(response => response.json()).then(users => {
pubsub.publish('user', users)
})
}, []);
return (
<div>
<Navbar/>
<Sidebar />
<div>
<Users />
</div>
</div>
)
}
function Navbar() {
return (
<nav>
<a href="/home">Home</a>
<a href="/about">About</a>
</nav>
)
}
function Sidebar() {
return (
<div>
<Users />
</div>
)
}
/**
* O componente Users mantém controle dos seus próprios usuários
* Removeu um usuário? Publica o evento para todos os componentes que estão escutando que esse usuáro foi removido
*
* Seus componentes se "conversam", mas sem vc ter acesso direto a eles. O acesso ao dado é direto, não precisa esperar
* rerender nenhum executar, quando publico uma mensagem, apenas o componente <Users /> que está escutando o evento
* triggará o re-render, deixando a aplicação mais performática.
*
* Seu código fica co-localizado. Se eu precisar de users em outro lugar, é só eu "escutar" o evento e criar o
* const [users, setUsers] = useState([]) e já era.
*
* Mas isso causa um problema, consegue ver? Beleza, vamos dizer que eu fiz o fetch dos usuários, mas o componente 2 renderizou
* só depois que eu fiz o fetch. Ou seja, vai renderizar sem os usuários. Como resolver isso?
*
* Simples, aqui a gente usa o recurso nativo do React: props
* (Vc pode usar um contexto global tbm ou libs como o Zustand, vc ainda mantém sua lib de estado global (redux, zustand, etc),
* mas idealmente apenas no primeiro render, depois modifica o dado localmente)
* OU, vc pode manter o estado global fora do React (imagina um const globalState = { users: [] }), e no primeiro render vc pega
* o dado desse objetão
*
* ```jsx
* function Users(props) {
* const [users, setUsers] = useState(props.initialUsers)
*
* // resto do código
* }
* ```
*
* Ou seja, no primeio render, usa o dado vindo da prop pra carregar o dado local, a partir dai vc gerencia ele localmente
* dentro do seu próprio componente.
*
* Com event bus um componente pode "conversar" com outro independente de onde ele tiver na árvore, componente filho
* se comunica com o pai e componente pai se comunica com o filho (esse segundo, só é possível com passagem de props).
*/
function Users() {
const [users, setUsers] = useState([])
useEffect(() => {
const getUsersUnsubscribe = pubsub.subscribe('user', (users) => {
setUsers(users)
})
const removeUserUnsubscribe = pubsub.subscribe('remove-user', (userId) => {
setUsers(users.filter(user => user.id !== userId))
})
return () => {
removeUserUnsubscribe();
getUsersUnsubscribe()
}
}, [])
return (
<div>
{users.map(user => (
<div key={user.id}>
<span>{user.name}</span>
<button onClick={() => pubsub.publish('remove-user', user.id)}>Remove</button>
</div>
))}
</div>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment