Well, for example I have container and api:
container:
...
const mapDispatchToProps = dispatch => ({
handleSubmitForm: (signin_data) => {
dispatch(getUserInfo(signin_data))
}
})
...
api:
...
export function logIn( data ){
return dispatch => {
dispatch(startFetching('signin'))
return axios.post(domain + '/signin', {user: data})
.then(response => {
setTokensToLocalStorage( response.data )
dispatch(fetchingSuccess('signin'))
dispatch(getCurrentUser())
hashHistory.push('/')
dispatch(callFlash('success', 'signIn'))})
.catch(error => {
dispatch(fetchingFailed('signin'))
dispatch(callFlash('error', 'signInFail'))})
}}
/some simple functions for logout, signup etc/
...
It seems, that it should be like that:
...
export function logIn( data ){
return dispatch => {
dispatch(startFetching('signin'))
return axios.post(domain + '/signin', {user: data})
.then(response => {
dispatch(fetchingSuccess('signin'))})
.catch(error => {
dispatch(fetchingFailed('signin'))})
}}
/some simple functions for logout, signup etc/
...
But in which place should I call other methods? May be in container, but it will be strange to import this methods in every container, when I can import them just once in api
dispatch(getCurrentUser())
hashHistory.push('/')
dispatch(callFlash('success', 'signIn'))
dispatch(callFlash('error', 'signInFail'))
Related
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 this reducer which shall return all comments on the page :
case actionTypes.GET_COMMENT:
return {
...state,
comments: action.comments
}
export const getComment = (comments : Object[]) => {
return {
type : actionTypes.GET_COMMENT,
comments
}
}
Here is how i call it in component
useEffect(() => {
const getAllCommentsOnCurrentPostFromBE = (id: Number) => {
axios.get(`http://localhost:4000/getComment/${id}`)
.then(res => {
console.log('--------res,get', res.data);
dispatch(actions.getComment(res.data))
console.log('--------posts', posts);
})
.catch(err => {
console.log('--------err', err);
})
}
getAllCommentsOnCurrentPostFromBE(grabIdFromLocation())
},[])
res.data is collection of key value pairs like this {"comment":"123"}
But it is not rendering anything,any suggestions please?
There is no dispatch() function. Downloaded data do not pass to the reducer. You have to use redux-thunk to use async functions with redux.
I recommend using actions in separate files:
export const fetchDataFromDatabase = () => async (
disapatch,
getState,
) => {
const response = await axios.get();
disapatch({
type: TYPE,
data: response.data,
});
};
Then export your component export default connect(yourProps,{fetchDataFromDatabase})(YourComponent)
In your component you can call props.fetchDataFromDatabase()
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)}
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.
I am writing a metrics page using React-Redux, which I haven't used before, and am having trouble structuring it.
The basic structure is something like this:
<input id=start_date />
<input id=end_date />
<button id=submit onClick={ this.props.fetchChartData() }/>
<Chart1 />
<Chart2 />
The store structure is this:
dates
start_date: "2016-09-16"
end_date: "2016-09-16"
charts
Chart1
api_func: "get_supported_events"
fetching: false
fetched: false
data: null
error: null
Chart2
api_func: "get_events_closed"
fetching: false
fetched: false
data: null
error: null
Using thunk, my actions right now include these functions:
function getStateURL(state){
return state.charts.Chart1['api_func'];
}
export function fetchChartData(){
return (dispatch, getState) => {
dispatch(fetchChartDataStart());
return fetch(getStateURL(getState()))
.then((response) => response.json())
.then((json) => dispatch(receiveChartData(json)))
.catch((err) => dispatch(fetchChartDataError(err)));
}
}
The problem is, I don't want to hard code the chart name because I feel like I should be able to write one action since all of the charts need to do the same thing.
The best solution I could guess is to have the button trigger an event that the chart components could listen for so that when the state is requested it is limited to the chart's portion, not the entire state. Is there a way to make a react component trigger an event that can be caught by other components?
The solution you are proposing seems more like old Flux model when store was just an instance of EventEmitter.
Using Flux, you can make <Chart /> like
class Chart extends Component {
componentDidMount() {
store.addEventListener('fetchData', this.fetchData);
}
componentWillUnmount() {
store.removeEventListener('fetchData', this.fetchData);
}
this.fetchData() {
api.fetchChartData(store.get('chart1.url');
}
render() {
...
}
}
With Redux however it is not immediately obvious. But it is possible to do it:
class Chart1 extends Component {
componentWillReceiveProps(nextProps) {
if (!nextProps.fetching && !nextProps.fetched) {
const { fetchData, url } = this.props;
fetchData(url);
}
}
render() {
...
}
}
export default connect(state => ({
fetching: state.Chart1.fetching
fetched: state.Chart1.fetched
url: state.Chart1.url
}), {
fetchData
})(Chart1)
and in /action.js
export function fetchChartData(url){
return (dispatch) => {
dispatch(fetchChartDataStart());
return fetch(url)
.then((response) => response.json())
.then((json) => dispatch(receiveChartData(json)))
.catch((err) => dispatch(fetchChartDataError(err)));
}
}
Considering the similar functionalities in all the <Chart /> components, it's worth implementing Higher order component for this and keep url somewhere as constants rather than in store.
export const fetchData = (url) => (Wrapped) => {
class Wrapper extends Component {
componentWillReceiveProps(nextProps) {
if (!nextProps.fetching && !nextProps.fetched) {
const { fetchData, url } = this.props;
fetchData(url);
}
}
render() {
return <Wrapped {...this.props} />
}
}
return connect(null, { fetchData })(Wrapper);
}
In Chart.jsx use it like:
import { chart1Url } from '.../someconstants';
import { fetchData } from '/hocs/fetchData'
const Chart1 = () => {
return <div>...</div>;
}
export default fetchData(chartUrl)(Chart1);
Although it is possible, I still think the best solution would be to store URLs in a constants file, and put api functions in another module. You can do something like:
./api/fetchData.js
export function fetchData(url) {
return new Promise((resolve, reject) =>
fetch(url)
.then((response) => response.json())
.then((json) => resolve(json))
.catch((err) => reject(err));
}
./actions.js
import { fetchData } from '../api/fetchData';
import { urls } from '.../constants';
export function fetchChartData(){
return (dispatch) => {
dispatch(fetchChartDataStart());
return Promise.all(urls.map((url) =>
fetchData(url)
.then((json) => dispatch(receiveChartData(json)))
.catch((err) => dispatch(fetchChartDataError(err))));
}
}