ReactJS Front-end with CodeIgniter backend : How build a Login System? - reactjs

I'm an jQuery/Angular Developer last 5 years, and now I've planned move to ReactJS. I'm learning ReactJS by my own, as comparing the similar stuff what I did in jQuery (or Angular). My plan is, just to make a Login-Logout App, with ReactJS front-end and CodeIgniter - MySQL are the backend and database, respectively. Also I know what is CORS, and I'm pretty clear about the REST concepts. Let me explain my react app in detail.
My App component, calls an API via fetch, and upon the response which tells either session is set (as user is logged in) or session isn't set (user logged out/session expired), then the component will render either the 'Login Panel' component or a 'Dashboard' component. It works fine, as I tested. This is my App component.
class App extends Component {
constructor(props) {
super(props); //compulsary
this.state = { isUserAuthenticated: false };
}
componentDidMount() {
var innerThis=this;
fetch('http://localhost/forReact/index.php/general/authenticate',{
method:'GET',
Content_type:'application/json',
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
mode: 'cors',
cache: 'no-cache'})
.then(res => { return res.json() })
.then(data => { alert(data.message); this.setState({isUserAuthenticated:data.result}); })
.catch(err => console.error(err));
}
render() {
return (this.state.isUserAuthenticated?<Home/>:<First/>);
}
}
export default App;
This is my CodeIgniter Controller's relevant method (the controller name is General.php)
public function authenticate(){
$session_data = $this->session->get_userdata();
if (is_null($session_data)) {
$this->sendJson(array("message"=>"No session","result"=>false));
}
else if (empty($session_data['username'])) {
$this->sendJson(array("message"=>"No username index","result"=>false));
}
else if ($session_data['username']=="") {
$this->sendJson(array("message"=>"Empty Username","result"=>false));
}
else{
$this->sendJson(array("message"=>"Valid Session","result"=>true));
}
}
This is the login component, which is being rendered in the App component, when there are no sessions set.
class LoginPanel extends React.Component {
constructor(props) {
super(props); //compulsary
this.unRef = React.createRef();
this.pwRef = React.createRef();
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(){
var toServer={"userName":this.unRef.current.value,"passWord":this.pwRef.current.value};
fetch('http://localhost/forReact/index.php/general/login',{
method:'POST',
Content_type:'application/json',
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
body: JSON.stringify(toServer),
mode: 'cors',
cache: 'no-cache'})
.then(res => { return res.json() })
.then(data => { alert(data.message); })
.catch(err => console.log(err));
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input ref={this.unRef} type="text" placeholder="Username"/>
<input ref={this.pwRef} type="password" placeholder="Password"/>
<button style={{margin:'1%'}} type="submit">Login</button>
<button style={{margin:'1%'}} type="reset">Help</button>
</form>);
}
}
export default LoginPanel;
Finally, this is the CodeIgniter Controller's relevant method (the same controller General.php)
public function login(){
$arrived = json_decode(file_get_contents('php://input'), true);
if (!empty($arrived['userName'])&&!empty($arrived['passWord'])) {
$fromDB=$this->Mdl_general->login($arrived['userName'],$arrived['passWord']);
if ($fromDB['result']) {
$this->session->set_userdata('username',$arrived['userName']);
$this->sendJson(array("message"=>$fromDB['message'],"result"=>$fromDB['result']));
}
else{
$this->sendJson(array("message"=>"Username and or Password is/are incorrect!","result"=>false));
}
}
else{
$this->sendJson(array("message"=>"Invalid or Missing Input Parameters!","result"=>false));
}
}
My issue is, when I logged in with the correct username and password (what I have in the mysql table), request was successfull and the response alert is showing; However, it seems the session is not set by the below php code,
$this->session->set_userdata('username',$arrived['userName']);
Whereas, if I did the above 'set session' code via a separate controller function, it sets the session successfully. However, even after that, the App component's API Call, still getting the response as the session is not yet set.
What might be the reason? I tried this via jQuery's Ajax Call and it works. I knew the fetch is much more different from the Ajax Call of jQuery. Could anyone explain, why this login component, didn't trig the PHP code to set the session?

That's not how it works. I will cut it to the short. The thing that you are trying to implement is not possible. In a very simple term, you are dealing with 2 different components now, your front end and the backend. And to communicate, you are using HTTP protocol as it is stateless every new request won't know anything about the previous one. In that case we need to reauthenticate every request. Check this comment to understand how to authenticate.
EDIT:
Just in case if you want to use REST
check this out

Related

first request in axios not getting data back

Hello I am using axios with react and and I have a problem with the first time requests. Every time my web has a full refresh then it is suppose to make two requests to the server:
to load the web global content
the child should load the current page related content
However, when I refresh the page, since the web global content is loaded in the App.js compontDidMount, then the global content is loaded, however for HomePage.js which in this case is the child component to App.js send the request in compontDidMount but checking the server logs, only the option handshake request is received and not the actual get request to get the page related content, however since am using react-router-dom's Link to navigate. If I navigate to another page then come back to home page then I get all the content since in that case the App.js compontDidMount will not be executed. could anyone help or explain why this is happening please
my hoempage.js componentDidMount
looks like this
axios.defaults.xsrfCookieName="csrftoken"
axios.defaults.headers = get_headers()
axios.get(`${API_URL}/dns/`).then(res=>{
this.setState({
top6:res.data.top,
latest:res.data.latest
})
})
my App.js componentDidMount is calling a functions in another file that uppdates redux and looks like this
componentDidMount() {
this.props.loadGlobal();
}
loadGlobal looks like this
export const getGlobal = () => {
return dispatch => {
dispatch(getGlobalStart());
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.headers = get_headers();
axios.get(`${API_URL}/api/get-global/`)
.then(res => dispatch(getGlobalSuccess(res.data)))
.catch(err => {
console.log(err.message)
dispatch(getGlobalFail(err.response.data))
}).catch(err=>{
dispatch(setAlert({ "show": true, error: true, "message":"network error fetching data, check your internet and try again or contact side admin if your network is fine"}))
});
};
};
my get_headers() looks like this
function get_headers() {
var headers = {
"Content-Type": "application/json",
};
if (localStorage.getItem('token')) {
headers = {
"Content-Type": "application/json",
"Authorization": `Token ${localStorage.getItem('token')}`,
};
}
return headers
}
i my backend am using django which fetching data from postman or even visiting the url works just fine for me

How do you change parent's state from child when the state is a database table?

I have a parent component, a class App.js and has a state, say accounts = []. I fetch the data from a Postgres table in the componentDidMount() function.
Then render follows:
render() {
const { accounts } = this.state;
return (
<div className="container">
<section className="section">
<Form ... />
<br />
<br />
<Table accountsList={accounts} />
</section>
</div>
);
}
As you can see I have two child components, a class Form and a functional component Table. I have managed to actually print on screen the Postgres table using the Table component with the current accounts state.
The problem: Form accepts account fields. So, onSubmit, it creates and post request and passes a json account that creates using the form inputs. The post request will take a while to update Postgres table.
What I want: When the onSubmit from child component Form, actually completes, that is not the code but actually the Postgres table update completes, the accounts state will change. So I will have to change parent's state from a child. I guess I will have to fetch again, maybe this time inside Form.js.
Then if I am corrent, the Table will hot reload and the data on screen will be up to date.
The function (inside Form) triggered onSubmit is the following
onFormSubmit = (event) => {
event.preventDefault();
fetch("/accounts", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
rName: this.state.rName,
rPass: this.state.rPass,
}),
});
};
Any ideas? Please, note that I don't want to just append accounts list. I actually want to fetch again because by the time the post request completes, other users may have delete or also update the Postgres table.
You can pass the function to update the accounts in the Form component from the App component. When you invoke the onFormSubmit function you can also invoke that function and pass the new account.
But there is one moment: fetch returns Promise, so you should to process it. I mean you should add new account to account only in then.
So function to add new account may look like:
/* App component */
const addAccount = account => {
this.setState({ accounts: [...this.state.account, account],
};
And your onFormSubmit function will look like:
/* Form component */
onFormSubmit = (event) => {
event.preventDefault();
fetch("/accounts", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
rName: this.state.rName,
rPass: this.state.rPass,
}),
})
.then(() => addAccount({ rName, rPass }))
.catch(() => console.log("error"));
};
I don't know which properties your accounts have so I pass to addAccount function the rName and rPass but you should pass right data.

How to handle POST request in reactjs?

I have my ReactJS app running in http://localhost:3000/. I am receiving below form data to my React page as a POST request
<form action="http://localhost:3000/" method="post">
Employee Id: <input type="text" name="eid"><br>
Department Id: <input type="text" name="did"><br>
<input type="submit" value="Submit">
</form>
My react app should be able to handle this POST request and render the UI as below
<h1>{eid}</h1>
<h1>{did}</h1>
I am able to handle GET request using react router but struggling to handle POST request. How can I achieve this?
That is not possible if your React app is static(not server side rendered).
When you send some POST request to your react app, nginx(or other server) will not allow that kind of action(you cannot post to static files)
Even if you bypass that restriction, react script will not have any data from your POST request, because nginx will process your request and return just a html with react script to you
It will not work like php.. you need to have something like backend (node or php to pass the data) or even some site to accept the request..
First, you need maybe some theoretical view:
https://pusher.com/tutorials/consume-restful-api-react
https://www.robinwieruch.de/react-hooks-fetch-data
You should firstly save data
You save them to the state
You display them in the part where it is rendered
To download data from api (GET)- you don't do it directly in form - you only use either ComponentDidMount or UseEffect.
componentDidMount() {
fetch(ApiURL)
.then(res => res.json())
.then(res => this.setState({ planets: res }))
.catch(() => this.setState({ hasErrors: true }));
}
useEffect(async () => {
const result = await axios(
ApiURL,
);
setData(result.data);
});
To send data to api (POST)- It's complicated - you need information about client-server communication
Straight from the React docs:
fetch('https://mywebsite.com/endpoint/', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
firstParam: 'yourValue',
secondParam: 'yourOtherValue',
})
})

How to handle the case where the axios response comes after the component is rendered?

I am creating a blog application in rest framework and reactjs. On the home page, under componentDidMount, I send an API call using axios to get all the articles and setState of articles to the return. As I have studied, axios works on the idea of promise such that the code doesnt proceed, if the API is not fetched for a particular component. Please tell me, if I am wrong.
Then, I send a GET call to get the writer's name, who wrote the article by the id. Though, I assumed that the axios works as a promise. But, it doesnt work that way. Now, I am not sure how to move ahead.
Here is a snippet. So, in mainBody.js, I make the api call as:
class MainBody extends Component {
state = {};
componentDidMount () {
this.get_all_articles();
};
get_writer_name (id) {
let authstr = 'Bearer ' + window.localStorage.token;
let writer_url = "http://localhost:8000/api/writer/" + id.toString() + "/";
axios.get(writer_url, { headers: { Authorization: authstr }})
.then(response => {
console.log(response.data['name'])
return response.data['name'];
})
.catch(err => {
console.log("Got error")
})
};
get_all_articles () {
let authstr = 'Bearer ' + window.localStorage.token;
axios.get("http://localhost:8000/api/articles/", { headers: { Authorization: authstr }})
.then(response => {
this.setState({articles: response.data});
})
.catch(err => {
console.log("Got error")
})
}
render () {
return (
{this.state.articles.map((article, key) =>
<ArticleView key={article.id} article={article} writer_name={this.get_writer_name(article.created_by)} />
)}
)
}
}
In articleview2, I print all the data that is present in each of the articles along with the writer's name.
My articleview class is:
class ArticleView extends Component {
state = {article: this.props.article};
componentDidMount() {
console.log(this.props.writer_name;
}
render () {
return (
<React.Fragment>
<h2>{article.title}</h2>
<p>{article.body}</p>
<span>{this.props.writer_name}</span>
</React.Fragment>
)
}
}
If you see closely, I wrote two console.log statements to get the writer names. Based on the order, first the console log present in articleview class runs, which is undefined, and thenafter the data is fetched from the API call and the console log runs which returns the correct writer name.
I wanted to know, where is the error? Also, as I noticed, there are too many API calls being made to get the writer's name multiple time for all the listed articles. What are the industry best practices for these cases?
I want to know where is the error.
When you are writing this.state.articles.map(), means you're using property map of the Array articles which may be undefined before the data is fetched that will cause you the error Cannot read property map of undefined.
Solution
Now, as the API request is asynchronous, means render method will not wait for the data to come. So what you can do is use a loader variable in the state, and set it to true as long as the request is being made, and when the response has come, make it false, and show the loader in render when this.state.loader is true, and show articles when it is false.
Or you can initialize this.state.articles with an empty array that won't cause you the error.
Also, as I noticed, there are too many API calls being made to get the writer's name multiple time for all the listed articles. What are the industry best practices for these cases?
It is extremely bad practice to make an API request in the loop. Even myself has been scolded on it once I did it in my company.
Solution
You have tell your backend engineer to provide you filter for including the writer's name in each object of the article. We use Loopback on our backend, which provides a filter for including the related model in each object internally.
Since your API calls have a lot of things in common, you should first set up an axios instance that re-uses those common features:
const api = axios.create({
baseURL: 'http://localhost:8000/api/',
headers: { Authorization: `Bearer ${localStorage.token}` }
});
Now since your MainBody needs to fetch the resources from the API asynchronously, there will be a short period where the data is not yet available. There are two ways you can handle this. Either the MainBody can be responsible for making all the calls, or it can be responsible for just making the call to get all the articles, then each of the ArticleView components can be responsible for getting the writer's name. I'll demonstrate the first approach below:
class MainBody extends Component {
state = { articles: null, error: null, isLoading: true };
async componentDidMount () {
try {
const response = await api.get('articles/');
const articles = await Promise.all(
response.data.map(async article => {
const response = await api.get(`writer/${article.created_by}/`);
return { ...article, writer_name: response.data.name };
})
);
this.setState({ articles, isLoading: false });
} catch (error) {
this.setState({ error, isLoading: false });
}
}
render () {
const { articles, error, isLoading } = this.state;
return isLoading ? 'Loading...' : error
? `Error ${error.message}`
: articles.map(article => (
<ArticleView
key={article.id}
article={article}
writer_name={article.writer_name}
/>
)
);
}
}

rerender after fetch POST and PUT request

I have an app that GET's data from a REST API. I am able to fetch the data. I also need to be able to POST data to the API and PUT edits to the API. My code is able to successfully perform all of these operations, however I need to refresh the page to see the submissions. The trouble I am having is with automatically rerender the page upon submissions. I know I either have to setState or refetch the API after the call. I do not know how to structure this though. Any suggestions?
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: [],
isLoaded: false,
}
this.editBeersLikes = this.editBeersLikes.bind(this);
this.addNewBeer = this.addNewBeer.bind(this);
}
seeBeers() {
return fetch("https://beer.fluentcloud.com/v1/beer/")//specify id number to show single beer ex. "https://beer.fluentcloud.com/v1/beer/99"
.then(response => response.json())
.then(responseJson => {
this.setState({
isLoaded: true,
dataSource: responseJson,
});
return responseJson;
})
.catch(error => console.log(error)); //to catch the errors if any
}
addNewBeer() {
fetch("https://beer.fluentcloud.com/v1/beer/", {
body: "{\"name\":\"\",\"likes\":\"\"}",//input beer name and like amount ex "{\"name\":\"Michelob Ultra\",\"likes\":\"-5\"}"
headers: {
"Content-Type": "application/json"
},
method: "POST"
})
console.log("Beer Added!");
}
editBeersLikes() {
fetch("https://beer.fluentcloud.com/v1/beer/99", {//must specify id number to edit a single beer's likes ex "https://beer.fluentcloud.com/v1/beer/99"
body: "{\"likes\":\"\"}", //input amount of likes to edit ex "{\"likes\":\"22\"}"
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
method: "PUT"
})
console.log("Likes Successfully Updated!");
}
componentDidMount() {
this.seeBeers();
//this.editBeersLikes(); //uncomment when you want to edit likes
//this.addNewBeer(); //uncomment when you want to add beers
}
render() {
return (
<div className="App">
<h1>What is in My Fridge?</h1>
<ul>
{this.state.dataSource.map(dataSource => {
return <li key={`dataSource-${dataSource.id}`}>{dataSource.name} | {dataSource.likes}</li>
})}
</ul>
</div>
);
}
}
export default App;
The easiest way is probably to re-fetch the data after you have successfully POSTed or PUT them to the server. That would also be the cleanest and least error prone way because you always get a fresh response from the server with the most recent set of data.
However, you could use a pattern that is often referred to as "optimistic ui", meaning that you keep two states: the current state and the state how it will look like if the server request is successful. You would then push the new or updated item to your dataSource state and show it in your UI but revert it to the old state from before the server request if the request fails.
The downside of the latter approach is that you sometimes don't know all the data (especially auto generated IDs or lastUpdated fields) without querying the server again.
So if you want a relatively easy, clear, and solid solution you can call this.seeBeers() after each POST/PUT request to receive an updated set of data.
Keep in mind that this might not always work as expected when you're using e.g. sharding or an elastic search layer for read operations as they might not be in sync directly after you finished the write operation. That's probably not relevant for you though, as that's usually only a challenge when working with large scale setups.

Resources