Search code examples
flaskurlhyperlinkreact-router-dom

React-router-dom: link to url is not working, add 7%B in the url


I have trouble creating links in my webapp. I'm new to both Flask and React, so apologies if i don't use the correct vocabulary. When creating a link <Link to='/${todo.id}'>{todo.content}</Link>, it goes to the url http://localhost:3000/$%7Btodo.id%7D which is empty. I know %7B is the translation for bracket, but i don't get why it's not taking the value inside.

My App.js file is the following:

import {BrowserRouter as Router , Routes, Route} from "react-router-dom";

function App() {
  console.log('trying')
  return (
    <div className="App">
      <Router>
        <Routes>
          <Route exact path='/' element={<TodoPage/>}></Route>
          <Route path='/:id' element={<Show/>}></Route>
        </Routes>
      </Router>
    </div>
  );
}

export default App;

And I have the following functions:

import React from 'react';
import {Link} from "react-router-dom";

export const Card = ({listOfTodos}) => {
    return (
        <>
            {listOfTodos.map(todo => {
                return(
                    <ul key={todo.id}>
                        <li>
                            <Link to='/${todo.id}'>{todo.content}</Link>
                        </li>
                    </ul>
                )
            })}
        </>
    )
}
import React, {useState, useEffect} from 'react';
import {useParams} from "react-router-dom";

export const Show = () => {

    const {id} = useParams()
    const [todo, setTodo] = useState([])

    useEffect( () => {
        fetch('/api/'+id)
        .then(response => response.json())
        .then(data => setTodo(data)) 
    }, [id] ) // [id] means that if the id changes, it makes a new request

    return (
        <div>
            {todo.length >0 && todo.map(data => <div>{data.content}</div>)}  
        </div>
    )

}

And finally I have the flask app:

from flask import Flask, jsonify, request, json
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///example.db"
db = SQLAlchemy(app)

class Todo(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    content = db.Column(db.Text, nullable = False)

    def __str__(self):
        return f'{self.id} {self.content}'

def todo_serializer(todo):
    return {
        'id': todo.id, 
        'content': todo.content
    }

@app.route('/api', methods = ['GET'])
def index():
    # will return all the entries in the database
    todo = Todo.query.all()
    return jsonify([*map( todo_serializer, Todo.query.all()  )])

@app.route('/api/<int:id>')
def show(id):
    # to click on one to do and have the details
    return jsonify([*map(todo_serializer, Todo.query.filter_by(id=id))])


if __name__ == "__main__":
    app.run(debug=True)

Thanks in advance !


Solution

  • Issue

    You have coded the literal string value with the "$" and bracket character literals.

    <Link to='/${todo.id}'>{todo.content}</Link>
    

    In other words, the link target you are navigating to is "/${todo.id}", and some of the characters will encoded, e.g. to "/$%7Btodo.id%7D".

    Solution

    Use string templates (Template Literals) if you are injecting a todo's id property into the link target string.

    <Link to={`/${todo.id}`}>{todo.content}</Link>
    

    or use the generatePath utility function to inject the todo's id into a target path string.

    import { generatePath } from 'react-router-dom';
    
    ...
    
    <Link to={generatePath("/:id", todo)}> // *
      {todo.content}
    </Link>
    

    * generatePath("/:id", todo) works because todo has an id property. It's equivalent to generatePath("/:id", { id: todo.id }).