【问题标题】:Pass data between react hooks在反应钩子之间传递数据
【发布时间】:2020-06-15 10:30:32
【问题描述】:

如何将数据从一个React hooks 表单(组件)传递到另一个组件。例如,如果我需要玩家 name and photoProfile.js 传递并使其在 Navigation.js 中可用,我该怎么做?

Player.js

import React, { useContext , useEffect, useState } from "react";
import { useForm } from 'react-hook-form';
import { useHistory } from "react-router-dom";
import Axios from "axios";
import UserProfileContext from '../context';

const Profile = () => {

  const { setData } = useContext(UserProfileContext);
  const [email, setEmail] = useState('');
  const [picture, setPicture] = useState('');
  const [playerProfile, setPlayerProfile] = useState([]);
  const loginUserEmail = localStorage.getItem('loginEmail');
  const [updateProfile, setUpdateProfile] = useState({ _id: '', photo: '', name: '', email:'', phonenumber: '', position: '', password: '' })
  const [isSent, setIsSent] = useState(false);
  const [helperText, setHelperText] = useState('');
  const [disabled, setDisabled] = useState(true);
  const { handleSubmit, register, errors } = useForm();
  const history = useHistory();

  const onChangePicture = e => {
    console.log('picture: ', picture);
    if (e.target.files.length) {
      setPicture(URL.createObjectURL(e.target.files[0]));
    } else {
      return false;
    }
  };

  // If no profile image is being uploaded, to avoid the broken display of image, display a default image.
  const addDefaultSrc = e => {
    e.target.src = '/images/default-icon.png';
  }

  // Pass the id to the handler so you will know which item id changing.
  const handleChange = (e, id) => {
    e.persist();
    let itemIndex;
    const targetPlayer = playerProfile.find((player, index) => {
      console.log({ player, id, index });
      itemIndex = index; // Track the index so you can use it to update later.
      return player.id === id;
    });

    console.log({ targetPlayer, id, e });

    const editedTarget = {
      ...targetPlayer,
      [e.target.name]: e.target.value
    };
    const tempPlayers = Array.from(playerProfile);
    tempPlayers[itemIndex] = editedTarget;
    /*
    // Alternatively:: you can just  map over the array if you dont want to track the index
    const tempPlayers = playerProfile.map((profile, index) => {
      return profile.id === id ? editedTarget : profile;
    });
    */
    setPlayerProfile(tempPlayers);
    setUpdateProfile({ ...updateProfile, [e.target.name]: e.target.value }); // this is added just to see if its working
  };

  useEffect(() => {
    const fetchData = async () => {
      try {
        const params = {
          email: loginUserEmail,
        };
      const res = await Axios.get('http://localhost:8000/service/profile', {params});
        setPlayerProfile(res.data.playerProfile);
      } catch (e) {
        console.log(e);
      }
    }
    fetchData();
  }, []);

  const onSubmit = () => {
    setDisabled(disabled);
    const fetchData = async () => {
      try {
        const params = {
          email: loginUserEmail,
        };
        const data = {photo: updateProfile.photo, name: updateProfile.name, email: updateProfile.email, phonenumber: updateProfile.phonenumber, position: updateProfile.position, password: updateProfile.password}
        const res = await Axios.put('http://localhost:8000/service/profile', data, {params}); 
        console.log("Front End update message:" + res.data.success);
        if (res.data.success) {
          setIsSent(true);
          history.push('/')
        }
        else {
          console.log(res.data.message);
          setHelperText(res.data.message);
        }
      } catch (e) {
        setHelperText(e.response.data.message);
      }
    }
    fetchData();
  }

  return (
    <div className="register_wrapper">
      <div className="register_player_column_layout_one">
        <div className="register_player_Twocolumn_layout_two">
          <form onSubmit={handleSubmit(onSubmit)} className="myForm">
            {
              playerProfile.map(({ id, photo, name, email, phonenumber, position, privilege, password }) => (
                <div key={id}>
                  <div className="formInstructionsDiv formElement">
                    <h2 className="formTitle">Profile</h2>
                    <div className="register_profile_image">
                      <input id="profilePic" name="photo" type="file" onChange={onChangePicture} />
                    </div>
                    <div className="previewProfilePic" >
                      <img alt="" onError={addDefaultSrc} name="previewImage" className="playerProfilePic_home_tile" src={photo} onChange={e => handleChange(e, id)}></img>
                    </div>
                  </div>
                  <div className="fillContentDiv formElement">
                    <label>
                      <input className="inputRequest formContentElement" name="name" type="text" value={name} 
                      onChange={e => handleChange(e, id)}
                      maxLength={30}
                      ref={register({
                        required: "Full name is required", 
                        pattern: {
                          value: /^[a-zA-Z\s]{3,30}$/,
                          message: "Full name should have minimum of 3 letters"
                        }
                      })}
                      />
                      <span className="registerErrorTextFormat">{errors.name && errors.name.message}</span>
                    </label>
                    <label>
                      <input className="inputRequest formContentElement" name="email" type="text" value={email} 
                      onChange={e => handleChange(e, id)}
                      disabled={disabled}
                      />
                    </label>
                    <label>
                      <input className="inputRequest formContentElement" name="phonenumber" type="text" value={phonenumber} 
                      onChange={e => handleChange(e, id)}
                      maxLength={11}
                      ref={register({
                        required: "Phone number is required",
                        pattern: {
                          value: /^[0-9\b]+$/,
                          message: "Invalid phone number"
                        }
                       })}
                      />
                      <span className="registerErrorTextFormat">{errors.phonenumber && errors.phonenumber.message}</span>
                    </label>
                    <label>
                      <input className="inputRequest formContentElement" name="position" type="text" value={position} 
                      onChange={e => handleChange(e, id)}
                      maxLength={30}
                      ref={register({
                        pattern: {
                          value: /^[a-zA-Z\s]{2,30}$/,
                          message: "Position should have minimum of 2 letters"
                        }
                      })}
                      />
                      <span className="registerErrorTextFormat">{errors.position && errors.position.message}</span>
                    </label>
                    <label>
                      <div className="select" >
                        <select name="privilege" id="select" value={privilege} onChange={e => handleChange(e, id)}>
                          {/*<option selected disabled>Choose an option</option> */}
                          <option value="player">PLAYER</option>
                          {/*<option value="admin">ADMIN</option>*/}
                        </select>
                      </div>
                    </label>
                    <label>
                      <input className="inputRequest formContentElement" name="password" type="password" value={password} 
                      onChange={e => handleChange(e, id)}
                      minLength={4}
                      maxLength={30}
                      ref={register({
                      required: "Password is required",
                      pattern: {
                        value: /^(?=.*?\d)(?=.*?[a-zA-Z])[a-zA-Z\d]+$/,
                        message: "Password begin with a letter and includes number !"
                      }
                      })}
                      />
                      <span className="registerErrorTextFormat">{errors.password && errors.password.message}</span>
                    </label>
                  </div>
                  <label>
                    <span className="profileValidationText">{helperText}</span>
                  </label>
                  <div className="submitButtonDiv formElement">
                    <button type="submit" className="submitButton">Save</button>
                  </div>
                </div>
              ))
            }
          </form>

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

Navigation.js

import React, { useContext } from 'react';
import { NavLink, useHistory } from 'react-router-dom';
import UserProfileContext from '../context';


const Navigation = () => {
    const history = useHistory();
    const { data } = useContext(UserProfileContext);

    const divStyle = {
        float:'left',
        color: '#64cad8', 
        padding: '0px 0px 0px 10px',
        font:'Lucida, sans-serif'
      };

    function logout() {
        localStorage.removeItem('loginEmail')
        localStorage.removeItem('Privilege')
        history.push('/login')
        window.location.reload(true);
      }

    return localStorage.getItem('loginEmail') &&
        <div className="App">
            <div className="wrapper">
                <nav className="siteNavigation_nav_links">
                <div className="clubLogo landing"style={divStyle}><b>Southside Soccer</b></div>
                    <NavLink className="mobile_register_link" to="/">Home</NavLink>
                    <NavLink className="mobile_register_link" to="/profile">Profile</NavLink>
                    <NavLink className="mobile_login_link" to="/login" onClick={logout}>Logout</NavLink>
                    <NavLink className="mobile_login_link" to='/aboutus'>About us</NavLink>
                <div className="profileImage nav menu">
                <span>{data.name}</span>|<img src=""></img>
                </div>
                </nav>
            </div>
        </div>
}

export default Navigation;

App.js

import React, { useState } from 'react';
import "./App.css";
import "./CSSModules/home.css";
import "./CSSModules/register.css";
import "./CSSModules/login.css";
import "./CSSModules/aboutus.css";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Home from "./components/Home";
import Register from "./components/Register";
import Login from "./components/Login";
import Aboutus from "./components/Aboutus";
import Navigation from "./components/Navigation";
import Profile from "./components/Profile";
import { ProtectedRoute } from "./components/protected.route";
import UserProfileContext from './context';

var ReactDOM = require("react-dom");

const App = () => {
  const [data, setData] = useState({
    id: '',
    name: '',
    email: '',
    photo: '',
  });

  return (
   <UserProfileContext.Provider value={{ data, setData }}>
      <BrowserRouter>
        <>
     <Navigation />
      <Switch>
          <ProtectedRoute exact path="/" component={Home} />
          <ProtectedRoute path="/profile" component={Profile} />
          <ProtectedRoute path="/aboutus" component={Aboutus} />
          <Route path="/register" component={Register} />
          <Route path="/login" component={Login} />
      </Switch>
    </>
   </BrowserRouter>
  </UserProfileContext.Provider>
  );
};
ReactDOM.render(
  React.createElement(App, null),
  document.getElementById("root")
);

export default App;

context.js

import React from 'react';

export default React.createContext();

【问题讨论】:

标签: javascript reactjs typescript react-hooks


【解决方案1】:

就像 cmets 中提到的,一个选项是 left your application's state up(这应该是简单状态的首选选项)。

实际上,这看起来像:

App.js

import React, { useState } from 'react';

import Navigation from './Navigation';
import Profile from './Profile';

function App() {
  const [name, setName] = useState('');

  return (
    <div className="App">
      <Navigation name={name} />
      <hr />
      <Profile name={name} setName={setName} />
    </div>
  );
}

export default App;

Profile.js:

import React from 'react';

const Profile = ({ name, setName }) => {
  return (
    <>
      <div>Profile: {name}</div>
      <input
        type="text"
        name="name"
        value={name}
        onChange={e => setName(e.target.value)}
      />
    </>
  );
};

export default Profile;

Navigation.js:

import React from 'react';

const Navigation = ({ name }) => {
  return <div>Navigation: {name}</div>;
};

export default Navigation;

编辑:仔细查看您的代码后,我认为在这种情况下使用上下文 API 更有意义。

尝试以下方法:

context.js

import React from 'react';

export default React.createContext();

App.js

import React, { useState } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';

import './styles.css';
import Profile from './components/Profile';
import Navigation from './components/Navigation';
import UserProfileContext from './context';

const App = () => {
  const [data, setData] = useState({
    id: 'player-1',
    name: 'Player One',
    age: 25,
    photo: 'rose.JPG',
  });

  return (
    <UserProfileContext.Provider value={{ data, setData }}>
      <BrowserRouter>
        <Navigation />
        <Switch>
          <Route path="/profile" component={Profile} />
        </Switch>
      </BrowserRouter>
    </UserProfileContext.Provider>
  );
};

export default App;

components/Navigation.js

import React, { useContext } from 'react';
import { NavLink } from 'react-router-dom';

import UserProfileContext from '../context';

const Navigation = () => {
  const { data } = useContext(UserProfileContext);

  const divStyle = {
    float: 'left',
    color: '#64cad8',
    padding: '0px 0px 0px 10px',
    font: 'Lucida, sans-serif',
  };

  return (
    <div className="App">
      <div className="wrapper">
        <nav className="siteNavigation_nav_links">
          <div className="clubLogo landing" style={divStyle}>
            <b>Soccer</b>
          </div>

          <NavLink className="mobile_register_link" to="/profile">
            Profile
          </NavLink>

          <div className="profileImage nav menu">
            <span>{data.name}</span> | <img alt="" src={data.photo} />
          </div>
        </nav>
      </div>
    </div>
  );
};

export default Navigation;

components/Profile.js

import React, { useContext } from 'react';
import { useForm } from 'react-hook-form';

import UserProfileContext from '../context';

const Profile = () => {
  const { setData } = useContext(UserProfileContext);
  const { register, handleSubmit } = useForm();

  return (
    <div>
      <form onSubmit={handleSubmit(setData)}>
        <b>Profile</b>
        <input name="id" ref={register} />
        <input name="name" ref={register} />
        <input name="age" ref={register} />
        <button type="submit" className="submitButton">
          Click
        </button>
      </form>
    </div>
  );
};

export default Profile;

【讨论】:

  • 谢谢你在我的真实例子中,当我按照建议进行更改时,我遇到了很多错误。
  • 我的 App.js 如下所示:var ReactDOM = require("react-dom"); const App = () =&gt; ( &lt;BrowserRouter&gt; &lt;&gt; &lt;Navigation /&gt; &lt;Switch&gt; &lt;ProtectedRoute exact path="/" component={Home} /&gt; &lt;ProtectedRoute path="/profile" component={Profile} /&gt; &lt;ProtectedRoute path="/aboutus" component={Aboutus} /&gt; &lt;Route path="/register" component={Register} /&gt; &lt;Route path="/login" component={Login} /&gt; &lt;/Switch&gt; &lt;/&gt; &lt;/BrowserRouter&gt; ); ReactDOM.render( React.createElement(App, null), document.getElementById("root") );
  • 谢谢,就我而言,Profile.js 已经从数据库中加载了我的登录播放器数据:const [playerProfile, setPlayerProfile] = useState([]); 以下是我的 axios get >> `useEffect(() => { const fetchData = async () => { try { const params = { email: loginUserEmail, }; const res = await Axios.get('localhost:8000/service/profile', {params}); setPlayerProfile(res.data.playerProfile); } catch (e) { console.log(e); } } fetchData(); }, []);`
  • 我已按照建议添加了更新后的App.js, Navigation.js and Profile.js。玩家数据在 Profile.js 中可用,我们如何将 const [playerProfile, setPlayerProfile] = useState([]); 传递给 useContext(UserProfileContext); ?当我尝试 const { setPlayerProfile} = useContext(UserProfileContext) 而不是 setData 时,它会抛出它已经声明的错误..
  • 我已经用更新的App.js, Navigation.js and Profile.js 用我的原始代码更新了我的问题。
【解决方案2】:

您可以使用react-redux 进行全局状态处理或将回调函数从Navigation.js 传递给Profile.js

【讨论】:

  • 除了使用redux还有其他方法吗?
  • Ofc。对于这种单一用途,我建议您提升您的状态 reactjs.org/docs/lifting-state-up.html ,而不是如果您更频繁地使用它,请选择 React Context。
【解决方案3】:

使用 React 的内置上下文 API。将你的 App 包装在 context 提供者中,然后你可以使用 useContext 钩子来访问状态,在组件之间调度状态更新。

应用级设置 - 一次

// Define Context
const AppContext = React.createContext()

// Define initial state
const initialState = {}

// Define Reducer
const Reducer = (state, dispatch) => {
  switch(action.type) {
    case "ACTION_TYPE": return {...state, prop: action.payload}
    default: return state
  }
}


//Wrap main App in context, pass state from React's useReducer hook
const [state, dispatch] = useReducer(Reducer, initialState)
<AppContext.Provider data={{
  state,
  dispatch
}}>
  <ReactAppMainWrapper />
</AppContext.Provider>

组件级

const {state, dispatch} = useContext(AppContext);

// to update state call dispatch from anywhere, all components consuming state will be updated

dispatch({
   type: "ACTION_TYPE",
   payload: "new_value"
})

说明

状态就像商店一样,在整个应用程序中都可用。

在任何组件中,导入 AppContext 并使用 React 的内置 useContext 钩子与组件中的 store 进行交互。

在上面的上下文提供者中传递的数据对象可以从这里获得。

【讨论】:

  • 如果可能的话,你能给我一个简单的例子吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-03-11
  • 2016-11-30
  • 1970-01-01
  • 1970-01-01
  • 2020-09-02
  • 2020-12-25
  • 1970-01-01
相关资源
最近更新 更多