简体   繁体   中英

React context state sharing not working in sibling components

i have a simple ToDo app which has some components. All i want to do is to share state and update context data. I can do it with props but in future, if i have more components then it will be too cumbersome to pass data with props. Here is my code...

MainContext.js

import React, { createContext, useState } from "react";

const MainContext = createContext([{}, () => {}]);

const MainContextProvider = (props) => {
  const [state, setState] = useState({ title: "", body: "" });
  return (
    <MainContext.Provider value={[state, setState]}>
      {props.children}
    </MainContext.Provider>
  );
};

export { MainContext, MainContextProvider };

App.js

import MainContent from "./MainContent";
import { MainContextProvider } from "./MainContext";
export default class App extends Component {
  constructor() {
    super();
    this.state = {
    };
  }
  render() {
    return (
      <div className="container">
        <MainContextProvider>
          <Nav navigationItems={this.state.navItems} />
          <MainContent />
          <Footer />
        </MainContextProvider>
      </div>
    );
  }
}

MainContent.js

import React, { Component } from "react";
import CardType from "./CardType";
import FormData from "./FormData";

class MainContent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isLoading: true,
      initialCounter: 0,
      endCounter: 10,
      showLoadMore: true,
      cards: [],
    };
  }

  DeleteCard = (cardId) => {
    console.log(cardId);
    this.setState((prevState) => {
      const newCardArray = prevState.cards.filter((card) => card.id !== cardId);
      return {
        cards: newCardArray,
      };
    });
  };

  AddCard = (formData) => {
    this.setState((prevState) => {
      const newCardData = {
        id: prevState.cards[prevState.cards.length - 1].id + 1,
        userId: 12,
        title: formData.title,
        body: formData.description,
      };

      return {
        cards: [...prevState.cards, newCardData],
        // update the number of posts to +1
        endCounter: prevState.endCounter + 1,
      };
    });
  };
  componentDidMount() {
    fetch("https://jsonplaceholder.typicode.com/posts")
      .then((response) => response.json())
      .then((json) => {
        this.setState({ isLoading: false, cards: json });
      });
  }

  render() {
    if (this.state.isLoading) {
      return <h1>Loading....</h1>;
    } else {
      const cardsShow = this.state.cards.map((card) => {
        return (
          <CardType
            key={card.id}
            cardData={card}
            deleteCard={this.DeleteCard}
          />
        );
      });

      return (
        <main className="jumbotron">
          <h1>ToDo</h1>
          <FormData data={this.AddCard} />
          {cardsShow
            .reverse()
            .slice(this.state.initialCounter, this.state.endCounter)}
          {this.state.showLoadMore ? (
            <button
              onClick={() => {
                this.setState((prevState) => {
                  const newEndCounter = prevState.endCounter + 10;
                  const showLoadButton =
                    prevState.cards.length > newEndCounter ? true : false;

                  return {
                    endCounter: newEndCounter,
                    showLoadMore: showLoadButton,
                    // initialCounter: prevState.initialCounter + 10,
                  };
                });
              }}
            >
              Load More
            </button>
          ) : null}
        </main>
      );
    }
  }
}

export default MainContent;

CardType.js

import React, { useContext, useEffect } from "react";
import { MainContextProvider } from "./MainContext";

const CardType = (props) => {
  const [formData, setFormData] = useContext(MainContext);
  const updateValues = (card) => {
    setFormData(() => {
     return {
        title: card.title,
        body: card.body,
      };
    });
  };
  return (
    <div className="card">
      <div className="card-body">
        <h1>{props.cardData.title}</h1>
        <p className="card-text">{props.cardData.body}</p>
        <button onClick={() => props.deleteCard(props.cardData.id)}>
          Delete
        </button>
        <button onClick={() => updateValues(props.cardData)}>Update</button>
      </div>
    </div>
  );
};

export default CardType;

FormData.js

import React, { useState, useContext } from "react";
import { MainContextProvider } from "./MainContext";

const FormData = (props) => {
  const [formData, setFormData] = useContext(MainContext);
  const [title, setTitle] = useState(formData.title);
  const [description, setDescription] = useState("formData.body);

  const handleSubmit = (e) => {
    e.preventDefault();
    props.data({ title: title, description: description });
    setTitle("");
    setDescription("");
  };

  return (
    <form>
      <div className="form-group">
        <label>Name</label>
        <input
          className="col-sm-12"
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
        />
      </div>
      <div className="form-group">
        <label>Description</label>
        <textarea
          className="col-sm-12"
          name="cardDescription"
          value={description}
          onChange={(e) => {
            setDescription(e.target.value);
          }}
        />
      </div>

      <p>{description}</p>
      <button onClick={(e) => handleSubmit(e)}>Add</button>
    </form>
  );
};

export default FormData;

I want is when i click on Update button of each todo, my form should populate with selected TODO data and update the context and on save, update my context back to empty string. I am newbie in React. Please share if there is any other alternate way to do this without using context.

From the useContext docs:

Don't forget that the argument to useContext must be the context object itself:

  • Correct: useContext(MyContext)
  • Incorrect: useContext(MyContext.Consumer)
  • Incorrect: useContext(MyContext.Provider)

So in your code sample, replace this line

const [formData, setFormData] = useContext(MainContextProvider);

with

const [formData, setFormData] = useContext(MainContext);

To prevent similar errors in the future, you can make a wrapper hook to access the context.

In MainContext.js

const useMainContext = () => useContext(MainContext)

Now you can use them in any functional components like this

const [formData, setFormData] = useMainContext();

Live Demo

编辑 React - UseContext

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM