简体   繁体   中英

Convert React Context API Class to function of Hook

How to change this class base Context API to reach Hook without changing other components that already consumed it? I am new to react and spend all night and I got stuck.

The actual code is more than this but I'm trying to remove some of the code for simplicity purposes:

import React, { createContext, Component } from 'react'
 
const MainContext = createContext();

class MainContextProvider extends Component {

     state = {
        isLogin : false,
        loginData : [],
        spinner : false
     }                  
   
handleUserLogin  = (res) => {   
  this.setState({ 
      ...this.state,
      isLogin   : res.isLogin,
      loginData : res.data
   })        
}

showSpinner = (status) => {
   this.setState({ 
     ...this.state,
     spinner : status
   })           
}

    
 render() { 
    
   console.log(this.state)

    return (   
      <MainContext.Provider value = {{        
         
           ...this.state,
           showSpinner : this.showSpinner,
           handleUserLogin : this.handleUserLogin,
          }}>

      {this.props.children}

      </MainContext.Provider>
    );
  }
}

const MainContextConsumer = MainContext.Consumer; 
export {MainContextProvider,  MainContextConsumer, MainContext};

I wrap index.js with this MainContextProvider so all components can consume the states or use the methods.

Here is how to use context with hooks and keeping the same API as what you already have:

import React, { createContext, useContext, useState } from "react";
import "./style.css";

// Define your context, this is the same
const MainContext = createContext();

function Provider({ children }) {

  // Define some state to hold the data
  let [state, setState] = useState({
    isLogin: false,
    loginData: [],
    spinner: false
  });
  
  // Define a few functions that change the state
  let handleUserLogin = res => {
    setState(s => ({
      ...s,
      isLogin: res.isLogin,
      loginData: res.data
    }));
  };

  // Define a few functions that change the state
  let showSpinner = status => {
    setState(s => ({ ...s, spinner: status }));
  };

  // Pass the `state` and `functions` to the context value
  return (
    <MainContext.Provider
      value={{ ...state, handleUserLogin, showSpinner }}
    >
      {children}
    </MainContext.Provider>
  );
}

function Stuff() {
  // Inside your component use the context with `useContext` hook
  let { showSpinner, handleUserLogin, ...state  } = useContext(MainContext);
  return (
    <div>
      <div>
        <code>{JSON.stringify(state, null, 2)}</code>
      </div>

      <button onClick={() => showSpinner(Math.random())}>
        Show Spinner
      </button>
    </div>
  );
}

export default function App() {
  return (
    <Provider>
      <Stuff />
    </Provider>
  );
}

See the demo on StackBlitz

As Sam R. suggestion, I make little modification and works as expected. Maybe it's better to use Reducer but I prefer not. And I think Context API is more simple compare to Redux.

MainContext.js :

import React, { createContext, useState } from 'react'
 
const MainContext = createContext();

const MainContextProvider = ({ children }) => {

  // Define some state to hold the data
  let [state, setState] = useState({
    isLogin: false,
    loginData: [],
    spinner: false
  });
  
  // Define a few functions that change the state
  let handleUserLogin = res => {
    setState(s => ({
      ...s,
      isLogin: res.isLogin,
      loginData: res.data
    }));
  };

  // Define a few functions that change the state
  let showSpinner = status => {
    setState(s => ({ ...s, spinner: status }));
  };

  // Pass the `state` and `functions` to the context value
  return (
    <MainContext.Provider
      value={{ ...state, handleUserLogin, showSpinner }}
    >
      {children}
    </MainContext.Provider>
  );
}

const MainContextConsumer = MainContext.Consumer; 
export {MainContextProvider,  MainContextConsumer, MainContext};

Login.js :

import React, { useState, useContext } from "react";
import { Link } from "react-router-dom";
import { useHistory } from "react-router-dom";

import {MainContext} from "../contextApi/MainContext";
import { login } from "../api/Api_User";


const Login = () => {
  const history = useHistory();
    
  const { handleUserLogin, showSpinner } = useContext(MainContext);

  const [user , setUser] = useState({ email : "", password : "" })
  const [errors , setErrors] = useState({ emailErr : "", passErr : "" })
  
  
  const handleChange = e => {   
      const {name , value} = e.target    
      setUser( prevState => ({ ...prevState,[name] : value }))  
      setErrors({ emailErr : "", passErr : "" });   
  }
  
  const handleSubmit = (e) => { 
  
       // client side validation      
       if(!user.email)    { setErrors({ emailErr : "Please enter email" }); return false; }
       if(!user.password) { setErrors({ passErr : "Please enter password" }); return false; }
      
      
      showSpinner(true)

      const data = {
        email: user.email,
        password: user.password 
      }
      
       // axios call
       login(data).then(res => {
              
        setTimeout(() => {
            
            showSpinner(false)
            
            if (res) {
              if (res.status === true) {
               localStorage.setItem("token", res.token); // jwt token from server
               handleUserLogin(res) // store server respond to global states
               return history.push('/dashboard')
              }
               
              // server side validation
               if (res.status === false) {  
                 res.path === 'email' &&  setErrors({ emailErr : res.message }) 
                 res.path === 'password' && setErrors({ passErr : res.message }) 
               }
            }
        
        },100 )
        
        
   });
   }
 


   return (
     <div className="page">
          <div className="page-content mt-5 mb-5">
            <div className="content-sticky-footer">
            
          <div className="container">
            <div className="row">
              <div className="col">
              
                    <div className="card mb-0">
                      <div className="card-header">
                        <h3 className="mx-auto mt-4">LOGIN MEMBER</h3>
                      </div>
                      
                      <div className="card-body">
                          <div className="form-group">
                            <label>Email address *</label>
                            <input
                              type="email" className="form-control"
                               name="email"
                               value={user.email}
                                onChange={handleChange}
                            />
                            <span className="text-danger label-sm ">
                             {errors.emailErr}
                           </span>
                          </div>

                          <div className="form-group">
                            <label>Password *</label>
                            <input
                              type="password" className="form-control"
                               name="password"
                              value={user.password}
                              onChange={handleChange}
                            />
                            <span className="text-danger label-sm ">
                             {errors.passErr}
                           </span>
                          </div>
                         
                          <div className="form-footer mt-2">
                            <button
                              type="button"
                              className="btn btn-primary btn-block btn-lg btn-submit"
                              onClick={handleSubmit}
                             >
                              Login
                            </button>
                          </div>

                          <div className="text-center mt-3 text-dark">
                            Do not have account?
                            <Link to="/register"> Register</Link>
                          </div>

                      </div>
                    </div>
                  </div>
                </div>
          </div>
         </div>
     </div>
     </div>
   );

}

export default Login

Index.js :

import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import './css/App.css';
import App from './App';

import { BrowserRouter} from "react-router-dom";
import {MainContextProvider} from './contextApi/MainContext';  

import axios from "axios";

 // express server with mongodb
 axios.defaults.baseURL = "http://localhost:3001"; 
 
ReactDOM.render(
    <MainContextProvider>
      <BrowserRouter>
            <App />
          </BrowserRouter>
     </MainContextProvider>,
  document.getElementById('root')
);

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