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
Related
My useEffect is getting data from web api. I want to render all posts on home page and trigger again my useEffect when someone create new post. The problem is when I put dependancy on useEffect its start doing endless requests. When I pass empty array as dependancy , when someone create new post it doesnt render on home page until I refresh the page. I read a lot in internet about that problem but still I dont know how to do it. Thanks
function App() {
const [posts, setPosts] = useState([]);
useEffect(() => {
const jwt = localStorage.getItem("jwt");
fetch('https://localhost:44366/api/Posts/getAllPosts',
{
method: "GET",
headers: {
"Content-Type": "application/json",
'Authorization': 'Bearer ' + jwt
},
})
.then(r => r.json()).then(result => setPosts(result));
}, [posts]);
return (
<div >
<Router>
<Header />
<main className="App">
{
posts.map(post => (
<Post keyToAppend={post.CreatedOn} username={post.User.FirstName} title={post.Title} content={post.Content} images={post.ImageUrls} avatarImage={post.User.MainImageUrl} />
))
}
</main>
</Router>
<Footer />
</div>
);
}
Post component :
const Post = ({ keyToAppend, username, title, content, images, avatarImage }) => {
return (
<div className="post" key={keyToAppend}>
<div className="post__header">
<Avatar
className="post__avatar"
alt="avatar"
src={avatarImage}
/>
<h3 className="username">{username}</h3>
</div>
<h1 className="title">{title}</h1>
<p className="content">{content}</p>
<p>
{typeof images != "undefined" ? <ImageSlider slides={images} /> : ""}
</p>
</div>
)
}
export default Post;
Remove posts from the dependency array, so it's just []. That will run the effect once, when the component loads.
useEffect(() => {
const jwt = localStorage.getItem("jwt");
fetch('https://localhost:44366/api/Posts/getAllPosts',
{
method: "GET",
headers: {
"Content-Type": "application/json",
'Authorization': 'Bearer ' + jwt
},
})
.then(r => r.json()).then(result => setPosts(result));
}, []);
// ^^−−−−− remove `posts` here
The reason it runs endlessly with your current code is that your effect callback changes the posts state member, which triggers the effect again (because posts is in the dependency array).
You only need to include things in the dependency array that you read in the effect callback. You never read posts in the effect callback.
Side note: That code is falling prey to the fetch API footgun I describe here. You need to check r.ok before calling r.json (and handle errors):
useEffect(() => {
const jwt = localStorage.getItem("jwt");
fetch("https://localhost:44366/api/Posts/getAllPosts", {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + jwt
},
})
.then(r => {
if (!r.ok) { // <=================
throw new Error(`HTTP error ${r.status}`);
}
return r.json();
})
.then(result => setPosts(result))
.catch(error => {
// ...handle/report error here...
});
}, []);
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.
In the home screen, I have a function that's getting the 'user type: Paid / UnPaid' from API,
So in this screen, I have a drawer inside it a user can navigate to sign in screen, so after user sign in I navigate him back to Home screen
but in this case, the Home screen is already mounted that's mean userIsPaid function is already invoked for the first time, so I want to recheck if this account is paid or not again "invoke userisPaid()"?
because in Bottom Tab, I render a new tab conditionally based on user type and it does not work because I'm not rechecking user type.
Code
Root.js
const BottomTabStack = createBottomTabNavigator();
function BottomTabs() {
const isPaid = useSelector(state => state.userIsPaid.isPaid);
...
<BottomTabStack.Navigator
lazy={false}
tabBar={props => <TabBar {...props} />}
>
...
{isPaid && (
<BottomTabStack.Screen
name="Library"
component={YourLibraryStackScreen}
/>
)}
</BottomTabStack.Navigator>
}
Home.js
userIsPaid = async () => {
try {
const {token} = this.props;
let AuthStr = `Bearer ${token}`;
let response = await API.get('/profile', {
headers: {Authorization: AuthStr},
});
const {account} = response.data.data;
console.log('account??', account);
account === 'unpaid'
? this.props.isUserPaid(false)
: this.props.isUserPaid(true);
} catch (err) {
console.log(err);
}
};
componentDidMount() {
const {token} = this.props;
token && this.userIsPaid();
}
SignIn.js
sendData = async data => {
// this.setState({loading: true});
try {
let response = await API.post('/google', data);
// this.setState({loading: false});
let {
data: {
data: {
response: {token},
},
},
} = response;
this.props.dispatch(saveToken(token));
this.props.dispatch(isLoginFunc(true));
this.props.navigation.push('BottomTabNavigator'); // bottom navigation 'home,about....'
} catch (err) {
console.log(err);
}
};
Using shouldComponentUpdate() lifecycle method mybe useful.
Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases, you should rely on the default behavior.
shouldComponentUpdate() is invoked before rendering when new props or state are being received. Defaults to true. This method is not called for the initial render or when forceUpdate() is used.
I am using React js and want to render data that I got it from API using Fetch .
the problem is I can't display the fetch results because the Return scope of React excute befor the fetch method done!!
Please any help of how I can solve this ???
this is a part of the function that do the Fetch :
initFourSquare = () => {
return fetch(FourSquareAPI)
.then(data => {
return data.json()
})
.catch((error) => {
console.log(error.message)
})
};
this is the render part of react where I called the function (initFourSquare)
render() {
var info = []
this.initFourSquare().then(response => {
info = response.response.venues
console.log(info) //the result is appear here
})
setTimeout(function(){ console.log(info[0].name) }, 1000);//the result is appear here
return (
<div>
<h1>{info}</h1> // it display nothing !!!
</div>
);
}
}
Any API/asynchronous call shouldn't be made in render function instead, you should do that in componentDidMount function if it is to be done once which seems to be your case and set the response in state which you can use in render. Make sure that you either initialise the state correctly or provide a conditional check for existence before using state variable
class App extends React.Component {
state = {
info: []
}
componentDidMount() {
var info = []
this.initFourSquare().then(response => {
info = response.response.venues
this.setState({info})
})
}
initFourSquare = () => {
return fetch(FourSquareAPI)
.then(data => {
return data.json()
})
.catch((error) => {
console.log(error.message)
})
};
render() {
const { info } = this.state;
return (
<div>
<h1>{info}</h1>
</div>
);
}
}
TLDR - You should not implement/call your reusable component within your data fetching request. The correct way to do so is having a container component that is responsible for fetching the data, storing it in state, and passing it down to a presentational component that is responsible for rendering the data as UI. (example below)
Initially what I was trying to do (I assumed components are converted to HTML and can be created as string - BAD PRACTICE):
fetch('http://localhost:8000/api/Books')
.then(response => {
if (!response.ok) {
throw Error('Network request failed.')
}
return response;
})
.then(data => data.json())
.then(data => {
let output = ''
for (let book of data) {
output += (
<Book id={book._id}
title={book.title}
author={book.author}
genre={book.genre}
read={book.read} />
);
}
console.log('parsed json', data);
document.getElementById('database').innerHTML = output;
}, (ex) => {
this.setState({
requestError : true
});
console.log('parsing failed', ex)
})
My question was:
How do i make this work? how do I Implement my Book component inside the GET request to render a reusable Book for every object in the database?
My solution
having <BooksContainer /> as my container component books data is stored in state and is iterated using .map to render each book object as a <Book /> component - our presentational function.
//BooksContainer.js - responsible for data fetching and storing
class BooksContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
books: [];
};
}
componentDidMount () {
fetch('http://localhost:8000/api/Books')
.then(response => {
if (!response.ok) {
throw Error('Network request failed.')
}
return response;
})
.then(data => data.json())
.then(data => {
this.setState({
books: data
});
console.log('parsed json', data);
}, (ex) => {
this.setState({
requestError : true
});
console.log('parsing failed', ex)
})
}
render () {
return (
<div className="books-container">
<ul className="books-list">
{this.state.books.map(book => <Book {...book} />}
</ul>
</div>
)
}
//Book.js - responsible for presenting each book's data
const Book = (props) => (
<li>
<span className="title">{this.props.title}</span>
<span className="author">Author: {this.props.author</span>
<span className="genre">Genre: {this.props.genre}</span>
<span className="read">Read: {this.props.read ? "Yes" : "No"}</span>
</li>
)
You should store the result of the api inside the state and render the <Book /> inside the render function.
Btw it would be better to separate your components:
The container that do all the logic (data fetching, ...).
The presentational component that renders the ui.
You can even go further by using redux for handling a global state, and redux-saga for handling the side effects (api calls)
EDIT
Here is a small example.
The presentational component:
const BookListing = ({ books }) => (
<ul>
{books.map(book => <li key={book.id}>{book.title}</li>)}
</ul>
);
The container component:
class Books extends Component {
constructor(props) {
super(props);
this.state = {books: []};
}
componentDidMount() {
fetch('http://localhost:8000/api/Books')
.then(data => data.json())
.then((data) => { this.setState({ books: data }) });
}
render() {
return <BookListing books={this.state.books} />;
}
}
1) Create a container component where you can do your AJAX request and then save the result in your local state, say books in the render method.
2) Pass the this.state.books into your <Book /> component where you can iterate over the array.
3) (Optional but recommended). You can create another component like <BookDetail /> to render individual book item