React not re-rendering when setState following API calls - javascript

I have been struggling to understand what is going wrong with this simple todo list front end React app, which should interact with an express API.
my React code is:
import React, {Component} from 'react';
class App extends Component {
constructor(){
super();
this.state = {
todos:[],
currentItem: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleADDButton = this.handleADDButton.bind(this);
this.deleteItem = this.deleteItem.bind(this);
this.updateTodo = this.updateTodo.bind(this);
}
componentDidMount(){
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/list')
.then(res => res.json())
.then((todosList) =>
{this.setState({'todos': todosList});
});
}
handleChange(event){
this.setState({currentItem: event.target.value});
}
handleADDButton(event){
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/post', {
method: 'POST',
headers:{'Content-type': 'application/json'},
body: JSON.stringify({title: this.state.currentItem})
});
}
deleteItem(x){
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/' + x, {
method: 'DELETE',
headers:{'Content-type': 'application/json'}
})
}
updateTodo(y){
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/' + y, {
method: 'PUT',
headers:{'Content-type': 'application/json'},
body: JSON.stringify({title: this.state.currentItem})
})
}
render() {
return(
<div>
<h1> Todo List </h1>
<ul>
{this.state.todos.map((todo) => <li> {todo.title}
<button type="button" onClick={() => this.deleteItem(todo.key)} >x</button>
<button type="button" onClick={() => this.updateTodo(todo.key)}>update</button> </li>)}
</ul>
<input type="text" value={this.state.currentItem} onChange={this.handleChange} />
<button type="submit" onClick={this.handleADDButton}>ADD</button>
</div>
)
}
}
export default App
The calls do update the API, and if I manually refresh the page, the React app picks up on the new data coming through from the API. However, when clicking the buttons it doesn't re-render by itself.
Say for example I click the ADD Button. It sends an OPTIONS to which I get back a 200 code, a POST which also comes back with a 200 and only sometimes, a GET with a 200. There is no pattern in when it performs the last GET call and also there is no pattern in when it re-renders following a button click. To obtain the latest data I always have to refresh.
Don't know what to make of this and have been stuck for days.

I think there is no state update on button actions
try to add a state updates for the actions same as componentDidMount
For ex:
handleADDButton(event){
event.preventDefault();
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/post', {
method: 'POST',
headers:{'Content-type': 'application/json'},
body: JSON.stringify({title: this.state.currentItem})
}).then(res => res.json())
.then((data) => {
this.setState((prevState) {
const todos = [...prevState.todos, data.todo];
return {
todos: todos
}
})
});
}
In this case you have to return the new todo which will catch in data.todo
And for delete action
deleteItem(x){
fetch('http://ec2-x-xx-xx-xxx.eu-west-2.compute.amazonaws.com:3001/' + x, {
method: 'DELETE',
headers:{'Content-type': 'application/json'}
}).then(res => res.json())
.then((data) => {
this.setState((prevState) {
const newTodos = prevState.todos.filter(t => t.key !== x);
return {
todos: newTodos
};
})
});
}
These codes are not tested.

actually you don't have any state update in your code. you have to use "setState" when fetching data from API. I recommended learning arrow function and Hooks and use somethings like AXIOS to manage API calls.

Related

ReactJs - My POST request is always empty

I'm trying to send a POST request using axios with ReactJS, my POST request will submit when I hit the button, but the data from the textbox is not present. Hoping somebody could point out what I've done wrong. I think I might be going wrong with the state setting.
import React, { Component } from "react";
import axios from "axios";
class SearchBar extends Component {
state = {
search: '',
};
handleChange = event =>{
this.setState({ search: event.target.value});
}
handleSubmit = event => {
event.preventDefault();
const data = {
search: this.state.search
}
axios
.post("http://127.0.0.1:8080/find", {data})
.then(response=>{
console.log(response);
this.setState({ data: response.data });
})
.catch((error) => {
console.log(error);
});
};
render() {
return (
<div className="post">
<form className="post" onSubmit={this.handleSubmit}>
<input
placeholder="Search for music 'Artist - Track'"
type="text" name="name" onChange={this.handleChange}/>
<button type="submit">Search</button>
</form>
<div>
{this.state.data}
</div>
</div>
);
}
}
export default SearchBar;
data is already an object you defined and you wrapped it in another object. So to get your response you will have to do response.data.data
To fix this all you need to do is remove the braces around data in your axios call
axios
.post("http://127.0.0.1:8080/find", data) //removed braces around data
.then(response=>{
console.log(response);
this.setState({ data: response.data });
})
.catch((error) => {
console.log(error);
});

React Functional Component - useState / useCallback - value changes back / reverts to initial value on submit

I have a functional component where I am submitting a text value entered by the user.
import React, { useState, useEffect, useCallback } from 'react'
// other imports
function Settings (props) {
const [primaryColor, setPrimaryColor] = useState('#E02E26');
useEffect(() => {
fetch(`//URL`, {...})
.then(res => res.json())
.then(
(result) => {
setPrimaryColor(result.primaryColor);
})
},[]);
const handlePrimaryColorChange = useCallback((newValue) => {
setPrimaryColor(newValue);
}, []);
const handlePCChange = useCallback((newValue) => {
setPrimaryColor(newValue.hex)
}, []);
const handleSubmit = useCallback((_event) => {
fetch(`//URL`, {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
primaryColor: primaryColor
})})
.then(res => res.json())
.then((result) => {
console.log('response recieved from post api');
})
}, []);
return (
<div>
<Page title="Customise UI">
<Form onSubmit={handleSubmit}>
<TextField type="text" onChange={handlePrimaryColorChange} value={primaryColor} />
<SketchPicker disableAlpha={true} color={primaryColor} onChangeComplete={handlePCChange}/>
<Button primary submit>Save Settings</Button>
</Form>
</Page>
</div>
)
Settings.getInitialProps = async (context) => {
return context.query;
}
The data is correctly loaded by 'useEffect' and 'primaryColor' is set and the correct values are displayed on TextField and SketchPicker components.
When I change values in either TextField and SketchPicker then the value gets updated on-screen in the other component correctly.
Now, when I click on Submit, the value that is received on the backend or if I print it just before fetch is '#E02E26' (the initial value in useState). The fetch request is successful.
What is going wrong here? I want to send the current primaryColor value in the fetch body.
Try adding primaryColor to the array:
const handleSubmit = useCallback((_event) => {
fetch(`//URL`, {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
primaryColor: primaryColor
})})
.then(res => res.json())
.then((result) => {
console.log('response recieved from post api');
})
}, [primaryColor]);

componentDidMount not rendering

I have an Header component that suppose to render his child components by a the condition of if the user is logged. It recognize the condition by the session-storage. I tried to control the rendering by
componentDidMount:
renderUserHeader = () => {
if (sessionStorage.getItem('user-auth')) {
var tokenToSend = { token: sessionStorage.getItem('user-auth') }
var regJSONED = JSON.stringify(tokenToSend)
fetch('http://localhost:4000/users/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: regJSONED
})
.then(response => {
if (!response.ok) {
throw new Error('HTTP error ' + response.status)
}
return response.text()
})
.then(data => {
let JsonedUserName = JSON.parse(data)
this.setStateUserName(JsonedUserName.name, JsonedUserName.admin)
})
if (!this.state.admin) {
return <UserHeader name={this.state.userName} />
} else if (this.state.admin) {
return <AdminHeader name={this.state.userName} />
}
} else if (!sessionStorage.getItem('user-auth')) {
return (
<Link to='/login'>
{' '}
<LoginLink />{' '}
</Link>
)
}
}
componentDidMount() {
this.renderUserHeader()
}
As you can see the renderUserHeader is being the component did mount but it is not working.
I have tried calling renderUserHeader inside the render and it worked but it keeps bugging and I have to refresh the page everytime.
render() {
return (
<header>
<Logo />
{this.renderUserHeader()}
</header>
)
}
Can someone tell me why componentDidMount doesn't not work?
componentDidMount is used for side effects like fetching data and updating component state only. If you return some component (eg <Link />) from componentDidMount it won't be rendered.
And you should never do any side effects inside render.
Instead, you should fetch and update state in the componentDidMount and based on the state render corresponding components.
componentDidMount not rendering
as said in above answer:componentDidMount is used for side effects like fetching data and updating component state only.
now
how to make it work with your code
in order to make it work,your componentDidMount should be like
componentDidMount(){
if(sessionStorage.getItem('user-auth')){
var tokenToSend = {token: sessionStorage.getItem('user-auth')}
var regJSONED = JSON.stringify(tokenToSend)
fetch('http://localhost:4000/users/token', {
method: 'POST',
headers:{
"Content-Type": "application/json"
},
body: regJSONED,
}).then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response.text();
})
.then(data => {
let JsonedUserName = JSON.parse(data)
this.setStateUserName(JsonedUserName.name,JsonedUserName.admin )
})
}
and your renderUserHeader should be like
renderUserHeader = () =>{
if(!sessionStorage.getItem('user-auth')){
return <Link to="/login"> <LoginLink /> </Link>
}
if(!this.state.admin){
return <UserHeader name ={this.state.userName}/>
}
else if(this.state.admin){
return <AdminHeader name ={this.state.userName}/>
}
}
and you can call it from render method.
you can use conditional rendering with ternary operator.
{this.state.isLoggedIn ? (some JSX if true) : (some JSX if false or just NULL)}
in the componentDidMount you can set the isLoggedIn property in the state, so every time the component loads - (render method will run) - the condition will be checked again

Access variable modified in function in render method

I have the following code (trimmed the form and some other boilerplate):
import React, { Component } from 'react';
import Modal from 'react-modal';
var responseMessages;
export default class ContactForm extends Component {
handleSubmit(event) {
responseMessages = []
fetch('http://127.0.0.1:4000/test', {
method: 'POST',
mode: 'cors',
headers: {
"Access-Control-Allow-Origin":"*",
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}).then((response) => response.json())
.then((responseJson) => {
for(var i = 0; i < responseJson.errors.length; i++) {
responseMessages.push(
<p>{responseJson.errors[i].msg}</p>
);
}
})
.then(this.openModal());
}
render() {
return (
<div>
<Modal isOpen={this.state.modalIsOpen}
onRequestClose={this.closeModal}
ariaHideApp={false}
style={customStyles}>
<div>
{responseMessages}
</div>
</Modal>
</div>
)}}
Adding {responseMessages} in the Modal displays nothing, but if I change it to {console.log(responseMessages)} it shows in the console that responseMessages is not empty (it has different length, but not empty)
What could be the reason for that?
EDIT: openModal function:
openModal() {
this.setState({modalIsOpen: true});
}
ResponseJson:
{"errors":[{"location":"body","param":"message","msg":"error message","value":"entered value"}]}
This is a javascript issue and has nothing to do with react.
when you write .then(this.openModal()) the openModal function will be called immediately. So the actual code should be
.then(this.openModal.bind(this));
or using arrow function
or .then(() => this.openModal());
if you are using some autobind decorator or if you are binding functions inside constructor then simply .then(this.openModal); should also work.

How to use the response in fetch request React Native

I am new in react native world and (JS).
I want to send phone number and password to the server to login. I can send data and receive response, however, I don't how I should handle response. I have a function called _response_recognizer. But it is not working. Even setStat. All of the tutorials only say how to connect to server and how fetch data from it. What is the best approach to use response in my login form. If it's status is 200 I want to navigate another screen, else I want to toast a message.
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
View,
TextInput, Button
} from 'react-native';
export default class LoginForm extends Component<{}> {
constructor(props) {
super(props);
this._onLogInPressed = this._onLogInPressed.bind(this);
this._response_recognizer = this._response_recognizer.bind(this);
this.state = {
phone_number: '',
password: '',
data: {}
};
}
_response_recognizer(data: string ){
}
_onLogInPressed = () => {
var data = {
'phone_number': this.state.phone_number,
'password': this.state.password
}
fetch("http://192.168.1.12:5000/login", {
method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
}).then(function(response){
return response.json();
}).then(function(data){
console.log(data)
this._response_recognizer(data)
}) .catch((error) => {
console.log("Error" + error);
});
};
render() {
return (
<View style={styles.flow}>
<Text style={styles.text}>phone number:</Text>
<TextInput keyboardType='numeric'
style={styles.input}
ref="phone_number"
onChangeText={(phone_number) => this.setState({phone_number})}
maxLengt={11}
value={this.state.phone_number}
/>
<Text style={styles.text}>password:</Text>
<TextInput style={styles.input} secureTextEntry={true} ref="password"
onChangeText={(password) => this.setState({password})}
value={this.state.password}/>
<Button style={styles.button} onPress={this._onLogInPressed} title='login'/>
</View>
);
}
}
Two things.
Your _response_recognizer function is requesting data: string but you are returning an json object:
.then(function(response){
return response.json();
}).then(function(data){
console.log(data)
this._response_recognizer(data)
})
Change that to data: object.
Secondly, you are using function declarations (function(){}) in your .then's. Without directly binding this, you lose the scope your Class functions. Change them to an arrow function (() => {}) to fix the scope issue:
.then((response) => response.json())
.then((data) => {
console.log(data)
this._response_recognizer(data)
})
You can also opt to remove one of the .then operations:
.then((response) => {
console.log(response.json())
this._response_recognizer(response.json())
})
✌🏾
check this ...
i hope this code helps you
export default class LoginForm extends Component<{}> {
state = {
data:[],
}
_onLogInPressed = () => {
fetch('http://192.168.1.12:5000/login',{
method:'POST',
headers:{
'Accept':'application/json',
'Content-Type':'application/json',
},
body:JSON.stringify({
'phone_number': this.state.phone_number,
'password': this.state.password
})
})
.then((response) => response.json())
.then((res) =>{
if(res.success === true){
alert(res.response);
let datadata = res.data;
this.setState({data:datadata})
} else {
alert(res.response);
}
})
.done();
};
//// Render function
////Rander function
}

Categories

Resources