当Axios调用单独的组件时,React-setState不会重新呈现页面



我正在做一些R&D在工作中,决定学习React并在这样做的同时构建应用程序。

我遇到了一个特殊的问题,这让我很头疼。我在Stackoverflow上找不到一个与我的问题完全匹配的问题,所以我想发一篇帖子。

tl;问题的解决方案:在我的组件外进行Axios调用,然后设置状态不会触发重新应答器,但在组件内进行Axios呼叫会触发。

我已经重命名了代码的某些部分。

应用程序摘要

我已经创建了MVC应用程序的前端部分,它将与我同事的Spring Boot服务器进行通信,以检索"配置"数据,这些数据将从SQL数据库中检索。我的前端将显示此配置数据,并允许对其进行编辑。

我正在使用Axios进行REST调用。

我在我的一个组件的componentDidMount()中有一个Axios调用,它对服务器进行初始调用,然后将这些结果添加到该组件的状态中。然后页面会重新发送,并在一个表中填充结果。表是它自己的组件,每个条目也是它自己的组成部分。数据通过道具传递给这些组件。

问题

如果我将Axios调用保留在componentDidMount()中,那么我的应用程序将按预期工作。

然而,我有多个组件进行REST调用,所以存在重复的代码,所以我想在我的应用程序中创建一个任何组件都可以调用的REST服务。我通过遵循Command设计模式来实现这一点,甚至用Typescript编写了它。Overkill我知道,但这是为R&D和我只是想把它当作一种学习练习。

因此,为了使用它,我在组件中有一个函数,它设置命令、带有命令和url的调用程序,然后在调用程序上调用execute,并将结果添加到变量中。

所以问题来了:如果我使用我的REST服务,它在我的组件之外的类中有Axios调用,那么就不会发生重新应答,我的表也不会填充数据。但是,如果我将Axios调用保留在我的componentDidMount()中,那么一切都会正常工作。

我尝试了什么

在玩了很多console.log之后,我发现setState在设置状态之前没有得到数据,所以我在状态中的"项"是空白的。我通过在函数中添加"async"并在setState调用中添加"await"来解决此问题。现在,我可以通过console.log看到状态有数据,但是重新发布程序仍然没有触发。

我还试着移动一些东西——所以我有了componentDidMount()中的函数和所有东西,然后我把所有东西都移走了,只是在componentDidMont()内调用了那个函数——但这并没有奏效。

我的代码

maincomponent.js这是引发Axios调用的组件。你会看到我最初的Axios通话被评论掉了。

class ParameterList extends React.Component {
constructor(props) {
super(props)
this.state = {
error: null,
isLoaded: false,
items: [],
}
}
componentDidMount() {
// Axios.get(URLS.GET_CONFIG_PARAMETERS).then((results) => {
//     console.log(results.data)
//     this.setState({
//         items: results.data
//     })
// })
this.retrieveCONFIGParameters()
}

async retrieveCONFIGParameters() {
const serviceCommand = new GetServiceCommand()
const invoker = new ServiceInvoker(serviceCommand, URLS.GET_CONFIG_PARAMETERS)
const returnData = invoker.ExecuteRequest()
await this.setState({
items: returnData
}, function() {
console.log(this.state.items)
});  
console.log(this.state.items)
}
render() {
return (
<div>
<h1>Parameter List</h1>
<Container>
<Grid celled>
<Grid.Row>
<Label attached='top left' color='blue'>Current EDI Message Status</Label>
<Grid.Column width={16}>
<Table celled structured color='blue'>
<Table.Header>
<Table.Row>
<Table.HeaderCell><Radio label='Active'></Radio></Table.HeaderCell>
<Table.HeaderCell><Radio label='Inactive'></Radio></Table.HeaderCell>
<Table.HeaderCell><Radio label='All'></Radio></Table.HeaderCell>
</Table.Row>
<Table.Row>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
<Table.HeaderCell>XXXX</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{this.state.items.map((param, i) => {
if (param.active === true) {
return <ParameterListItem key={i} paramData={param} />
} else {
return false // TODO
}
}
)}
</Table.Body>
</Table>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column width={16}>
<Button color='teal'>
<Button.Content>Add</Button.Content>
</Button>
<Button color='teal'>
<Button.Content>Modify</Button.Content>
</Button>
<Button color='teal'>
<Button.Content>Remove</Button.Content>
</Button>
<Button color='teal'>
<Button.Content>Print</Button.Content>
</Button>
<Button color='teal'>
<Button.Content>Position</Button.Content>
</Button>
<Button color='teal'>
<Button.Content>Re-Instate</Button.Content>
</Button>
</Grid.Column>
</Grid.Row>
</Grid>
</Container>
<br></br>
</div>
);
}

}

GetServiceCommand.ts这是在.中具有Axios调用的GET命令

export class GetServiceCommand implements IServiceCommand {
returnData = [] as any
execute(serviceURL: string): Object {
Axios.get(serviceURL).then(results => {
console.log(results.data)
results.data.forEach((element: any) => {
this.returnData.push(element)
});
})
console.log(this.returnData)
return this.returnData
}

}

如果需要,我可以提供其他类的代码,但它们只是标准的命令模式类,所以我认为不需要。

最后的想法

我知道我所做的可能有些过头了,回到每个组件中简单地使用Axios调用会让这个问题消失。但这几天我一直在想,我很想知道我做错了什么。

最后一件事是:Axios调用和状态数据的console.log看起来不同。Axios的数据显示如下:

(6) [{…}, {…}, {…}, {…}, {…}, {…}]
0: {code: "8", active: true, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
1: {code: "5", active: true, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
2: {code: "2", active: true, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
3: {code: "6", active: true, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
4: {code: "6", active: false, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
5: {code: "6", active: false, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
length: 6

来自国家:

[]
0: {code: "8", active: true, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
1: {code: "5", active: true, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
2: {code: "2", active: true, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
3: {code: "6", active: true, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
4: {code: "6", active: false, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
5: {code: "6", active: false, description: "Sample Param Desc", XXXX: "System1", networkId: "Network1", …}
length: 6

您会注意到状态以[]开头,而Axios的数据为(6)。我不确定这是否是问题的原因。。。我认为这不太可能,因为它们只是数组,但我想我会提到它以防万一。

感谢所有花时间看这篇文章的人。

编辑

感谢所有提出建议的人。我现在已经设法解决了这个问题,尽管感觉有点脏。

这些建议突出了Axios通话和退货的问题——在整个节目中花了大量时间处理控制台日志后,我意识到GetServiceCommand中的退货实际上是空白的;这是令人困惑的,因为其他console.log显示,一旦它进入组件,就会有数据,但这不可能是正确的,因为Return在数据添加到数组之前就会触发。我认为console.log本质上误导了我(或者更准确地说,我误解了它们)。

解决方案是提供一个setState赋值函数,该函数可以在任何地方传递。

问题是,因为我使用的是Typescript,所以简单的<Component functionMutator={this.functionToSetstate}不起作用,所以我不得不更改REST层以接受一个单独的参数,即函数类型function。

然后将GetServiceCommand更改为不返回任何内容,而是在Axios调用完成后使用数据调用此函数,其方式与Qiarash的建议类似。

万一有人感兴趣,这里是我更新的代码:

maincomponent.js在这里,我创建了一个赋值函数,并将其绑定到构造函数中的"this"。然后,稍后在ComponentDidMount()中调用的函数中调用REST

setStateItems(items) {
this.setState({
items: items
}, function() {
console.log("setStateItems called")
})
}
constructor(props) {
super(props)
this.state = {
error: null,
isLoaded: false,
items: [],
}
this.setStateItems = this.setStateItems.bind(this)
}
retrieveCONFIGParameters() {
const serviceCommand = new GetServiceCommand()
const invoker = new ServiceInvoker(serviceCommand, EdiUrls.GET_CONFIG_PARAMETERS, this.setStateItems)
invoker.ExecuteRequest()
}

GetServiceCommand.ts您会注意到,现在只是对从maincomponent.js 传入的函数的调用,而不是Return

export class GetServiceCommand implements IServiceCommand {
execute(serviceURL: string, functionToCall: Function) {
let returnData: Array<string>;
returnData = []
Axios.get(serviceURL).then(results => {
console.log(results.data)
results.data.forEach((element: any) => {
console.log("test4")
returnData.push(element)
});
functionToCall(returnData)
})
}

}

再次感谢您的帮助和建议。我不相信这是一个很好的方法,但我确实学到了很多关于React、state和Typescript的知识。

问题是axios调用不同步,这意味着在执行axios.get之后,编译器会转到下一行,即:

console.log(this.returnData)
return this.returnData // [] empty array

因此,为了得到正确的结果,请确保在收到axios的回复时返回:

export class GetServiceCommand implements IServiceCommand {
returnData = [] as any
execute(serviceURL: string): Object {
// return the promise itself
return Axios.get(serviceURL).then(results => {
console.log(results.data)
results.data.forEach((element: any) => {
this.returnData.push(element)
});
// move return to here
return this.returnData
})
}
async retrieveCONFIGParameters() {
const serviceCommand = new GetServiceCommand()
const invoker = new ServiceInvoker(serviceCommand, URLS.GET_CONFIG_PARAMETERS)
const returnData = await invoker.ExecuteRequest()
this.setState({
items: returnData
}, function() {
console.log(this.state.items)
});  
console.log(this.state.items)
}

export class GetServiceCommand implements IServiceCommand {
returnData = [] as any
execute(serviceURL: string): Object {
return Axios.get(serviceURL).then(results => {
results.data.forEach((element: any) => {
this.returnData.push(element)
});
return this.returnData;
})
}

按照以下步骤操作。

  1. componentWillMount()调用axios
  2. 在您的状态isLoading: true中添加属性
  3. 获取完成后,只需更改isLoading: false

逻辑为:if (isLoading) ? 'Loading' : render()

最新更新