'Rendering new list item after adding it from a nested form. React hooks, redux, React Router V6

I am creating a list tracking app with React hooks, Redux, and Ruby on Rails. There is a List model, with a title as a string and completed as a boolean, and a ListItem model with descriptions as a string (the list item), completed boolean, and list_id as an integer.

I am using react route V6 for this and getting a little lost in re-rendering/ updating the page. Here is the breakdown of the application:

On the home screen, you can click to view all Lists and add a new list. when viewing all list each list title is displayed as a link to that list show page. The show page shows the list title, list items and a form to add another list item. Now where I am having trouble is being able to add a new list item, and it display on the page right after submission. Right now when I add a new item, and refresh the page it is not there. But if I click back to view all lists, then click that list again it shows up under the list items.

I tried using useNavigate to navigate to that list show page even though it is already on it but I am getting this error

Uncaught TypeError: Cannot destructure property 'list' of 'location.state' as it is null.

Here is all my components:

App.js

class App extends React.Component {

  render(){
    return (
      <div className="App">
        <Navbar/>
        <br></br>
        <Routes>
          <Route path="/" element={<Home/>} />
          <Route path="/lists" element={<Lists />} />
          <Route path="/lists/new" element={<ListForm />} />
          <Route path="/lists/:id" element={<ListContainer />} />
        </Routes>
      </div>
    );
  }
}

Lists.js

export default function Lists() {
  const lists = useSelector(state => state.lists)
  // replaces mapStateToProps
  const dispatch = useDispatch()
  // replaces mapDispatchToProps

  useEffect(() => {
    dispatch(fetchLists())
  }, [])

      return (
        <div>
            {Array.isArray(lists) && lists.map((list) => {
              return (
                <Link
                  key={list.id}
                  to={`/lists/${list.id}`}
                  state={{ list: list }}
                >
                  <h2>{list.title}</h2>
                </Link>
              )

            })}
        </div>
      )
}

ListContainer.js

export default function ListContainer() {

  const location = useLocation();
  const { list } = location.state;
  console.log(list)

  return (
    <div>
      <List list={list}/>
      <ListItemForm list={list}/>
    </div>
  );
}

List.js

export default function List({list}) {

  return (
    <div>
      <h4>{list.title}</h4>
      {list.list_items.map((item) => {
        return (
          <div key={item.id}>
            <li key={item.id}>{item.description}</li>
          </div>
        );  
      })}
      <br></br>
    </div>
  );
}

and ListItemForm.js

export default function ListItemForm({list}) {
  const [item, setItem] = useState("")
  const dispatch = useDispatch()
  const navigate = useNavigate()

  function handleSubmit(e) {
    e.preventDefault()
    let newItem = {description: item, completed: false, list_id: list.id}
    dispatch(createListItem(newItem, list.id))
    setItem("")

    navigate(`/lists/${list.id}`)
  }


  return (
    <div>
        <br></br>
      <form onSubmit={handleSubmit}>
        <label>Add to your list: </label>
        <input value={item} onChange={(e) => setItem(e.target.value)} />
      </form>
    </div>
  )
}

I have been stuck on this for quite some time now and not sure where to go from here or where I am going wrong. Any help is appreciated!!



Solution 1:[1]

Sometimes when you navigate to "/lists/:id" you send route state, sometimes you don't. It's undefined when you navigate to "/lists/:id" when adding new list items. This navigation to the route you are already on for editing a list is unnecessary.

Since you are using Redux I don't think there's any need to send a list item in route state at all. Use the id route parameter and your lists redux state to derive the specific list you want to view/edit.

Example

Given: <Route path="/lists/:id" element={<ListContainer />} />

Lists

function Lists() {
  const dispatch = useDispatch();
  const lists = useSelector((state) => state.lists);

  useEffect(() => {
    if (!lists.length) {
      dispatch(fetchLists());
    }
  }, [dispatch, lists]);

  return (
    <div>
      {lists.map((list) => (
        <Link key={list.id} to={`/lists/${list.id}`}>
          <h2>{list.title}</h2>
        </Link>
      ))}
    </div>
  );
}

ListContainer

import { useParams } from 'react-router-dom';

function ListContainer() {
  const { id } = useParams();
  const lists = useSelector((state) => state.lists);
  const list = lists.find((list) => list.id === id);

  return (
    <div>
      <List list={list} />
      <ListItemForm list={list} />
    </div>
  );
}

ListItemForm

function ListItemForm({ list }) {
  const [item, setItem] = useState("");
  const dispatch = useDispatch();

  function handleSubmit(e) {
    e.preventDefault();
    dispatch(actions.createListItem(item, list.id));
    setItem("");
  }

  return (
    <div>
      <br></br>
      <form onSubmit={handleSubmit}>
        <label>Add to your list: </label>
        <input value={item} onChange={(e) => setItem(e.target.value)} />
      </form>
    </div>
  );
}

Edit rendering-new-list-item-after-adding-it-from-a-nested-form-react-hooks-redux

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Drew Reese