I'm new to React and I'm trying to figure out how to work with fetch correctly.
I have a React component that I'd like to update from a remote server whenever its parent's state updated.
i.e - parent's state changed -> myComponent calls remote server and re-renders itself.
I've tried the following:
If I only perform the .fetch call on componentDidMount, it disregards any state updates.
If I perform the .fetch call on componentDidUpdate as well it calls the server endlessly (I assume because of some update-render loop)
I have tried using the componentWillReceiveProps function, and it works, but I understand it's now deprecated.
How can I achieve this kind of behavior without componentWillReceiveProps ?
class myComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
images: []
};
}
componentDidMount() {
let server = "somethingserver.html";
fetch(server)
.then(res => {
if (res.ok) {
return res.json();
}
else {
console.log(res);
throw new Error(res.statusText);
}
})
.then(
(result) => {
this.setState({
images: result.items
});
}
).catch(error => console.log(error));
}
componentWillReceiveProps(nextprops) {
if (this.state !== nextprops.state) {
//same as componentDidMount
}
}
render() {
return (
<Gallery images={this.state.images} enableImageSelection={false} />
);
}
}
Given our conversation in the comments I can only assume that your search term is in a parent component. So what I recommend you to do is pass it to this component as a prop so you can do the following in your componentDid update:
componentDidUpdate(prevProps) {
const { searchTerm: previousSearch } = prevProps;
const { searchTerm } = this.props;
if (searchTerm !== previousSearch) fetch() ....
}
You can use getDerivedStateFromProps. It's the updated version of componentWillReceiveProps.
You should also read this, though: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
Using props to update internal state in a component can lead to complex bugs and there are often better solutions.
Related
I have a component (OOP) that has this in componentDidUpdate:
componentDidUpdate = (prevProps) => {
const prevLoading = prevProps.benefitsReducer.loading;
const { benefitsReducer: { loading } } = this.props;
if (prevLoading && !loading) {
this.setState({ showModal: true }); // I NEED TO REACH THIS LINE
}
}
Some onSubmit function changes the props, but I have mocked the props and also the store (I'm using Redux).
How could have 2 versions of the same prop? Currently I have a big object with all the props needed to the test, but I have this problem on how to use the previous one.
I am doing a school project and currently I am just trying out Vuex, I want to retrieve a list of workshops from Vuex, but I can't seem to update my state.
This is my Node backend:
router.get('/all', (req, res) => {
Workshop.find({})
.then( workshop => {
return res.status(201).json({
workshop: workshop,
success: true
})
})
.catch( err => {
console.log(err)
})
})
This is my result in Postman:
This is my Vuex store:
import axios from 'axios'
const state = {
workshop: {}
}
const getters = {
workshop: state => state.workshop
}
const actions = {
async getWorkshop({ commit }) {
let res = await axios.get('http://localhost:5000/api/workshops/all');
commit('workshop_success', res.data.workshop);
return res.data.workshop;
}
};
const mutations = {
workshop_success(state, workshop) {
state.workshop = workshop
}
};
export default {
state,
getters,
actions,
mutations
}
This is my component:
<template>
<p>{{ workshop }}</p>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
computed: {
...mapGetters(['workshop'])
},
methods: {
...mapActions(['getWorkshop'])
},
created() {
this.getWorkshop
},
}
</script>
The problem is, I am able to get the workshop state through Vuex, it displays a simple empty object "{}" (which is the initial state), but it seems like I am unable to trigger the action through the created hook, and the state does not change. If anyone has an idea of what I did wrong, that would be really helpful, because I am really lost right now. Thank you in advance!
state is not a state but context object in mutation, etc parameters. Otherwise commit, etc couldn't be accessed.
It is:
workshop_success({ state }, workshop) {
state.workshop = workshop
}
Also this is no-op:
created() {
this.getWorkshop
},
A function should be called like this.getWorkshop().
As the title says, when my state changes in my component, the sub components aren't rerendering.
class App extends Component {
constructor() {
super()
this.state = {
url: ""
}
this.handleWorkerSelect = this.handleWorkerSelect.bind(this)
}
handleWorkerSelect(url) {
this.setState({ url })
}
render() {
return (
<div className="App">
<Workers className="workers" handleClick={this.handleWorkerSelect}/>
<HermesWorker url={this.state.url}/>
</div>
)
}
}
const Workers = (props) => {
return (
<div>
<button onClick={() => props.handleClick("http://localhost:5000/api")}>Worker 1</button>
<button onClick={() => props.handleClick("http://localhost:2000/api")}>Worker 2</button>
</div>
)
}
export default App
here is hermesworker.js
class HermesWorker extends Component {
constructor() {
super()
this.state = {
items: [],
visited: [{name: "This Drive", path: "#back", root: ""}]
}
this.handleFolderClick = this.handleFolderClick.bind(this)
this.handleFileClick = this.handleFileClick.bind(this)
}
componentDidMount() {
if (this.props.url.length === 0) return
fetch(this.props.url)
.then(res => res.json())
.then(items => this.setState({ items }))
}
render() {
const folders = this.state.items.map((item) => {
if (!item.isfile) {
return <Card handleClick={this.handleFolderClick} root={item.root} path={item.path} isfile={item.isfile} name={item.name} size={item.size}/>
}
})
const files = this.state.items.map((item) => {
if (item.isfile) {
return <Card handleClick={this.handleFileClick} root={item.root} path={item.path} isfile={item.isfile} name={item.name} s ize={item.size}/>
}
})
const pathButtons = this.state.visited.map((item) => {
return <PathButton handleClick={this.handleFolderClick} root={item.root} path={item.path} name={item.name}/>
})
return (
<div>
{pathButtons}
<div className="flex-container">
{folders}
{files}
</div>
</div>
)
}
}
Essentially the issue is that the HermesWorker component is not being rerendered to use the new url prop. I am not sure why this is happening because for example, in the hermesworker it renders other subcomponents that do get rerendered during a state change.
Any information is appreciated
EDIT updated to add hermes worker, the file is over 100 lines so i cut out and only pasted the stuff I thought was important to the issue, can supply more if needed
I tested that code and it seems to be working fine. Could you provide What is set in HermesWorker component?
Edit: You'll require to set your state with setState on component updates. To do this, you may look for componentDidUpdate, which will run on every update. This is different from componentDidMount, which (hopefully) will run once and then the component may update and re-render, but re-render it's not considered as "mount". So you may try this instead:
constructor(props) {
super(props);
this.state = {
url: '',
items: [],
visited: [{name: "This Drive", path: "#back", root: ""}]
}
this.fetchData = this.fetchData.bind(this);
}
componentDidMount() {
//Mount Once
}
componentDidUpdate(prevProps, prevState) {
if (this.state.url !== this.props.url) {
this.setState({url: this.props.url});
// Url state has changed.
}
if(prevState.url !== this.state.url){
//run your fetch
this.fetchData();
}
}
fetchData(){
if (this.props.url.length === 0) return
fetch(this.props.url)
.then(res => res.json())
.then(items => this.setState({ items }));
}
Note: I moved the fetch to its own function, but that's completly up to you.
Also notice i added url to the state. Make sure to keep your props set to avoid unexpected behaviours.
Edit 2: componentDidUpdate will hand you prevProps and prevState as parameters. With prevProps you get access to whatever props you got on the previous update, and with prevState, as you may guess, you get access to whatever-your-state-was on the previous update. And by "on the previous update" i mean before the update got executed.
I am a beginner in nodejs and reactjs and I am developing a simple application. I am now in the front part, I installed all the necessary packages and modules. I launched my front part but nothing is displayed here is a screenshot of the result obtained. thank you in advance
And here a part of my code in reactjs
import React from 'react';
import {connect} from 'react-redux';
import {fetchVideos} from '../actions/videoActions';
var listVideos
class videos extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() { this.props.fetchVideos(); }
render() {
if (this.props.data) {
listVideos = this.props.videos.map(video =>
<li key={video._id}>
{video.titre}
</li>
);
}
return (
<div>
<center><h1>All videos </h1></center>
{listVideos}
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
return {
clients : state.clients,
};
}
const mapDispatchToProps = (dispatch) => {
return {
fetchVideos :()=> dispatch(fetchVideos())
};
}
export default connect(mapStateToProps, mapDispatchToProps)(videos);
If you are using redux, probably you will need to map the videos in the mapStateToProps method
// ...
componentDidMount() { this.props.fetchVideos(); }
// ...
const mapStateToProps = (state, ownProps) => {
return {
clients : state.clients,
videos: state.videos // HERE
};
}
Note the state.videos param is got from the reducer.
In your component, you are trying to read videos from its props. However, as Luiz mentioned, your mapStateToProps is only mapping the clients state variable to your component. mapStateToProps is the logic that binds redux state with your component props. So, assuming that you have set the videos object on your state (via reducers) and adding the videos mapping on the mapStateToProps you should get said data on your component props.
I want to receive data via a REST service call in my React Universal (with Next.js) app using fetch() and then render the result into JSX like this:
class VideoPage extends Component {
componentWillMount() {
console.log('componentWillMount');
fetch(path, {
method: 'get',
})
.then(response =>
response.json().then(data => {
this.setState({
video: data,
});
console.log('received');
})
);
}
render() {
console.log('render');
console.log(this.state);
if (this.state && this.state.video) {
return (
<div>
{this.state.video.title}
</div>
);
}
}
}
export default VideoPage;
Unfortunately, the output is this:
componentWillMount
render
null
received
Which does make sense because the call to fetch is asynchronously and render() finishes before the call to the REST service has finished.
In a client-side app this would be no problem because a change of state would call render() which then updates the view, but in a universal app, especially with JavaScript turned off on the client, this is not possible.
How can I solve this?
Is there a way to call the server synchronously or delay render()?
In order to get it working, I had to do 3 things:
Replace componentWillMount with getInitialProps() method
Combine fetch with await and return the data
Use this.props instead of this.state
Code looks like this now:
static async getInitialProps({ req }) {
const path = 'http://path/to/my/service';
const res = await fetch(path);
const json = await res.json();
return { video: json };
}
Then, in render() I can access the data via this.props.video, for example:
render() {
return (
<div>{this.props.video.title}</div>
);
}
You can add static async getInitialProps () {} to load data into props before the page component gets rendered.
More info here: https://github.com/zeit/next.js/blob/master/readme.md#fetching-data-and-component-lifecycle