Related
I just started learning react hooks and react-redux hooks and as far I understood everything. But one thing is keep drilling my brain, so I would like to ask more experienced developers here.
If I have more robust app, where I intend to have Redux taking care of whole state and wanna use React hooks fro side effects, do I really need React Hooks?
I have separate functional layer (containers => where all the decisions are being made with redux) and displaying layer (components => where components are dumb and obtain just data they are suppose to render)
Whats bugging me is I make a API call in initial page loading and I would like to use useEffect hook, but im not conviced I should do that when I can useSelector from redux and useDispatch.
here is the code I would like to update into hook style:
const mapStateToProps = (state) => {
return {
cities: state.weather.cities,
}
}
const mapDispatchToProps = (dispatch) => {
const fetchForUpdate = (cities) => {
return cities.map((city) => {
return dispatch({ type: FETCH_START, payload: city.name })
})
}
return {
fetchForUpdate: fetchForUpdate,
}
}
const WeatherListContainer = (props) => {
const { cities } = props
const cityData = cities.map((oneCity) => {
return (
<WeatherItemContainer
name={oneCity.name}
data={oneCity.data}
key={oneCity.name}
/>
)
})
return <WeatherList item={cityData} />
}
const enhance: Function = compose(
connect(mapStateToProps, mapDispatchToProps),
lifecycle({
componentDidMount() {
console.log(this.props.cities, 'this.props.cities')
this.props.fetchForUpdate(this.props.cities)
},
}),
)
export default enhance(WeatherListContainer)
how can I fetch with redux hooks or react hooks? Or can I combine it? like use useEffect and then save it from local store to global store? Isnt it a bit ineffective?
Redux requires a middleware such as redux-thunk to dispatch asynchronous actions (an API call). If you plan on calling an API multiple times throughout your app, it makes sense to use redux-thunk and dispatch an asynchronous action, though this dispatch might still need to occur within useEffect/componentDidMount. If you only plan on a single API call, or if a specific side effect is unique to one component, there is no need to implement the middleware. For a single API call, you can send your request within useEffect/componentDidMount and then dispatch the result with a synchronous action to the redux store, without having to ever store it in component state.
https://redux.js.org/advanced/async-actions
I think there are some confusions. React hooks are used for sideEffects where redux hooks are for using the store more efficientl. Lets consider a scenario like bellow.
You are fetching a todo list from API and wanna use it all over the app. You have multiple components and you are gonna need the todo list in every component. In that case, you will call the api either using a middleware like redux-thunk or by other means like caling it in a useEffect( which is not a good practice) and save it to redux store using redux hooks. And whenever you redux store is updated, you will need to show the data in components. How will we do that? we will use react hooks to apply the sideEffects.
Here we will get the data from redux store using redux hooks. Than in a reactHooks like useEffect we will update a state of the component using useState. So here you can see, both reactHooks and reduxHooks are completely different in terms of functionality. one is storing and serving data which is reduxHooks and another one is showing the data when ever its added or updated which is reactHooks.
Hope you will find it understandable.
I am new to React, and when I was reading about the docs, I found there were two ways to implement React components, functional-based and class-based. I know before React 16.8 it's not possible to manage state in functional components, but after that there is React Hooks.
The problem is, there seems to be one restriction for React Hooks, they can only be used inside functional components. Take a server-client as an example, which needs to change an isAuthenticated state while 401 received.
//client.js
import { useUserDispatch, signOut } from "auth";
export function request(url, args) {
var dispatch = useUserDispatch();
return fetch(url, args).then(response => {
if (response.status === 401) {
logout(dispatch);
}
}
);
//auth.js
import React from "react";
var UserStateContext = React.createContext();
var UserDispatchContext = React.createContext();
function userReducer(state, action) {
...
}
function UserProvider({ children }) {
var [state, dispatch] = React.useReducer(userReducer, {
isAuthenticated: false,
});
return (
<UserStateContext.Provider value={state}>
<UserDispatchContext.Provider value={dispatch}>
{children}
</UserDispatchContext.Provider>
</UserStateContext.Provider>
);
}
function useUserState() {
return React.useContext(UserStateContext);
}
function useUserDispatch() {
return React.useContext(UserDispatchContext);
}
function signOut(dispatch) {
dispatch({});
}
export { UserProvider, useUserState, useUserDispatch, loginUser, signOut };
The client code above will produce error "Hooks can only be called inside of the body of a function component".
So maybe I have to move line var dispatch = useUserDispatch() upward to the component where request is called, and pass dispatch as props to request.
I feel this is not right, no only request is forced to care about some meaningless(to it) dispatch, but also this dispatch will spread everywhere a component needs to request.
For class-based components, this.state doesn't solve this problem either, but at least I can use mobx.
So are there some other ideal ways to solve this problem?
I came at this point too. Long story short you need to use Redux and Thunk with Async Logic, as described in detail with examples in the link below [1] if you want to do all of the stuff by hand on your own.
[1] https://redux.js.org/tutorials/essentials/part-5-async-logic
There is another solution that gives out-of-the box experience with Asynchronous API (can work with OpenAPI and GraphQL, handles request, provides caching with lifecycle, etc) wrapping stuff from [1] and its called RTK Query [2].
[2] https://redux-toolkit.js.org/rtk-query/overview
Diagram below explains [1] process visually.. but I think RTK Query [2] wraps everything in one place and could be better solution. There is a Quick Start Guide [3]. I will give it a try :-)
[3] https://redux-toolkit.js.org/tutorials/rtk-query/
Mobx and hooks are very similar in implementation. Both use a render context that is in a sense "global". React ties that render context to the component render context, but Mobx keeps that render context separate. Therefore that means that hooks have to be created within a component render lifecycle (but can sometimes be called outside that context). Mobx-react ties the Mobx render lifecycle to the react lifecycle, triggering a react re-render when observed objects change. So Mobx-react nests the react render context within the Mobx render context.
React internally keeps tracks of hooks by the number of times and order the hook is called within a component render cycle. Mobx, on the other hand, wraps any "observable" object with a proxy that lets the Mobx context know if any of its properties were referenced during a Mobx "run context" (an autorun call, essentially). Then when a property is changed, Mobx knows what "run contexts" care about that property, and re-runs those contexts. This means that anywhere you have access to an observable object you can change a property on it and Mobx will react to it.
For react state hooks, react provides a custom setter function for a state object. React then uses calls to that setter to know when it needs to re-render a component. That setter can be used anywhere, even outside a React render, but you can only create that hook inside a render call, because otherwise react has no way to tell what component to tie that hook to. Creating a hook implicitly connects it to the current render context, and that's why hooks have to be created inside render calls: hook builders have no meaning outside a render call, because they have no way to know what component they are connected to -- but once tied to a component, then they need to be available anywhere. In fact, actions like onClick or a fetch callback don't occur within a render context, although the callback is often created within that context - the action callback happens after react finishes rendering (because javascript is single threaded, so the render function must complete before anything else happens).
Hooks comes as an alternatively to class based components, you should pick up one to your project and stick to it, don't mix it up. there are some motivation for the creation of hooks, as it's better stated at docs: hook motivation.
you can create hook functions apart, but they are meant to be consumed by components. it's something like using HOC (high order component) with class based components.
const myHook = () => {
[foo, setFoo] = useState('john')
// use effect for example if you need to run something at state updates
useEffect(() => {
// do something on foo changes
}, [foo])
return [foo, setFoo] // returning state and setState you can use them by your component
}
now you have a reusable hook and you can consume at your components:
const myComponent = (props) => {
[foo, setFoo] = myHook()
const handleFoo = () => {
// some logic
setFoo(someValue)
}
return (
<div>
<span>{foo}<span>
<button onClick={handleFoo}>click</button>
</div>
)
}
obs: you should avoid declare variables as var nowadays, pick const for most, and if it's a value variable (like number) that needs update use let.
When you are creating a hooks you must refer to the Rules of Hooks
You can only call hooks from a react functions.
Don’t call Hooks from regular JavaScript functions. Instead, you can:
✅ Call Hooks from React function components.
✅ Call Hooks from custom Hooks (learn about them on this page).
If you want to create a reusable hooks then you can create a custom hooks for your functions.
You can call as many functions inside a hooks.
For example, here I'm refactoring the request function as a hook.
export function useRequest(url, args) {
var userDispatch = useUserDispatch();
const fetcher = React.useCallback(() => {
return new Promise((resolve, reject) =>
fetch(url, args)
.then((response) => {
if (response.status === 401) {
logout();
reject();
}
resolve(response);
})
.catch(reject)
);
}, [url, args]);
return [fetcher, userDispatch];
}
and then consumes it.
function App() {
const [fetch, userDispatch] = useRequest("/url", {});
React.useEffect(() => {
fetch().then((response) => {
userDispatch({ type: "USER_REQUEST", payload: response });
});
}, []);
return <div>Hello world</div>;
}
Yes, you have to use Redux or MobX to solve this problem. You have to maintain isAuthenticated state in the global state of Redux or MobX. Then make an action that could be named like, toggleAuthState and pass is to the child component and toggle the state from there.
Also you can use functional components for this case. Class based components is not mandatory to use MobX or Redux. If you maintain a HOC as a Container then you can pass the actions and states to the child.
I am showing an example of using a container as a HOC:
// Container
import React from "react"
import * as actions from "../actions"
import ChildComponent from "../components/ChildComponent"
import { connect } from "react-redux"
import { bindActionCreators } from "redux"
const Container = props => <ChildComponent { ...props } />
const mapStateToProps = state => ({ ...state })
const mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Container)
Then in ChildComponent you can use your states and dispatch actions whenever you need.
The reason React has contexts is to allow for multiple sibling components to share a piece of state-data. It is the go-to method for allowing two unrelated components to read/write in shared variables. The reason it is necessary is that React has no way to easily source a data value to multiple screens without actually passing that data between screens. Instead, it allows each screen access to the data when it needs it.
So... The implementation requires that a component be created, called a Context.Provider component, and then you have to wrap the components who need access to the shared data inside the Context.Provider. But why? Why on earth is that a requirement? Contexts are designed sharing data between components who aren't hierarchally related, and were required to put the components within a heirarchy to do so?
It would be 100 times more straight forward and just as effective to simply drop the requirement of using a Context.Provider, simple have the useContext function give access to a set variable by default:
// In ctx.js
import React from 'react';
export default CTX = React.createContext({val: "value"});
// In compA.js
import CTX from './ctx.js';
import {useContext} from 'react';
function A(props) {
var [context, setContext] = useContext(CTX);
console.log(context); //Logs {val: 'value'};
setContext({val: "newValue"});
}
Then later on, assuming component B renders after A:
import CTX from './ctx.js';
import {useContext} from 'react';
function B(props) {
var [context, setContext] = useContext(CTX);
console.log(context); //Logs {val: 'newValue'};
}
The above usage, if it actually worked, solves the task of "sharing data between unrelated components", and is much much simpler than requiring an entire new component be defined in the context file. This solution is better because:
1. No required restructuring of the application. You don't need to wrap components in a provider.
2. Any Components can just ask for any shared state easily, and they can set the shared state easily.
3. Easier to understand with much less code involved (One line of code for import and one line to initiate the context).
4. Doesn't sacrifice anything. This method allows for easy sharing of state between components, which is the entire reason of contexts in the first place.
Am I crazy? Is there a legitamate reason that we'd absolutely need to wrap our components up in a special component to share data?.. Why can't the shared state just exist independently? Its like they chose a bad solution... Why make every developer wrap there components in another component before using shared state, why not just let the developer use the damned shared state when they need to use it instead of jumping through a hoop? Someone please educate me.
Edit: One answer said that with my described method we wouldn't be able to access multiple contexts with a single component. That is false. It is actually easier with my described method:
// In context.js
export const CTX = React.createContext({val: "val"});
export const CTX2 = React.createContext({val2: "val2"});
// In app.js
function App(props) {
const [state, setState] = useContext(CTX);
const [state2, setState2] = userContext(CTX2);
return (<></>);
}
Easy. No need for Context.Provider. This is multiple contexts being used in one component, requiring just two calls to useContext versus wrapping your entire application in two nested contexts, which is what is what you have to do with current Context.Provider method...
Mate, answer is simple. React component only re-renders when it's props or state changes. Without Context.Provider component react will never understand when to re-render child components, thus you will have stale, render-blocked components.
The purpose for having a Context Provider wrap around children is to keep track of state and props, read on how state and props between parents and children affect each other. If there was no way for the Context Provider to keep track of its children, how would the components that use the Context be able to update(Changing parent state affects children, so there may be rerendering).
It's also important to understand React's philosophy and it's focus on components, it is a component-based library after all.
Important thing to remember:
Parent state change will affect children, so if state changes in parent, children components will be reevaluated and depending on how your components, state, and data are optimized (memo, callback, etc.) a rerender may occur, thus updating those children components as well.
Contexts Are Made To Handle All Use Cases
I've since spent more time using Contexts in my applications and have come to realize that Context.Provider is quite useful in a variety of situations. My initial complaint has merit in that often times when using Context we are simply wanting a variant of state that can be shared between components. In this common use case, Context.Provider does indeed requires us to write a bit of unnecessary boilerplate code and requires us to wrap elements in the provider so that they have access to the context.
However any time our shared state becomes a little more complicated having a dedicated Context.Provider component can make our lives a lot easier. Here is a use case to consider
Shared Data From External Sources (Post, Get)
Contexts may allow us to store any code related to the initialization of the shared state within the context itself, resulting in more easily readable and maintainable code. For example, lets say we have some user text posts on our server that are displayed by multiple components within our application, and we would also like for our users to be able to add new posts. All of this can be handled quite neatly within the Context.Provider:
import React, {useContext, useEffect, useState} from 'react';
export const PostsContext = React.createContext([]);
export default PostsContextProvider({children}) {
const [posts, setPosts] = useState([]);
function fetchPosts() {
// Here we will fetch the posts from our API, and then set the state
// stored within the Context.Provider equal to the fetched posts.
fetch('https://www.fakewebsite.com/api/posts/get', {
method: 'GET',
headers: {'Content-Type': 'application/json'}
}).then((response)=>{
// Convert response to json
return response.json();
}).then((json)=>{
// json here is the posts we fetched from the server, so we set the state
// equal to this value. This will update the state within all components
// that are using the context.
setPosts(json.posts);
})
}
useEffect(function(){
// This function will run a single time when the application is started
fetchPosts();
},[])
function addNewPost(post) {
// This is the function that will be used by the components.
// First, we will send the new post to the server so that it can store it.
fetch('https://www.fakewebsite.com/api/posts/post', {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({post: post})
}).then((response)=>{
if(response.ok) {
// The server has updated its database with our new post.
// Now we just need to fetch the posts from the server again to get the updated data.
fetchPosts();
}
})
}
return (
<PostsContext.Provider
value={[posts, addNewPost]}
>
{children}
<PostsContext.Provider />
)
}
Notice that the value prop we are passing does not actually pass the state setter function directly. Instead, we pass the addNewPost function. So, when a component calls useContext(PostsContext) they will get the addNewPost function. This is extremely useful, it will allow our components to easily add a single post to the shared state, while also handling the server update! Very cool. With the solution I originally proposed, this would be impossible, because we would only ever get a simple state setting function from our useContext call.
Now, we must wrap our application in the provider to make it available to all components:
// App.js
import React from 'react';
import PostsContextProvider from './posts_context';
import MyComponent from './my_component';
import MyOtherComponent from './my_other_component';
export default function App() {
return (
<PostsContextProvider>
<MyComponent/>
<MyOtherComponent/>
</PostsContextProvider>
)
}
At this point, MyComponent and MyOtherComponent now have access to the context using the useContext hook. It is now extremely simple for the components to access the posts data and also update it with a new post.
import React, {useContext} from 'react';
import {PostContext} from './posts_context';
export default function MyComponent() {
const [posts, addPost] = useContext(PostsContext); // 'posts' will always be up to date with the latest data thanks to the context.
...
}
import React, {useContext} from 'react';
import {PostContext} from './posts_context';
export default function MyOtherComponent() {
const [posts, addPost] = useContext(PostsContext);
...
function handleAddPost(title, text) {
// Now when this component wants to add a new post,
// we just use the `addPost` function from the context.
addPost({title, text});
}
...
}
The beauty of this is that all the code related to the fetching and posting of data can be neatly contained within the provider, separated from the UI code. Each component has easy access to the posts data, and when either component adds a new post the other component will be updated with the new data.
Final Thoughts
This is just scratching the surface of the usefulness of Context.Provider. It's easy to imagine using a Context.Provider to handle persistent data storage using a method very similar to the above, replacing the fetch calls with function that store/fetch persistent data. Or even, some combination of persistent data and fetched data.
Upon revisiting my original question, it actually made me laugh. I was sort of right, there should perhaps be a way to handle simple shared state between components that does not require wrapping components in a provider and does not require any provider code at all. However, providers are just so dang useful in any kind of state management within an application that it is actually probably a good thing to force people to use them for simple shared state, because then they will have to learn about this wonderful tool.
I am learning react hooks, and so far, I´ve seen that are a way to make react 'reactive' which it was not before this cool new thing.
I've also tried to share the state between different components using hooks, and when a component change any value inside the state, the other components get the updates. So that made me think, can Redux or any other state management solution be completely replaced by react hooks? Is there any pros and cons?
Anything I should consider before trying to migrate my redux based app to hooks? I am not a fan of big 3rd party libraries and if I can achieve the same goals with the tools that react can offer, why not?
If your use of redux is only based on the need to avoid prop-drilling, then react context ( using hooks or not ) could be enough for you.
But to answer your question: no. You can totally implement your version of dispatch action and get state with context and hooks alone, without redux, but redux offer more to the table than them. Middleware, ability to persist the state of your app, easier debug, etc.
If your app doesn't require all the benefits that redux could provide, then don't use it. But the statement "can context + hooks replace redux?" is false.
I've found this pattern to replicate my use cases of redux (code below).
The idea is that the setValue function fires an event with a parameter carrying the value and the event handler updates the hooks internal state.
import { useState, useEffect } from 'react'
export function useValue(name, initial) {
const [value, _setValue] = useState(initial)
function setValue(value) {
const evt = new CustomEvent(name, {detail: {value}})
window.document.dispatchEvent(evt)
}
useEffect(() => {
function handleEvent (evt) {
_setValue(evt.detail.value)
evt.stopPropagation()
}
window.document.addEventListener(name, handleEvent)
return () => {
window.document.removeEventListener(name, handleEvent)
}
}, [])
return [value, setValue]
}
First, some context.
I'm using Redux to manage authentication state of my app and have Auth as a Redux container (or smart component).
I've created a wrapper (a higher-order component) that takes Auth and returns it:
export default function AuthWrapper(WrappedComponent) {
class Auth extends Component {
... <Auth stuff here> ...
}
return connect(mapStateToProps, mapDispatchToProps)(Auth);
}
It seems to me that in order to use the wrapper, I just need to invoke it with a component I want to have behind my auth. For example, let's say I'm authenticating a component called UserPage with the wrapper, à la:
const AuthenticatedUserPage = AuthWappper(UserPage)
However, when I use the wrapper like this, React isn't happy with me. I get the following error:
Warning: AuthenticatedApp(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
My best guess is that it doesn't like the connect-ified component that Redux will create when I return it from AuthWrapper... which leads me to my question:
Does React support higher-order components when those components create Redux containers? And if so, why would React be throwing this error?
Here's my two cents. I think the error is occurring elsewhere.
According to this simplified version of the connect function in react-redux, the connect function is simply returning another react component. So in your case, you're returning a component, wrapped inside another component, which is still valid. A container is basically a component.
Read https://gist.github.com/gaearon/1d19088790e70ac32ea636c025ba424e for a better understanding of the connect function.
I also tried the following in my own application and it worked.
import Layout from '../components/Layout'
//Do some other imports and stuff
function wrapper(Layout) {
return connect(null, mapDispatchToProps)(Layout);
}
export default wrapper()
Like the error states, you might just simply be returning an invalid component somewhere in your app. Your app might be throwing the error because you're not wrapping a return call in parentheses on your render method.