I'm new and just started to learn react. I encountered the problem that when I add an element, I do not overload the entire list. Although the update and deletion work fine, and the changes are immediately overwritten. A new item appears in the list after the page is reloaded.
it turns out, I get a list when I mount the component and I pull it out, and when I add a new element, the state is not aware of its change. Probably you need to immediately transfer to the state what came to me with fetchNotes (). How to create it correctly, please tell me, I have already tried to play with willMount () and do all sorts of manipulations, but either I manage to fill in the state, but then I don’t work this.state.map () or any other nonsense ...
My method for adding item:
class Note extends Component {
state = {
text: "",
updateNoteId: null,
};
componentDidMount() {
this.props.fetchNotes();
};
resetForm = () => {
this.setState({text: "", updateNoteId: null});
};
selectForEdit = (id) => {
let note = this.props.notes[id];
this.setState({text: note.text, updateNoteId: id});
};
submitNote = (e) => {
e.preventDefault();
if (this.state.updateNoteId === null) {
this.props.addNote(this.state.text).then(this.resetForm);
} else {
this.props.updateNote(this.state.updateNoteId,
this.state.text).then(this.resetForm);
}
this.resetForm();
};
render() {
return (
<div>
<div style={{textAlign: "right"}}>
{this.props.user.username} (<a onClick={this.props.logout}>logout</a>)
</div>
<h3>Add new note</h3>
<form onSubmit={this.submitNote}>
<input
value={this.state.text}
placeholder="Enter note here..."
onChange={(e) => this.setState({text: e.target.value})}
required />
<input type="submit" value="Save Note" />
</form>
<button onClick={this.resetForm}>Reset</button>
<h3>Notes</h3>
<table>
<tbody>
{this.props.notes.map((note, id) => (
<tr key={`note_${id}`}>
<td>{note.text}</td>
<td><button onClick={() => this.selectForEdit(id)}>edit</button></td>
<td><button onClick={() => this.props.deleteNote(id)}>delete</button></td>
</tr>
))}
</tbody>
</table>
</div>
)
}
}
const mapStateToProps = state => {
return {
notes: state.notes,
user: state.auth.user,
}
};
const mapDispatchToProps = dispatch => {
return {
fetchNotes: () => {
dispatch(notes.fetchNotes());
},
addNote: (text) => {
return dispatch(notes.addNote(text));
},
updateNote: (id, text) => {
return dispatch(notes.updateNote(id, text));
},
deleteNote: (id) => {
dispatch(notes.deleteNote(id));
},
logout: () => dispatch(auth.logout()),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Note);
reducers/
const initialState = [];
export default function notes(state=initialState, action) {
let noteList = state.slice();
switch (action.type) {
case 'FETCH_NOTES':
return [...state, ...action.notes];
case 'ADD_NOTE':
return [...state, ...action.note];
case 'UPDATE_NOTE':
let noteToUpdate = noteList[action.index];
noteToUpdate.text = action.note.text;
noteList.splice(action.index, 1, noteToUpdate);
return noteList;
case 'DELETE_NOTE':
noteList.splice(action.index, 1);
return noteList;
default:
return state;
}
}
action
export const fetchNotes = () => {
return (dispatch, getState) => {
let headers = {"Content-Type": "application/json"};
let {token} = getState().auth;
if (token) {
headers["Authorization"] = `Token ${token}`;
}
return fetch("/api/notes/", {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) {
return dispatch({type: 'FETCH_NOTES', notes: res.data});
} else if (res.status === 401 || res.status === 403) {
dispatch({type: "AUTHENTICATION_ERROR", data: res.data});
throw res.data;
}
})
}
};
export const addNote = text => {
return (dispatch, getState) => {
let headers = {"Content-Type": "application/json"};
let {token} = getState().auth;
if (token) {
headers["Authorization"] = `Token ${token}`;
}
let body = JSON.stringify({text, });
return fetch("/api/notes/", {headers, method: "POST", body})
.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 === 201) {
return dispatch({type: 'ADD_NOTE', note: res.data});
} else if (res.status === 401 || res.status === 403) {
dispatch({type: "AUTHENTICATION_ERROR", data: res.data});
throw res.data;
}
})
}
};
I think that I should somehow call setState in order to explicitly indicate the changes, or maybe I need to re-create the request for the backend as when initializing the component?
I will be glad to any hints and help from your side. Thank you in advance!
you should change reducer ADD case as follow :
case 'ADD_NOTE':
return {
...state,
noteList:[action.note,...state.noteList]
}
Related
here I have a problem when creating a search bar in reactjs.
// So, I have an endpoint like this
export const getCountPoiCategoryProvinsi = (provinsi) => {
return new Promise((resolve, reject) => {
axios
.get(
`${baseUrl}/api/dashboard/v1/getCountPoiCategoryProvinsi?provinsi=${provinsi}`,
{
headers: { Authorization: `Bearer ${token}` },
}
)
.then((response) => {
resolve(response.data.data);
})
.catch((error) => {
if (error.response?.data.code === 404)
resolve({ lists: [], totalCount: 0 });
console.log(error.response);
reject(error?.response?.data?.message || "Network error.");
});
});
};
// The code for the fetch is like this
const loadPosts = async (provinsi) => {
try {
setLoading(true);
const result = await getCountPoiCategoryProvinsi(provinsi);
setPosts(result);
console.log(result);
} catch (error) {
console.log("salah");
} finally {
setLoading(false);
}
};
loadPosts();
// and the code in the return section is like this
{loading ? (
<h4>Loading ...</h4>
) : (
posts
// eslint-disable-next-line array-callback-return
.filter((value) => {
if (searchTitle === "") {
return value;
} else if (
value.title.toLowerCase().includes(searchTitle.toLowerCase())
) {
return value;
}
})
.map((item, index) => (
<h5 key={index}>
{item.category} + {item.jumlah_category}
</h5>
))
)}
When I try in the browser and type in the search bar the data doesn't appear.
the console doesn't appear either.
what do you think is wrong in my code? Thank You
According to your fetch code, your loadPosts function did not have any input when called so likely your getCountPoiCategoryProvinsi function return an empty array.
nestjs controller.ts
#Patch(':id')
async updateProduct(
#Param('id') addrId: string,
#Body('billingAddr') addrBilling: boolean,
#Body('shippingAddr') addrShipping: boolean,
) {
await this.addrService.updateProduct(addrId, addrBilling, addrShipping);
return null;
}
nestjs service.ts
async updateProduct(
addressId: string,
addrBilling: boolean,
addrShipping: boolean,
) {
const updatedProduct = await this.findAddress(addressId);
if (addrBilling) {
updatedProduct.billingAddr = addrBilling;
}
if (addrShipping) {
updatedProduct.shippingAddr = addrShipping;
}
updatedProduct.save();
}
there is no problem here. I can patch in localhost:8000/address/addressid in postman and change billingAddr to true or false.the backend is working properly.
how can i call react with axios?
page.js
const ChangeBillingAddress = async (param,param2) => {
try {
await authService.setBilling(param,param2).then(
() => {
window.location.reload();
},
(error) => {
console.log(error);
}
);
}
catch (err) {
console.log(err);
}
}
return....
<Button size='sm' variant={data.billingAddr === true ? ("outline-secondary") : ("info")} onClick={() => ChangeBillingAddress (data._id,data.billingAddr)}>
auth.service.js
const setBilling = async (param,param2) => {
let adressid = `${param}`;
const url = `http://localhost:8001/address/`+ adressid ;
return axios.patch(url,param, param2).then((response) => {
if (response.data.token) {
localStorage.setItem("user", JSON.stringify(response.data));
}
return response.data;
})
}
I have to make sure the parameters are the billlingddress field and change it to true.
I can't make any changes when react button click
Since patch method is working fine in postman, and server is also working fine, here's a tip for frontend debugging
Hard code url id and replace param with hard coded values too:
const setBilling = async (param,param2) => {
// let adressid = `${param}`;
const url = `http://localhost:8001/address/123`; // hard code a addressid
return axios.patch(url,param, param2).then((response) => { // hard code params too
console.log(response); // see console result
if (response.data.token) {
// localStorage.setItem("user", JSON.stringify(response.data));
}
// return response.data;
})
}
now it worked correctly
#Patch('/:id')
async updateProduct(
#Param('id') addrId: string,
#Body('billingAddr') addrBilling: boolean,
) {
await this.addrService.updateProduct(addrId, addrBilling);
return null;
}
const ChangeBillingAddress = async (param) => {
try {
await authService.setBilling(param,true).then(
() => {
window.location.reload();
},
(error) => {
console.log(error);
}
);
}
catch (err) {
console.log(err);
}
}
const setBilling= async (param,param2) => {
let id = `${param}`;
const url = `http://localhost:8001/address/`+ id;
return axios.patch(url,{billingAddr: param2}).then((response) => {
if (response.data.token) {
localStorage.setItem("user", JSON.stringify(response.data));
}
return response.data;
})
}
I am using React/Redux.
The main issue is that when i use Promise then component is not re-rendered, whereas the code is working fine when promise code is not used.
Action Creator
const updateColor = colorobj => {
return dispatch =>
new Promise(function(resolve, reject) {
dispatch(fetchColorBegin());
axios
.post(config.APIURL.color.update, colorobj)
.then(response => {
const data = response.data;
if (data.errorno !== 0) {
dispatch(fetchColorFailure(data.errormsg));
reject(data.errormsg);
} else {
dispatch(updateColorSuccess(colorobj));
resolve('Color Updated');
}
})
.catch(error => {
dispatch(fetchColorFailure(error.message));
reject(error.message);
});
});
};
Reducer
case UPDATE_COLOR_SUCCESS:
const todoIndex = state.data.findIndex(todo => todo.id === action.payload.id);
return update(state, {
loading: { $set: false },
data: { [todoIndex]: { $merge: action.payload } },
error: { $set: null}
});
Component
the state is updated but the component is not updated.
const handleEditOk = values => {
let colorobj = {
id: state.updateData.id,
colorname: values.colorname,
colorcode: values.colorcode,
};
dispatch(updateColor(colorobj))
.then(response => {
message.success(response);
onCancel();
})
.catch(error => {
message.error(error);
});
};
The component update itself only on commenting the promise code.
The problem now is that it is not showing success/failure message.
const handleEditOk = values => {
let colorobj = {
id: state.updateData.id,
colorname: values.colorname,
colorcode: values.colorcode,
};
dispatch(updateColor(colorobj))
// .then(response => {
// message.success(response);
// onCancel();
// })
// .catch(error => {
// message.error(error);
// });
};
Kindly suggest.
I have created a codesandbox with a simplified version of my problem
https://codesandbox.io/s/new-react-context-api-ei92k
I get something from a fetch (in this case a user)
I then create a local copy of this user and make some changes to it
The problem: Any changes update my initial user object
Can someone tell me how this is possible? and how can I avoid this?
import React, { useState, useEffect } from "react";
import { AppSessionContext } from "./AppContext";
import Header from "./Header";
const user = {
userName: "jsmith",
firstName: "John",
lastName: "Smith",
isAdmin: true
};
const loadProfile = () => Promise.resolve(user);
function createUserWithNewName(userToUpdate) {
userToUpdate["userName"] = "Dummy";
return userToUpdate;
}
const App = () => {
const [user, setUser] = useState({});
const [Loaded, setLoaded] = useState(false);
var amendedUser = {};
useEffect(() => {
loadProfile()
.then(user => {
setUser(user);
console.log(user);
})
.then(() => {
amendedUser = createUserWithNewName(user);
console.log(amendedUser);
console.log(user);
})
.then(setLoaded(true));
}, []);
if (!Loaded) {
return "Loading";
}
return (
<AppSessionContext.Provider value={{ user }}>
<div className="App">
<Header />
</div>
</AppSessionContext.Provider>
);
};
export default App;
snippet of production code
loadTableDefault() {
fetch(defaultUrl(), {method: 'GET'})
.then(res => res.json())
.then(response => {
this.setState({
data: response,
})
return response
})
.then(response => {
this.setState({
table_data: formatResponsePretty(response),
})
})
.catch(error => console.error('Error:', error));
}
formatResponsePretty
export function formatResponsePretty(oldData) {
const newData = {
...oldData,
};
// consider re-writting the flask response to this format
const obj = { allocations: [] };
var theRemovedElement = ''
var ports = []
ports = Object.values(newData['allocations']['columns']);
ports.shift();
var dataArray = ['allocations', 'conditions', 'liquidity', 'hedging']
for (const index of dataArray) {
for (const i of newData[index]['data']) {
theRemovedElement = i.shift();
if (index === 'allocations') {
obj[index][theRemovedElement] = i
}
else {
obj[theRemovedElement] = i;
}
}
}
const rows = []
let index = 0;
Object.keys(obj).forEach(element => {
index = formatting.findIndex(x => x.name === element)
if (formatting[index] && formatting[index]['type'] === 'number') {
var new_obj = obj[element].map(function (el) {
return Number(el * formatting[index]['multiplier']).toFixed(formatting[index]['decimal']) + formatting[index]['symbol']
})
rows.push(new_obj)
}
else if (formatting[index] && formatting[index]['type'] === 'string') {
rows.push(obj[element])
}
else if (formatting[index] && formatting[index]['type'] === 'boolean') {
// there should be logic here to display true or false instead of 1 and 0
// this could be in the upload
rows.push(obj[element])
}
else {
rows.push(obj[element])
}
})
const arrOfObj = createRecords(ports, rows)
return {obj: obj, ports: ports, rows: rows, arrOfObj: arrOfObj}
}
In createUserWithNewName() you are updating the original user object and returning it.
You instead want to create a new object with all the old user properties, but with just the username changed. Thankfully, object destructuring makes this super easy:
function createUserWithNewName(oldUser) {
const newUser = {
...oldUser,
userName: 'Dummy',
};
return newUser;
}
This will copy all the properties of oldUser to a new object and then just update userName!
You're also going to want to pass user down to that second .then() as it won't currently be available in there:
.then(user => {
setUser(user);
console.log(user);
return user;
})
.then(user => {
amendedUser = createUserWithNewName(user);
console.log(user, amendedUser);
})
Update CodeSandbox link: https://codesandbox.io/s/new-react-context-api-tgqi3
I'm using Redux with redux-thunk to retrieve categories from an API. I have an action called viewCategory that depends on having categories in the store state.
I used the example of fetching Reddit posts from the Redux site:
https://redux.js.org/advanced/asyncactions#actions-js-asynchronous
The problem I have is that when I call viewCategory the promise thinks it's resolved when REQUEST_CATEGORIES is dispatched and not RECEIVE_CATEGORIES. So if log my state in the then statement I have an
empty list of categories.
export function viewCategory(urlKey) {
return (dispatch, getState) => {
dispatch(fetchCategoriesIfNeeded()).then(() => {
let state = getState();
console.log(state); // should have categories
let categories = [...state.categories.mainCategories,
...state.categories.specialCategories];
let matchCategory = categories.find((category) => {
return category.custom_attributes.find(x => x.attribute_code === "url_key").value === urlKey;
});
dispatch({
type: Categories.VIEW_CATEGORY,
category: matchCategory
});
});
};
}
The functions that decide if categories should be fetched at all:
function shouldFetchCategories(state) {
const categories = state.categories;
if(categories.isFetching || categories.mainCategories.length > 0) {
return false;
} else {
return true;
}
}
export function fetchCategoriesIfNeeded() {
return (dispatch, getState) => {
if(shouldFetchCategories(getState())) {
return dispatch(fetchCategories());
} else {
return Promise.resolve();
}
};
}
The function that contains the actual fetch call:
function fetchCategories() {
return (dispatch, getState) => {
dispatch(requestCategories());
const {locale} = getState().settings;
return fetch(`${BASE_URL}/categories/list`, {
method: "POST",
headers: {
"Accept-Language": locale
},
body: "Not interesting for stackoverflow"
})
.then(response => response.json())
.then(json => {
if(json !== undefined && json.items){
dispatch(receiveCategories(json.items));
}
});
};
}
The functions where I dispatch types REQUEST_CATEGORIES & RECEIVE_CATEGORIES:
function requestCategories() {
return {
type: Categories.REQUEST_CATEGORIES
};
}
function receiveCategories(result) {
const mainCategories = result.filter(category => category.level === 2);
const subCategories = result.filter(category => category.level === 3);
const categories = mainCategories.map(category => {
let children = subCategories.filter(x => x.parent_id === category.id);
return {
...category,
children
};
});
let specialCategories = categories.splice(categories.length - 2, 2);
return {
type: Categories.RECEIVE_CATEGORIES,
categories: categories,
specialCategories: specialCategories,
receivedAt: Date.now()
};
}
Any idea what I am doing wrong here? If you need any extra code or information please let me know.