Jose the hose
Jose the hose

Reputation: 1895

When API post is successful, dispatch a get request - React/Redux

I have a small app that displays a component that is a list (JobsList) and another component that that contains a text field and submit button (CreateJob). While I am able to populate JobsList with API data (passing through Redux), I am not sure how I should update JobsList with a new API call, once I have successfully posted a new job in CreateJob. This is the code I have so far:

JobsList.js

import React, { Fragment, useEffect } from 'react';
import { connect } from 'react-redux';
import JobCard from './JobCard';
import CreateJob from './CreateJob';
import api from './Api';
import { JOBS_LOADED } from './ActionTypes';

const JobsList = ({ jobs, onLoad }) => {

useEffect(() => {
    const fetchJobs = async () => {

        try {
            const data = await api.Jobs.getAll();
            onLoad({ data });
        } catch (err) {
            console.error(err);
        }
    };
    fetchJobs();
}, [onLoad]);

return (
    <Fragment>
        <CreateJob />
        {teams.map(job => (
            <JobCard job={job} key={team.jobId} />
        ))}
    </Fragment>
);
}

const mapStateToProps = state => ({
    jobs: state.jobsReducer.teams
});

const mapDispatchToProps = dispatch => ({
    onLoad: payload =>
        dispatch({ type: JOBS_LOADED, payload }),
});

export default connect(mapStateToProps, mapDispatchToProps)(JobsViewer);

CreateJob.js

import React, { useState } from 'react';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import api from './Api';

const CreateJob = () => {
const [state, setState] = React.useState({
    jobName: '',
    creator: ''
});

const handleInputChange = event => {
    setState({
        ...state,
        [event.target.name]: event.target.value
    });
    // validation stuff
}

const handleSubmit = async e => {
    api.Jobs.create({state})
    try {
        await request;
        // Reload the Jobs list so it does an another API request to get all new data
        // DO I CALL A DISPATCH HERE?????
    } catch (err) {
        console.error(err);
    }
}

return (
    <div>
        <TextField
            name="jobName"
            value={state.jobName || ''}
            onChange={handleInputChange}
        />
        <Button onClick={handleSubmit}>Create job</Button>
    </div>
);
}

export default CreateJob;

JobsReducer.js

import { TEAMS_LOADED } from './ActionTypes';

export default (state = {teams: []}, action) => {
switch (action.type) {
    case TEAMS_LOADED:
        return {
            ...state,
            teams: action.payload.data,
        };
    default:
        return state;
}
};

In the success result in handleSubmit in CreateJob.js, how do I trigger/dispatch a new API call to update JobsList from CreateJob.js? I'm new to react/redux so apologies for any poor code. Any advice for a learner is greatly appreciated.

Upvotes: 1

Views: 1088

Answers (2)

Pengson
Pengson

Reputation: 875

The simplified solution to take is wrapper the function for fetching jobs as a variable in the JobsList, and assign it to CreateJob as a prop. Then from the CreateJob, it's up to you to update the job list.

The shortage of this solution is it doesn't leverage redux as more as we can. It's better to create action creator for shared actions(fetch_jobs) in the JobsReducer.js and map these actions as props to the component which need it exactly.

JobsReducer.js

export const fetchJobsAsync = {
  return dispatch => {
    try {
      const data = await api.Jobs.getAll();
      dispatch({type: TEAMS_LOADED, payload: {data}})
    } catch (err) {
      console.error(err);
    }
  }
}

tips: You must install redux-thunk to enable the async action.

After, you will be able to fire the API to update the jobs(or teams anyway) from any component by dispatching the action instead of calling the API directly.

JobsList.jsx or CreateJob.js

const mapDispatchToProps = dispatch => ({
  fetchAll: () => dispatch(fetchJobsAsync())
})

At the end of CreateJob.js, it's totally the same as calling the fetchAll to reload the jobs list like calling other regular functions.

And, if you are ok to go further, move the API call which creates new job to the reducer and wrapper it as an action. Inside it , dispatching the fetchJobsAsync if the expected conditions meet(If create new job finished successfully). Then you will end up with a more clearly component tree with only sync props without the data logic regarding to when/how to reload the jobs list.

Upvotes: 2

kooskoos
kooskoos

Reputation: 4859

Yes, your approach is absolutely right.

Once you have posted a new job, based on it's response you can trigger fetchJobs which you can pass as prop to <CreateJob fetchJobs={fetchJobs}/>.

For that you will have to declare it outside useEffect() like this:

import React, { Fragment, useEffect } from 'react';
import { connect } from 'react-redux';
import JobCard from './JobCard';
import CreateJob from './CreateJob';
import api from './Api';
import { JOBS_LOADED } from './ActionTypes';

const JobsList = ({ jobs, onLoad }) => {

const fetchJobs = async () => {
  try {
    const data = await api.Jobs.getAll();
    onLoad({ data });
  } catch (err) {
    console.error(err);
  }
};

useEffect(() => {
  fetchJobs();
}, [onLoad]);

return (
  <Fragment>
    <CreateJob fetchJobs={fetchJobs}/>
    {teams.map(job => (
      <JobCard job={job} key={team.jobId} />
    ))}
  </Fragment>
  );
}

const mapStateToProps = state => ({
  jobs: state.jobsReducer.teams
});

const mapDispatchToProps = dispatch => ({
  onLoad: payload =>
  dispatch({ type: JOBS_LOADED, payload }),
});

export default connect(mapStateToProps, mapDispatchToProps)(JobsViewer);

Once you trigger the api call new data will be loaded in redux state:

import React, { useState } from 'react';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import api from './Api';

const CreateJob = props => {
const [state, setState] = React.useState({
    jobName: '',
    creator: ''
});

const handleInputChange = event => {
    setState({
        ...state,
        [event.target.name]: event.target.value
    });
    // validation stuff
}

const handleSubmit = async e => {
    api.Jobs.create({state})
    try {
        await request;
        props.fetchJobs()
    } catch (err) {
        console.error(err);
    }
}

return (
    <div>
        <TextField
            name="jobName"
            value={state.jobName || ''}
            onChange={handleInputChange}
        />
        <Button onClick={handleSubmit}>Create job</Button>
    </div>
);
}

export default CreateJob;

As JobsList component is subscribed to the state and accepts state.jobsReducer.teams as props here:

const mapStateToProps = state => ({
    jobs: state.jobsReducer.teams
});

The props will change on loading new jobs from <CreateJobs />and this change in props will cause <JobsLists /> to be re-rendered with new props.

Upvotes: 1

Related Questions