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
|
4 Answers
Reset to default 10Use 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')
[state, setState] = useState(initialState)
oruseReducer
– azium Commented Jul 3, 2019 at 17:32hooks
. I want to stick with that without being verbose. – Ola John Ajiboye Commented Jul 3, 2019 at 17:34