javascript useContext在上下文值更改时不强制同级组件重新呈现

wn9m85ua  于 2023-03-16  发布在  Java
关注(0)|答案(1)|浏览(152)

我正在学习React,正在为我正在学习的课程做一个项目。我创建了一个React应用程序,它有许多功能组件,并使用React路由。
我在这里尝试实现的是,在用户完成登录过程后重新呈现NavBar组件,以便我可以基于当前上下文值以编程方式显示不同的Navbar项。
当userContext更新时,我可以看到其他组件的更新,但不能使用导航栏。我错过了什么/做错了什么?
index.js

import 'bootstrap/dist/css/bootstrap.css';
import React from 'react';
import ReactDOM from 'react-dom/client';
import reportWebVitals from './reportWebVitals';
import App from './App';
const UserContext   = React.createContext(null);

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

reportWebVitals();

export default UserContext;

App.js

import 'bootstrap/dist/css/bootstrap.css';
import React from 'react';
import {HashRouter, Route, Routes} from 'react-router-dom';
import NavBar from './navbar';
import Home from './home';
import CreateAccount from './createaccount';
import Login from './login';
import Deposit from './deposit';
import Withdraw from './withdraw';
import Balance from './balance';
import AllData from './alldata';
import UserContext from './index.js'


export default function App() {

  return(
    <HashRouter>
      <UserContext.Provider value={{users: [{name:'defaultuser', email: 'default@default.com', password: 'DFS)(Uqadfasfda234##', balance: 0, loggedIn: 0}]}}>
      <NavBar/>
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/CreateAccount/" element={<CreateAccount />} />
            <Route path="/login/" element={<Login />} />
            <Route path="/deposit/" element={<Deposit />} />
            <Route path="/withdraw/" element={<Withdraw />} />
            <Route path="/balance/" element={<Balance />} />
            <Route path="/alldata/" element={<AllData />} />
          </Routes>
      </UserContext.Provider>
    </HashRouter>
);

}

createaccount.js

import React from 'react';
import Card from './card.js';
import UserContext from './index.js';
import './styles.css';
import { useNavigate } from 'react-router-dom';


export default function CreateAccount(){
    const [show, setShow]    = React.useState(true);
    const [status, setStatus]    = React.useState('');
    const [name, setName]    = React.useState('');
    const [balance, setBalance]    = React.useState('');
    const [email, setEmail]    = React.useState('');
    const [password, setPassword]    = React.useState('');
    const [loggedIn, setLoggedIn]    = React.useState(0);
    const ctx = React.useContext(UserContext);
    const navigate = useNavigate();

    function handleClick(){
        navigate("#/login/");
    }

function validate(field, label){
    if (!field) {
        setStatus('Error: ' + label);
        setTimeout(() => setStatus(''), 3000);
        return false;
    }
    return true;
}

function handleCreate(){
    if (!validate(name,     'name'))        return;
    if (!validate(email,    'email'))       return;
    if (!validate(password, 'password'))    return;
    if (!validate(balance, 'balance'))    return;
    ctx.users.push({name,email,password,balance, loggedIn});
    setShow(false);
}

    function clearForm(){
        setName('');
        setEmail('');
        setPassword('');
        setBalance(0.00);
        setShow(true);
    }

    return (
    <div style={{display: "flex", alignItems:"center", justifyContent:"center"}}>
    <Card
        bgcolor="primary"
        header="Create Account"
        status={status}
        body={show ? (
            <>
            Name<br/>
            <input type="input" className="form-control" id="name" placeholder = "Enter name" value={name} onChange={e => setName(e.currentTarget.value)} /><br/>
            Email address<br/>
            <input type= "input" className="form-control" id="email" placeholder="Enter email" value={email} onChange={e => setEmail(e.currentTarget.value)} /><br/>
            Password<br/>
            <input type= "password" className ="form-control" id="password" placeholder="Enter password" value={password} onChange={e => setPassword(e.currentTarget.value)} /><br/>
            Starting Balance<br/>
            <input type="input" className="form-control" id="startingbalance" placeholder = "Enter starting balance" value={balance} onChange={e => setBalance(e.currentTarget.value)} /><br/>
            <button type="submit" className="btn btn-light" onClick={handleCreate}>Create Account</button>
             
            </>
            ):(
                <>
                <h5>Success</h5>
                <button type="submit" className="btn btn-light" onClick={clearForm}>Add another account</button>
                <div className="divider"/>
                <a href="#/login/"><button type="submit" className="btn btn-light" onClick={handleClick}>Login</button></a>
                </>
            )}
            
    />
    </div>

    
)

}

login.js

import React from 'react';
import Card from './card.js';
import UserContext from './index.js';
import DynamicTable from "./DynamicTable";

let startingBalance = 0.00;

export default function Login(){
    const [loggedIn, setLoggedIn]           = React.useState(0);
    const [userName, setUserName]           = React.useState('');
    const [userPassword, setUserPassword]   = React.useState('');
    const [loggedinUser, setLoggedInUser]   = React.useState(null);
    const [user, setUser]                 = React.useState();
    const [users, setUsers]                 = React.useState([]);
    
    const ctx                               = React.useContext(UserContext);

    function handleLogin(){
        const activeLogin = ctx.users.findIndex(x => x.loggedIn === 1)
        if (activeLogin === -1){
            console.log('Login.js - no active login');
        }
        else {
            if (window.confirm("There is already a logged in user session. Do you wish to log in as a different user?")) {
                alert("The logged in user has changed.");
                logOut();
              } else {
                    return;
              } 
        }
        findUser(userName, userPassword);
    }

    function logOut(){
        let updatedValue = [...ctx.users];
        updatedValue.forEach(element => element.loggedIn = 0);
        setUsers(updatedValue);

    }
    function handleUpdate(i){
        let updatedValue = [...ctx.users];
        updatedValue[i].loggedIn = 1;
        setUsers(updatedValue);
    }

    function findUser(userName){
        setUsers(ctx.users);
        const foundidx = ctx.users.findIndex(x => x.email === userName);
        const found = ctx.users[foundidx];
        let userFirstName = null;
        
        if (!found)
        {
            alert('No user found which matches the email entered. Please try again.');
        }
        else {
            validatePassword(found.password, userPassword, foundidx);
            userFirstName = found.name;
            setUser(userFirstName);
            handleUpdate(foundidx);
            
            startingBalance = parseFloat(found.balance).toFixed(2);

            found.balance = startingBalance;
            
            ctx.users[foundidx].balance = startingBalance;
        }
        
    }

    function validatePassword(password, userPassword, foundidx){

        if (password === userPassword)
        {
            setLoggedIn(1);
            setLoggedInUser(foundidx);
        }
        else {
            alert('You have entered an incorrect password. Repeated failed attempts will result in an account lockout.');
        }
        
    }

    return (
        <div>
        <Card
            bgcolor="warning"
            header={!loggedIn ? (
                "Login"
            ):(
                "Welcome Back " + user + "!"
            )}
            txtcolor="black"
            body={!loggedIn ? (
                <>
                Email<br/>
                <input type= "input" className="form-control" id="email" placeholder="Enter email" value={userName} onChange={e => setUserName(e.currentTarget.value)} /><br/>
                Password<br/>
                <input type= "password" className ="form-control" id="password" placeholder="Enter password" value={userPassword} onChange={e => setUserPassword(e.currentTarget.value)} /><br/>
                 <button type="submit" className="btn btn-light" onClick={handleLogin}>Login</button>
                </>
                 ):(
                    <>
                    <h6><b>Current balance:</b> ${startingBalance}</h6>
                    <h6>What would you like to do?</h6>
                    <a className="nav-link" href="#/deposit/">Make Deposit</a>
                    <a className="nav-link" href="#/withdraw/">Make Withdrawal</a>
                    </>
                )}

                
        />
        <DynamicTable />
        </div>
    )
}

navbar.js

import Nav from 'react-bootstrap/Nav';
import Navbar from 'react-bootstrap/Navbar';
import React from 'react';
import { OverlayTrigger, Tooltip} from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import UserContext from './index.js';


export default function NavBar(){
    const ctx = React.useContext(UserContext);
    const activeLogin = ctx.users.findIndex(x => x.loggedIn === 1);

    if (activeLogin >=0){
        console.log('Navbar.js - there is a logged in user');
    }
    else {
        console.log('Navbar.js - there is NOT a logged in user');
    }

    return (
        
    
    
        <Navbar collapseOnSelect expand="lg" bg="dark" variant="dark">
                <OverlayTrigger placement={'right'} overlay={<Tooltip>Show the Home Page</Tooltip>}>
                <Navbar.Brand href="/#">FNBRB</Navbar.Brand>
                </OverlayTrigger>
                <Navbar.Toggle aria-controls="respsonive-navbar-nav" />
                    <Navbar.Collapse id="responsive-navbar-nav">
                        <Nav className="me-auto">
                        <OverlayTrigger placement={'right'} overlay={<Tooltip>Create a new account</Tooltip>}>
                            <Nav.Link href="#/CreateAccount/">Create Account</Nav.Link>
                        </OverlayTrigger>
                        
                        <OverlayTrigger placement={'right'} overlay={<Tooltip>Log into an account</Tooltip>}>
                            <Nav.Link href="#/login/">Log In</Nav.Link>
                        </OverlayTrigger>
                        
                        <OverlayTrigger placement={'right'} overlay={<Tooltip>Deposit funds</Tooltip>}>
                            <Nav.Link href="#/deposit/">Deposit</Nav.Link>
                        </OverlayTrigger>                            
                        <OverlayTrigger placement={'right'} overlay={<Tooltip>Withdraw funds</Tooltip>}>
                            <Nav.Link href="#/withdraw/">Withdraw</Nav.Link>
                        </OverlayTrigger>
                        <OverlayTrigger placement={'right'} overlay={<Tooltip>Show all user accounts</Tooltip>}>
                            <Nav.Link href="#/alldata/">All Data</Nav.Link>
                        </OverlayTrigger>
                        </Nav>
                    </Navbar.Collapse>
        </Navbar>
    
    );
}

DynamicTable.js

import React from 'react';
import UserContext from './index.js';


  function myFunction(item, index, arr){
    arr[index]['key'] = index;
  }
function DynamicTable(){
// get table column
 
 const ctx = React.useContext(UserContext);
 const userData = ctx.users;
userData.forEach(myFunction);

 const column = Object.keys(userData[0]);
 // get table heading data
 const ThData =()=>{
    
     return column.map((data)=>{
         return <th key={data}>{data}</th>
     })
 }
// get table row data
const tdData =() =>{
   
     return userData.map((data)=>{
       return(
           <tr>
                {
                   column.map((v)=>{
                       return <td>{data[v]}</td>
                   })
                }
           </tr>
       )
     })
}
  return (
      <table className="table">
        <thead>
         <tr>{ThData()}</tr>
        </thead>
        <tbody>
        {tdData()}
        </tbody>
       </table>

)
}
export default DynamicTable;

card.js

import React from 'react';

function Card(props){
    function classes(){
        const bg = props.bgcolor ? ' bg-' + props.bgcolor : ' ';
        const txt = props.txtcolor ? ' text-' + props.txtcolor: ' text-white';
        return 'card mb-3' + bg + txt;
    }

    return(
        <div className={classes()} style={{maxWidth: "18rem"}}>
            <div className="card-header">{props.header}</div>
            <div className="card-body">
                {props.title && (<h5 className="card-title">{props.title}</h5>)}
                {props.text && (<p className="card-text">{props.text}</p>)}
                {props.body}
                {props.status && (<div id='createStatus'>{props.status}</div>)}
            </div>
        </div>
);
}

export default Card;
5rgfhyps

5rgfhyps1#

不要改变状态...或其他任何内容

你的组件不会重新呈现,因为你实际上并没有改变上下文的值,也就是说,上下文的值仍然是对同一个对象的引用。
您所做的就是使用以下代码行 mutate 上下文的值:

ctx.users.push({ name, email, password, balance, loggedIn });

ctx仍然是相同的对象,ctx.users仍然是相同的数组,但内容已更改。此类更改不会触发React重新呈现。若要更改上下文的值,必须用新对象替换整个对象。
您需要:
1.将上下文的值存储在App中的useState挂钩中。
1.为内部组件调用setState提供一些方法。
大致如下:

export default function App() {
  const [users, setUsers] = useState([{name:'defaultuser', email: 'default@default.com', password: 'DFS)(Uqadfasfda234##', balance: 0, loggedIn: 0}]});

  // Note: could memoize with useCallback.
  const addUser = (user) => setUsers(prevUsers => prevUsers.concat(user));

  return(
    <HashRouter>
      <UserContext.Provider value={{ users, addUser }}>
      <NavBar/>

相关问题