[SOLVED]
I'm trying to make a small application with Redux/React/Mongo/Typescript and faced a problem, that when I'm adding element to database, I can see new added row in a table but without values in it. But after refreshing the page, values are there.
I think that this is because the array with records immediately refreshes, while new element is not in database yet. I used await/async but it didn't solve this problem. Can anyone help me with this?
Action.ts
export const getAllTeams: ActionCreator<ThunkAction<Promise<any>,
ITeam[],
null,
ITeamGetAllAction
>> = () => {
return async (dispatch: Dispatch) => {
await axios.get('http://localhost:5000/teams/')
.then(res => {
dispatch({
teams: res.data,
type: TeamActionsTypes.GET_ALL,
})
}
);
};
};
export const addTeam: ActionCreator<ThunkAction<Promise<any>,
ITeamAddTeamAction,
ITeam,
ITeamAddTeamAction
>> = (team: ITeam) => {
return async (dispatch: Dispatch) => {
await axios.post('http://localhost:5000/teams/add', team)
.then(res => {
dispatch({
type: TeamActionsTypes.ADD_TEAM,
result: res,
});
})
};
};
Reducer.ts:
export const teamReducer: Reducer<ITeamState, TeamActions> = (
state = initialTeamState,
action,
) => {
switch (action.type) {
case TeamActionsTypes.GET_ALL: {
return {
...state,
teams: action.teams,
};
}
case TeamActionsTypes.ADD_TEAM: {
return{
...state,
teams: [action.result,...state.teams]
}
}
case TeamActionsTypes.GET_ONE: {
return{
...state,
}
}
default:
return state;
}
};
Component.tsx
interface RatingTableProps {
getTeams: () => Promise<TeamActionsTypes.GET_ALL>;
teams: ITeam[];
}
const RatingTable: React.FC<RatingTableProps> = ({
getTeams,
teams
}) => {
useEffect(()=>{
getTeams();
},[]);
return (
<table className="striped">
<thead>
<tr>
<th>Team</th>
<th className="right-align">Clicks</th>
</tr>
</thead>
<tbody>
{teams && teams.map(team => {
return <>
<tr key={team.name}>
<td>{team.name}</td>
<td className="right-align">{team.count}</td>
</tr>
</>
})}
</tbody>
</table>
)
};
const mapStateToProps = (store: IAppState) => {
return {
teams: store.teamState.teams,
};
};
const mapDispatchToProps = (dispatch: ThunkDispatch<any, any, AnyAction>) => {
return {
getTeams: () => dispatch(getAllTeams()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(RatingTable);
ComponentAdd.tsx
interface RatingFormProps{
addTeam: (team: ITeam) => Promise<TeamActionsTypes.ADD_TEAM>;
}
const RatingForm: React.FC<RatingFormProps> = ({
addTeam
})=> {
const [teamName, setTeamName] = useState<string>('');
const changeHandle = (event: React.ChangeEvent<HTMLInputElement>) => {
setTeamName(event.target.value);
};
const handleSubmit = (event: React.FormEvent) =>{
event.preventDefault();
addTeam({
name: teamName,
count: 0,
});
setTeamName('')
};
return (
<div className="row">
<form onSubmit={handleSubmit}>
<div className="inputField col s6">
<label htmlFor="teamName" className="active">
Name your team:
</label>
<input
onChange={changeHandle}
value={teamName}
type="text"
id="teamName"
placeholder="Best team name ever"
/>
</div>
<div className="col s6">
<button className="btn-large waves-effect waves-light" type="submit" name="action">Submit
</button>
</div>
</form>
</div>
)
};
const mapDispatchToProps = (dispatch: ThunkDispatch<any, any, AnyAction>) => {
return {
addTeam: (team: ITeam) => dispatch(addTeam(team)),
};
};
export default connect(
null,
mapDispatchToProps,
)(RatingForm);
teams.js(Routes)
router.route('/').get(async (req, res) => {
await Team.find()
.then(teams => res.json(teams))
.catch(err => res.status(400).json('Error: ' + err));
});
router.route('/add').post(async (req, res) => {
const name = req.body.name;
const count = 0;
const newTeam = new Team({name, count,});
await newTeam.save()
.then(() => res.json('Team added to database!'))
.catch(err => res.status(400).json('Error: ' + err));
});
How it looks in UI:
before adding new element
after adding new element
If you need any additional information or code, please let me know. Thank you!
UPDATE 1:
Thank to the comment, of course one big error is to return actual object after posting, not a string(Shame on me)
router.route('/add').post(async (req, res) => {
const name = req.body.name;
const count = 0;
const newTeam = new Team({name, count,});
await newTeam.save()
.then((team) => res.json(team))
.catch(err => res.status(400).json('Error: ' + err));
});
UPDATE 2:
OMG this was such a stupid error
The previous update solved an issue, but after I've recieved undefiened value from dipatch and this was because I was returning Promis, but not value.
export const addTeam: ActionCreator<ThunkAction<Promise<any>,
ITeamAddTeamAction,
ITeam,
ITeamAddTeamAction
>> = (team: ITeam) => {
return async (dispatch: Dispatch) => {
await axios.post('http://localhost:5000/teams/add', team)
.then(res => {
dispatch({
type: TeamActionsTypes.ADD_TEAM,
result: res.data, //<- Instead of just res
});
})
};
};
Thanks to everyone, who spent some time on it, stupid errors as always.
You should return the updated team from your back-end API. You can modify your API like this.
router.route('/add').post(async (req, res) => {
const name = req.body.name;
const count = 0;
const newTeam = new Team({name, count,});
await newTeam.save()
.then(team => res.json(team))
.catch(err => res.status(400).json('Error: ' + err));
});
Drop a comment if the problem still persists.
Related
I want to display the response from my fetch request in react. So far, I built the fetch request and set up the useEffect and useState hooks. The response is an object. What am I doing wrong?
function App() {
const url = 'https://api.gemini.com/v1/book/btcusd'
const [orders, setOrders] = useState([])
const fetchData = () => {
fetch(url).then(response => {
return response.json();
}).then(data => {
console.log(data)
setOrders(data)
}).catch(error => {
console.log(error)
})
}
useEffect(() => {
fetchData()
}, [])
return (
<div className="App">
<h1>{orders.asks}</h1>
</div>
);
}
export default App;
Taking a quick look at that API, the asks property holds an array of objects. You would need to map those to JSX elements in order to display them.
Also, if orders is meant to be an object, you should not initialise it as an array.
Finally, you should always check the Response.ok property to see if the request resolved successfully.
// fetch-data.js
const url = "https://api.gemini.com/v1/book/btcusd";
export const fetchData = async () => {
const res = await fetch(url);
if (!res.ok) {
throw Object.assign(new Error(`${res.status}: ${res.statusText}`), {
url,
text: await res.text(),
});
}
return res.json();
};
// App.jsx
import { useEffect, useState } from "react";
import { fetchData } from "./fetch-data";
function App() {
// init with an object with empty `asks` array
const [orders, setOrders] = useState({ asks: [] });
useEffect(() => {
fetchData().then(setOrders).catch(console.error);
}, []);
return (
<div className="App">
{/* Map over the data */}
{orders.asks.map(({ price, amount }, i) => (
<dl key={i}>
<dt>Price</dt>
<dd>{price}</dd>
<dt>Amount</dt>
<dd>{amount}</dd>
</dl>
))}
</div>
);
}
export default App;
Your data is an object. You should use map to loop.
const url = "https://api.gemini.com/v1/book/btcusd";
const [orders, setOrders] = useState({ asks: [{price: 0, amount: 0}], bids: [{price: 0, amount: 0}] });
const fetchData = () => {
fetch(url)
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data); //Data: {bids: Array(50), asks: Array(50)}
setOrders(data);
})
.catch((error) => {
console.log(error);
});
};
useEffect(() => {
fetchData();
}, []);
return (
<div className="App">
<table className="table">
<thead>
<th>Ask price</th>
<th>Ask amount</th>
<th>Bid price</th>
<th>Bid amount</th>
</thead>
{orders.asks?.map((e, i) => {
return (
<>
<tr>
<td>{e.price}</td>
<td>{e.amount}</td>
<td>{orders.bids[i].price}</td>
<td>{orders.bids[i].amount}</td>
</tr>
</>
)
})}
</table>
</div>
);
You Can use map function to display each item
eg:
orders.asks.map(item=>
<div>
<h1>{item.price}</h1>
<h1>{item.amount}</h1>
</div>
)
I'm trying to send and see my data status in my console log, when I click on 'Cancel' button, the status will be change by status:cancel, if I click on 'finish' button then the status is status:finish and same idea for the last one with save. Here what I've try to do but the status is not working
export default function App() {
const [data, setData] = useState({
status: ""
});
const [status, setStatus] = useState("");
const saveState = () => {
setStatus("saved");
};
const finishState = () => {
setStatus("finish");
};
const pendingState = () => {
setStatus("pending");
};
useEffect(() => {
axios
.post("")
.then((res) => {
console.log(res);
setInvitations(res.data.invitations[0]);
})
.catch((err) => {
console.log(err);
});
}, []);
function submit(e) {
e.preventDefault();
axios
.post("", {
status: data.status
})
.then((res) => {
console.log(res.data);
});
}
return (
<>
<form onSubmit={(e) => submit(e)}>
<button onClick={saveState}>Save</button>
<button onClick={finishState}> Finish</button>
<button onClick={pendingState}> Cancel</button>
</form>
</>
);
}
you can use simple setsate
export default function App() {
const [data, setData] = useState({
status: "",
});
const [status, setStatus] = useState("");
useEffect(() => {
axios
.post("")
.then((res) => {
console.log(res);
setInvitations(res.data.invitations[0]);
})
.catch((err) => {
console.log(err);
});
}, []);
function submit(e) {
e.preventDefault();
axios
.post("", {
status: data.status,
})
.then((res) => {
console.log(res.data);
});
}
return (
<>
<form onSubmit={(e) => submit(e)}>
<button onClick={() => setStatus({ status: "saved" })}>Save</button>
<button onClick={() => setStatus({ status: "finish" })}> Finish</button>
<button onClick={() => setStatus({ status: "pending" })}>
{" "}
Cancel
</button>
</form>
</>
);
}
You are using setStatus to change the status, but you are using axios.post() on your data.status
You need to either setData in your 3 functions
const saveState = () => {
setData({status:"saved"});
};
const finishState = () => {
setData({status:"finish"});
};
const pendingState = () => {
setData({status:"pending"});
};
or you can change axios.post to:
function submit(e) {
e.preventDefault();
axios
.post("", {
status: status //This is the change
})
.then((res) => {
console.log(res.data);
});
}
My parent component use hook useEffect for get data from API and pass props to child component.
const ParentComoponent = () => {
const [adsData, setAdsData] = useState([]);
useEffect(() => {
api
.get(`MyUrl`, { headers: authHeader() })
.then((res) => {
console.log(res);
setAdsData(res.data.data);
})
.catch((err) => {
console.log(err);
});
}, []);
return <Child adsData={adsData} />;
};
My Child component has handleDeleteClick function for delete request from API
const Child = () => {
const [deletedItem, setDeletedItem] = useState("");
const handleDeleteClick = (e, id) => {
e.preventDefault();
axios
.delete(`MyUrl`, { params: { id: id } })
.then((res) => console.log(res))
.catch((err) => console.log(err));
};
return (
<div>
// array.map Items list
<a
href=""
onClick={(e) => handleDeleteClick(e, ads.id)}
className="tables__link"
>
Delete
</a>
</div>
);
};
Delete request works successfully, but my list not updated.
How update my items list after deleted item?
You would need to pass another function that is called when a delete is executed. Something like:
const ParentComoponent = () => {
const [adsData, setAdsData] = useState([]);
const fetchData = () => {
api
.get(`MyUrl`, { headers: authHeader() })
.then((res) => {
console.log(res);
setAdsData(res.data.data);
})
.catch((err) => {
console.log(err);
});
};
const onDelete = () => {
fetchData();
};
useEffect(() => {
fetchData();
}, []);
return <Child adsData={adsData} onDelete={fetchData} />;
};
const Child = (props) => {
const [deletedItem, setDeletedItem] = useState("");
const handleDeleteClick = (e, id) => {
e.preventDefault();
axios
.delete(`MyUrl`, { params: { id: id } })
.then((res) => {
console.log(res);
props.onDelete();
})
.catch((err) => console.log(err));
};
return (
<div>
// Items list
<a
href=""
onClick={(e) => handleDeleteClick(e, ads.id)}
className="tables__link"
>
Delete
</a>
</div>
);
};
Put your delete function in the parent and pass it to the child. Then after deleting, update your list in the parent.
<ParentComponent>
const [adsData, setAdsData] = useState([]);
const handleDeleteClick = (e, id) => {
e.preventDefault();
axios
.delete(`MyUrl`, {params: {id: id}})
.then(res => {
console.log(res)
//TODO:: Implement list.pop or similar
})
.catch(err => console.log(err));
};
useEffect(() => {
api.get(`MyUrl`, { headers: authHeader() })
.then(res => {
console.log(res);
setAdsData(res.data.data);
})
.catch(err => {
console.log(err);
})
}, []);
return (
<Child
adsData={adsData}
handleClick={handleDeleteClick}
/>
)
</ParentComponent>
return (
<div>
// array.map Items list
<a href="" onClick={(e) =>
handleDeleteClick(e, ads.id)}className="tables__link">Delete</a>
</div>
)```
i have already tried useMemo and useEffect, but i can't seem to figure out why my code don't work:
const App: React.FC = () => {
const dispatch = useDispatch();
const [page, setPage] = useState(1);
const { userIds, users, totalUsers } = useSelector(
({ users }: RootState) => users
);
const renderUsers = useMemo(() => {
return userIds.map(userId => (
<div key={users[userId].first_name}>{users[userId].first_name}</div>
));
}, [userIds, users]);
const hasMore = useMemo(() => {
return userIds.map(userId => userId).length < totalUsers;
}, [userIds, totalUsers]);
const fetchUsers = useCallback(
async (page: number) => {
dispatch({
type: FETCH_USERS_REQUEST,
payload: { page }
});
try {
const { data, ...result } = await api.fetchUsers(page);
const user = new schema.Entity('users');
const {
entities,
result: { users: userIds }
} = normalize({ users: data }, { users: [user] });
dispatch({
type: FETCH_USERS_SUCCESS,
payload: {
...result,
users: entities.users,
userIds
}
});
} catch (error) {
dispatch({ type: FETCH_USERS_FAILURE, payload: { error } });
}
},
[dispatch]
);
useEffect(() => {
fetchUsers(1);
}, [fetchUsers]);
let scrollParentRef: HTMLDivElement | null = null;
return (
<div className="vh-100 vw-100">
<Header />
<div
className="container overflow-auto"
ref={div => {
scrollParentRef = div;
}}
>
<InfiniteScroll
pageStart={0}
loadMore={async page => await fetchUsers(page)}
hasMore={hasMore}
loader={
<div className="loader" key={0}>
Loading ...
</div>
}
useWindow={false}
getScrollParent={() => scrollParentRef}
>
{renderUsers}
</InfiniteScroll>
</div>
</div>
);
};
I am using infinite scroll and the error i got is call stack, please help me fix this?
'id' gets sent through action and reducer and logs out 'deleted' but doesn't actually delete from firestore database...
clientlist:
class Clients extends Component {
handleClick = (id) => {
// e.preventDefault();
this.props.deleteClient(id)
}
render() {
const {clientList} = this.props
return (
<div className="container mt-5 text-center">
<h2>Here Are Your List of Clients...</h2>
{clientList && clientList.map(client => {
return(
<div key={client.id}>
<div className="my-2">
Client Name: {client.name} | Client Price: ${client.price}
<button className="ml-2" onClick={() => {this.handleClick(client.id)}}>x</button>
</div>
</div>
)
})}
<AddClient/>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
clientList : state.firestore.ordered.clientList,
}
}
const mapDispatchToProps = (dispatch) => {
return{
deleteClient : (id) => dispatch(deleteClient(id))
}
}
export default compose(
connect(mapStateToProps, mapDispatchToProps),
firestoreConnect([
{collection: 'clientList', orderBy: 'name'},
])
)(Clients)
action:
export const deleteClient = (id) => {
return(dispatch, getState, {getFirestore, getFirebase}) => {
const firestore = getFirestore();
firestore.collection("clientList").doc(id).delete().then(() => {
dispatch({type: 'DELETE CLIENT'})
}).catch((err) => {
dispatch({type: 'DELETE CLIENT ERROR', err})
});
}
}
let me know if you need any other code or information. ps, there is no error logging out into the console.
Try this. .doc(id) because id has to be string.
I dont think you need / inside doc.
Check the api.
export const deleteClient = (id) => {
console.log(id);
return(dispatch, getState, {getFirestore, getFirebase}) => {
const firestore = getFirestore();
firestore.collection('clientList').doc(id).delete().then(() =>{
dispatch({type: 'DELETE CLIENT'})
}).catch((err) => {
dispatch({type: 'DELETE CLIENT ERROR', err})
})
}
}