Use component props in onClick redux-thunk dispatch React-Redux - reactjs

Trying to use props from <button> of component in the dispatch of a redux-thunk function that has been set up for Async process but I can't quite get how to use both props and the function (that's being connected to the component through react-redux connect in the mapDispatchToProps) but I just can't figure out how to call both the props and the function.
function loadData(dispatch, medium) {
console.log(dispatch)
return dispatch({type: 'LOADING_COMPONENT'})
return axios.get(`/professionals/${medium}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
);
}
const mapDispatchToProps = (dispatch) => {
return {
LogInClick : () => dispatch(loadData()),
}
}
const LoginButtons = ({props, LogInClick}) => (
<button onClick={() => LogInClick(props.medium)} type="button">{props.buttonName}</button>
)
const LoginConnect = connect(null, mapDispatchToProps)(LoginButtons)
And Then I export that and try to call it so it can be reused in the render file like
<LoginConnect medium='suhhhh' buttonName='To log in to suhhh'/>

function loadData(dispatch, medium) {
console.log(dispatch)
return dispatch({type: 'LOADING_COMPONENT'})
return axios.get(`/professionals/${medium}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
);
}
const mapDispatchToProps = (dispatch) => {
return {
LogInClick : () => dispatch(loadData()),
}
}
const LoginButtons = ({medium, buttonName, LogInClick}) => (
<button onClick={() => LogInClick(medium)} type="button">{buttonName}</button>
)
const LoginConnect = connect(null, mapDispatchToProps)(LoginButtons)
This should work !! actually connect merges mapStateToProps, mapDispatchToProps into this.props. Read this documenation for better understanding https://github.com/reactjs/react-redux/blob/master/docs/api.md

Try returning a function, which redux-thunk will then call with dispatch as an argument.
You can then call dispatch from that returned function:
function loadData(medium) {
return (dispatch) => {
dispatch({ type: 'LOADING_COMPONENT' })
axios.get(`/professionals/${medium}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
)
}
}
Your LogInClick function can then take an argument which can be passed into loadData:
const mapDispatchToProps = (dispatch) => {
return {
LogInClick: (medium) => dispatch(loadData(medium)),
}
}
const LoginButtons = (props) => (
<button onClick={() => props.LogInClick(props.medium)} type="button">{props.buttonName}</button>
)
I hope this helps.

Related

Redux store not getting updated

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.

Async Action: Getting a class in return instead of dispatch function

I'm using react, redux-thunk, async action for signin gets a dispatch function like it should be async action for signout gets a class with properties of event like target etc when consoling the dispatch.
Navbar.jsx
const Navbar = ({ profile, history }) => {
return (
<nav>
<Button type="danger" onClick={signOut(history)}>
Logout
</Button>
</nav>
)
}
const mapStateToProps = state => ({
profile: state.firebase.profile,
})
const mapDispatchToProps = dispatch => ({
signOut: history => dispatch(signOut(history)),
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(withRouter(Navbar))
Async Action
export const signIn = ({ email, password }, history) => {
return (dispatch, getState) => {
auth.signInWithEmailAndPassword(email, password)
.then(() => {
console.log('TCL: dispatch', dispatch) // returns dispatch function
history.push('/')
dispatch({ type: 'LOGIN_SUCCESS' })
})
.catch(err => {
dispatch({ type: 'LOGIN_ERROR', err })
})
}
}
export const signOut = history => (dispatch, getState) => {
auth.signOut()
.then(() => {
console.log('TCL: dispatch', dispatch) // return class and throws dispatch is not a function
history.push('/login')
dispatch({ type: 'SIGNOUT_SUCCESS' })
})
.catch(err => console.log(err))
}
Found the solution - I also needed to get the signOut from the props as well
import {signOut} from '../store/actions/authActions'
const Navbar = ({ profile, history, signOut }) => { // adding "signOut" solved it.
return (
<nav>
<Button type="danger" onClick={() => signOut(history)}>
Logout
</Button>
</nav>
)
}
You are calling the signOut function when hooking up the event handler which assigns the result to the onClick handler i.e.
onClick={signOut(history)}
Which means onClick would trigger (dispatch, getState) => ... and explains why dispatch === evt. You need to wrap your call with an event handler to swallow the click event:
onClick={() => signOut(history)}

How do you dispatch after looping through an array?

import { FETCH_DATA } from "./types";
export const fetchData = () => dispatch => {
const array = [];
fetch(
"https://example-api-endpoint.com"
)
.then(res => res.json())
.then(data =>
data.forEach(element => {
fetch(
"https://another-example-api-endpoint.com"
)
.then(res => res.json())
.then(data => {
array.push(data);
dispatch({
type: FETCH_DATA,
payload: array
});
});
})
);
};
Currently, I am dispatching for every element. I was wondering if there was a way I could dispatch after every iteration of the forEach has run.
It's a bit primitive but here we go:
import { FETCH_DATA } from "./types";
export const fetchData = () => dispatch => {
const array = [];
var dispatchData = () => {
dispatch({
type: FETCH_DATA,
payload: array
});
}
fetch(
"https://example-api-endpoint.com"
)
.then(res => res.json())
.then(data =>{
var fetchCount = 0
data.forEach((element,index) => {
fetch(
"https://another-example-api-endpoint.com"
)
.then(res => res.json())
.then(data => {
array.push(data);
fetchCount++;
if(fetchCount === data.length){
dispatchData()
}
});
})
});
};
You could map the final promises into an array and then dispatch in Promise.all.
import { FETCH_DATA } from "./types";
export const fetchData = () => dispatch => {
fetch("https://example-api-endpoint.com")
.then(res => res.json())
.then(data => {
const promises = data.map(element =>
fetch("https://another-example-api-endpoint.com").then(res =>
res.json()
)
);
Promise.all(promises).then(payload =>
dispatch({
type: FETCH_DATA,
payload
})
);
});
};

How to pass async data to dispatch action so I can use it in action creator?

Redux is harrrrd... At least to me, it is!!! Can someone please explain to me how can I pass this fetched json[0] through mapDispatchToProps to my action creator? And am I doing it right? I am using redux-thunk, is this the correct way of using it?
state = {
articles: {
article: []
}
};
qrCodeOnReadHandler = ({ data }) => {
this.props.onQRRead();
console.log(this.props.art);
fetch(data)
.then(response => response.json())
.then(json => {
console.log(json),
// () => this.props.onQRRead(json[0]),
this.setState({
...this.state,
articles: {
...this.state.articles,
article: json[0]
}
});
});
};
connecting redux
const mapStateToProps = state => {
return {
art: state.articles.article
};
};
const mapDispatchToProps = dispatch => {
return {
onQRRead: () => dispatch(article())
};
};
action creators
export const fetchedArticle = res => {
return {
type: ARTICLE,
res: res
};
};
export const article = res => {
return dispatch => {
dispatch(fetchedArticle(res));
};
};
how can I pass this fetched json[0] through mapDispatchToProps to my
action creator
You have to make your onQRRead receive an argument like this:
const mapDispatchToProps = dispatch => {
return {
onQRRead: payload => dispatch(article(payload))
};
};
The name of the function parameter is arbitrary.
For now, you can use it like the way you just did:
this.props.onQRRead(json[0])

How to wait for dispatch to complete using thunk middleware?

I am using redux-thunk and want like to dispatch an action and once that is finished make an api call with part of that updated store.
store.js
const middleware = composeEnhancers(applyMiddleware(promise(), thunk, logger()))
const localStore = loadStore()
const store = createStore(reducer, localStore, middleware)
graphActions.js:
First add an Element:
export function addElement(element) {
return dispatch => {
dispatch({
type: ADD_ELEMENT,
payload: element
})
}
}
Then make api call via different action creator:
export function saveElements() {
return (dispatch, getState) => {
let graphId = getState().elements.id
let elements = getState().elements.elements
axios.put(Config.config.url + '/graph/' + graphId, {
'data': JSON.stringify({elements: elements}),
}).then(() => {
dispatch({type: SHOW_SUCCESS_SNACKBAR})
}).catch((err) => {
dispatch({type: SHOW_ERROR_SNACKBAR})
dispatch({type: UPDATE_ELEMENTS_REJECTED, payload: err})
})
}
}
I need to make sure, that addElement() is finished before saveElements(), so that saveElements() accesses the updated store.
I tried the following:
export function addElement(element) {
const promise = (dispatch) => new Promise((resolve) => {
dispatch({
type: ADD_ELEMENT,
payload: element
})
resolve()
})
return dispatch => {
promise(dispatch).then(() => {
saveElements()
})
}
}
ADD_ELEMENT is dispatched, but the actions within saveElements() are not dispatched, no api call is made.
I was missing to dispatch saveElements() and returning dispatch(saveElements()).
export function addElement(element) {
const promise = (dispatch) => new Promise((resolve) => {
dispatch({
type: ADD_ELEMENT,
payload: element
})
resolve()
})
return (dispatch) => {
return addElements(dispatch).then(() => {
return dispatch(saveElements())
})
}
UPDATE:
Noticed I can simply do:
export function addElement(element)
return (dispatch) => {
dispatch({
type: ADD_ELEMENT,
payload: element
})
dispatch(saveElements())
})
}

Resources