reactjs – React context does not update state until the page is refreshed

I have a list of patients. I need to display the data of a single patient. The patients are identified uniquely using an id. When I get a particular patient’s data from the backend using axios, it seems to work perfectly. But the problem is that I need the state to update to that object when I click on the patient’s link.

When I log from the reducer.ts file, the payload loads perfectly. But then, when I click on the link to actually show the data, it does not show it until the page is refreshed. However, after it is refreshed it does change the state to the unique object I want, and then reverts back to the initial state. I want it to get the object and keep it in the state. Where am I going wrong?
index.ts:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { reducer, StateProvider } from "./state";

ReactDOM.render(
  <StateProvider reducer={reducer}>
    <App />
  </StateProvider>,
  document.getElementById('root')
);

Reducer.ts:

import { State } from "./state";
import { Patient } from "../types";

export type Action =
  | {
      type: "SET_PATIENT_LIST";
      payload: Patient[];
    }
  | {
      type: "SINGLE_PATIENT";
      payload: Patient;
    };

export const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case "SET_PATIENT_LIST":
      return {
        ...state,
        patients: {
          ...action.payload.reduce(
            (memo, patient) => ({ ...memo, [patient.id]: patient }),
            {}
          ),
          ...state.patients
        }
      };
    case "SINGLE_PATIENT":
      console.log(action.payload); 
      return {
        ...state,
        patients: {
          ...state.patients,
          [action.payload.id]:  {
            ...state.patients[action.payload.id],
            ...action.payload,
          },
        }
      };
    default:
      return state;
  }
};

export const setPatientList = (patientList: Patient[]): Action => {
  return {
    type: "SET_PATIENT_LIST",
    payload: patientList
  };
};

export const setSinglePatient = (patient: Patient): Action => {
  return {
    type: "SINGLE_PATIENT",
    payload: patient
  };
};

State.tsx:

import React, { createContext, useContext, useReducer } from "react";
import { Patient } from "../types";
import { Action } from "./reducer";

export type State = {
  patients: { [id: string]: Patient };
};

const initialState: State = {
  patients: {}
};

export const StateContext = createContext<[State, React.Dispatch<Action>]>([initialState, () => initialState]);

type StateProviderProps = {
  reducer: React.Reducer<State, Action>;
  children: React.ReactElement;
};

export const StateProvider = ({
  reducer,
  children
}: StateProviderProps) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <StateContext.Provider value={[state, dispatch]}>
      {children}
    </StateContext.Provider>
  );
};

export const useStateValue = () => useContext(StateContext);

App.tsx:

import React from "react";
import axios from "axios";
import { BrowserRouter as Router, Route, Link, Routes } from "react-router-dom";
import { Button, Divider, Container, Typography } from "@material-ui/core";

import { apiBaseUrl } from "./constants";
import { useStateValue, setPatientList } from "./state";
import { Patient } from "./types";

import PatientListPage from "./PatientListPage";
import SinglePatientPage from "./SinglePatientPage";

const App = () => {
  const [, dispatch] = useStateValue();
  React.useEffect(() => {
    void axios.get<void>(`${apiBaseUrl}/ping`);

    const fetchPatientList = async () => {
      try {
        const { data: patientListFromApi } = await axios.get<Patient[]>(
          `${apiBaseUrl}/patients`
        );
        dispatch(setPatientList(patientListFromApi));
      } catch (e) {
        console.error(e);
      }
    };

    void fetchPatientList();
  }, [dispatch]);


  return (
    <div className="App">
      <Router>
        <Container>
          <Typography variant="h3" style={{ marginBottom: "0.5em" }}>
            Patientor
          </Typography>
          <Button component={Link} to="/" variant="contained" color="primary">
            Home
          </Button>
          <Divider hidden />
          <Routes>
            <Route path="/" element={<PatientListPage />} />
            <Route path="/patients/:id" element={<SinglePatientPage />} />
          </Routes>
        </Container>
      </Router>
    </div>
  );
};

export default App;

PatientListPage.tsx:

import React from "react";
import axios from "axios";
import {
  Box,
  Table,
  Button,
  TableHead,
  Typography,
  TableCell,
  TableRow,
  TableBody
} from "@material-ui/core";
import { PatientFormValues } from "../AddPatientModal/AddPatientForm";
import AddPatientModal from "../AddPatientModal";
import { Patient } from "../types";
import { apiBaseUrl } from "../constants";
import HealthRatingBar from "../components/HealthRatingBar";
import { useStateValue } from "../state";
import { Link } from 'react-router-dom';

const PatientListPage = () => {
  const [{ patients }, dispatch] = useStateValue();
  const [error, setError] = React.useState<string>();

  return (
    <div className="App">
      <Box>
        <Typography align="center" variant="h6">
          Patient list
        </Typography>
      </Box>
      <Table style={{ marginBottom: "1em" }}>
        <TableHead>
          <TableRow>
            <TableCell>Name</TableCell>
            <TableCell>Gender</TableCell>
            <TableCell>Occupation</TableCell>
            <TableCell>Health Rating</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {Object.values(patients).map((patient: Patient) => (
            <TableRow key={patient.id}>
              <TableCell>
                <Link to={`/patients/${patient.id}`}>
                  {patient.name}
                </Link>
              </TableCell>
              <TableCell>{patient.gender}</TableCell>
              <TableCell>{patient.occupation}</TableCell>
              <TableCell>
                <HealthRatingBar showText={false} rating={1} />
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
};

export default PatientListPage;

SinglePatientPage:

import React from "react";
import { Patient } from "../types";
import { useStateValue, setSinglePatient } from "../state";
import { useParams } from "react-router-dom";
import { Typography } from "@material-ui/core";
import { apiBaseUrl } from "../constants";
import axios from "axios";

const SinglePatientPage = () => {
  const [{ patients }, dispatch] = useStateValue();
  const { id } = useParams<{ id: string }>();
  React.useEffect(() => {
    const fetchSinglePatient = async () => {
      if(id !== undefined) {
        try {
          const { data: patientFromApi } = await axios.get<Patient>(
            `${apiBaseUrl}/patients/${id}`
          );
          dispatch(setSinglePatient(patientFromApi));
        } catch (e) {
          console.error(e);
        }
      }
    };
    void fetchSinglePatient();
  }, [dispatch]);

  if (patients) {
    console.log('inside singlepatientpage', patients);
     return (
       <div className="app">
         <Typography variant="h6" style={{ marginBottom: "0.5em" }}>
           {patient.name}
           <p>ssn: {patient.ssn}</p>
           <p>occupation: {patient.occupation}</p>
         </Typography>
       </div>
     );
  }

  return null;
};

export default SinglePatientPage;

Leave a Comment