this is app.js
class App extends Component {
constructor() {
super();
this.state = {
todo_lists: [
{ id: 1, name: "Hoc React" },
{ id: 2, name: "Hoc HTML" },
{ id: 3, name: "Hoc Jquery" },
{ id: 4, name: "Hoc CSS" }
],
showList : []
};
}
componentDidMount(){
let {showList, todo_lists} = this.state
this.setState({
showList : [...todo_lists]
})
console.log(showList)
}
}
when console.log(showList) on browser it return empty array like this [], clearly I assigned showList : [...todo_lists] in setState. help me
From Reactjs.org
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
https://reactjs.org/docs/react-component.html#setstate
So what you wrote will NOT happen immediately, and your console.log will NOT get the right values immediately afterwards.
Issue :
// this.setState is async method
this.setState({
showList : [...todo_lists]
});
console.log(showList) // <--- this will not give you updated value right after
you can use this.setState callback method for that, it is being called right after the state is set
this.setState({
// showList : [...todo_lists]
showList : this.state.todo_lists.map(todo => ({ ...todo}))
},() => {
console.log(this.state.showList) //<------ Check here
})
Suggestion :
constructor() {
super();
this.todos = [
{ id: 1, name: "Hoc React" },
{ id: 2, name: "Hoc HTML" },
{ id: 3, name: "Hoc Jquery" },
{ id: 4, name: "Hoc CSS" }
]
this.state = {
todo_lists: this.todos.map(todo => ({ ...todo})),
showList : this.todos.map(todo => ({...todo}))
};
}
The problem here is that React's setState is an async method, so it may delay the update in favor of perceived improvement in performance.
What you could do, is something like this (if you don't want to use the callback provided by the setState):
componentDidMount() {
const {showList, todo_lists} = this.state,
nextShowList = [...todo_lists];
this.setState({
showList: nextShowList
});
console.log(nextShowList); // executes immediatelly
}
If you want to use the callback provided by the setState method, simply use it like this:
componentDidMount() {
const {showList, todo_lists} = this.state;
this.setState({
showList: [...todo_lists]
}, () => console.log(this.state.showList)); // MAY NOT execute immediately
}
Also, as an additional tip (if I may), use the first argument as a function, or you may have problems setting your todo_list!
componentDidMount() {
this.setState(prevState => {
return {
showList: [...prevState.todo_lists]
};
}, () => console.log(this.state.showList));
}
Again, for the same reason: Since setState is async, you can't guarantee that the this.state outside of the setState is up-to-date!
Related
In my React component, I have the following code:
const [additionalGuestAmounts] = useState<RatePlan['additionalGuestAmounts']>(
useAdditionalGuestAmounts(roomType, occupantsForBaseRate)
)
And here is my custom hook:
const useAdditionalGuestAmounts = (
roomType: RoomType,
occupantsForBaseRate: number
): RatePlan['additionalGuestAmounts'] => {
console.log('executing useAdditionalGuestAmounts hook')
if (!roomType) {
return []
}
console.log('returning array')
return [
{
foo: 'bar'
},
]
}
When I modify roomType from null to a value, it logs 'executing useAdditionalGuestAmounts hook' and then 'returning array', however the value of additionalGuestAmounts is not updated; it stays as an empty array. Why is the value not being updated if the custom hook is being executed?
The value that is passed in the useState hook is only the initial value of the state as explained on the documentation of the useState hook: https://reactjs.org/docs/hooks-state.html, to make sure that the state is updated whenever the useAdditionalGuestAmounts hook is called we will have to call setAdditionalGuestAmounts with the new value. This would then look like:
// Initial state
const [additionalGuestAmounts, setAdditionalGuestAmounts] = useState<RatePlan['additionalGuestAmounts']>([])
const useAdditionalGuestAmounts = (
roomType: RoomType,
occupantsForBaseRate: number
) => {
if (!roomType) {
return []
}
// Update the state, this causes the page to re-render
setAdditionalGuestAmounts( [
{
foo: 'bar'
},
])
}
If you need the useAdditionalGuestAmounts to be called on page load you can use the useEffect hook for this (https://reactjs.org/docs/hooks-effect.html)
The value of the additionalGuestAmounts state variable is not being updated when you modify roomTypeId because the useAdditionalGuestAmounts hook is not being called again. This is because the useAdditionalGuestAmounts hook is only called when the component is first rendered, and is not called again.
As stated previously by rowan, you can a useEffect hook inside the useAdditionalGuestAmounts hook.
const useAdditionalGuestAmounts = (
roomType: RoomType,
occupantsForBaseRate: number
): RatePlan['additionalGuestAmounts'] => {
console.log('executing useAdditionalGuestAmounts hook')
const [additionalGuestAmounts, setAdditionalGuestAmounts] = useState<
RatePlan['additionalGuestAmounts']
>([]);
useEffect(() => {
if (!roomType) {
return;
}
console.log('returning array');
setAdditionalGuestAmounts([
{
foo: 'bar',
},
{
foo: 'bar',
},
{
foo: 'bar',
},
{
foo: 'bar',
},
{
foo: 'bar',
},
]);
}, [roomTypeId, occupantsForBaseRate]);
return additionalGuestAmounts;
};
You also need to add the occupantsForBaseRate argument to the dependencies array, because the useAdditionalGuestAmounts hook depends on it.
Hope this helps.
I need to persist step form's data as if the user clicks on other step. So during unmounting of step form component, I need to update the state to REDUX store
Issue
The state seems to be resetting to default as soon as I am accessing it while unmounting;
// local state
const [state, setState] = React.useState({
named: [{ id: uuid(), value: '', name: '', type: ' ' }], // 👈 initial state
});
React.useEffect(() => {
// update redux state before unmounting
return () => {
// 👇 sets itself to initial state ignoring what user has typed
console.log(state); // prints [{ id: uuid(), value: '', name: '', type: ' ' }]
updateStepThreeReduxState(state); // redux action
};
}, []);
Is there any way we can get an access to the state just before unmouting ??
State and updateStepThreeReduxState are dependencies of your effect:
//custom hook defined outside the component
function useIsMounted() {
const isMounted = useRef(false)
useEffect(() => {
isMounted.current = true
return () => {
isMounted.current = false
}
}, [])
return () => isMounted.current
}
// local state
const [state, setState] = React.useState({
named: [{ id: uuid(), value: '', name: '', type: ' ' }], // 👈 initial state
});
const mounted = useIsMounted()
React.useEffect(() => {
// update redux state before unmounting
return () => {
if(!mounted()){
console.log(state); // prints [{ id: uuid(), value: '', name: '', type: ' ' }]
updateStepThreeReduxState(state); // redux action
}
};
}, [state, updateStepThreeReduxState,mounted]);//<- added dependencies
Because you didn't add it to dependencies you have a state value that is a stale closure. Missing dependencies should cause a warning with the linter, create react app should have set all of this up for you so either use create-react-app or stop ignoring the warnings it produces.
I have a toggle button in my page, when it is OFF it sends 0 value to my post API and when button is ON it sends 1 in API. But the problem is when project is loaded or refreshed by toggling the button state value doesn't change first time but by toggling one or two times it works well after. code is given below any solution would be great help. thanks.
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: false,
value: 0
};
this.handleChange = this.handleChange.bind(this);
handleChange() {
if (this.state.checked === true) {
this.setState({ value:1, checked: false })
}
else if (this.state.checked == false) {
this.setState({ value:0, checked: true })
}
const article = {
"all_value":this.state.value
};
const headers = {
'api-key': 'key',
};
axios.post('url', article, { headers })
.then(function(response) {console.log(response);
})
};
This is only applicable where the component is functional. An example is below to change class component to functional component.
You're calling setState() and then using the value immediately. That will cause the component to rerender, with the correct state but by that point you've made the call with the previous state.
I would suggest the axios.post call is inside a useEffect hook, to detect changes
this.state = {
checked: false,
value: 0
};
useEffect(() => {
const article = {
"all_value": this.state.value
}
const headers = {
'api-key': 'key',
};
axios.post('url', article, { headers })
.then(function (response) {
console.log(response);
})
}, [this.state])
This will use setState to trigger a rerender with the new values, and then useEffect will trigger seeing the values have changed, and make a new call to the API.
EDIT: The this.state needed to have [ ]
Class based component to functional component
Taking the example from the question, it's trivial to convert a class component to a functional component
import React, { useState, useEffect } from 'react'
const Dashboard = (props) => {
const [dashboardState, setDashboardState] = useState({
checked: false,
value: 0
});
useEffect(() => {
const article = {
"all_value": dashboardState.value
}
const headers = {
'api-key': 'key',
};
axios.post('url', article, { headers })
.then(response => {
console.log(response);
})
}, [setDashboardState])
const handleChange = () => {
dashboardState.checked ?
setDashboardState({ value: 1, checked: false }) : setDashboardState({ value: 0, checked: true })
const article = {
"all_value": dashboardState.value
};
const headers = {
'api-key': 'key',
};
axios.post('url', article, { headers })
.then(response => {
console.log(response);
})
};
return (
<div>Do your rendering / use handleChange() down here</div>
)
}
export default Dashboard
I have a search component containing an input on which I defined a key up event handler function for fetching data based on entered string. As you can see below:
class SearchBox extends Component {
constructor(props) {
super(props);
this.state = {
timeout: 0,
query: "",
response: "",
error: ""
}
this.doneTypingSearch = this.doneTypingSearch.bind(this);
}
doneTypingSearch(evt){
var query = evt.target.value;
if(this.state.timeout) clearTimeout(this.state.timeout);
this.state.timeout = setTimeout(() => {
fetch('https://jsonplaceholder.typicode.com/todos/1/?name=query' , {
method: "GET"
})
.then( response => response.json() )
.then(function(json) {
console.log(json,"successss")
//Object { userId: 1, id: 1, title: "delectus aut autem", completed: false } successss
this.setState({
query: query,
response: json
})
console.log(this.state.query , "statesssAfter" )
}.bind(this))
.catch(function(error){
this.setState({
error: error
})
});
}, 1000);
}
render() {
return (
<div>
<input type="text" onKeyUp={evt => this.doneTypingSearch(evt)} />
<InstantSearchResult data={this.state.response} />
</div>
);
}
}
export default SearchBox;
The problem is the setState which I used in the second .then(). The response won't update . I want to update it and pass it to the InstantSearchResult component which is imported here. Do you have any idea where the problem is ?
Edit - Add InstantSearchResult component
class InstantSearchBox extends Component {
constructor(props) {
super(props);
this.state = {
magicData: ""
}
}
// Both methods tried but got error => Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
componentDidUpdate(props) {
this.setState({
magicData: this.props.data
})
}
shouldComponentUpdate(props) {
this.setState({
magicData: this.props.data
})
}
render() {
return (
<h1>{ this.state.magicData}</h1>
);
}
}
export default InstantSearchBox;
Edit:
Be aware that setState is asynchronous reading this article.
I've understand that the setState works fine in my fetch success the problem was the console.log which I shouldn't use it after setState instead I console.log in render() and I found out that the state updates correctly .
The other thing I wasn't careful for was the InstantSearchResult Constructor! When I re-render the SearchBox component consequently the InstantSearchResult renders each time but it's constructor runs just once. And if I use setState in InstantSearchResult I will face an infinite loop so I have to use this.props instead to pass the data to the second component.
this has been overridden inside the promise callback function. You to save it to a variable:
doneTypingSearch(evt){
var _this = this,
query = evt.target.value;
if(this.state.timeout) clearTimeout(this.state.timeout);
this.state.timeout = setTimeout(() => {
fetch('https://jsonplaceholder.typicode.com/todos/1/?name=query' , {
method: "GET"
})
.then( response => response.json() )
.then(function(json) {
console.log(json,"successss")
//Object { userId: 1, id: 1, title: "delectus aut autem", completed: false } successss
_this.setState({
query: query,
response: json
})
console.log(_this.state.query , "statesssAfter" )
}/*.bind(this)*/)
.catch(function(error){
_this.setState({
error: error
})
});
}, 1000);
}
What is the best way to test that an async call within componentDidMount sets the state for a React component? For context, the libraries I'm using for testing are Mocha, Chai, Enzyme, and Sinon.
Here's an example code:
/*
* assume a record looks like this:
* { id: number, name: string, utility: number }
*/
// asyncComponent.js
class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
records: []
};
}
componentDidMount() {
// assume that I'm using a library like `superagent` to make ajax calls that returns Promises
request.get('/some/url/that/returns/my/data').then((data) => {
this.setState({
records: data.records
});
});
}
render() {
return (
<div className="async_component">
{ this._renderList() }
</div>
);
}
_renderList() {
return this.state.records.map((record) => {
return (
<div className="record">
<p>{ record.name }</p>
<p>{ record.utility }</p>
</div>
);
});
}
}
// asyncComponentTests.js
describe("Async Component Tests", () => {
it("should render correctly after setState in componentDidMount executes", () => {
// I'm thinking of using a library like `nock` to mock the http request
nock("http://some.url.com")
.get("/some/url/that/returns/my/data")
.reply(200, {
data: [
{ id: 1, name: "willson", utility: 88 },
{ id: 2, name: "jeffrey", utility: 102 }
]
});
const wrapper = mount(<AsyncComponent />);
// NOW WHAT? This is where I'm stuck.
});
});
So, what you are really trying to test is that based on some mock data it "should render correctly ...".
As some people pointed out, a good way to achieve that is by placing the data fetching logic into a separate container and have a "dumb" presentation component that only knows how to render props.
Here is how to do that:
(I had to modify it a bit for Typescript with Tslint, but you'll get the idea)
export interface Props {
// tslint:disable-next-line:no-any
records: Array<any>;
}
// "dumb" Component that converts props into presentation
class MyComponent extends React.Component<Props> {
// tslint:disable-next-line:no-any
constructor(props: Props) {
super(props);
}
render() {
return (
<div className="async_component">
{this._renderList()}
</div>
);
}
_renderList() {
// tslint:disable-next-line:no-any
return this.props.records.map((record: any) => {
return (
<div className="record" key={record.name}>
<p>{record.name}</p>
<p>{record.utility}</p>
</div>
);
});
}
}
// Container class with the async data loading
class MyAsyncContainer extends React.Component<{}, Props> {
constructor(props: Props) {
super(props);
this.state = {
records: []
};
}
componentDidMount() {
fetch('/some/url/that/returns/my/data')
.then((response) => response.json())
.then((data) => {
this.setState({
records: data.records
});
});
}
// render the "dumb" component and set its props
render() {
return (<MyComponent records={this.state.records}/>);
}
}
Now you can test MyComponent rendering by giving your mock data as props.
Ignoring the, sane, advice to think again about the structure, one way to go about this could be:
Mock the request (fx with sinon), to make it return a promise for some records
use Enzyme's mount function
Assert that the state to not have your records yet
Have your rest function use done callback
Wait a bit (fx with setImmediate), this will make sure your promise is resolved
Assert on the mounted component again, this time checking that the state was set
Call your done callback to notify that the test has completed
So, in short:
// asyncComponentTests.js
describe("Async Component Tests", () => {
it("should render correctly after setState in componentDidMount executes", (done) => {
nock("http://some.url.com")
.get("/some/url/that/returns/my/data")
.reply(200, {
data: [
{ id: 1, name: "willson", utility: 88 },
{ id: 2, name: "jeffrey", utility: 102 }
]
});
const wrapper = mount(<AsyncComponent />);
// make sure state isn't there yet
expect(wrapper.state).to.deep.equal({});
// wait one tick for the promise to resolve
setImmediate(() => {
expect(wrapper.state).do.deep.equal({ .. the expected state });
done();
});
});
});
Note:
I have no clue about nock, so here I assume your code is correct
IMO, this is actually a common issue which appears more complicated because of promises and componentDidMount:
You're trying to test a functions which are only defined within the scope of another function. i.e. You should split your functions out and test them individually.
Component
class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
records: []
};
}
componentDidMount() {
request.get('/some/url/that/returns/my/data')
.then(this._populateState);
}
render() {
return (
<div className="async_component">
{ this._renderList() }
</div>
);
}
_populateState(data) {
this.setState({
records: data.records
});
}
_renderList() {
return this.state.records.map((record) => {
return (
<div className="record">
<p>{ record.name }</p>
<p>{ record.utility }</p>
</div>
);
});
}
}
Unit Test
// asyncComponentTests.js
describe("Async Component Tests", () => {
describe("componentDidMount()", () => {
it("should GET the user data on componentDidMount", () => {
const data = {
records: [
{ id: 1, name: "willson", utility: 88 },
{ id: 2, name: "jeffrey", utility: 102 }
]
};
const requestStub = sinon.stub(request, 'get').resolves(data);
sinon.spy(AsyncComponent.prototype, "_populateState");
mount(<AsyncComponent />);
assert(requestStub.calledOnce);
assert(AsyncComponent.prototype._populateState.calledWith(data));
});
});
describe("_populateState()", () => {
it("should populate the state with user data returned from the GET", () => {
const data = [
{ id: 1, name: "willson", utility: 88 },
{ id: 2, name: "jeffrey", utility: 102 }
];
const wrapper = shallow(<AsyncComponent />);
wrapper._populateState(data);
expect(wrapper.state).to.deep.equal(data);
});
});
});
Note: I've written the unit tests from documentation alone, so the use of shallow, mount, assert, and expect might not be best practices.