Understanding withtracker() component in Meteor - javascript

I was trying to learn meteor and not very familiar with this pattern of HOC (it's meteor.js with react).
I was going though their offical docs of tutorials. Here is what they did (You can click here to visit the page)
They imported following package in App.js
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { Tasks } from '../api/tasks.js';
Then there is a simple to do class App extends component wrapped by this HOC
export default withTracker(() => {
return {
tasks: Tasks.find({}).fetch(),
};
})(App);
The official docs for the same says
The wrapped App component fetches tasks from the Tasks collection and
supplies them to the underlying App component it wraps as the tasks
prop. It does this in a reactive way, so that when the contents of the
database change, the App re-renders, as we'll soon see!
Now this language isn't exactly alien to me but I am having hard to comprehend and understand it. So Can someone explain me the same?
To be specific what is The wrapped App component fetches tasks and supplies it to underline app component

A higher order component is in the most basic form a function that takes a component type as input and returns a component class that wraps the input component and adds functionality to it.
Usually the signature is function that takes the argument to apply to the wrapped component which returns a HOC as described above so you can use it with multiple components.
Here is a very basic example that shows an error message if the component it's used on or any of it's child components throw an exception:
const catchError = ({ errorMessage }) => InputComponent => class extends Component {
render() {
try {
return <InputComponent {...this.props} />;
} catch {
return <div>{errorMessage}</div>
}
}
}
const ComponentWithErrorMessage = catchError({ errorMessage: 'Whoops!' })(MyComponent);
// This is the same as the following, the first just immediately invokes the returned function
const whoopsErrorHoc = catchError({ errorMessage: 'Whoops!' });
const ComponentWithWhoopsError = whoopsErrorHoc(MyComponent);
The meteor HOC will be a bit more complicated but the idea is the same. It receives a reference to the meteor Task store and will return a component that re-renders the input component whenever the data changes in the store and add the data to the props of that component.

Related

Is it possible to use React Hooks outside of functional component, or i have to use mobx or redux?

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.

How not to pass down props using Redux?

I just learned that we can reduce the complexity of a react project using redux. With the single source of truth (store), we don't need to pass down states to components that don't need them. I'm struggling with understanding this statement.
Say I have three components, A, B and C. A is a container with a state called text. B is a custom button and C only displays the text. Whenever B is clicked, it updates the state in A. Then C will display the updated text.
A
/ \
C B
I have tried to apply redux to the app and found that I still need to pass down the props. The only difference is that I am passing down this.props.text instead of this.state.text.
I can't see how redux can benefit an app like this.
App.js
import React, { Component } from "react";
import { connect } from 'react-redux';
import MyButton from "./MyButton";
import { handleClick } from "./actions";
import Display from "./Display"
class App extends Component {
render() {
return (
<div className="App">
<MyButton onClick={()=>this.props.handleClick(this.props.text)} />
<Display text={this.props.text} />
</div>
);
}
}
const mapStateToProps = state => ({
text: state.text.text
})
const mapDispatchToProps = dispatch => ({
handleClick: (text) => dispatch(handleClick(text))
})
export default connect(mapStateToProps, mapDispatchToProps)(App)
Also, if we have another app with structure shown below. Say B doesn't care about A's state but C needs it to display the text. Can we skip B and just let C use A's state?
A
|
B
|
C
I think I found the solution. I simply created a file stores.js and
export the store. So I can import it and retrieve the state by
invoking store.getState() whenever a child component needs the it.
You shouldn't do that.
Instead you should use the connect function with each component, everywhere in the structure, that needs access to a property of your store.
But, if you only have three components, you probably don't need Redux or a global store for your app state.
Redux comes with a lot of opinions on how to handle your global state that are meant to secure your data flow.
Otherwise, if you only need to avoid prop drilling (i.e. passing down props through many levels, as in your second exemple) you may use the native React context API that does just that: reactjs.org/docs/context.html
Edit
Things should be clearer with an exemple:
import React, { Component } from "react";
import { connect } from 'react-redux';
import MyButtonCmp from "./MyButton";
import DisplayCmp from "./Display"
import { handleClick } from "./actions";
// I am doing the connect calls here, but tehy should be done in each component file
const mapStateToProps = state => ({
text: state.text.text
})
const Display = connect(mapStateToProps)(DisplayCmp)
const mapDispatchToProps = dispatch => ({
onClick: (text) => dispatch(handleClick(text))
})
const MyButton = connect(null, mapDispatchToProps)(MyButtonCmp)
class App extends Component {
render() {
return (
<div className="App">
{/* No need to pass props here anymore */}
<MyButton />
<Display />
</div>
);
}
}
// No need to connect App anymore
// export default connect(mapStateToProps, mapDispatchToProps)(App)
export default App
In this example, you may map app state to props using redux.
I don't see why you would process the information this way(with redux) unless you were planning on using the data in multiple parts of the application and wanted to re-use the action code.
See more:
https://react-redux.js.org/using-react-redux/connect-mapstate
2nd question
Also, if we have another app with structure shown below. Say B doesn't care about A's state but C needs it to display the text. Can we skip B and just let C use A's state?
In Redux, yes.
With React Hooks, yes.

useContext only works in stateless functional component

I'm trying to get to grips with the new useContext function in React. Works great in stateless functionality components. For example:
import React from 'react';
import LocaleContext from '../LocaleContext';
const Link = ({ text, url }) => {
const locale = useContext(LocaleContext);
return (
<a href={`/${locale}/${url}`}>
{text}
</a>
);
};
export default Link;
I also want to use useContext in stateful components, and even non React functions, but when I do so, I get the following error:
Hooks can only be called inside the body of a function component.
The message seems simple enough to understand, but is this really true? I can only use it in a stateless functional component? If so, it seems kind of pointless to me, because it's super easy to use a simple HOC or the traditional method of:
<Locale Consumer>
{locale => (
...
)}
</LocaleConsumer>
So what gives here? I have the latest version of every package in my project. Not sure if it matters but I'm developing a NextJS site here.
If you really want to use classes (i actually came from Angular and i still prefer use classes) you can workaround easily like that:
class ComponentImpl extends React.Component<any> {
constructor(props?) {
super(props);
}
render() {
return (
<div>
CounterButton: <button onClick={() => {this.props.appContext.setCount(this.props.appContext.count + 5)}}>App Counter + 5</button>
</div>
)
}
}
export function Component() {
let appContext = useContext(AppContext);
return <ComponentImpl appContext={appContext}></ComponentImpl>
};
And you just use it: <Component></Component>
The problem is what the error says. React hooks aren't available in class components. Due to differences between class components and function components, hooks cannot be used with the former.
As the documentation says,
Hooks let you use more of React’s features without classes. Conceptually, React components have always been closer to functions. Hooks embrace functions, but without sacrificing the practical spirit of React. Hooks provide access to imperative escape hatches and don’t require you to learn complex functional or reactive programming techniques.
Hooks are supposed to address common use cases that are specific to class components which couldn't be previously implemented with stateless functional components alone. Functional components aren't stateless since React 16.8 and are allowed to have a state and trigger own updates.
As for useContext hook,
When the provider updates, this Hook will trigger a rerender with the latest context value.
It would be messed up in class component due to difference between functional and class components. Component function is called each time the component is rendered:
const Foo = props => {
const context = useContext(Context);
// use context
}
There's no place in class component that would behave the same way except render function. And if lifecycle-specific tasks go to render function, this means that a class was a wrong choice, and class component needs to be refactored to a function. A counterpart to useContext in class components is contextType, which is currently restricted to single context.
For multiple contexts it's still required to receive them through context Consumer inside render, or as props from higher-order component wrapper:
const contextsHOC = (contexts = {}) => Comp => (
props => {
const contextProps = {};
for (const prop in contexts) {
// eslint-disable-next-line react-hooks/exhaustive-deps
contextProps[prop] = React.useContext(contexts[prop]);
}
return <Comp {...props} {...contextProps}/>;
}
);
#contextsHOC({ bar: BarContext, baz: BazContext });
export default class FooComponent extends Component {
// contexts are mapped to this.props.bar and this.props.baz
...
}
// or
class FooComponent extends Component { ... }
export default contextsHOC({ ... })(FooComponent);
Passing contexts as props allows for additional optimization with PureComponent or shouldComponentUpdate.
useContext is a hook that consumes a context and can only be used in functional components.
If you want to consume context in class components, you will need to look at alternative methods such as Consumer Component, official docs for this here

React SSR - Rendering a component following an async action

React's component lifecycle goes hand in hand with the DOM, so when I'm trying to render on the server side following an async action I'm encountering issues.
I have a component (let's call it the containing component) which is responsible for dynamically importing another component (let's call it the inner component) according to data passed in its props.
Once the import promise is resolved, I'd like to render the inner component inside of the containing component.
The problem is, I have no way of accessing the containing component's lifecycle.
render() is only triggered once upon the construction of the containing component.
Updating the component via forceUpdate() and setState() are not possible for the same reason componentDidMount() will never be called.
How do I render a component following an async action on the server side when using server rendering?
My code so far:
import React from 'react';
export default class ComponentLoader extends React.Component<{ component: string }, { component: any }> {
constructor(props) {
super(props);
this.state = {
component: null
}; //no-op
}
componentDidMount(): void {
//never called
}
componentWillMount(): void {
import('./' + this.props.component).then(module => {
this.forceUpdate(); //no-op
this.setState({ component: React.createElement(module.default) }); //no-op
});
}
render() {
//called only once during construction
return this.state.component || null; //this.state won't be available, nor will it matter if I access component any other way since render() won't be called again once that component is resolved anyway.
}
}
I ended up using loadable components, a different implementation than what you see in the comments though.
https://github.com/jamiebuilds/react-loadable
https://github.com/konradmi/react-loadable-ssr-code-splitting
On the server side it makes sure to only render the react components once all the dependencies are loaded async. That way the render is sync since that's the only thing that's supported at the moment (though there's a feature request for supporting async react ssr), but the loading is done async, so it doesn't block any other operations.

Using higher order components with Redux containers

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.

Categories

Resources