while I’m trying to apply redux_react with functional code
I have a problem with .subscribe()
it runs multiple times every state update = (count of all previous updates + 1)
how can I re-render the UI only once .. every state update
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- Redux -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.2.0/redux.min.js" integrity="sha512-1/8Tj23BRrWnKZXeBruk6wTnsMJbi/lJsk9bsRgVwb6j5q39n0A00gFjbCTaDo5l5XrPVv4DZXftrJExhRF/Ug==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- react & react-dom -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<!-- Babel: transpiles JSX code into Common JS - that browser can understand -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="app"></div>
<!-- Babel JSX code -->
<script type="text/babel">
// =============================== Redux (State management) =============================================
// action types
const [ADD_TODO, REMOVE_TODO, TOGGLE_TODO] = ["ADD_TODO", "REMOVE_TODO", "TOGGLE_TODO"]
const [ADD_GOAL] = ["ADD_GOAL"]
// action creators
const addTodo = todo => ({
type: ADD_TODO,
todo
})
const removeTodo = id => ({
type: REMOVE_TODO,
id
})
const toggleTodo = id => ({
type: TOGGLE_TODO,
id
})
const addGoal = goal => ({
type: ADD_GOAL,
goal
})
// Reducers
const todosReducer = (state=[], action) => {
switch (action.type) {
case ADD_TODO:
return [...state, action.todo]
case REMOVE_TODO:
return state.filter((s) => s.id !== action.id)
case TOGGLE_TODO:
return state.map((s) => (s.id == action.id)? {...s, complete: !s.complete} : s)
default:
return state
}
}
const goalsReducer = (state=[], action) => {
switch (action.type) {
case ADD_GOAL:
return [...state, action.goal]
default:
return state
}
}
// Redux Store
const store = Redux.createStore(
// Root Reducer
Redux.combineReducers({
todos: todosReducer,
goals: goalsReducer
})
)
// =============================== React App =============================================
// Components
const List = ({items, removeItem, toggle}) => {
return (
<ul>
{
items.map((itm) =>
<li key={itm.id}>
<span id={itm.id} onClick={(toggle)? toggle : null} style={{textDecoration: (itm.complete)? 'line-through' : ''}}>
{itm.text}
</span>
<button id={itm.id} onClick={removeItem}> X </button>
</li>
)
}
</ul>
)
}
const Todos = ({items}) => {
const newTodoInput = React.useRef()
// add new
const handleAddTodo = () => {
const newTodo = newTodoInput.current
// Redux <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// dispatch the addTodo action --> to update Redux State
// ** No need to lift-up handleAddTodo() to all parents :)
store.dispatch(
addTodo({
id: (new Date()).getTime().toString(36),
text: newTodo.value,
complete: false
})
)
newTodo.value = ""
}
// remove
const handleRemoveTodo = (e) => {
store.dispatch(removeTodo(e.target.id))
}
// toggle
const handleToggle = (e) => {
store.dispatch(toggleTodo(e.target.id))
}
return (
<div>
<h3>My Todos:</h3>
<div>
<input type="text" placeholder="new todo ..." ref={newTodoInput} />
<button onClick={handleAddTodo}>Save</button>
</div>
<List items={items} removeItem={handleRemoveTodo} toggle={handleToggle} />
</div>
)
}
const Goals = ({items}) => {
return (
<div>
<h3>My Goals:</h3>
<List items={items} />
</div>
)
}
const App = ({store}) => {
const [todos, setTodos] = React.useState([])
const [goals, setGoals] = React.useState([])
// Redux <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
store.subscribe(() => {
console.log(store.getState())
const newState = store.getState()
setTodos(newState.todos)
setGoals(newState.goals)
})
return (
<div>
<Todos items={todos} />
<Goals items={goals} />
</div>
)
}
// Root
const root = ReactDOM.createRoot(document.getElementById("app"))
root.render(
<App store={store} />
)
</script>
</body>
</html>
how can i make the state updates without subscribing on all previous updates
>Solution :
You can avoid to use subscribe with react, here is the recommended way:
You can use redux with react hooks to avoid using subscribe
To track redux update when state change you can use useSelector
which is made for
Then you avoid to use subscribe
to your state change, useSelector do it for you
Do not forget to use Provider
from react-redux
Here is a small example
const Goals = () => {
const items = useSelector(goalsSelector)
return (
<div>
<h3>My Goals:</h3>
<List items={items} />
</div>
)
}
const App = ({store}) => {
return (
<Provider store={store}>
<div>
<Todos items={todos} />
<Goals items={goals} />
</div>
</Provider>
)
}
If you still want to subscribe inside a react function component you must use useEffect
With your current implementation on every renderer you subscribe again to redux.
Here you subscribe only when the component is mounted then you unsubscribe when you unmount it
const App = ({store}) => {
const [todos, setTodos] = React.useState([])
const [goals, setGoals] = React.useState([])
// Redux <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
React.useEffect(() => {
const unsubscribe = store.subscribe(() => {
console.log(store.getState())
const newState = store.getState()
setTodos(newState.todos)
setGoals(newState.goals)
})
return unsubscribe;
}, [])
return (
<div>
<Todos items={todos} />
<Goals items={goals} />
</div>
)
}