Skip to content

finalData is not a function

I’m trying to learn redux by creating multi step form (reactjs), everything is going fine, except when i click a button then all the data should go to one variable finalData, but its not going and i’m getting:

TypeError: finalData.map is not a function

When i check console.log(userData); i get all the data, but i want data to go to finalData, and changeFinalData is the one which updates it.

When i console log finalData i get: finalData => [...finalData, userData].

What am i doing wrong?

my code:

    import React, { useContext } from "react";
    import { Form, Input, Button } from "antd";
    import "antd/dist/antd.css";
    // import { multiStepContext } from "../StepContext";
    //-------------
    import { useDispatch, useSelector } from "react-redux";
    import {
      changeName,
      changeUserId,
      selectId,
      selectName,
      changeSetStep,
      changeUserData,
      changeFinalData,
      selectCurrentStep,
      selectUserData,
      selectFinalData,
    } from "../features/app/appSlice";

    function ThirdStep() {
      // const { setStep, userData, setUserData, submitData } =
      //   useContext(multiStepContext);

      //----
      const dispatch = useDispatch();
      const userData = useSelector(selectUserData);
      const finalDataa = useSelector(selectFinalData);
      console.log(userData);

      const submitData = () => {
        dispatch(changeFinalData((finalDataa) => [...finalDataa, userData]));

        changeUserData("");
        changeSetStep(1);
      };

      return (
        <Form>
          <Form.Item
            label="City"
            name="City"
            initialValue={userData["city"] || ""}
            onChange={(e) =>
              dispatch(changeUserData({ ...userData, city: e.target.value }))
            }
          >
            <Input type="string" />
          </Form.Item>

          <Form.Item
            label="Landmark"
            name="Landmark"
            initialValue={userData["landmark"] || ""}
            onChange={(e) =>
              dispatch(changeUserData({ ...userData, landmark: e.target.value }))
            }
          >
            <Input type="string" />
          </Form.Item>

          <Form.Item
            label="Postal Code"
            name="Postal Code"
            initialValue={userData["postcode"] || ""}
            onChange={(e) =>
              dispatch(changeUserData({ ...userData, postcode: e.target.value }))
            }
          >
            <Input type="string" />
          </Form.Item>

          <div>
            <Button
              variant="contained"
              onClick={() => dispatch(changeSetStep(2))}
              color="secondary"
            >
              Back
            </Button>
            <span> </span>
            <Button
              variant="contained"
              onClick={() => submitData()}
              color="primary"
            >
              Submit
            </Button>
          </div>
        </Form>
      );
    }

    export default ThirdStep;

my appSlice.js:

    import { createSlice } from "@reduxjs/toolkit";

    export const appSlice = createSlice({
      name: "app",
      initialState: {
        userData: [],
        finalData: [],
        currentStep: 1,
      
      },
      reducers: {
        changeSetStep: (state, action) => {
          state.currentStep = action.payload;
        },
        changeUserData: (state, action) => {
          state.userData = action.payload;
        },
        changeFinalData: (state, action) => {
          state.finalData = action.payload;
        },

       
      },
    });
    export const {
      
      changeSetStep,
      changeUserData,
      changeFinalData,
    } = appSlice.actions;

    export const selectCurrentStep = (state) => state.app.currentStep;
    export const selectUserData = (state) => state.app.userData;
    export const selectFinalData = (state) => state.app.finalData;

    export default appSlice.reducer;

DisplayData.js:

    import React, { useContext } from "react";

    import {
      TableContainer,
      TableHead,
      TableBody,
      TableCell,
      TableRow,
      Table,
    } from "@material-ui/core";
    import { useSelector } from "react-redux";
    import { selectFinalData } from "../features/app/appSlice";
    // import { multiStepContext } from "../StepContext";

    function DisplayData() {
      // const { finalData } = useContext(multiStepContext);
      const finalDataa = useSelector(selectFinalData);
      console.log(finalDataa);
      return (
        <div>
          <TableContainer style={{ display: "flex", justifyContent: "center" }}>
            <Table
              border="1"
              style={{ width: "70%", justifyContent: "center" }}
              size="small"
              aria-label="caption table"
            >
              <TableHead>
                <TableRow
                  style={{ backgroundColor: "burlywood", color: "aliceblue" }}
                >
                  <TableCell>First Name </TableCell>
                  <TableCell>Last Name </TableCell>
                  <TableCell>Contact Number </TableCell>
                  <TableCell>Email Address </TableCell>
                  <TableCell>Country </TableCell>
                  <TableCell>District </TableCell>
                  <TableCell>City </TableCell>
                  <TableCell>Landmark </TableCell>
                  <TableCell>Postal Code </TableCell>
                </TableRow>
              </TableHead>
              <TableBody>
                {finalDataa.map((data) => (
                  <TableRow key={data.email}>
                    <TableCell>{data.firstName} </TableCell>
                    <TableCell>{data.lastName} </TableCell>
                    <TableCell>{data.contact} </TableCell>
                    <TableCell>{data.email} </TableCell>
                    <TableCell>{data.country} </TableCell>
                    <TableCell>{data.district} </TableCell>
                    <TableCell>{data.city} </TableCell>
                    <TableCell>{data.landmark} </TableCell>
                    <TableCell>{data.postcode} </TableCell>
                  </TableRow>
                ))}
              </TableBody>
            </Table>
          </TableContainer>
        </div>
      );
    }

    export default DisplayData;

my App.js:

    import { useState } from "react";
    import "./App.css";
    import Header from "./components/Header";

    import FirstStep from "./components/FirstStep";
    import SecondStep from "./components/SecondStep";
    import ThirdStep from "./components/ThirdStep";
    import { Stepper, StepLabel, Step } from "@material-ui/core";
    // import { multiStepContext } from "./StepContext";
    import { useContext } from "react";
    import DisplayData from "./components/DisplayData";
    //---

    import { useDispatch, useSelector } from "react-redux";
    import {
      changeName,
      changeUserId,
      selectId,
      selectName,
      changeSetStep,
      changeUserData,
      changeFinalData,
      selectCurrentStep,
      selectUserData,
      selectFinalData,
    } from "./features/app/appSlice";
    //--

    function App() {
      const SelectFinalData = useSelector(selectFinalData);
      const SelectCurrentStep = useSelector(selectCurrentStep);

      // const [name,setName]=useState('kalle')

      // const { currentStep, finalData } = useContext(multiStepContext);
      const showStep = (step) => {
        switch (step) {
          case 1:
            return <FirstStep />;
          case 2:
            return <SecondStep />;

          case 3:
            return <ThirdStep />;
        }
      };
      return (
        <div className="App">
          <header className="App-header">
            <h3 style={{ color: "red", textDecoration: "underline" }}>
              Reactjs multi step Application
            </h3>
            <div className="center-stepper">
              <Stepper
                style={{ width: "18%" }}
                activeStep={SelectCurrentStep - 1}
                orientation="horizontal"
              >
                <Step>
                  <StepLabel></StepLabel>
                </Step>
                <Step>
                  <StepLabel></StepLabel>
                </Step>
                <Step>
                  <StepLabel></StepLabel>
                </Step>
              </Stepper>
            </div>
            {showStep(SelectCurrentStep)}
            {SelectFinalData.length > 0 ? <DisplayData /> : ""}
          </header>{" "}
        </div>
      );
    }

    export default App;

Answer

Issue

The issue here is that you are trying to implement your reducer logic in your UI code. You are storing a function in state, not actually updating state values.

dispatch(changeFinalData((finalDataa) => [...finalDataa, userData]));

This just stores the function (finalDataa) => [...finalDataa, userData] into state in the state.app.finalData state.

Solution

While you could just invoke the action payload function in your reducer and pass it the current finalData state

changeFinalData: (state, action) => {
  state.finalData = action.payload(state.finalData);
},

This is not how you’d want to maintain your state. You will instead want to pass in the action payload the data you want to update your state with.

changeFinalData: (state, action) => {
  state.finalData.push(action.payload);
},

And dispatch the action with the data payload.

dispatch(changeFinalData(userData));

Since you use similar logic in all your other reducer cases you will want to implement similar updates. This is so the reducer function maintains control over how state is updated.