'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})
})
}
}
Related
I have a collection of profile documents in firebase and I want to render them in the profiles page, however after I have updated the userProfiles state and use useDispatch to store the state in the slice, I get an infinite loop when rendering the profile page.
I have tried putting the dispatch() into a useEffect, not in a useEffect and inside the querySnapshot promise but I'm still getting an infinite loop wherever I put it.
Any feedback is appreciated, thank you.
\\ profiles.js
export const Profiles = () => {
const [userProfiles, setUserProfiles] = useState([]);
const dispatch = useDispatch();
const navigate = useNavigate();
const user = useSelector(selectUser);
db.collection("customers")
.doc(user.info.uid)
.collection("profiles")
.get()
.then((querySnapshot) => {
const documents = querySnapshot.docs.map((doc) => doc.data());
setUserProfiles(documents);
});
useEffect(() => {
dispatch(profiles(userProfiles));
}, []);
console.log({ userProfiles });
return (
<div className="profile_container">
<h1 className="profile_title">Who's Watching?</h1>
<div className="profile_row">
{userProfiles.map((profile) => {
return (
<div className="profile_individualProfile">
<img
src="https://occ-0-300-1167.1.nflxso.net/dnm/api/v6/K6hjPJd6cR6FpVELC5Pd6ovHRSk/AAAABY5cwIbM7shRfcXmfQg98cqMqiZZ8sReZnj4y_keCAHeXmG_SoqLD8SXYistPtesdqIjcsGE-tHO8RR92n7NyxZpqcFS80YfbRFz.png?r=229"
alt="profile"
/>
<p>{profile.name}</p>
</div>
);
})}
<div
onClick={() => navigate("/add-profile")}
className="profile_addProfile_container"
>
<img
src="https://img.icons8.com/ios-glyphs/30/FFFFFF/plus--v1.png"
alt="add profile"
/>
<h2>Add Profile</h2>
</div>
</div>
</div>
);
};
\\ userSlice.js
export const userSlice = createSlice({
name: "user",
initialState: {
user: {
info: null,
profiles: [],
},
},
reducers: {
login: (state, action) => {
state.user.info = action.payload;
},
logout: (state) => {
state.user.info = null;
},
profiles: (state, action) => {
state.user.profiles.push(action.payload);
},
},
});
In the current implementation, when your page is rendered, db.collections runs and you set state setUserProfiles(documents) which renders your app and again db.collections runs. to prevent this you should run db.collections in useEffect.
// fetch users only when your app renders
useEffect(() => {
db.collection("customers")
.doc(user.info.uid)
.collection("profiles")
.get()
.then((querySnapshot) => {
const documents = querySnapshot.docs.map((doc) => doc.data());
setUserProfiles(documents);
});
}, []);
have another useEffect
useEffect(() => {
dispatch(profiles(userProfiles));
}, [userProfiles]);
this will NOT work neither. setUserProfiles will be causing issue. Because when app renders, you fetch data, you set the state, change the userProfiles, this will rerender app again.
The problem with your code is you do not need setUserProfiles. instead in db.collections() when you get the documents, you dispatch the documents and then access the profiles from redux with useSelector
// fetch users only when your app renders
useEffect(() => {
db.collection("customers")
.doc(user.info.uid)
.collection("profiles")
.get()
.then((querySnapshot) => {
const documents = querySnapshot.docs.map((doc) => doc.data());
// setUserProfiles(documents); You do not need this
dispatch(profiles(userProfiles))
});
}, []);
Now use useSelector to reach the state in redux
// assuming reducers name is "users"
const usersState = useSelector((state) => state.users);
now when you use map guard your app
// make sure you use the correct data
// you migh need to destructure
{usersState && usersState.map((profile) => {
For anyone that runs into this issue you may find this useful. Following from yilmaz's helpful answer, I had to update the Profiles.js and userSlice.js as follows...
// Profiles.js
export const Profiles = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const usersState = useSelector(profiles);
useEffect(() => {
db.collection("customers")
.doc(usersState.payload.user.user.info.uid)
.collection("profiles")
.get()
.then((querySnapshot) => {
const documents = querySnapshot.docs.map((doc) => doc.data());
!usersState.payload.user.user.profiles.includes((arr) =>
documents.every(arr)
) && dispatch(profiles(documents));
});
}, []);
return (
<div className="profile_container">
<h1 className="profile_title">Who's Watching?</h1>
<div className="profile_row">
{usersState.payload.user.user.profiles.map((profile) => {
console.log(profile);
return (
<div className="profile_individualProfile">
<img
src="https://occ-0-300-1167.1.nflxso.net/dnm/api/v6/K6hjPJd6cR6FpVELC5Pd6ovHRSk/AAAABY5cwIbM7shRfcXmfQg98cqMqiZZ8sReZnj4y_keCAHeXmG_SoqLD8SXYistPtesdqIjcsGE-tHO8RR92n7NyxZpqcFS80YfbRFz.png?r=229"
alt="profile"
/>
<p>{profile.name}</p>
</div>
);
})}
<div
onClick={() => navigate("/add-profile")}
className="profile_addProfile_container"
>
<img
src="https://img.icons8.com/ios-glyphs/30/FFFFFF/plus--v1.png"
alt="add profile"
/>
<h2>Add Profile</h2>
</div>
</div>
</div>
);
};
// userSlice.js
export const userSlice = createSlice({
name: "user",
initialState: {
user: {
info: null,
profiles: [],
},
},
reducers: {
login: (state, action) => {
state.user.info = action.payload;
},
logout: (state) => {
state.user.info = null;
},
profiles: (state, action) => {
state.user.profiles.length = 0;
state.user.profiles.push(...action.payload);
},
},
});
I am trying to load the user data and display on my dashboard page, I have tried many methods but failed to do so. Anyone can help?
below is my dashboard page:
const Dashboard = (props) => {
const { user } = props.auth;
useEffect(() => {
getCurrentProfile();
}, []);
return (<Fragment>
<h1>Dashboard</h1>
<i className='fas fa-user'></i>Welcome
<p>{user && user.fullName}</p>
</Fragment>);
};
below is my redux action:
export const getCurrentProfile = () => async dispatch => {
const [user] = useState('')
await authAxios.get('/user/profile').then(res => {
const result = res.data.user.result;
dispatch({
type: GET_PROFILE,
payload: res.data
});
}).catch(error => {
dispatch({
type: PROFILE_ERROR,
payload: {
msg: error.response.statusText,
status: error.response.status
}
});
});
};
I need to display an nested array. But I am unable to display the nested list as my redux store is not getting updated. Below is the sample of the structure of the data:
{
email: "fgh#gmail.com"
tId: 2
teacherClasses: null
teacherUserRef: 3
user:
{
admin: null
firstName: "fgh"
id: 3
lastName: "fgh"
}}
I am unable to display anything which is inside user.
below is the code:
Reducer:
import { ACTION_TYPES } from "../actions/teacher";
const initialState = {
list: []
}
export const teacher = (state = initialState, action) => {
switch (action.type) {
case ACTION_TYPES.FETCH_ALL:
return {
...state,
list: [
...action.payload]
}
case ACTION_TYPES.FETCHBYID:
return {
...state,
list: [action.payload]
}
case ACTION_TYPES.CREATE:
return {
...state,
list: [...state.list, action.payload]
}
case ACTION_TYPES.UPDATE:
return {
...state,
list: state.list.map(x => x.id == action.payload.id ? action.payload : x)
}
case ACTION_TYPES.DELETE:
return {
...state,
list: state.list.filter(x => x.id != action.payload)
}
default:
return state
}
}
Component page:
Teacher.js:
const Teacher = ({ ...props }) => {
const [currentId, setCurrentId] = useState(0)
useEffect(() => {
console.log("teacher call")
props.fetchAllTeacher()
console.log(props.teacherList)
}, [currentId])//componentDidMount
return (
<div className="site-layout-background" style={{ padding: 24, textAlign: 'center' }}>
<Space direction="vertical" align="center">
<TableContainer>
<Table>
<TableHead >
<TableRow>
<TableCell>Name</TableCell>
<TableCell>Email</TableCell>
<TableCell></TableCell>
</TableRow>
{
props.teacherList.map((record, index) => {
return (<TableRow key={index} hover>
<TableCell>{record.email}</TableCell>
<TableCell>{record.user.firstName}</TableCell>
<TableCell>
<ButtonGroup variant="text">
<Button icon={<DeleteOutlined />} onClick={() => onDelete(record.user.id)}></Button>
</ButtonGroup>
</TableCell>
</TableRow>)
})}
</TableHead>
<TableBody>
</TableBody>
</Table>
</TableContainer>
</Space>
</div>
);
}
const mapStateToProps = state => ({
teacherList: state.teacher.list,
userList: state.user.list
})
const mapActionToProps = {
fetchAllTeacher: actions.fetchAll,
deleteUser: actions1.Delete
}
export default connect(mapStateToProps, mapActionToProps)(Teacher);
Action creator:
import api from "./api";
export const ACTION_TYPES = {
CREATE: 'CREATE',
UPDATE: 'UPDATE',
DELETE: 'DELETE',
FETCH_ALL: 'FETCH_ALL',
FETCHBYID: 'FETCHBYID'
}
export const fetchAll = () => dispatch => {
api.teacher().fetchAll()
.then(response => {
dispatch({
type: ACTION_TYPES.FETCH_ALL,
payload: response.data
})
})
.catch(err => console.log(err))
}
export const fetchById = (id) => dispatch => {
api.teacher().fetchById(id)
.then(response => {
dispatch({
type: ACTION_TYPES.FETCHBYID,
payload: response.data
})
})
.catch(err => console.log(err))
}
export const create = (data, onSuccess) => dispatch => {
api.teacher().create(data)
.then(res => {
dispatch({
type: ACTION_TYPES.CREATE,
payload: res.data
})
onSuccess()
})
.catch(err => console.log(err))
}
export const update = (id, data, onSuccess) => dispatch => {
api.teacher().update(id, data)
.then(res => {
dispatch({
type: ACTION_TYPES.UPDATE,
payload: { id, ...data }
})
onSuccess()
})
.catch(err => console.log(err))
}
export const Delete = (id, onSuccess) => dispatch => {
api.teacher().delete(id)
.then(res => {
dispatch({
type: ACTION_TYPES.DELETE,
payload: id
})
onSuccess()
})
.catch(err => console.log(err))
}
I am getting an error saying firstName is undefined.
Please help.
Recommendation
Since you are using functional component, you should use react-redux hooks like useSelector, useDispatch.
import { useSelector, useDispatch } from "react-redux"
...
teacherList = useSelect(state => state.teacher.list)
userList = useSelect(state => state.user.list)
const dispatch = useDispatch()
...
{
dispatch(actions.fetchAll(...))
dispatch(actions1.Delete(...))
}
Problem
First, you don't need to set currentId as a dependency of useEffect.
When dependency is an empty list, the callback will only be fired once, similar to componentDidMount.
Second, fetchAllTeacher is an asynchronous action which means you need to wait until all teachers are fetched successfully.
So you need to add a lazy loading feature.
The reason that your redux store is not getting updated is because you must dispatch the actions. The correct signature for mapDispatchToProps is:
const mapDispatchToProps = (dispatch) => {
fetchAllTeacher: () => dispatch(actions.fetchAll()),
deleteUser: (id) => dispatch(actions.Delete(id)),
}
export default connect(mapStateToProps, mapDispatchToProps)(Teacher);
BUT the there is a better way. You are actually mixing two paradigms, and while the above will work, you should use redux hooks, since you have created a functional component and you are already using the useEffect hook.
It could work like this:
import { useSelector, useDispatch } from "react-redux"
const Teacher = ({ ...props }) => {
const dispatch = useDispatch();
useEffect(() => {
console.log("teacher call")
const teachers = props.fetchAllTeacher();
// dispatch the action that will add the list to the redux state
dispatch(actions.fetchAll(teachers));
}, [currentId]);
// fetch the teacher list from redux store
const teacherList = useSelector(state => state.teacher.list);
return (...);
}
Consider moving the selector definition state => state.teacher.list to its own module so that you can reuse it in other components and update it in one place if the structure of your store changes.
It looks like no actions were getting dispatched in your code, so the problem was not due to nesting of the data. You can have nested data in your state without a problem.
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?
[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.