最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How do I pass all state variables inside react hooks to my child component - Stack Overflow

programmeradmin1浏览0评论

I am trying to get all the states within the functional components using hooks. Equivalent to ...this.state. I am avoiding passing the state individually to the Context.Provider.

Since this.state is not available within the function. state is undefined.

import React, { useState, useEffect } from 'react'

const RecipeContext = React.createContext()

const RecipeProvider = (props) => {
  const [showHomeButton, setShowHomeButton] = useState(false)
  const [recipes, setRecipes] = useState([])
  const [loading, setLoading] = useState(true)
  const [search, setSearch] = useState('')


  const fetchRecipe = async () => {
    const recipeData = await fetch(``)
    const { recipes } = await recipeData.json()
    setRecipes(recipes)
    setLoading(false)

  }
  const handleSubmit = async (e) => {
    e.preventDefault()
    setLoading(true)
    url = `${url}&q=${search}`
    fetchRecipe(url)
    setShowHomeButton(true)

  }
  const handleSearchChange = (e) => {
    setSearch(e.target.value)
  }
  const handleReturnHome = () => {
    fetchRecipe()
  }
  useEffect(() => {
    fetchRecipe()

  }, [])
  return (
    <RecipeContext.Provider value={}>
      {props.children}
    </RecipeContext.Provider>
  )
}
const RecipeConsumer = RecipeContext.Consumer
export { RecipeProvider, RecipeConsumer }

What's best way to pass all the states within the component to value in the provider.

 <RecipeContext.Provider value={}>
      {props.children}
    </RecipeContext.Provider>

I am trying to get all the states within the functional components using hooks. Equivalent to ...this.state. I am avoiding passing the state individually to the Context.Provider.

Since this.state is not available within the function. state is undefined.

import React, { useState, useEffect } from 'react'

const RecipeContext = React.createContext()

const RecipeProvider = (props) => {
  const [showHomeButton, setShowHomeButton] = useState(false)
  const [recipes, setRecipes] = useState([])
  const [loading, setLoading] = useState(true)
  const [search, setSearch] = useState('')


  const fetchRecipe = async () => {
    const recipeData = await fetch(`https://api.myjson.com/bins/t7szj`)
    const { recipes } = await recipeData.json()
    setRecipes(recipes)
    setLoading(false)

  }
  const handleSubmit = async (e) => {
    e.preventDefault()
    setLoading(true)
    url = `${url}&q=${search}`
    fetchRecipe(url)
    setShowHomeButton(true)

  }
  const handleSearchChange = (e) => {
    setSearch(e.target.value)
  }
  const handleReturnHome = () => {
    fetchRecipe()
  }
  useEffect(() => {
    fetchRecipe()

  }, [])
  return (
    <RecipeContext.Provider value={}>
      {props.children}
    </RecipeContext.Provider>
  )
}
const RecipeConsumer = RecipeContext.Consumer
export { RecipeProvider, RecipeConsumer }

What's best way to pass all the states within the component to value in the provider.

 <RecipeContext.Provider value={}>
      {props.children}
    </RecipeContext.Provider>
Share Improve this question edited Jul 3, 2019 at 22:23 0DDC0 5,1791 gold badge30 silver badges41 bronze badges asked Jul 3, 2019 at 17:26 Ola John AjiboyeOla John Ajiboye 1231 gold badge1 silver badge9 bronze badges 3
  • Why not just use a class here? class syntax has use cases where you'd want to use it. If you are defining a lot of elements on your state hooks is more verbose. – John Ruddell Commented Jul 3, 2019 at 17:28
  • 1 @JohnRuddell I don't think a class is worth it for that use case. you can always do [state, setState] = useState(initialState) or useReducer – azium Commented Jul 3, 2019 at 17:32
  • 1 Yea, I understand class might be useful here. However I already started a tutorial with just functional component and just hooks. I want to stick with that without being verbose. – Ola John Ajiboye Commented Jul 3, 2019 at 17:34
Add a comment  | 

4 Answers 4

Reset to default 10

Use an object as the state

const RecipeProvider = (props) => {
  //Declare an object as the state
  const [megaState, setMegaState] = useState({
      showHomeButton: false,
      recipes : [],
      loading : true,
      search: ''
  })



  const fetchRecipe = async () => {
    const recipeData = await fetch(`https://api.myjson.com/bins/t7szj`)
    const { recipes } = await recipeData.json()

    //UPDATE STATE WITHOUT MUTATING
    setMegaState({
        ...megaState
        recipes,
        loading: false
    })    
  }
  const handleSubmit = async (e) => {
    e.preventDefault()
    setLoading(true)
    url = `${url}&q=${search}`
    fetchRecipe(url)
    setShowHomeButton(true)
    //UPDATE STATE WITHOUT MUTATING
    setMegaState({
        ...megaState
        showHomeButton : true 
    })
  }
  const handleSearchChange = (e) => {
    //UPDATE STATE WITHOUT MUTATING
    setMegaState({
        ...megaState
        search : e.target.value 
    })
  }
  const handleReturnHome = () => {
    fetchRecipe()
  }
  useEffect(() => {
    fetchRecipe()

  }, [])
  return (
    <RecipeContext.Provider value={megaState}>
      {props.children}
    </RecipeContext.Provider>
  )
}

This can be further improved by using useReducer! :)

You already have many states. Don't use useState as you were using the setState function from classes.

An advice, If you don't wanna get confused and work with useState like you were using the setState from classes, use the same "labels" for the variable and try, if you can, to have one state.

// From this
const [showHomeButton, setShowHomeButton] = useState(false);
const [recipes, setRecipes] = useState([]);
const [loading, setLoading] = useState(true);
const [search, setSearch] = useState('');

// to this - common understanding
const [state, setState] = useState({
  showHomeButton: false,
  recipes: [],
  loading: true,
  search: '',
});

(Less code, easy to maintain)

About avoiding to passing the state through the Context Provider; it's not an option you have to. Otherwise, there's no reason to use it.

What I would do, it would be to keep the rest of your code and change the last lines of code. Having something like this:

(btw, your fetchRecipe function is not receiving a parameter)

import React, { useState, useEffect } from 'react'

const RecipeContext = React.createContext()

const RecipeProvider = (props) => {
  const [state, setState] = useState({
    showHomeButton: false,
    recipes: [],
    loading: true,
    search: '',
  });

  const fetchRecipe = async () => {
    const recipeData = await fetch(`https://api.myjson.com/bins/t7szj`);
    const { recipes } = await recipeData.json();
    setState({
      ...state,
      recipes,
      loading: false,
    });
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    fetchRecipe(`${url}&q=${search}`);

    setState({
      ...state,
      loading: true,
      showHomeButton: true
    });
  }

  const handleSearchChange = (e) => {
    e.persist();

    setState({
      ...state,
      search: e.target.value
    });
  };

  // this might not needed
  const handleReturnHome = () => {
    fetchRecipe()
  };

  useEffect(() => {
    fetchRecipe()

  }, []);

  return (
    <RecipeContext.Provider value={{
      store: state,
      actions: {
         fetchRecipe,
         handleSearchChange,
         handleSubmit,
      }
     }}>
      {props.children}
    </RecipeContext.Provider>
  )
}

export default RecipeProvider;

Of course this is just and example. You could also make use of useReducer like someone says. This way you could treat your local state like you were working with Redux.

Now you have two options depending if you are using an Stateful or Stateless component. For Stateful component: Get access to the context (value) of your provider using:

<RecipeContext.Consumer>
  {value => (
   <SomeComponent />
  )}
</RecipeContext.Consumer>

// OR

class SomeComponent extends Component {
  render() {
   let value = this.context;
  }
}
SomeComponent. contextType = RecipeContext;

For Stateless components:

const SomeComponent = props => {
  const value = useContext(RecipeContext);
};

What I explained above it could be found here: https://es.reactjs.org/docs/hooks-reference.html#usecontext. Also in the link, you will find an example of how to use useReducer. That would be great in this case, instead of passing all the functions as I did, you could pass one single action dispatch and pass a type as the action you wanna trigger and get a new state from it.

But, you HAVE TO use the value from the context Provider.

You can use reducer this way, and add your context, you can follow this architecture example:

const initState = {
  is_logged: false,
  token: "",
  error: { type: "", msg: "" },
  form: {
    first_name: "",
    last_name: "",
    password: "",
    email: ""
  }
}

const reducer = (state, action) => {
  const { payload } = action
  switch (action.type) {

    case "form_first_name":
      return { ...state, form: { ...state.form, first_name: payload } }
    case "form_last_name":
      return { ...state, form: { ...state.form, last_name: payload } }
    case "form_email":
      return { ...state, form: { ...state.form, email: payload } }
    case "form_password":
      return { ...state, form: { ...state.form, password: payload } }
    case "error":
      return { ...state, error: payload }
    case "success":
      return {
        ...state,
        token: payload,
        error: { type: "", msg: "" },
        is_logged: true
      }
    default:
      throw new Error()
  }
}

const AdminClinicContainer = () => {

  const [state, dispatch] = useReducer(reducer, initState)

  const _register = async () => {
    const result = await axios(API_ADMIN_REGISTER)
    console.log(result.data)
  }

  const _login = async () => {
    try {
      const response = await axios.post(API_ADMIN_LOGIN, {
        email: state.form.email,
        password: state.form.password
      })
      console.log(response.data)
      dispatch({ type: "success", payload: response.data.token })
    } catch (error) {
      console.log(error.response.data.error)
      dispatch({ type: "error", payload: error.response.data.error })
    }
  }

  const _forgetPsw = async () => {
    const result = await axios(API_ADMIN_LOGIN)
    console.log(result.data)
  }
  const _form = (type, payload) => dispatch({ type, payload })

  return (
    <div>
      <AdminClinic
        _register={_register}
        _login={_login}
        _forgetPsw={_forgetPsw}
        _form={_form}
        state={state}
      />
    </div>
  )
}

export default AdminClinicContainer

One pattern I have used is to make individual state variables, then create a schema object that pulls them all together. This seems redundant, but it makes setting state simple (no crazy nested spread operators to set state) and then if you need to access your state variables by name somewhere else in your application, you can do that too. I do wish there was some easy way to automatically use a string to access the state variables, but I'm not aware of one.

const [name, setName] = useState('')
const [email, setEmail] = useState('')

// for easy access to all state variables in child components
// using a regular definition within the component means you're
// always referring to the latest version of the state variables
// imagine if you had like 20 state variables to pass...
const schema = {
  name: {
    state: name,
    setState: setName,
  },
  email: {
    state: email,
    setState: setEmail,
  },
}

// elsewhere, maybe in a child component
schema['name'].setState('Steve')
发布评论

评论列表(0)

  1. 暂无评论