问题
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.
回答1:
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
回答2:
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')
);
来源:https://stackoverflow.com/questions/64562934/convert-react-context-api-class-to-function-of-hook