How to integrate redux inside a react.js app?

Advertisements

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>
            )
        }

Leave a Reply Cancel reply