Last active
June 8, 2022 06:40
-
-
Save mfrancois3k/823527cdd9a49eeb072332b1680ce39e to your computer and use it in GitHub Desktop.
React State Management
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
App.js | |
import React, { useState } from 'react'; | |
import { usePicture } from './usePicture'; | |
export const App = () => { | |
let [date, setDate] = useState('2020-05-05'); | |
let picture = usePicture(date); | |
if (!picture) return <div>Loading...</div>; | |
return ( | |
<div> | |
<input | |
type="date" | |
value={date} | |
onChange={(e) => setDate(e.target.value)} | |
/> | |
<h3>{picture.title}</h3> | |
<div>{picture.explanation}</div> | |
<img src={picture.url} alt={picture.title} /> | |
</div> | |
); | |
}; | |
usePicture.js | |
import { useState, useEffect } from 'react'; | |
const fetchPicture = async (date, setPicture) => { | |
try { | |
let response = await fetch( | |
`https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY&date=${date}`, | |
); | |
let json = await response.json(); | |
setPicture(json); | |
} catch (e) { | |
console.error(e); | |
return null; | |
} | |
}; | |
export const usePicture = (date) => { | |
let [picture, setPicture] = useState(); | |
useEffect(() => { | |
fetchPicture(date, setPicture); | |
}, [date]); | |
return picture; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Referencce | |
https://github.com/manucho007/UltimateCourses/tree/master/react-state-management/course/src | |
import { atom, useRecoilState } from 'recoil'; | |
// An atom is basically a piece of state | |
const pageState = atom({ | |
key: 'pageState', | |
default: 'Home', | |
}); | |
export const usePageState = () => useRecoilState(pageState); | |
-recoil | |
import React from 'react'; | |
import { RecoilRoot } from 'recoil'; | |
import { usePageState } from './usePageState'; | |
const pages = ['Home', 'About']; | |
const Navbar = () => { | |
let [page, setPage] = usePageState(); | |
return ( | |
<ul> | |
{pages.map((name) => ( | |
<li | |
key={name} | |
onClick={() => setPage(name)} | |
style={{ color: page === name ? 'red' : 'black' }} | |
> | |
{name} | |
</li> | |
))} | |
</ul> | |
); | |
}; | |
const BlogPost = () => { | |
console.log('rendering blog post'); | |
return <div>Here's a blog post</div>; | |
}; | |
const Footer = () => { | |
let [page] = usePageState(); | |
return <div>This is the footer for {page}</div>; | |
}; | |
const App = () => { | |
return ( | |
<div> | |
<Navbar /> | |
<BlogPost /> | |
<Footer /> | |
</div> | |
); | |
}; | |
export const AppContainer = () => { | |
return ( | |
<RecoilRoot> | |
<App /> | |
</RecoilRoot> | |
); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
app.js | |
import React from 'react'; | |
import { Provider, useDispatch, useSelector } from 'react-redux'; | |
import store from './store'; | |
export const App = () => { | |
const dispatch = useDispatch(); | |
const todoState = useSelector((state) => state.todos); | |
return ( | |
<div> | |
<input | |
type="text" | |
value={todoState.todo} | |
onChange={(e) => | |
dispatch({ type: 'TODO_TYPING', todo: e.target.value }) | |
} | |
onKeyUp={(e) => | |
e.keyCode === 13 | |
? dispatch({ type: 'ADD_TODO', todo: e.target.value }) | |
: null | |
} | |
/> | |
<ul> | |
{todoState.todos.map((todo) => ( | |
<li key={todo}>{todo}</li> | |
))} | |
</ul> | |
</div> | |
); | |
}; | |
export const AppContainer = () => ( | |
<Provider store={store}> | |
<App /> | |
</Provider> | |
); | |
store.js | |
import { createStore, combineReducers } from 'redux'; | |
import { composeWithDevTools } from 'redux-devtools-extension'; | |
const todos = (state = { todos: [], todo: '' }, action) => { | |
switch (action.type) { | |
case 'TODO_TYPING': { | |
return { | |
...state, | |
todo: action.todo, | |
}; | |
} | |
case 'ADD_TODO': { | |
return { | |
...state, | |
todo: '', | |
todos: [...state.todos, action.todo], | |
}; | |
} | |
default: | |
return state; | |
} | |
}; | |
export default createStore(combineReducers({ todos }), composeWithDevTools()); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
app.js | |
import React, { useCallback, useState } from 'react'; | |
import { useComplete } from './useComplete'; | |
export const App = () => { | |
const [clicked, setClicked] = useState(); | |
let hello = 'hello'; | |
let completeCallback = useCallback((data) => console.log(data + hello), [ | |
hello, | |
]); | |
useComplete(completeCallback); | |
return ( | |
<div | |
onClick={() => { | |
setClicked(!clicked); | |
}}> | |
Hello | |
</div> | |
); | |
}; | |
useComplete.js | |
import React, { useEffect } from 'react'; | |
export const useComplete = (completedRequest) => { | |
useEffect(() => { | |
// Network Request | |
completedRequest('test Data'); | |
}, [completedRequest]); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Content.js | |
import React from 'react'; | |
import { useAppContext } from './appContext'; | |
export const Content = () => { | |
let { theme } = useAppContext(); | |
return ( | |
<div style={{ color: theme === 'dark' ? 'black' : 'red' }}> | |
Here's our main content | |
</div> | |
); | |
}; | |
SideBar.js | |
import React from 'react'; | |
import { ThemeControl } from './ThemeControl'; | |
export const Sidebar = () => { | |
return ( | |
<div> | |
<h2>Sidebar</h2> | |
<ThemeControl /> | |
</div> | |
); | |
}; | |
themeControl.js | |
import React from 'react'; | |
import { useAppContext } from './appContext'; | |
export const ThemeControl = () => { | |
let { theme, setTheme } = useAppContext(); | |
return ( | |
<div onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}> | |
Toggle the theme! | |
</div> | |
); | |
}; | |
AppContext | |
import React, { useContext, useState } from 'react'; | |
const AppContext = React.createContext(); | |
export const useAppContext = () => useContext(AppContext); | |
export const AppProvider = ({ children }) => { | |
let [state, setState] = useState({}); | |
return ( | |
<AppContext.Provider | |
value={{ ...state, setTheme: (theme) => setState({ ...state, theme }) }} | |
> | |
{children} | |
</AppContext.Provider> | |
); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
App.js | |
import React from 'react'; | |
import { useDarkMode } from './useDarkMode'; | |
export const App = () => { | |
let isDarkMode = useDarkMode(); | |
return ( | |
<div | |
style={{ | |
height: 500, | |
width: 500, | |
color: isDarkMode ? 'white' : 'black', | |
backgroundColor: isDarkMode ? 'black' : 'white', | |
}} | |
> | |
Here's some content | |
</div> | |
); | |
}; | |
useDarkMode.js | |
import { useEffect, useState } from 'react'; | |
let initiallyDark = window.matchMedia('(prefers-color-scheme: dark)').matches; | |
export const useDarkMode = () => { | |
let [dark, setDark] = useState(initiallyDark); | |
const listener = (event) => { | |
setDark(event.matches); | |
}; | |
useEffect(() => { | |
window | |
.matchMedia('(prefers-color-scheme: dark)') | |
.addEventListener('change', listener); | |
return () => | |
window | |
.matchMedia('(prefers-color-scheme: dark)') | |
.removeEventListener('change', listener); | |
}, []); | |
return dark; | |
} | |
whats is matchMedia query | |
it alow u to target the window of the dom based on events like breakpoint | |
strings or event targets | |
https://webdevetc.com/blog/matchmedia-events-for-window-resizes/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
App.js | |
import React, { useState } from 'react'; | |
import { usePicture } from './usePicture'; | |
export const App = () => { | |
let [date, setDate] = useState('2020-05-05'); | |
let { picture, loading } = usePicture(date); | |
if (loading) return <div>Loading!</div>; | |
return ( | |
<div> | |
<input | |
type="date" | |
value={date} | |
onChange={(e) => setDate(e.target.value)} | |
/> | |
<h3>{picture.title}</h3> | |
<div>{picture.explanation}</div> | |
<img src={picture.url} alt={picture.title} /> | |
</div> | |
); | |
}; | |
useNetwork.js | |
import { useState, useEffect } from 'react'; | |
export const useNetwork = ({ url }) => { | |
let [state, setState] = useState({ loading: true }); | |
useEffect(() => { | |
setState({ loading: true }); | |
const makeRequest = async () => { | |
try { | |
let response = await fetch(url); | |
let data = await response.json(); | |
setState({ data, loading: false }); | |
} catch (error) { | |
setState({ error, loading: false }); | |
} | |
}; | |
makeRequest(); | |
}, [url]); | |
return state; | |
}; | |
usePictures.js | |
import { useNetwork } from './useNetwork'; | |
export const usePicture = (date) => { | |
let { data, loading } = useNetwork({ | |
url: `https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY&date=${date}`, | |
}); | |
return { picture: data, loading }; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
App.js | |
import React, { useState } from 'react'; | |
import { usePicture } from './usePicture'; | |
export const App = () => { | |
let [date, setDate] = useState('2020-05-05'); | |
let { picture, loading } = usePicture(date); | |
if (loading) return <div>Loading!</div>; | |
return ( | |
<div> | |
<input | |
type="date" | |
value={date} | |
onChange={(e) => setDate(e.target.value)} | |
/> | |
<h3>{picture.title}</h3> | |
<div>{picture.explanation}</div> | |
<img src={picture.url} alt={picture.title} /> | |
</div> | |
); | |
}; | |
useNetwork.js | |
import { useState, useEffect } from 'react'; | |
export const useNetwork = ({ url }) => { | |
let [state, setState] = useState({ loading: true }); | |
useEffect(() => { | |
setState({ loading: true }); | |
const makeRequest = async () => { | |
try { | |
let response = await fetch(url); | |
let data = await response.json(); | |
setState({ data, loading: false }); | |
} catch (error) { | |
setState({ error, loading: false }); | |
} | |
}; | |
makeRequest(); | |
}, [url]); | |
return state; | |
}; | |
UsePictures.js | |
import { useMemo, useState } from 'react'; | |
import { useNetwork } from './useNetwork'; | |
export const usePicture = (date) => { | |
const [test] = useState('test'); | |
let options = useMemo( | |
() => ({ | |
method: 'GET', | |
headers: { test }, | |
url: `https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY&date=${date}`, | |
}), | |
[test, date] | |
); | |
let { data, loading } = useNetwork(options); | |
return { picture: data, loading }; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useReducer } from 'react'; | |
const reducer = (state, action) => { | |
switch (action.type) { | |
case 'buttonClick': | |
return { ...state, count: state.count + 1 }; | |
case 'setUsername': | |
return { ...state, username: action.username }; | |
default: | |
return state; | |
} | |
}; | |
export const App = () => { | |
let [state, dispatch] = useReducer(reducer, { count: 0, username: '' }); | |
return ( | |
<div> | |
<button onClick={() => dispatch({ type: 'buttonClick' })}> | |
Click me | |
</button> | |
current count is {state.count} | |
<input | |
type='text' | |
value={state.username} | |
onChange={(e) => | |
dispatch({ type: 'setUsername', username: e.target.value }) | |
} | |
/> | |
Your current username is {state.username} | |
</div> | |
); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
UseState | |
Status.js | |
import React, { useState } from 'react'; | |
export const Status = ({ onEnter }) => { | |
let [message, setMessage] = useState(''); | |
return ( | |
<input | |
type="text" | |
value={message} | |
onKeyUp={(e) => { | |
if (e.keyCode === 13) { | |
onEnter(message); | |
setMessage(''); | |
} | |
}} | |
onChange={(e) => setMessage(e.target.value)} | |
/> | |
); | |
}; | |
App.js | |
import React, { useState } from 'react'; | |
import { Status } from './Status'; | |
export const App = () => { | |
let [messages, setMessages] = useState(['test', 'test2']); | |
return ( | |
<div> | |
<Status onEnter={(value) => setMessages([value, ...messages])} /> | |
<ul> | |
{messages.map((message) => ( | |
<li key={message}>{message}</li> | |
))} | |
</ul> | |
</div> | |
); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
app.js | |
import React from 'react'; | |
import { useStorage } from './useStorage'; | |
export const App = () => { | |
let [count, setCount] = useStorage('count', 0); | |
return <div onClick={() => setCount(count + 1)}>The count is: {count}</div>; | |
}; | |
useStorage.js | |
import { useState, useEffect } from 'react'; | |
export const useStorage = (key, initialState) => { | |
let [state, setState] = useState(initialState); | |
useEffect(() => { | |
let existingState = localStorage.getItem(key); | |
if (existingState) setState(JSON.parse(existingState)); | |
}, [key]); | |
return [ | |
state, | |
(state) => { | |
setState(state); | |
localStorage.setItem(key, JSON.stringify(state)); | |
}, | |
]; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
useScrollToBottom.js | |
import { useRef, useEffect } from 'react'; | |
export const useScrollToBottom = (messages) => { | |
let scrollContainer = useRef(); | |
useEffect(() => { | |
if (!scrollContainer?.current) return; | |
scrollContainer.current.scrollTo(0, scrollContainer.current.scrollHeight); | |
}, [messages]); | |
return scrollContainer; | |
}; | |
useFakeMessage.js | |
import { useEffect } from 'react'; | |
export const useFakeMessage = ({ | |
setMessages, | |
message, | |
from = 'Test', | |
timeout = 5000, | |
}) => { | |
useEffect(() => { | |
setTimeout(() => { | |
setMessages((messages) => [ | |
...messages, | |
{ id: messages.length + 1, content: message, from }, | |
]); | |
}, timeout); | |
}, [setMessages, message, from, timeout]); | |
}; | |
useFakeConvo.js | |
export const useFakeConvo = (setMessages) => { | |
useFakeMessage({ setMessages, message: 'That is cool!', timeout: 1000 }); | |
useFakeMessage({ | |
setMessages, | |
message: 'I know right?', | |
from: 'me', | |
timeout: 3000, | |
}); | |
useFakeMessage({ | |
setMessages, | |
message: 'So what should we do now....', | |
}); | |
useFakeMessage({ | |
setMessages, | |
message: 'So what should we do now....', | |
}); | |
useFakeMessage({ | |
setMessages, | |
message: 'So what should we do now....', | |
}); | |
useFakeMessage({ | |
setMessages, | |
message: 'So what should we do now....', | |
}); | |
useFakeMessage({ | |
setMessages, | |
message: 'I guess we should test scroll positioning', | |
from: 'me', | |
timeout: 9000, | |
}); | |
}; | |
Message.js | |
import React from 'react'; | |
export const Message = ({ message }) => { | |
return ( | |
<div style={message.from === 'me' ? styles.sent : styles.received}> | |
<div>{message.content}</div> | |
</div> | |
); | |
}; | |
const container = { | |
backgroundColor: 'black', | |
borderRadius: 8, | |
padding: 12, | |
marginBottom: 6, | |
marginTop: 6, | |
marginRight: 12, | |
marginLeft: 12, | |
color: '#FFF', | |
width: '80%', | |
}; | |
const styles = { | |
sent: { | |
...container, | |
alignSelf: 'flex-end', | |
}, | |
received: { | |
...container, | |
}, | |
}; | |
Input.js | |
import React from 'react'; | |
export const Input = ({ value, onEnter, onChange }) => { | |
return ( | |
<textarea | |
style={{ padding: 12 }} | |
value={value} | |
onChange={(e) => onChange(e.target.value)} | |
onKeyUp={(e) => (e.keyCode === 13 ? onEnter(e.target.value) : null)} | |
/> | |
); | |
}; | |
App.js | |
import React, { useState } from 'react'; | |
import { Message } from './Message'; | |
import { Input } from './Input'; | |
import { useFakeConvo } from './useFakeConvo'; | |
import { useScrollToBottom } from './useScrollToBottom'; | |
const initialMessages = [ | |
{ id: 1, content: 'Hello there!', from: 'me' }, | |
{ id: 2, content: 'How are you doing?', from: 'Steven' }, | |
{ id: 3, content: 'Pretty Good', from: 'me' }, | |
]; | |
export const App = () => { | |
let [messages, setMessages] = useState(initialMessages); | |
let [currentMessage, setCurrentMessage] = useState(''); | |
useFakeConvo(setMessages); | |
let scrollRef = useScrollToBottom(messages); | |
return ( | |
<div style={styles.wrapper}> | |
<div style={styles.container} ref={(ref) => (scrollRef.current = ref)}> | |
{messages.map((message) => ( | |
<Message key={message.id} message={message} /> | |
))} | |
</div> | |
<Input | |
value={currentMessage} | |
onChange={(content) => setCurrentMessage(content)} | |
onEnter={(content) => { | |
setCurrentMessage(''); | |
setMessages([ | |
...messages, | |
{ id: messages.length + 1, content, from: 'me' }, | |
]); | |
}} | |
/> | |
</div> | |
); | |
}; | |
const styles = { | |
wrapper: { | |
display: 'flex', | |
height: '100%', | |
flexDirection: 'column', | |
justifyContent: 'flex-end', | |
}, | |
container: { | |
display: 'flex', | |
overflow: 'scroll', | |
height: 'max-content', | |
flexDirection: 'column', | |
}, | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
useScrollToBottom.js | |
import { useRef, useEffect } from 'react'; | |
export const useScrollToBottom = (messages) => { | |
let scrollContainer = useRef(); | |
useEffect(() => { | |
if (!scrollContainer?.current) return; | |
scrollContainer.current.scrollTo(0, scrollContainer.current.scrollHeight); | |
}, [messages]); | |
return scrollContainer; | |
}; | |
useFakeMessage.js | |
import { useEffect } from 'react'; | |
export const useFakeMessage = ({ | |
setMessages, | |
message, | |
from = 'Test', | |
timeout = 5000, | |
}) => { | |
useEffect(() => { | |
setTimeout(() => { | |
setMessages((messages) => [ | |
...messages, | |
{ id: messages.length + 1, content: message, from }, | |
]); | |
}, timeout); | |
}, [setMessages, message, from, timeout]); | |
}; | |
useFakeConvo.js | |
export const useFakeConvo = (setMessages) => { | |
useFakeMessage({ setMessages, message: 'That is cool!', timeout: 1000 }); | |
useFakeMessage({ | |
setMessages, | |
message: 'I know right?', | |
from: 'me', | |
timeout: 3000, | |
}); | |
useFakeMessage({ | |
setMessages, | |
message: 'So what should we do now....', | |
}); | |
useFakeMessage({ | |
setMessages, | |
message: 'So what should we do now....', | |
}); | |
useFakeMessage({ | |
setMessages, | |
message: 'So what should we do now....', | |
}); | |
useFakeMessage({ | |
setMessages, | |
message: 'So what should we do now....', | |
}); | |
useFakeMessage({ | |
setMessages, | |
message: 'I guess we should test scroll positioning', | |
from: 'me', | |
timeout: 9000, | |
}); | |
}; | |
Message.js | |
import React from 'react'; | |
export const Message = ({ message }) => { | |
return ( | |
<div style={message.from === 'me' ? styles.sent : styles.received}> | |
<div>{message.content}</div> | |
</div> | |
); | |
}; | |
const container = { | |
backgroundColor: 'black', | |
borderRadius: 8, | |
padding: 12, | |
marginBottom: 6, | |
marginTop: 6, | |
marginRight: 12, | |
marginLeft: 12, | |
color: '#FFF', | |
width: '80%', | |
}; | |
const styles = { | |
sent: { | |
...container, | |
alignSelf: 'flex-end', | |
}, | |
received: { | |
...container, | |
}, | |
}; | |
chatReducer.js | |
import { useReducer } from 'react'; | |
const initialMessages = [ | |
{ id: 1, content: 'Hello there!', from: 'me' }, | |
{ id: 2, content: 'How are you doing?', from: 'Steven' }, | |
{ id: 3, content: 'Pretty Good', from: 'me' }, | |
]; | |
const reducer = (state, action) => { | |
switch (action.type) { | |
case 'setMessages': | |
return { ...state, messages: action.messages }; | |
case 'addMessage': | |
return { | |
...state, | |
currentMessage: '', | |
messages: [ | |
...state.messages, | |
{ | |
id: state.messages.length + 1, | |
content: action.message, | |
from: action.from, | |
}, | |
], | |
}; | |
case 'setCurrentMessage': | |
return { ...state, currentMessage: action.message }; | |
default: | |
return state; | |
} | |
}; | |
export const useChatReducer = () => | |
useReducer(reducer, { messages: initialMessages }); | |
useChat.js | |
import React, { createContext, useContext } from 'react'; | |
import { useChatReducer } from './chatReducer'; | |
const ChatContext = createContext(); | |
export const ChatProvider = ({ children }) => { | |
let [state, dispatch] = useChatReducer(); | |
return ( | |
<ChatContext.Provider value={{ state, dispatch }}> | |
{children} | |
</ChatContext.Provider> | |
); | |
}; | |
export const useChat = () => useContext(ChatContext); | |
Input.js | |
import React from 'react'; | |
export const Input = ({ value, onEnter, onChange }) => { | |
return ( | |
<textarea | |
style={{ padding: 12 }} | |
value={value} | |
onChange={(e) => onChange(e.target.value)} | |
onKeyUp={(e) => (e.keyCode === 13 ? onEnter(e.target.value) : null)} | |
/> | |
); | |
}; | |
App.js | |
import React, { useState } from 'react'; | |
import { Message } from './Message'; | |
import { Input } from './Input'; | |
import { useFakeConvo } from './useFakeConvo'; | |
import { useScrollToBottom } from './useScrollToBottom'; | |
const initialMessages = [ | |
{ id: 1, content: 'Hello there!', from: 'me' }, | |
{ id: 2, content: 'How are you doing?', from: 'Steven' }, | |
{ id: 3, content: 'Pretty Good', from: 'me' }, | |
]; | |
export const App = () => { | |
let [messages, setMessages] = useState(initialMessages); | |
let [currentMessage, setCurrentMessage] = useState(''); | |
useFakeConvo(setMessages); | |
let scrollRef = useScrollToBottom(messages); | |
return ( | |
<div style={styles.wrapper}> | |
<div style={styles.container} ref={(ref) => (scrollRef.current = ref)}> | |
{messages.map((message) => ( | |
<Message key={message.id} message={message} /> | |
))} | |
</div> | |
<Input | |
value={currentMessage} | |
onChange={(content) => setCurrentMessage(content)} | |
onEnter={(content) => { | |
setCurrentMessage(''); | |
setMessages([ | |
...messages, | |
{ id: messages.length + 1, content, from: 'me' }, | |
]); | |
}} | |
/> | |
</div> | |
); | |
}; | |
const styles = { | |
wrapper: { | |
display: 'flex', | |
height: '100%', | |
flexDirection: 'column', | |
justifyContent: 'flex-end', | |
}, | |
container: { | |
display: 'flex', | |
overflow: 'scroll', | |
height: 'max-content', | |
flexDirection: 'column', | |
}, | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
useScrollToBottom.js | |
import { useRef, useEffect } from 'react'; | |
export const useScrollToBottom = (messages) => { | |
let scrollContainer = useRef(); | |
useEffect(() => { | |
if (!scrollContainer?.current) return; | |
scrollContainer.current.scrollTo(0, scrollContainer.current.scrollHeight); | |
}, [messages]); | |
return scrollContainer; | |
}; | |
useFakeMessage.js | |
import { useEffect } from 'react'; | |
export const useFakeMessage = ({ | |
setMessages, | |
message, | |
from = 'Test', | |
timeout = 5000, | |
}) => { | |
useEffect(() => { | |
setTimeout(() => { | |
setMessages((messages) => [ | |
...messages, | |
{ id: messages.length + 1, content: message, from }, | |
]); | |
}, timeout); | |
}, [setMessages, message, from, timeout]); | |
}; | |
useFakeConvo.js | |
export const useFakeConvo = (setMessages) => { | |
useFakeMessage({ setMessages, message: 'That is cool!', timeout: 1000 }); | |
useFakeMessage({ | |
setMessages, | |
message: 'I know right?', | |
from: 'me', | |
timeout: 3000, | |
}); | |
useFakeMessage({ | |
setMessages, | |
message: 'So what should we do now....', | |
}); | |
useFakeMessage({ | |
setMessages, | |
message: 'So what should we do now....', | |
}); | |
useFakeMessage({ | |
setMessages, | |
message: 'So what should we do now....', | |
}); | |
useFakeMessage({ | |
setMessages, | |
message: 'So what should we do now....', | |
}); | |
useFakeMessage({ | |
setMessages, | |
message: 'I guess we should test scroll positioning', | |
from: 'me', | |
timeout: 9000, | |
}); | |
}; | |
Message.js | |
import React from 'react'; | |
export const Message = ({ message }) => { | |
return ( | |
<div style={message.from === 'me' ? styles.sent : styles.received}> | |
<div>{message.content}</div> | |
</div> | |
); | |
}; | |
const container = { | |
backgroundColor: 'black', | |
borderRadius: 8, | |
padding: 12, | |
marginBottom: 6, | |
marginTop: 6, | |
marginRight: 12, | |
marginLeft: 12, | |
color: '#FFF', | |
width: '80%', | |
}; | |
const styles = { | |
sent: { | |
...container, | |
alignSelf: 'flex-end', | |
}, | |
received: { | |
...container, | |
}, | |
}; | |
chatReducer.js | |
import { useReducer } from 'react'; | |
const initialMessages = [ | |
{ id: 1, content: 'Hello there!', from: 'me' }, | |
{ id: 2, content: 'How are you doing?', from: 'Steven' }, | |
{ id: 3, content: 'Pretty Good', from: 'me' }, | |
]; | |
const reducer = (state, action) => { | |
switch (action.type) { | |
case 'setMessages': | |
return { ...state, messages: action.messages }; | |
case 'addMessage': | |
return { | |
...state, | |
currentMessage: '', | |
messages: [ | |
...state.messages, | |
{ | |
id: state.messages.length + 1, | |
content: action.message, | |
from: action.from, | |
}, | |
], | |
}; | |
case 'setCurrentMessage': | |
return { ...state, currentMessage: action.message }; | |
default: | |
return state; | |
} | |
}; | |
export const useChatReducer = () => | |
useReducer(reducer, { messages: initialMessages }); | |
Input.js | |
import React from 'react'; | |
export const Input = ({ value, onEnter, onChange }) => { | |
return ( | |
<textarea | |
style={{ padding: 12 }} | |
value={value} | |
onChange={(e) => onChange(e.target.value)} | |
onKeyUp={(e) => (e.keyCode === 13 ? onEnter(e.target.value) : null)} | |
/> | |
); | |
}; | |
App.js | |
import React, { useState } from 'react'; | |
import { Message } from './Message'; | |
import { Input } from './Input'; | |
import { useFakeConvo } from './useFakeConvo'; | |
import { useScrollToBottom } from './useScrollToBottom'; | |
const initialMessages = [ | |
{ id: 1, content: 'Hello there!', from: 'me' }, | |
{ id: 2, content: 'How are you doing?', from: 'Steven' }, | |
{ id: 3, content: 'Pretty Good', from: 'me' }, | |
]; | |
export const App = () => { | |
let [messages, setMessages] = useState(initialMessages); | |
let [currentMessage, setCurrentMessage] = useState(''); | |
useFakeConvo(setMessages); | |
let scrollRef = useScrollToBottom(messages); | |
return ( | |
<div style={styles.wrapper}> | |
<div style={styles.container} ref={(ref) => (scrollRef.current = ref)}> | |
{messages.map((message) => ( | |
<Message key={message.id} message={message} /> | |
))} | |
</div> | |
<Input | |
value={currentMessage} | |
onChange={(content) => setCurrentMessage(content)} | |
onEnter={(content) => { | |
setCurrentMessage(''); | |
setMessages([ | |
...messages, | |
{ id: messages.length + 1, content, from: 'me' }, | |
]); | |
}} | |
/> | |
</div> | |
); | |
}; | |
const styles = { | |
wrapper: { | |
display: 'flex', | |
height: '100%', | |
flexDirection: 'column', | |
justifyContent: 'flex-end', | |
}, | |
container: { | |
display: 'flex', | |
overflow: 'scroll', | |
height: 'max-content', | |
flexDirection: 'column', | |
}, | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
to visualize ur state debugging tool | |
App.js | |
import React from 'react'; | |
import { useTodos } from './useTodos'; | |
export const App = () => { | |
let [state, send] = useTodos(); | |
return ( | |
<div> | |
<input | |
type="text" | |
value={state.context.todo} | |
onChange={(e) => send('TODO.TYPING', e.target)} | |
onKeyUp={(e) => (e.keyCode === 13 ? send('TODOS.ADD', e.target) : null)} | |
/> | |
<ul> | |
{state.context.todos.map((todo) => ( | |
<li key={todo}>{todo}</li> | |
))} | |
</ul> | |
</div> | |
); | |
}; | |
useTodos.js | |
import { useMachine } from '@xstate/react'; | |
import { Machine, assign } from 'xstate'; | |
const todosMachine = Machine({ | |
id: 'todos', | |
context: { | |
todo: '', | |
todos: ['first item'], | |
}, | |
initial: 'initializing', | |
states: { | |
initializing: { | |
actions: { | |
todos: (ctx) => ctx.todos, | |
}, | |
}, | |
}, | |
on: { | |
'TODO.TYPING': { | |
actions: assign({ todo: (ctx, target) => target.value }), | |
}, | |
'TODOS.ADD': { | |
actions: assign({ | |
todo: '', | |
todos: (ctx, target) => [...ctx.todos, target.value], | |
}), | |
cond: (ctx, target) => target.value.trim().length, | |
}, | |
}, | |
}); | |
export const useTodos = () => useMachine(todosMachine); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment