React component : setState worked but didnt update component - javascript

sorry i'm new to React. I'm trying to make a basic social network to learn react.
Context:
When i click on the "like" button, the setState should call the function to update the state of my component, but it is updated only when i refresh the page. I think the ComponentDidUpdate function isn't called like it should. What did i do wrong? Thanks for your help!
Here are the parts of the code :
Like button component:
class Like_Button extends React.Component {
constructor(props) {
super(props);
this.state = {liked : "Like"};
}
isliked(){
fetch("likes_of_user/")
.then(res => res.json())
.then((result) => {
result.map(x => {if(this.props.pk == x.liked_post){this.setState({liked: "Unlike"});}});
})
}
componentDidMount() {
this.isliked();
}
componentDidUpdate(prevProps, prevState) {
if (prevState.liked !== this.state.liked) {
this.isliked();
}
}
render() {
return (
<button className = "buttons" onClick={() => {
var csrftoken = getCookie('csrftoken');
fetch(`like_post/${this.props.pk}`, {method: "POST", headers: {'Accept': 'application/json', 'Content-Type': 'application/json','X-CSRFToken': csrftoken}})
}}>{this.state.liked}</button>
)
}
}
Newsfeed component:
class Newsfeed_comp extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch("get_newsfeed/")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map((item ,index) => (
<li className="postbox" key={`${item}${index}`}>
{item.author}
{item.date}
{item.content}
<Like_Button pk={item.id} />
</li>
))}
</ul>
);
}
}
}
ReactDom render:
ReactDOM.render(<Newsfeed_comp />, document.getElementById("newsfeed_view"))

Try something like this:
LikeButton.js
import React, { useEffect, useState } from 'react';
export default function LikeButton({ pk }) {
const [like, setLike] = useState(false);
useEffect(() => {
const fetchLike = async () => {
const res = await fetch("likes_of_user/");
const result = await res.json();
if (result.length > 0) {
setLike(result.find(item => item.liked_post === pk));
}
};
try {
fetchLike();
} catch (error) {
// handle error
}
});
const handleClick = async () => {
const csrftoken = getCookie('csrftoken');
return fetch(`like_post/${pk}`, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
},
method: 'POST',
});
};
return (
<button className='buttons' onClick={handleClick}>
{like}
</button>
);
};
NewsFeed.js
import React, { useEffect, useState } from 'react';
export function NewsFeed() {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
const getNewsFeed = async () => {
const res = await fetch('get_newsfeed/');
const result = await res.json();
setIsLoaded(true);
setItems(result);
};
try {
getNewsFeed();
} catch (error) {
setIsLoaded(true);
setError(error);
}
});
if (error) return <div>Error: {error.message}</div>;
if (isLoaded) return <div>Loading...</div>;
const list = items.map((item) => (
<li className='postbox' key={item.content}>
{item.author}
{item.date}
{item.content}
<LikeButton pk={item.id} />
</li>
));
return <ul>{list}</ul>;
};
App.js
ReactDOM.render(<NewsFeed />, document.getElementById('newsfeed_view'));

Looks like you've reversed your logic, i.e. your button directly updates the data in the backend but does nothing to update component state, so the componentDidUpdate isn't called as you've seen. The refresh is required so the component is remounted and the componentDidMount can fetch the likes data.
Try instead to update local state first, then use componentDidUpdate to issue the side-effect of updating the backend.
constructor(props) {
super(props);
this.state = { liked: true };
}
isliked() {
fetch("likes_of_user/")
.then(res => res.json())
.then((result) => {
result.map(x => {
if (this.props.pk === x.liked_post) {
this.setState({ liked: false });
}
});
})
}
componentDidUpdate(prevProps, prevState) {
if (prevState.liked !== this.state.liked) {
const csrftoken = getCookie('csrftoken');
fetch(
`like_post/${this.props.pk}`,
{
method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken,
},
}
);
}
}
<button
className="buttons"
onClick={() => this.setState(
prevState => ({ liked: !prevState.liked })
)}
>
{this.state.liked ? "Liked" : "Unliked"}
</button>

Related

Warning: unstable_flushDiscreteUpdates when rendering component in React

I've been trying to render a component using map, the render happens but with a warning:
Warning: unstable_flushDiscreteUpdates: Cannot flush updates when
React is already rendering.
MyBooks.js
import React, { useState, useEffect } from 'react';
import ActionAreaCard from '../components/ActionAreaCard';
const MyBooks = ({address}) => {
const [metadata, setMetadata] = useState([]);
const URL = `http://localhost:3001/api/tatumapi`;
const chain = 'CELO';
const params = { address: address, chain: chain };
useEffect(() => {
fetch(URL,
{
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
})
.then(response => response.json())
.then(data => setMetadata(
(data.data).map((data2) => {
return data2.metadata.map((data3) => {
return data3;
})
})
))
}, [])
return (
<div>
{metadata.map((data4) => {
return (
<div>
{data4.map(({metadata}) => {
return (
<div>
{metadata!= null && console.log(metadata)}
{metadata!=null && <ActionAreaCard name={metadata.name} description={metadata.description} image={metadata.image}/>}
</div>
)
})}
</div>
)
})}
</div>
)
}
export default MyBooks;
Console output:
{description: 'The very first edition', name: 'BOOK', image: 'ipfs://bafkreidny67q3xxjulstouk7vzp6bomdbnokg3zzhg6k4gqbdtutqzz5h4'}
description: "The very first edition"
image: "ipfs://bafkreidny67q3xxjulstouk7vzp6bomdbnokg3zzhg6k4gqbdtutqzz5h4"
name: "BOOK"
[[Prototype]]: Object
Why I'm getting this warning and how can I solved it?
I've just improved the mapping and added a key.
import React, { useState, useEffect } from 'react';
import ActionAreaCard from '../components/ActionAreaCard';
const MyBooks = ({address}) => {
const [metadata, setMetadata] = useState([]);
const URL = `http://localhost:3001/api/tatumapi`;
const chain = 'CELO';
const params = { address: address, chain: chain };
useEffect(() => {
fetch(URL,
{
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
})
.then(response => response.json())
.then(data => setMetadata(data.data.map(data2 => {
return data2;
})))
}, [])
return(
<div>
{metadata!=undefined && metadata!=null && metadata.map((data) => {
return data.metadata.map(({metadata}, i) => {
return (
<div key={i}>
{metadata!==null && <ActionAreaCard name={metadata.name} description={metadata.description} image={metadata.image}/>}
</div>
)
})
})}
</div>
)
}
export default MyBooks;

TypeError: Cannot read property 'type' of undefined - React-Redux

I want to call my rest api. but I got this error:
restApi.js:
import {customerbyidGetAction} from '../../redux/actions/restActions'
export default function customerbyidGet(dispatch) {
return () => {
dispatch(customerbyidGetAction());
fetch('https://***.com/api/v1/home',{
method:'GET',
headers:{
'Accept': 'application/json',
'Content-Type': 'application/json',
'token':1234
}//,
// body: JSON.stringify({'customerId': 1})
})
.then(res => {
const r = res.json();
return r;
})
.then(res => {
if(res.error) {
throw(res.error);
}
console.log('then rest')
dispatch(customerbyidGetAction());
return res;
})
.catch(error => {
console.log('r456r'+error);
dispatch(customerbyidGetAction());
})
}
}
Panel.js
import customerbyidGet from '../../../api/rest/restApi';
class Panelclass extends React.Component {
componentDidMount(){
const {customerbyidGet} = this.props;
customerbyidGet()
}
render() {
return (
<div>
hi
</div>
);
}
}
Panelclass.propTypes = {
classes: PropTypes.object.isRequired,
customerbyidGet: PropTypes.func.isRequired,
};
const mapStateToProps = (state) => ({ data: {} });
const mapDispatchToProps = dispatch => bindActionCreators({
customerbyidGet: customerbyidGet(dispatch)
}, dispatch)
const Panel = connect(
mapStateToProps,
mapDispatchToProps
)(Panelclass);
export default withStyles(styles)(Panel);
restActions.js
import * as types from '../constants/restConstants';
export const customerbyidGetAction = (data = { "default value": "some value" }) => ({ type: types.customerbyidGetAction, payload:data });
restReducer.js
import * as types from '../constants/restConstants';
const initialState = {};
const initialImmutableState = fromJS(initialState);
export default function restReducer(state = initialImmutableState, action) {
switch(action.type) {
case types.customerbyidGetAction:
return {
...state,
data: action.payload
}
default:
// the dispatched action is not in this reducer, return the state unchanged
return state;
}
}

How to filter props in Next.js? Can't filter data in props from componentDidMount in Next.js

I get data into the props of my component by using getStaticProps. I then want to filter that data before I use it in the component. Usually I'd do this in componentDidMount, but that's not possible as it seems like the props are populated after componentDidMount is called.
What's the best practice for working around this?
Here's my current code:
class Definition extends Component {
constructor({ router }, ...props) {
super(props);
this.state = {
songsArray: [],
};
}
filterSpotifyResults = () => {
const filteredArray = [];
this.props.songsData.tracks.items.forEach((obj) => {
if (obj.explicit === true) {
return;
} else {
filteredArray.push(obj);
}
});
this.setState({ songsArray: filteredArray });
};
componentDidMount = () => {
this.filterSpotifyResults();
};
render() {
if (this.props.router.isFallback) {
return <h4>Loading...</h4>;
}
return (
<div>
<h3>this is where the definition will go</h3>
<ul>
{this.props.wordsData.definitions.map((obj, i) => (
<li key={i}>{obj.definition}</li>
))}
</ul>
<iframe
src={`https://open.spotify.com/embed/track/${this.props.songsData.tracks.items[0].id}`}
width="300"
height="380"
allowtransparency="true"
allow="encrypted-media"
></iframe>
</div>
);
}
}
export default withRouter(Definition);
export async function getStaticProps(context) {
const wordsRes = await fetch(
`https://wordsapiv1.p.rapidapi.com/words/${context.params.word}/definitions`,
{
method: "GET",
headers: {
"x-rapidapi-key": process.env.NEXT_PUBLIC_DB_KEY,
"x-rapidapi-host": "wordsapiv1.p.rapidapi.com",
},
}
)
.then((response) => {
return response;
})
.catch((err) => {
console.error(err);
});
const songsRes = await fetch(
`https://api.spotify.com/v1/search?q=${context.params.word}&type=track`,
{
method: "GET",
headers: {
authorization:
"Bearer " + process.env.NEXT_PUBLIC_ENV_SPOTIFY_ACCESS_TOKEN,
},
}
)
.then((response) => {
return response;
})
.catch((err) => {
console.error(err);
});
const wordsData = await wordsRes.json();
const songsData = await songsRes.json();
return {
props: {
wordsData,
songsData,
searchTerm: context.params.word,
},
};
}
Best practice would definitely be filtering the data on the server, already in your getStaticProps.
So move the filtering there, and only return the data you actually want to use/render.

Display Dynamic Data in Render Method

I need to create a Card that is dynamically created by populating data from an API. I am able to get this data but I am unable to show the view in the render method.
Kindly assist me to fix my code.
Below is my class Component where I use axios to get a form data, then I iterate through to get the key and value and assign it to the card i want to display. Now I cannot seem to see the Card at all.
class Cards extends Component {
constructor(props) {
super(props);
this.state = { users: [] }
}
componentDidMount() {
let formData = new FormData();
const username = localStorage.getItem("username");
formData.append("username", username);
const config = {
headers: { "content-type": "multipart/form-data" },
};
axios
.post("http://", formData, config)
.then((response) => {
let rows = []
let count = 0
for (var i = 0; i < response.data.length; i++) {
console.log("data: "+response.data[i].key);
rows.push(<div className="col-md-4">
<div className="card">
<p>Data {count++}</p>
<h1>{response.data[i].key}</h1>
<p>{response.data[i].value}</p>
</div>
</div>
)
this.setState({ users: rows })
}
})
.catch((error) => {
console.log(error);
});
}
render() {
return (
<div className="cards">
{this.users}
</div>
);
}
}
export default Cards;
It's not a good practice to add HTML tags into the state Instead, add your API response to the state and use the render() to render the data in proper HTML tags.
class Cards extends Component {
constructor(props) {
super(props);
this.state = {
response: {},
};
}
const apiCall = () => {
let formData = new FormData();
const username = localStorage.getItem("username");
formData.append("username", username);
const config = {
headers: { "content-type": "multipart/form-data" },
};
axios
.post("http://", formData, config)
.then((response) => {
this.setState({ response: response });
}).catch((error) => {
console.err(error);
});
}
componentDidMount() {
apiCall();
}
render() {
const { response } = this.state;
return (
<div className="cards">
{response.data.map((item, index) => {
<div key={`user-${index}`} className="col-md-4">
<div className="card">
<p>Data {index+1}</p>
<h1>{item.key}</h1>
<p>{item.value}</p>
</div>
</div>
})}
</div>
);
}
}
export default Cards;
class Cards extends Component {
constructor(props) {
super(props);
this.state = { users: [] };
}
componentDidMount() {
let formData = new FormData();
const username = localStorage.getItem("username");
formData.append("username", username);
const config = {
headers: { "content-type": "multipart/form-data" },
};
axios
.post("http://", formData, config)
.then((response) => {
this.setState({ users: response.data });
})
.catch((error) => {
console.log(error);
});
}
render() {
return (
<div className="cards">
{this.state.users?.map((user, id) => (
<div className="col-md-4" key={user.key}>
<div className="card">
<p>Data {id}</p>
<h1>{user.key}</h1>
<p>{user.value}</p>
</div>
</div>
)}
</div>
);
}
}
export default Cards;

data is fetched but the state is not updated

I'm fetching data from an endpoint. But the state is not updated. it's always undefined.
For some reason this.props.users is undefined. Am I doing something wrong?
After componentDidMount() I trigger the action fetchUsers that send a request to the endpoint. The data is fetched successfully but at the end the state is not updated.
This is my Layout component
class Layout extends Component {
render() {
return (
<div className="container">
{
this.props.users.map((user, key) => {
return <a className="list-group-item list-group-item-action active">User #{user.id}</a>
})
}
</div>
)
}
}
const mapStateToProps = state => {
return {
channels: state.users.data,
}
}
const mapDispatchToProps = dispatch => {
return {
fetchUsers: () =>
dispatch(user.fetchUsers()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Layout);
This the action file
export const fetchUsers = () => {
return (dispatch, getState) => {
let headers = { "Content-Type": "application/json" };
return fetch("http://127.0.0.1:3030/api/users/", { headers, })
.then(res => {
if (res.status < 500) {
return res.json().then(data => {
return { status: res.status, data };
})
} else {
console.log("Server Error!");
throw res;
}
})
.then(res => {
if (res.status === 200) {
dispatch({ type: 'USERS_FETCHED', data: res.data });
return res.data;
}
})
}
}
And this is the reducer
const initialState = {
users: []
};
export default function channels(state = initialState, action) {
switch (action.type) {
case 'USERS_FETCHED':
return { ...state, users: action.data };
default:
return state;
}
}
I think the error comes from your call to the dispatcher in the mapDispatchToProps. Since you are exporting directly the function fetchUsers, you should not be calling user.fetchUsers.
const mapDispatchToProps = dispatch => {
return {
fetchUsers: () =>
dispatch(fetchUsers()),
}
}

Categories