I basically want to put each string in the array in a separated div, I'm trying to do this but is not rendering anything
export default class Loja extends Component {
state = {
loja: {},
lojaInfo: {},
category: []
}
async componentDidMount() {
const { id } = this.props.match.params;
const response = await api.get(`/stores/${id}`);
const { category, ...lojaInfo } = response.data
this.setState({ loja: category, lojaInfo });
console.log(category)
}
render() {
const { category } = this.state;
return (
<p>{category.map(cat => <div>{cat}</div>)}</p>
);
}
}
The console.log(category) shows this:
The error is that you're updating 2 properties inside the componentDidMount, one is loja: category and the second property is lojaInfo, but in the render() method you're accessing this.state.category which still is an empty string.
What you want to do instead is, inside of your componentDidMount update your state like this:
this.setState({
category,
lojaInfo,
});
you've added your category into the loja object in the state.
something like this should work:
async componentDidMount() {
const { id } = this.props.match.params;
const response = await api.get(`/stores/${id}`);
const { category, ...lojaInfo } = response.data
this.setState({ category, lojaInfo });
}
Related
I have a requirement in which once page gets loaded my dropdownlist should be populated. for that I put that code in componentDidMount().
componentDidMount() {
axios.get(`http://localhost:8080/country_code`).then((res) => {
const countryData = res.data;
this.setState({ countryData });
alert(countryData);
});
}
I have one user input field in which person enter the value and save it into database. I want once user save that value into DB, my dropdown should get refresh and that value should be visible in the dropdown. How can I externally call componentDidMount()? is there any better way to handle the same?
As of now list is getting refreshed only when user resfresh the page.
You can't call externally componentDidMount() method !. so you need set
common function which is call in componentDidMount() and onChange dropdown value. see below code !
class App extends Component {
componentDidMount() {
this.handleCallApi();
}
handleCallApi = () => {
axios.get(`http://localhost:8080/country_code`).then((res) => {
const countryData = res.data;
this.setState({ countryData });
alert(countryData);
});
}
render() {
return (
<div>
<button onClick={this.handleCallApi}>Call Api</button>
</div>
);
}
}
export default App;
You can't call componentDidMount externally but you can extract the code in componentDidMount to a method and can call it in both componentDidMount and onSave.
alertDropDown = () => {
axios.get(`http://localhost:8080/country_code`).then((res) => {
const countryData = res.data;
this.setState({ countryData });
alert(countryData);
});
}
componentDidMount
componentDidMount() {
this.alertDropDown()
}
On DB save method
onSave = () => {
this.alertDropDown()
}
You can't call the componentDidMount(), as it's a lifecycle method and is called at initial render. You can expose a function and call that function from inside the componentDidMount() something like:
updateDropdownData = () => {
axios.get(`http://localhost:8080/country_code`).then((res) => {
const countryData = res.data;
this.setState({ countryData });
alert(countryData);
});
}
componentDidMount() {
this.updateDropdownData()
}
And you can call this.updateDropdownData() from anywhere you want. Just like:
onSomeUserAction = () => {
this.updateDropdownData()
}
I'm trying to modify an element in the array in a state. lets say I want to modify "id".
class example extends Component {
state = {
recipes: [{
id: 5
}]
}
changeID = (newID, arrayIndex) => {
}
What would I need to put in changeID to modify id?
You can use something like this:
function changeID(ID){
const { recipes } = this.state;
const filteredRecipes = recipes.filter(item => item.id === ID);
/*
Any logic to change the element here!
You can check if the element exists before doing anything.
*/
const recipe = filteredRecipes.shift();
const updatedRecipe = { ...recipe, id:6 }
//And if you want to update the state...
const updatedRecipes = [ ...recipes, updatedRecipe ];
this.setState({ recipes: updatedRecipes });
}
You can use setState combined with map (since your state is an array) to modify a given element with a specific id
changeID = (id, index) =>{
this.setState(prev =>{
return {
recipes : prev.recipes.map((recipe, i) =>{
if(index === i) return {...recipe, id}
return recipe
})
}
})
}
Change your code:
class Example extends Component{
constructor(props) {
super(props);
this.state = { recipes: [{ id: 5 }] };
}
changeID = (newID, arrayIndex) => {
let { recipes } = this.state;
recipes[arrayIndex].id = newID;
this.setState({ recipes: recipes }, () => console.log(this.state.recipes));
};
}
Here is the live working solution: Visit to see
Happy coding :)
In React, state should be readonly. To modify a value within an array, you should create a new array with just the one element updated.
class example extends Component{
state={
recipes: [
{id: 5}
]
}
changeID = (newID,arrayIndex)=>{
// create a shallow copy of the recipe with an updated id
const newRecipe = { ...recipes[arrayIndex], id: newID };
const newRecipes = recipes
.slice() // shallow copy the array
.splice(arrayIndex, 1, newRecipe); // replace the element at arrayIndex with new Recipe
this.setState({ recipes: newRecipes });
}
}
You can change array values by using the assignment operator = and index lookup []. E.g.
let arr = [3,4,5];
arr[0] = 0;
arr; // 0,4,5
For your example, that's:
let state = {
recipes: [{
id: 5
}]
};
let changeId = (newId, arrayIndex) =>
state.recipes[arrayIndex].id = newId;
changeId(4, 0);
console.log(state);
I am facing the following issue and not able to figure it out.
I have two variables inside the state called userDetails & userDetailsCopy. In componentDidMount I am making an API call and saving the data in both userDetails & userDetailsCopy.
I am maintaining another copy called userDetailsCopy for comparison purposes.
I am updating only userDetails inside setState but even userDetailsCopy is also getting updated instead of have old API data.
Below is the code :
constructor(){
super()
this.state={
userDetails:{},
userDetailsCopy: {}
}
}
componentDidMount(){
// API will return the following data
apiUserDetails : [
{
'name':'Tom',
'age' : '28'
},
{
'name':'Jerry',
'age' : '20'
}
]
resp.data is nothing but apiUserDetails
/////
apiCall()
.then((reps) => {
this.setState({
userDetails: resp.data,
userDetailsCopy: resp.data
})
})
}
updateValue = (text,i) => {
let userDetail = this.state.userDetails
userDetail[i].name = text
this.setState({
userDetails: userDetail
})
}
submit = () => {
console.log(this.state.userDetials) // returns updated values
console.log(this.state.userDetailsCopy) // also return updated values instead of returning old API data
}
Need a quick solution on this.
The problem with this is that you think you are making a copy of the object in state by doing this
let userDetail = this.state.userDetails
userDetail.name = text
But, in Javascript, objects are not copied like this, they are passed by referrence. So userDetail at that point contains the referrence to the userDetails in your state, and when you mutate the userDetail it goes and mutates the one in the state.
ref: https://we-are.bookmyshow.com/understanding-deep-and-shallow-copy-in-javascript-13438bad941c
To properly clone the object from the state to your local variable, you need to instead do this:
let userDetail = {...this.state.userDetails}
OR
let userDetail = Object.assign({}, this.state.userDetails)
Always remember, Objects are passed by referrence not value.
EDIT: I didn't read the question properly, but the above answer is still valid. The reason userDetailCopy is being updated too is because resp.data is passed by referrence to both of them, and editing any one of them will edit the other.
React state and its data should be treated as immutable.
From the React documentation:
Never mutate this.state directly, as calling setState() afterwards may
replace the mutation you made. Treat this.state as if it were
immutable.
Here are five ways how to treat state as immutable:
Approach #1: Object.assign and Array.concat
updateValue = (text, index) => {
const { userDetails } = this.state;
const userDetail = Object.assign({}, userDetails[index]);
userDetail.name = text;
const newUserDetails = []
.concat(userDetails.slice(0, index))
.concat(userDetail)
.concat(userDetails.slice(index + 1));
this.setState({
userDetails: newUserDetails
});
}
Approach #2: Object and Array Spread
updateValue = (text, index) => {
const { userDetails } = this.state;
const userDetail = { ...userDetails[index], name: text };
this.setState({
userDetails: [
...userDetails.slice(0, index),
userDetail,
...userDetails.slice(index + 1)
]
});
}
Approach #3: Immutability Helper
import update from 'immutability-helper';
updateValue = (text, index) => {
const userDetails = update(this.state.userDetails, {
[index]: {
$merge: {
name: text
}
}
});
this.setState({ userDetails });
};
Approach #4: Immutable.js
import { Map, List } from 'immutable';
updateValue = (text, index) => {
const userDetails = this.state.userDetails.setIn([index, 'name'], text);
this.setState({ userDetails });
};
Approach #5: Immer
import produce from "immer";
updateValue = (text, index) => {
this.setState(
produce(draft => {
draft.userDetails[index].name = text;
})
);
};
Note:
Option #1 and #2 only do a shallow clone. So if your object contains nested objects, those nested objects will be copied by reference instead of by value. So if you change the nested object, you’ll mutate the original object.
To maintain the userDetailsCopy unchanged you need to maintain the immutability of state (and state.userDetails of course).
function getUserDerails() {
return new Promise(resolve => setTimeout(
() => resolve([
{ id: 1, name: 'Tom', age : 40 },
{ id: 2, name: 'Jerry', age : 35 }
]),
300
));
}
class App extends React.Component {
state = {
userDetails: [],
userDetailsCopy: []
};
componentDidMount() {
getUserDerails().then(users => this.setState({
userDetails: users,
userDetailsCopy: users
}));
}
createChangeHandler = userDetailId => ({ target: { value } }) => {
const { userDetails } = this.state;
const index = userDetails.findIndex(({ id }) => id === userDetailId);
const userDetail = { ...userDetails[index], name: value };
this.setState({
userDetails: [
...userDetails.slice(0, index),
userDetail,
...userDetails.slice(index + 1)
]
});
};
render() {
const { userDetails, userDetailsCopy } = this.state;
return (
<React.Fragment>
{userDetails.map(userDetail => (
<input
key={userDetail.id}
onChange={this.createChangeHandler(userDetail.id)}
value={userDetail.name}
/>
))}
<pre>userDetails: {JSON.stringify(userDetails)}</pre>
<pre>userDetailsCopy: {JSON.stringify(userDetailsCopy)}</pre>
</React.Fragment>
);
}
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I'm trying to access the first object from data[]. Then, grab the keys using Object.keys() but it gives me this error:
"TypeError: Cannot convert undefined or null to object".
I need the output to be an array of the keys.
import React, { Component } from 'react';
class CodecChart extends Component {
constructor(props) {
super(props);
this.state = {
post: [],
isLoaded: false,
}
}
componentDidMount() {
const url = 'https://jsonplaceholder.typicode.com/users';
fetch(url)
.then(result => result.json())
.then(post => {this.setState({ post: post })
})
}
render() {
const data = this.state.post;
// cannot reach the first object of data[]
var keys = Object.keys(data[0]);
return (
<div>
//output should be an array of the keys
<h5>{keys}</h5>
</div>
)
}
}
export default CodecChart;
The first time you try to access data[0], it's still empty:
this.state = {
post: [],
isLoaded: false,
}
and const data = this.state.post; means that data[0] is undefined.
it's only after the component is mounted, and the state is set correctly that data[0] is defined (or not, depending on what the API returns).
I found a way for it to work by adding another "then" so it can set the "keys" state right after the "posts" state was set. But I wonder if there is another way to make it more elegant. Thank you for trying to help.
constructor(props) {
super(props);
this.state = {
posts: [],
isLoaded: false,
keys: []
}
}
componentDidMount() {
const url = 'https://jsonplaceholder.typicode.com/users';
fetch(url)
.then(result => result.json())
.then(posts => {
this.setState({ posts: posts })
})
.then(_ => { this.setState({ keys: Object.keys(this.state.posts[0]) }) })
}
render() {
const keys = this.state.keys;
return (
<div>
<h5>{keys}</h5>
</div>
)
}
Im trying to create a filter function based on the user input. The filter works fine but it won't return items when I delete characters. I know it has something to do with updating the state. I hope someone can help me out.
import React, {Component} from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
class Fetch extends Component {
constructor(){
super();
this.state = {
data: []
}
this.handleChange = this.handleChange.bind(this)
}
handleChange = (event) => {
console.log(event.target.value);
return this.setState({data: this.state.data.filter(data => data.title.toLowerCase().includes(event.target.value.toLowerCase()))})
}
async componentDidMount() {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos`);
const json = await response.json();
this.setState({ data: json });
}
catch (error) {
console.error(error)
}
}
render() {
return(
<div>
<p><Link to={`/`}>Link to homepage</Link></p>
<form>
<input type="text" onChange={this.handleChange}></input>
</form>
<ul>
{
this.state.data.map(data => (
<li key={data.id}>{data.id} => {data.title}</li>
))
}
</ul>
</div>
)
}
}
export default Fetch;
It is because you don't keep the initial data obtained from the HTTP request. Here is the problem:
Initially state: data = []
ComponentDidMount: data = ['abc', 'bcd', 'cdf']
Filter for keyword b: data = ['abc', 'bcd'] (as cdf does not contain the letter b)
Erase the filter (filter = '') but your data variable has the value data = ['abc', 'bcd'], so it can return at most 2 values.
your code looks fine, but your filter function is overwriting the state's data property. I suggest storing the full array in data (as you are right now) and store the filtered results in another property of the state, something like this:
import React, {Component} from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
class Fetch extends Component {
constructor(){
super();
this.state = {
data: [],
filtered: [] // This will store your filtered elements from data
}
this.handleChange = this.handleChange.bind(this)
}
handleChange = (event) => {
console.log(event.target.value);
// Filter the array stored in data, but never update it.
// Update filtered instead.
return this.setState({filtered: this.state.data.filter(data => data.title.toLowerCase().includes(event.target.value.toLowerCase()))})
}
async componentDidMount() {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos`);
const json = await response.json();
// Keep the original data here.
this.setState({ data: json });
}
catch (error) {
console.error(error)
}
}
render() {
return(
<div>
<p><Link to={`/`}>Link to homepage</Link></p>
<form>
<input type="text" onChange={this.handleChange}></input>
</form>
<ul>
{
this.state.filtered.map(data => (
<li key={data.id}>{data.id} => {data.title}</li>
))
}
</ul>
</div>
)
}
}
export default Fetch;
Remember that filter don't modify the original array,it always returns a new one.
You can use the following solution to solve your problem:
this.setState({ data: this.state.data.filter(data => data.title.toLowerCase().indexOf(event.target.value.toLowerCase().trim() !== -1) ) })