3

I'm doing an attemp of Todo App in React- Redux; got store, actions, reducer & Addodo component running; but got stucked at showing todos:

if my initialState=[] how can i retrieve data from store?

Store:

import { createStore } from 'redux';
import rootReducer from '../reducer';

export const store = createStore(
  rootReducer, 
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  );

 export default store

My reducer:

const initialState = [];


const todos = (state = initialState, action) => {
  switch(action.type) {
    // Aca va tu codigo;    
      case 'AddTodo':     
        return [...state, action.payload]; 
        
      case 'RemoveTodo':
        return state.filter(todo => todo.id  !== action.payload)
        
      case 'ToInProgress':             
        return state.map((todo) =>
          todo.id === action.payload ? { ...todo, status: "InProgress" } : todo
        ); 
                     
        case 'ToDone':
         return state.map((todo) =>
          todo.id === action.payload ? { ...todo, status: "Done" } : todo
        );
       
        default: 
        return state
      }
    }
      
 export default todos;

App.js:

import React from 'react'
import { Route } from "react-router-dom"
import {Provider} from 'react-redux'
import store from './store/index'

import Nav from '../src/components/Nav/Nav'
import Home from '../src/components/Home/Home'
import AddTodo from '../src/components/AddTodo/AddTodo'
import TodoDetail from '../src/components/TodoDetail/TodoDetail'
import './App.css';

export function App() {
  return (
    <React.Fragment>
      <Provider store={store}>
        <Nav />
        <Route exact path="/" component={Home} />
        <Route path="/add" component={AddTodo} />
        <Route path="/edit/:id" component={TodoDetail} />
      </Provider>
    </React.Fragment>
  );
}

export default App;

Home.js:

import React from 'react';
import Todos from '../Todos/Todos'
import './home.css'

export function Home() {
  return (
  
    <div className="todosContainer"> 
    <div>
      <Todos status="Todo"/>
      <label>TODO</label>
    </div>
    <div>
      <Todos status='InProgress' />
      <label>InProgress</label>
    </div>
    <div>
      <Todos status='Done'/>
      <label>Done</label>
    </div>
    </div>
  )
};     

export default Home;

Here's AddTodo:

export function AddTodo() {
   const [state,setState]=React.useState({
     title:"",
     description:"",
     place:"",
     date:"",
   })

  function handleChange(e){ 
    const value=e.target.value;
    setState({
      ...state,
      [e.target.name]:value
    })
  }

  function handleSubmit(e){
    e.preventDefault();
    store.dispatch(addTodo({
      place:state.place,
      title:state.title,
      date:state.date,
      description:state.description
    })) // parameters "id" & "status" loaded in Actions.
    setState({title:"",
    description:"",
    place:"",
    date:"",})
  }

  return (
    <div className="todoContainer">
      <form id="formulario"onSubmit={handleSubmit} >
        <label>
          Title
          <input type="text" name="title" value={state.title} onChange={handleChange} />
        </label>
        <label>
          Description
          <textarea type="text" name="description" value={state.description} onChange= {handleChange}/>
        </label>
        <label>
          Place
          <input type="text" name="place" value={state.place} onChange={handleChange}/>
        </label>
        <label>
          Date
          <input type="text" name="date" value={state.date} onChange={handleChange}/>
        </label>
        <button className="boton" type="submit" onClick={addTodo}>Agregar</button>
      </form>
    </div>
  )
};

function mapDispatchToProps(dispatch){
  return{
    addTodo: todo => dispatch(addTodo(todo))
  }
}

export default connect(mapDispatchToProps)(AddTodo)

Now, i want to show at Todos component, every Todos "title", still didn't know how to perform it:

export function Todos(data) {
  
  return (
    <div className="todoDetail">

        <h4>{data.title}</h4>  
    </div>
  )
};
  
function mapStateToProps(state) {
  return {
    data: state.title,   
  };
  
}   
export default connect(mapStateToProps)(Todos)     
Martin Gonzalez
  • 77
  • 1
  • 1
  • 12

2 Answers2

3

You looking for useSelector hook.

Do not use store.getState() in functional component. Also, you do not need to connect functions components, use useDispatch hook.

Getting values from store will be like this:

const todos = useSelector(store => store.items);
  • Thanks a lot for your answer Anton! My mistake, ill edit my question. Ain't allowed to use UseSelector or UseDispatch. I wish i can do that! al code examples that i found advice me that – Martin Gonzalez Jul 15 '21 at 23:00
  • @MartinGonzalez why you trying avoiding default functionality? In this case, you can use class-components `connect` as you make. `props.data` will be collect your data. – Anton Neverov Jul 15 '21 at 23:10
  • Thing is Anton, im cursing a Development bootcamp, wasn't able to pass Redux check-point and now im studing to make the checkpòint again. They give me those rules: Can't use useDispatch, no useSelector, must be developed on functional components and the initial State must be just an array (initialState=[]) – Martin Gonzalez Jul 15 '21 at 23:20
1

It's very odd they (code bootcamp) would require you to use a function component and then not allow you to use the React hooks that make the function components so appealing. No worries, you can still use the older connect Higher Order Component to inject your redux state and dispatchable actions into the props passed to the component.

You've an issue in AddToDo component. You are passing the mapDispatchToProps to the connect HOC where the mapStateToProps callback would be passed. You also aren't using the injected addTodo action you just mapped. You almost won't ever need to access the store object directly to dispatch actions, use the React components/hooks.

export function AddTodo() {
  const [state, setState] = React.useState({
    title:"",
    description:"",
    place:"",
    date:"",
  });

  function handleChange(e) { 
    const { value } = e.target;
    setState(prevState => ({ // <-- use functional state update
      ...prevState,
      [e.target.name]:value
    }));
  }

  function handleSubmit(e) {
    e.preventDefault();
    this.props.addTodo({ // <-- addTodo action injected into props
      place:state.place,
      title:state.title,
      date:state.date,
      description:state.description
    });
    setState({
      title:"",
      description:"",
      place:"",
      date:"",
    });
  }

  return (
    <div className="todoContainer">
      ...
    </div>
  )
};

const mapDispatchToProps = {
  addTodo,
}

export default connect(null, mapDispatchToProps)(AddTodo); // <-- mapStateToProps is null

In Todos component, your todos state is an array, so you likely want to map it to JSX. Remember to destructure the passed props. Since you didn't share your reducers or how you combine your reducers I'm going to assume there is just one reducer for todos located at the state root.

[update] Since it seems your rootReducer is just your todos reducer function and I don't see where combineReducers is used to build a more complex state tree, state IS your todos array state you are trying to pass to Todos component. [/update]

function Todos({ todos }) {
  return (
    <div className="todoDetail">
      {todos.map((todo) => (
        <h4 key={todo.id}>{todo.title}</h4>
      ))}  
    </div>
  )
};
  
function mapStateToProps(state) {
  return {
    todos: state, // <-- state is todos array
  };
}

export default connect(mapStateToProps)(Todos);
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • I really appreciate your answer Drew, thank you for taking your time to help me. I'm getting this error: TypeError: todos is undefined. Trying to undestand...at the return from mapStateToProps, when you call state.todos: my state at Redux says: {title:"some title", place:"right here", description:"some example text", date:"right now", id:"1", status:"In progress"} i was gessing to name it state.title, but wasn't working either. Sorry for my bad english & coding!!! – Martin Gonzalez Jul 16 '21 at 00:23
  • @MartinGonzalez I think the issue may lie in your reducers since the UI code you shared and I used in my answer seems to be making some assumptions about the state shape. Can you update your question to add your reducers and how you create your store and provide the redux context (the store provider)? – Drew Reese Jul 16 '21 at 00:29
  • @MartinGonzalez So is `state.todos` undefined in `mapStateToProps`? – Drew Reese Jul 16 '21 at 00:32
  • TypeError: todos is undefined Todos F:/NETBUSTER/HENRY/EXAMEN/CP-M2-FT15a/src/components/Todos/Todos.js:6 3 | 4 | function Todos( {todos} ) { 5 | return ( > 6 |
    7 | {todos.map((todo) => ( 8 |

    {todo.title}

    9 | ))}
    – Martin Gonzalez Jul 16 '21 at 00:43
  • @MartinGonzalez I don't see where you are rendering a `Todos` component. If you are exporting `Todos` and default exporting the connected version, please double check that you are using the default export so it's properly connected to your redux store. – Drew Reese Jul 16 '21 at 00:46
  • export function Home() { return (
    ) }; export default Home;
    – Martin Gonzalez Jul 16 '21 at 00:47
  • @MartinGonzalez Ah, ok, I see we're getting closer, you are rendering several instances of `Todos` on your home page and passing in a filter prop. Sorry if this all seems tedious, can you now update your question to show the `Home` component code and how you import `Todos`? – Drew Reese Jul 16 '21 at 00:50
  • Component Home is the landing page, to pass the test, i was asking to render one Todo component for each kind of Status: TODO, In Progress and Done. Those are tasks with i must deal later, after to solve showing todos titles, thats why im bodering you guys....aint sleep in a while and start getting frustrated with this -.- – Martin Gonzalez Jul 16 '21 at 00:57
  • @MartinGonzalez Hang in there, we're almost there I think. Can you show *how* you import `Todos` into `Home` file? What I'm suspecting here is that you've imported the named export versus the default export, in other words, I think you've done `import { Todos } from '../path/to/todos';` when you want `import Todos from '../path/to/todos';`. – Drew Reese Jul 16 '21 at 01:05
  • Good morning Drew! Todos are imported at Home like:: import Todos from '../Todos/Todos' – Martin Gonzalez Jul 16 '21 at 13:00
  • @MartinGonzalez Ok, thanks for confirming how `Todos` is imported. I think I might see the issue now. If `rootReducer` ***is*** your `todos` reducer and you ***aren't*** using `combineReducers` to create a state tree, then I think `state` ***IS*** the todos array you are looking for, and would explain why `state.todos` is undefined. In the `Todos` component use `const mapStateToProps = state => ({ todos: state });` instead. – Drew Reese Jul 16 '21 at 15:34
  • Finally!!! It works!! Drew i have no words to thank you m8! – Martin Gonzalez Jul 16 '21 at 18:45