Hello, community! It's my first post here, and in this post, I want to show you how you can make a simple fullstack twitter clone using React and Flask. This will be a 3 part series and in this part, we will build the application.
Before we start, I assume you know flask, react, javascript, python and html
Project setup
I will be using VSCode as my IDE, but you can use anything you like (Pycharm, Webstorm, Atom are some examples). I'll be showing instructions for my IDE.
Creating our project
Create a folder which will house your project and open a terminal window there. I like to do things using the UNIX terminal (BASH SHELL), but, you can use a GUI based file explorer too. Let's create the backend
folder.
mkdir backend
I'll use create-react-app
to create the frontend
folder with a react app initialised.
npx create-react-app frontend
Now, you should have 2 folders, frontend and backend.
$ ls
backend
frontend
Nice! Now, let's set up the backend. cd
into the backend folder and create a virtual environment for flask.
You can use pipenv
or not use a virtual environment at all!
python3 -m venv venv && . venv/bin/activate
That should create a virtual environment named venv
and activate it. Now, cd
into our main project folder and open it using VSCode by typing code .
.
Creating a basic Flask
project
Now, let's set up the backend. First, we need to install flask.
pip install flask
Make sure your virtualenv is activated
Now, create a file named app.py
. This will be the main file for our backend.
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "Hello, world!"
if __name__ == "__main__":
app.run(debug=True) # debug=True restarts the server everytime we make a change in our code
Now, let's run the backend by typing
python3 app.py
Let's test if the server works. You can visit localhost:5000 in your browser or use a REST Client
like Insomnia if you want. For testing post methods, I'll be using cURL
, which is a command-line utility, but you can use a GUI REST Client.
For a database, we can use something called Flask-SQLAlchemy
, which is SQLAlchemy
or sqlite3
for Flask
. Let's first shutdown our server by pressing CTRL + C
to terminate it and let's install flask-sqlalchemy
.
pip install flask-sqlalchemy
Now, back in app.py
, let's import flask-sqlalchemy
.
from flask_sqlalchemy import SQLAlchemy
And under where we defined app
, write this code:
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///twitter.db"
# DB
db = SQLAlchemy(app)
class Users(db.Model):
id = db.Column('student_id', db.Integer, primary_key = True) # primary_key makes it so that this value is unique and can be used to identify this record.
username = db.Column(db.String(24))
email = db.Column(db.String(64))
pwd = db.Column(db.String(64))
# Constructor
def __init__(self, username, email, pwd):
self.username = username
self.email = email
self.pwd = pwd
So what this code does, is first, it sets our database file as twitter.db
. You can change this name if you want. Then, the users
class you see is a Model
, which lays out what your users
table's columns are. Pretty standard SQL Stuff.
Now, temporarily, above the return
statement in the index
function write this:
db.create_all()
Your file should look like this:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///twitter.db"
# DB
db = SQLAlchemy(app)
class Users(db.Model):
id = db.Column('student_id', db.Integer, primary_key = True) # primary_key makes it so that this value is unique and can be used to identify this record.
username = db.Column(db.String(24))
email = db.Column(db.String(64))
pwd = db.Column(db.String(64))
# Constructor
def __init__(self, username, email, pwd):
self.username = username
self.email = email
self.pwd = pwd
@app.route('/')
def index():
return "Hello, world!"
if __name__ == "__main__":
app.run(debug=True)
Start the server and visit localhost:5000 or perform a GET
request using your favorite REST
client. For example, in cURL
, you'd type
curl "http://localhost:5000"
And you should get the same response back, but now, when you look in your folder, you can find a new file called twitter.db
created. This is your database. Unlike something like MYSQL
, MariaDB
or MongoDB
, sqlite
does not need a server to function.
Basic CRUD routes
Now, let's perform some basic CRUD
(Create Read Update Destroy) actions by adding some more routes to our app. First, delete the index route entirely. We won't be needing it. I'm gonna prefix all of my routes with /api
.
@app.route("/api/users", methods=["GET", "POST", "DELETE"])
def users():
method = request.method
if (method.lower() == "get"): # READ
users = Users.query.all()
return jsonify([{"id": i.id, "username": i.username, "email": i.email, "password": i.pwd} for i in users]) # Get all values from db
elif (method.lower() == "post"): # CREATE
try:
username = request.json["username"]
email = request.json["email"]
pwd = request.json["pwd"]
if (username and pwd and email): # Checks if username, pwd or email are empty
try:
user = Users(username, email, pwd) # Creates a new record
db.session.add(user) # Adds the record for committing
db.session.commit() # Saves our changes
return jsonify({"success": True})
except Exception as e:
return ({"error": e})
else:
return jsonify({"error": "Invalid form"}) # jsonify converts python vars to json
except:
return jsonify({"error": "Invalid form"})
elif (method.lower() == "delete"): # DESTROY
try:
uid = request.json["id"]
if (uid):
try:
user = Users.query.get(uid) # Gets user with id = uid (because id is primary key)
db.session.delete(user) # Delete the user
db.session.commit() # Save
return jsonify({"success": True})
except Exception as e:
return jsonify({"error": e})
else:
return jsonify({"error": "Invalid form"})
except:
return jsonify({"error": "m"})
- We'll work on the PUT method a bit later *
Now, we have a route in our app called
/api/users
. We can perform Create, Read and Destroy actions through different HTTP methods. More about that here
Now, this is what our code should look like:
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///twitter.db"
# DB
db = SQLAlchemy(app)
class Users(db.Model):
id = db.Column('student_id', db.Integer, primary_key = True) # primary_key makes it so that this value is unique and can be used to identify this record.
username = db.Column(db.String(24))
email = db.Column(db.String(64))
pwd = db.Column(db.String(64))
# Constructor
def __init__(self, username, email, pwd):
self.username = username
self.email = email
self.pwd = pwd
# Routes
# Users
@app.route("/api/users", methods=["GET", "POST", "DELETE"])
def users():
method = request.method
if (method.lower() == "get"): # READ
users = Users.query.all()
return jsonify([{"id": i.id, "username": i.username, "email": i.email, "password": i.pwd} for i in users]) # Get all values from db
elif (method.lower() == "post"): # CREATE
try:
username = request.json["username"]
email = request.json["email"]
pwd = request.json["pwd"]
if (username and pwd and email): # Checks if username, pwd or email are empty
try:
user = Users(username, email, pwd) # Creates a new record
db.session.add(user) # Adds the record for committing
db.session.commit() # Saves our changes
return jsonify({"success": True})
except Exception as e:
return ({"error": e})
else:
return jsonify({"error": "Invalid form"}) # jsonify converts python vars to json
except:
return jsonify({"error": "Invalid form"})
elif (method.lower() == "delete"): # DESTROY
try:
uid = request.json["id"]
if (uid):
try:
user = Users.query.get(uid) # Gets user with id = uid (because id is primary key)
db.session.delete(user) # Delete the user
db.session.commit() # Save
return jsonify({"success": True})
except Exception as e:
return jsonify({"error": e})
else:
return jsonify({"error": "Invalid form"})
except:
return jsonify({"error": "m"})
return ({"error": "Invalid form"})
except:
return ({"error": "Invalid form"})
if __name__ == "__main__":
app.run(debug=True)
Let's give this man a test drive!
First, lets perform a GET
request to /api/users
.
curl "http://localhost:5000/api/users"
# OUTPUT: []
We get an empty array because the database has no data
Now, let's give it some data. Perform a POST
request to /api/users
with some data.
curl -X POST -H "Content-Type: application/json" -d '{"username": "foo", "email": "foo@bar.com", "pwd": "bar"}' "http://localhost:5000/api/users"
If you GET
the data again, you can notice a record has been made. Now, let's delete a user
curl -X DELETE -H "Content-Type: application/json" -d '{"id": 1}' "http://localhost:5000/api/users"
And if we query the data, we should get back an empty string! We'll do PUT
later.
And that should be it (for now) for our flask application. Go ahead and stop the server(^C
)
Creating the frontend
Now, let's focus on the frontend. Open the frontend
folder (or whatever you called it) in the terminal and type:
npm start
This should open a webpage at localhost:3000 and you should see this:
Creating the homepage
The default template create-react-app
gives us is not what we need, so, let's delete the content of our public
and src
folder. You will see your app crash, but that's fine. Also, create public/index.html
, src/index.js
, src/components
, src/components/App.jsx
, src/components/Home.jsx
, src/components/Navbar.jsx
rm -r public/* src/*
mkdir src/components
touch public/index.html src/index.js src/components/App.jsx src/components/Home.jsx src/components/Navbar.jsx
Now, let's set the code for index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Quickr</title>
<!-- W3CSS -->
<link rel="stylesheet" href="https://w3schools.com/w3css/4/w3.css" />
</head>
<body>
<div id="root"></div>
</body>
</html>
I am using W3CSS for my css styling as it is quick and easy to use
Now, for all the javascript files:
// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
ReactDOM.render(<App />, document.getElementById("root"));
// src/components/App.jsx
import React from "react";
import Home from "./Home";
import Navbar from "./Navbar";
function App() {
return (
<React.Fragment>
<Navbar />
<Home />
</React.Fragment>
);
}
export default App;
// src/components/Home.jsx
import React from "react";
function Home() {
return (
<React.Fragment>
<div
className="w3-container w3-center w3-blue"
style={{ padding: "2rem" }}>
<h1 className="w3-jumbo">Quickr - Quick Twitter</h1>
<button
className="w3-button w3-pink"
style={{ marginRight: "1rem" }}>
Login
</button>
<button className="w3-button w3-pink">Register</button>
</div>
<div
className="w3-container w3-blue"
style={{ padding: "2rem", marginTop: "2rem" }}>
<h2>Lorem ipsum dolor sit amet</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer consectetur libero nibh, non sodales urna malesuada
nec. Sed tortor eros, blandit eget fringilla at, gravida a
nibh. Etiam dui nulla, aliquam vitae purus a, auctor
malesuada arcu. Vestibulum venenatis orci nisl, sed
elementum leo tincidunt eget. Nullam convallis nisi in.
</p>
</div>
<div
className="w3-container w3-blue"
style={{ padding: "2rem", marginTop: "2rem" }}>
<h2>Lorem ipsum dolor sit amet</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer consectetur libero nibh, non sodales urna malesuada
nec. Sed tortor eros, blandit eget fringilla at, gravida a
nibh. Etiam dui nulla, aliquam vitae purus a, auctor
malesuada arcu. Vestibulum venenatis orci nisl, sed
elementum leo tincidunt eget. Nullam convallis nisi in.
</p>
</div>
<div
className="w3-container w3-blue"
style={{ padding: "2rem", marginTop: "2rem" }}>
<h2>Lorem ipsum dolor sit amet</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer consectetur libero nibh, non sodales urna malesuada
nec. Sed tortor eros, blandit eget fringilla at, gravida a
nibh. Etiam dui nulla, aliquam vitae purus a, auctor
malesuada arcu. Vestibulum venenatis orci nisl, sed
elementum leo tincidunt eget. Nullam convallis nisi in.
</p>
</div>
</React.Fragment>
);
}
export default Home;
// src/components/Navbar.jsx
import React from "react";
function Navbar() {
return (
<div className="w3-bar w3-black">
<a className="w3-bar-item w3-button" href="/">
Quickr
</a>
<div style={{ float: "right" }}>
<a className="w3-bar-item w3-button" href="/">
Login
</a>
<a className="w3-bar-item w3-button" href="/">
Register
</a>
</div>
</div>
);
}
export default Navbar;
And finally, your website should look like this:
Now, let's add some functionality!
Connecting backend to frontend
Now, let's implement login and register. Remember the routes we made earlier? Let's convert them to functions
def getUsers():
users = Users.query.all()
return [{"id": i.id, "username": i.username, "email": i.email, "password": i.pwd} for i in users]
def addUser(username, email, pwd):
if (username and pwd and email):
try:
user = Users(username, email, pwd)
db.session.add(user)
db.session.commit()
return True
except Exception as e:
print(e)
return False
else:
return False
def removeUser(uid):
uid = request.json["id"]
if (uid):
try:
user = Users.query.get(uid)
db.session.delete(user)
db.session.commit()
return True
except Exception as e:
print(e)
return False
else:
return False
Now, let's add a login and register api route. I'm gonna prefix my routes with /api
like before.
@app.route("/api/login", methods=["POST"])
def login():
try:
email = request.json["email"]
password = request.json["pwd"]
if (email and password):
users = getUsers()
# Check if user exists
return jsonify(len(list(filter(lambda x: x["email"] == email and x["password"] == password, users))) == 1)
else:
return jsonify({"error": "Invalid form"})
except:
return jsonify({"error": "Invalid form"})
@app.route("/api/register", methods=["POST"])
def register():
try:
email = request.json["email"]
email = email.lower()
password = request.json["pwd"]
username = request.json["username"]
# Check to see if user already exists
users = getUsers()
if(len(list(filter(lambda x: x["email"] == email, users))) == 1):
return jsonify({"error": "Invalid form"})
# Email validation check
if not re.match(r"[\w\._]{5,}@\w{3,}.\w{2,4}", email):
return jsonify({"error": "Invalid form"})
addUser(username, email, password)
return jsonify({"success": True})
except:
return jsonify({"error": "Invalid form"})
In our register method, we have some form validation. It checks if the email is already registered and if the email is a valid email. Now, let's test it out. Start your server and make some requests.
# REGISTER
curl -X POST -H "Content-Type: application/json" -d '{"email": "foo@bar.dev", "pwd": "foobar", "username": "foobar"}' "http://localhost:5000/api/register"
# LOGIN
curl -X POST -H "Content-Type: application/json" -d '{"email": "azeez@man.com", "pwd": "azeez"}' "http://localhost:5000/api/login"
So, now, we can login to our database using the CLI. But, we need to allow users to login using the Website. Let's head over to the frontend.
So, I have made 2 files, Login.jsx
and Register.jsx
. These files contain a login and register form separately. You can use your own markup, but for anyone wanting to use mine, here you go:
// src/components/Login.jsx
import React, { Component } from "react";
class Login extends Component {
render() {
return (
<div className="w3-card-4" style={{ margin: "2rem" }}>
<div className="w3-container w3-blue w3-center w3-xlarge">
LOGIN
</div>
<div className="w3-container">
<form>
<p>
<label htmlFor="email">Email</label>
<input
type="email"
class="w3-input w3-border"
id="email"
/>
</p>
<p>
<label htmlFor="password">Password</label>
<input
type="password"
class="w3-input w3-border"
id="password"
/>
</p>
<p>
<button type="submit" class="w3-button w3-blue">
Login
</button>
</p>
</form>
</div>
</div>
);
}
}
export default Login;
// src/components/Register.jsx
import React, { Component } from "react";
class Register extends Component {
render() {
return (
<div className="w3-card-4" style={{ margin: "2rem" }}>
<div className="w3-container w3-blue w3-center w3-xlarge">
REGISTER
</div>
<div className="w3-container">
<form>
<p>
<label htmlFor="email">Email</label>
<input
type="email"
class="w3-input w3-border"
id="email"
/>
</p>
<p>
<label htmlFor="username">Username</label>
<input
type="username"
class="w3-input w3-border"
id="text"
/>
</p>
<p>
<label htmlFor="password">Password</label>
<input
type="password"
class="w3-input w3-border"
id="password"
/>
</p>
<p>
<button type="submit" class="w3-button w3-blue">
Register
</button>
</p>
</form>
</div>
</div>
);
}
}
export default Register;
Ok, we're done with our markup, but, how can we render it in a different route? For example, if I type https://mywebsite.com/login
, I need to see the login page. This can be done by using react-router-dom
. First, install react-router-dom
npm i react-router-dom
And, now, let's implement it in our App.jsx
// src/components/App.jsx
import React from "react";
import Home from "./Home";
import Navbar from "./Navbar";
import Login from "./Login";
import Register from "./Register";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
function App() {
return (
<React.Fragment>
<Navbar />
<Router>
<Route path="/" exact component={Home} />
<Route path="/login" exact component={Login} />
<Route path="/register" exact component={Register} />
</Router>
</React.Fragment>
);
}
export default App;
I also updated the routes in Navbar.jsx
.
// src/components/Navbar.jsx
import React from "react";
function Navbar() {
return (
<div className="w3-bar w3-black">
<a className="w3-bar-item w3-button" href="/">
Quickr
</a>
<div style={{ float: "right" }}>
<a className="w3-bar-item w3-button" href="/login"> // Updated
Login
</a>
<a className="w3-bar-item w3-button" href="/register"> // Updated
Register
</a>
</div>
</div>
);
}
export default Navbar;
Now, when you click on the login and register buttons in the navbar, you should see the components we created. Now, let's add some functionality to the components.
Login
First, let's install something called axios
.
npm i axios
Now, let's add a function that sends a POST
request to the backend.
// Login.jsx
import React, { Component } from "react";
import axios from "axios";
class Login extends Component {
login = (e) => {
e.preventDefault();
axios
.post("http://localhost:5000/api/login", {
email: document.getElementById("email").value,
pwd: document.getElementById("password").value,
})
.then((res) => {
console.log(res.data);
});
};
render() {
return (
<div className="w3-card-4" style={{ margin: "2rem" }}>
<div className="w3-container w3-blue w3-center w3-xlarge">
LOGIN
</div>
<div className="w3-container">
<form onSubmit={this.login}>
<p>
<label htmlFor="email">Email</label>
<input
type="email"
className="w3-input w3-border"
id="email"
/>
</p>
<p>
<label htmlFor="password">Password</label>
<input
type="password"
className="w3-input w3-border"
id="password"
/>
</p>
<p>
<button type="submit" class="w3-button w3-blue">
Login
</button>
</p>
</form>
</div>
</div>
);
}
}
export default Login;
import React, { Component } from "react";
import axios from "axios";
class Register extends Component {
register = (e) => {
e.preventDefault();
axios
.post("http://localhost:5000/api/register", {
email: document.getElementById("email").value,
username: document.getElementById("username").value,
pwd: document.getElementById("password").value,
})
.then((res) => {
console.log(res.data);
});
};
render() {
return (
<div className="w3-card-4" style={{ margin: "2rem" }}>
<div className="w3-container w3-blue w3-center w3-xlarge">
REGISTER
</div>
<div className="w3-container">
<form onSubmit={this.register}>
<p>
<label htmlFor="email">Email</label>
<input
type="email"
className="w3-input w3-border"
id="email"
/>
</p>
<p>
<label htmlFor="username">Username</label>
<input
type="text"
className="w3-input w3-border"
id="username"
/>
</p>
<p>
<label htmlFor="password">Password</label>
<input
type="password"
className="w3-input w3-border"
id="password"
/>
</p>
<p>
<button type="submit" class="w3-button w3-blue">
Register
</button>
</p>
</form>
</div>
</div>
);
}
}
export default Register;
So what we're doing here, is whenever the form is submitted, we send a request to the backend to the login and register routes. And when we get a response, we log it. Now, enter some rubbish data in Register, and open the in-browser console (or devtools) by pressing Ctrl+Shift+I
for Windows/Linux or Cmd+Option+I
for Mac. Press the register button and in the console, you should see an error like this:
Now, this error is a security measure put in place by something called CORS. To bypass this, we need to install something called flask-cors
in our backend.
pip install flask-cors
Now, change the starting of your app.py
to this:
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
import re
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///twitter.db"
CORS(app)
# ...
If you now click register, you should see {success: true}
in the console. And now, if we login using the same email and password, you can see true
in the console, meaning everything is working! Now, let's give the user some feedback. Create a new file called Alert.jsx
and put this in it:
import React from "react";
function Alert(props) {
return (
<div
className="w3-pale-red w3-text-red w3-border w3-border-red w3-round-large"
style={{ padding: "1rem", marginTop: "1rem" }}>
{props.message}
</div>
);
}
export default Alert;
Alert
is now a reusable component that we can use anywhere in our code and we can give it a message. It'll display that message in a red background and in red text. We can make use of this component in Register.jsx
:
import React, { Component } from "react";
import axios from "axios";
import Alert from "./Alert";
class Register extends Component {
state = { err: "" };
register = (e) => {
e.preventDefault();
axios
.post("http://localhost:5000/api/register", {
email: document.getElementById("email").value,
username: document.getElementById("username").value,
pwd: document.getElementById("password").value,
})
.then((res) => {
if (res.data.error) {
this.setState({ err: res.data.error });
} else {
this.setState({ register: true });
}
});
};
render() {
return (
<div className="w3-card-4" style={{ margin: "2rem" }}>
<div className="w3-container w3-blue w3-center w3-xlarge">
REGISTER
</div>
<div className="w3-container">
{this.state.err.length > 0 && (
<Alert
message={`Check your form and try again! (${this.state.err})`}
/>
)}
<form onSubmit={this.register}>
<p>
<label htmlFor="email">Email</label>
<input
type="email"
class="w3-input w3-border"
id="email"
/>
</p>
<p>
<label htmlFor="username">Username</label>
<input
type="text"
class="w3-input w3-border"
id="username"
/>
</p>
<p>
<label htmlFor="password">Password</label>
<input
type="password"
class="w3-input w3-border"
id="password"
/>
</p>
<p>
<button type="submit" class="w3-button w3-blue">
Register
</button>
{this.state.register && <p>You're registered!</p>}
</p>
</form>
</div>
</div>
);
}
}
export default Register;
Now, if we enter an invalid email, (a valid email, according to my RegEx, is one that has 5 or more alphanumberic characters including . and _, an @, 3 or more alphanumeric characters, a . and 2-4 more alphanumberic characters), we should get this:
Awesome! Login.jsx
can also use some error handling.
import React, { Component } from "react";
import axios from "axios";
import Alert from "./Alert";
class Login extends Component {
state = { err: "" };
login = (e) => {
e.preventDefault();
axios
.post("http://localhost:5000/api/login", {
email: document.getElementById("email").value,
pwd: document.getElementById("password").value,
})
.then((res) => {
if (res.data.error) {
this.setState({ err: res.data.error });
} else {
this.setState({ login: true });
}
});
};
render() {
return (
<div className="w3-card-4" style={{ margin: "2rem" }}>
<div className="w3-container w3-blue w3-center w3-xlarge">
LOGIN
</div>
<div className="w3-container">
{this.state.err.length > 0 && (
<Alert
message={`Check your form and try again! (${this.state.err})`}
/>
)}
<form onSubmit={this.login}>
<p>
<label htmlFor="email">Email</label>
<input
type="email"
class="w3-input w3-border"
id="email"
/>
</p>
<p>
<label htmlFor="password">Password</label>
<input
type="password"
class="w3-input w3-border"
id="password"
/>
</p>
<p>
<button type="submit" class="w3-button w3-blue">
Login
</button>
{this.state.register && <p>You're logged in!</p>}
</p>
</form>
</div>
</div>
);
}
}
export default Login;
And now, if we type wrong credentials, we should get this:
Conclusion
Nice! Now, you've learnt how to connect flask and react together using a RESTful API. In the next part, we'll add the ability to post and see tweets. This post has been pretty long. I'll try to make a youtube video about it, and if I do, I'll be sure to link it here. All the code is available on Github. This was pretty long for a first post!
Top comments (12)
great article, thanks!
one note - with axios, there is no need to insert the entire url with localhost - just the endpoint would be enough. this way it can stay the same for dev and deployment.
just add a proxy to package.json
I keep receiving
{
"error": "Invalid Form"
}
After running this to the terminal
curl -X POST -H "Content-Type: application/json" -d '{"email": "foo@bar.dev", "pwd": "foobar", "username": "foobar"}' "localhost:5000/api/register"
I need help here
Hmm, sorry for the late reply, but I think that email is already registered. Try changing each of the errors to something meaningful, so that you can understand what's going on
nice Article,
i can't wait part two
Thanks! I'm working on it right now.
UPDATE: It is out dev.to/arnu515/build-a-twitter-clo...
Thanks for this piece
Your welcome!
Part two is now out! Check it out here
hey you mustn't manipulate DOM directly on react :-)
But considering you are only 14 years old. I am so impressed by your skillsets!
Thanks! I will keep it in mind!
Also I'm 15 now :)
Very nice!
Thanks!