mobx-react-lite useObserver hook outside of render - reactjs

I've seen examples of the useObserver hook that look like this:
const Test = () => {
const store = useContext(storeContext);
return useObserver(() => (
<div>
<div>{store.num}</div>
</div>
))
}
But the following works too, and I'd like to know if there's any reason not to use useObserver to return a value that will be used in render rather than to return the render.
const Test = () => {
const store = useContext(storeContext);
var num = useObserver(function (){
return store.num;
});
return (
<div>
<div>{num}</div>
</div>
)
}
Also, I don't get any errors using useObserver twice in the same component. Any problems with something like this?
const Test = () => {
const store = useContext(storeContext);
var num = useObserver(function (){
return store.num;
});
return useObserver(() => (
<div>
<div>{num}</div>
<div>{store.num2}</div>
</div>
))
}

You can use observer method in the component. And use any store you want.
import { observer } from "mobx-react-lite";
import { useStore } from "../../stores/StoreContext";
const Test = observer(() => {
const { myStore } = useStore();
return() => (
<div>
<div>{myStore.num}</div>
<div>{myStore.num2}</div>
</div>
)
}
);
StoreContext.ts
import myStore from './myStore'
export class RootStore{
//Define your stores here. also import them in the imports
myStore = newMyStore(this)
}
export const rootStore = new RootStore();
const StoreContext = React.createContext(rootStore);
export const useStore = () => React.useContext(StoreContext);

Related

passing object using context and doing iteration with map

This is a simple question but I couldn't reach the final result after a lot of attempts. The problem is that I want to pass an object in context and use it in another file. And then do an iteration and create a specific element for each value.
App.jsx
const [activities, setActivity] = useState([
{
key: Math.random() * Math.random(),
name: 'Hello',
}
]);
const inputValue = useRef(null);
const addActivity = () => {
const activity = {
key: Math.random() * Math.random(),
name: inputValue.current.value,
};
setActivity(activities.concat(activity));
};
const value = {
// I want to pass this parameter - only activities has problem (Activity.jsx <h1>)
// I can't achieve activities.name in Activity.jsx
activities: [...activities],
functions: {
addActivity: addActivity
},
ref: {
inputValue: inputValue
}
};
<Context.Provider
value={value}
>
Context.js
export const Context = createContext();
Activity.jsx
const { activities, functions, ref } = useContext(Context);
return (
<section className="activity-container">
<input type="text" ref={ref.inputValue} />
<button onClick={functions.addActivity}>add!</button>
{
activities.map(activity => (
<h1>activity.name</h1>
))
}
</section>
);
I believe this is what you want:
// Sharing data through context
Context file:
// Context.js
import React, { useState, useRef, createContext } from "react";
export const DataContext = createContext();
const getRandom = () => Math.random() * Math.random();
const defaultValue = {
key: getRandom(),
name: "Hello"
};
const ContextProvider = ({ children }) => {
const [activities, setActivity] = useState([defaultValue]);
const inputValue = useRef(null);
const addActivity = () => {
const activity = {
key: getRandom(),
name: inputValue.current.value
};
setActivity([...activities, activity]);
};
const value = {
activities: [...activities],
functions: { addActivity },
ref: { inputValue }
};
return <DataContext.Provider value={value}>{children}</DataContext.Provider>;
};
export default ContextProvider;
Hook to read from context:
// useDataContext
import { useContext } from "react";
import { DataContext } from "./Context";
const useDataContext = () => {
const contextValue = useContext(DataContext);
return contextValue;
};
export default useDataContext;
Child Element where you want to receive the value from context:
// Child.js
import React from "react";
import useDataContext from "./useDataContext";
const Child = () => {
const data = useDataContext();
return (
<>
{data.activities.map((val, idx) => (
<div key={idx}>Name is {val.name}</div>
))}
</>
);
};
export default Child;
And the App container:
// App.js
import Child from "./Child";
import ContextProvider from "./Context";
export default function App() {
return (
<div className="App">
<ContextProvider>
<Child />
</ContextProvider>
</div>
);
}
I've created a sandbox for you to test.
You should make sure that the Activity.jsx component is wrapped with context provider, to get the proper value from the context.
I tried in this codesandbox, and it's working properly. You can refer to this and check what you are missing.

map() is not rendering html elements

I had spent 4hrs in this issue but can't figure out what's the problem. Through props I had passed an array(props.content) and wanted to render each element with <h4> tag(not specific) but I can't. It works when I try to console.log each elements of array.
import classes from "./Memes.module.css";
const Memes = (props) => {
return (
<div className={classes.memes__body}>
{Array.prototype.forEach.call(props.content,items=>{
console.log(items)
})}
</div>
);
}
export default Memes;
Output -
but It didn't work when I run the same to render all items in some html tags.
import classes from "./Memes.module.css";
const Memes = (props) => {
return (
<div className={classes.memes__body}>
{Array.prototype.forEach.call(props.content,items=>{
<h1>{items}</h1>
})}
</div>
);
}
export default Memes;
OutPut
Doesn't work.
Note - Here props.content is an array of strings.
Fetch_memes.js (Parent one)
import { useState } from "react";
import { useEffect } from "react";
import Memes from "./Memes";
const Fetch_memes = () => {
const [image, setImage] = useState();
const [text, setText] = useState("");
useEffect(() => {
const memes = async () => {
const response = await fetch('https://www.reddit.com/r/memes.json');
if (!response.ok) {
throw new Error("Something went wrong");
}
const responseData = await response.json();
const img = responseData.data.children[0].data.thumbnail;
const memesCollection = [];
memesCollection.push("If you can Code then this doesn't mean that your are developer");
for(let i=0 ; i<20 ; i++){
memesCollection.push(responseData.data.children[i].data.title);
}
console.log(memesCollection[1]);
setImage(img);
setText(memesCollection);
}
memes();
}, []);
return (
<Memes src={image} content={text}/>
);
}
export default Fetch_memes;
You need to use return in your code:
import classes from "./Memes.module.css";
const Memes = (props) => {
return (
< >
{props.content.map((items)=>{
return <h1>{items}</h1>
})}
</>
);
}
export default Memes;
you can either use
{props.content.map(items=>{
return <h4>{items}</h4>
})}
or replace {} by ()and react returns the value by default:
{props.content.map(items=>(<h4>{items}</h4>))}
UPDATE: try this:
import classes from "./Memes.module.css";
import React from "react"
const Memes = ({content})=>{
return (
<div>
{content.map(item=>(<h1>{item}</h1>))}
</div>
);
}
export default Memes;
let me know the result.

React: Is there a way to access component state from function in another file?

I've a react component which includes a large function that updates the component state, the function is large so I want to move it to a separate file and export it in the react component. But I don't find anyway to access the component state if I move the function to its own file.
Is there anyway to do this ?
example:
component.tsx
import { myFunction } from './function.ts'
const [toggle, setToggle] = useState(false)
const my_component = () => {
return (
<div>
<button onClick={myFunction}>Run function</button>
</div>
)
}
export default my_component
function.ts
export const myFunction = () => {
// do something that updates `toggle`
}
you can do the logic apart from the component and return the result to the component. have a look at the code below.
https://codesandbox.io/s/hopeful-dubinsky-930p7?file=/src/App.js
This is just a raw example of what you can do with custom state hooks (reference: https://dev.to/spukas/react-hooks-creating-custom-state-hook-300c)
import React from 'react';
export function useMyFunction(value) {
const [toggle, setToggle] = React.useState(value || false);
const myFunction = () => {
// do something that updates `toggle` with setToggle(...)
}
return { toggle, myFunction };
}
import { useMyFunction } from './function.ts'
const my_component = () => {
const [toggle, myFunction] = useMyFunction(false)
return (
<div>
<button onClick={myFunction}>Run function</button>
</div>
)
}
export default my_component
This can be achieved by 2 different ways one using HOC components and another just by using functions.
Approach 1: Using HOC
handler.js
const withHandlers = (WrappedComponent) => {
class HandlerComponent extends Component {
state = {toggle:false};
myFunction = () => {
//Do your update here
}
render() {
return <WrappedComponent
toggle={this.state.toggle
myFunction={this.myFunction}
/>
}
};
my_component.js
const my_component = (props) => {
return (
<div>
<button onClick={props.myFunction}>Run function</button>
</div>
}
export default withHandlers(my_component);
Approach 2: Using Functions
handler.js
export const myFunction(toggle) => {
return !toggle; //return the changed value
}
my_component.js
const my_component = () => {
const [toggle, setToggle] = useState(false);
const myFunction = () => {
setToggle(handler.myFunction); //the state will be passed as a parameter by default
};
return(
<div>
<button onClick={myFunction}>Run function</button>
</div>
);
};
For the toggle to work, it must be passed to the function as a props then for update it used state management (redux or react context).
The best solution is to define the toggle in the function itself and pass it a Boolean props to control it.
import { myFunction } from './function.ts'
const my_component = () => {
return (
<div>
<button onClick={myFunction(false)}>Run function</button>
</div>
)
}
export default my_component
function.ts
export const myFunction = (props) => {
const [toggle, setToggle] = useState(props || false);
// your codes
};

ReactJs Functional Component - How to call function from outside?

How to call a function from outside of the functional component.
I have a functional component like this
import React, { useState } from 'react';
const Hello = () => {
// call updateField() here
};
const Headline = () => {
const [greeting, setGreeting] = useState(
'Hello Function Component!'
);
// Function inside Headline, I want to call this function in Hello()
const updateField = () => {
}
return <h1>{greeting}</h1>;
};
export default Headline;
I want to call updateField() in Hello() outside of Headline(). Please suggest.
Here are two ways to do this,
Method 1: Move the common state to a parent component
const ParentComponentWithHelloAndHeadline = () => {
const [field, setField] = useState()
const updateField = () => { ... }
return (
<>
<Headline field={field} updateField={updateField} />
<Hello updateField={updateField} />
</>
)
}
Method 2: Use React.Context (avoids prop-drilling, incase that is a concern using method 1)
const CommonContext = React.createContext({
field: 'commonField',
updateField: () => { ... }
})
const Hello = () => {
const { field, updateField } = useContext(CommonContext)
// call updateField() here
};
const Headline = () => {
const { field, updateField } = useContext(CommonContext)
const [greeting, setGreeting] = useState(
'Hello Function Component!'
);
return <h1>{greeting}</h1>;
};
export default Headline;
function RootApp() {
return (
<CommonContext.Provider>
<Headline />
...
...
<Hello />
</CommonContext.Provider>
);
}

React: save ref to state in a custom hook

I want to create a ref to an element, save it in state and use it somewhere else, down the line. Here is what I have so far:
const Header = () => {
const topElement = useRef();
const { setRootElement } = useScrollToTop();
useEffect(() => {
setRootElement(topElement);
}, []);
return (
<div ref={topElement}>
...
</div>
)
}
The useScrollToTop hook:
export const useScrollToTop = () => {
const [rootElement, setRootElement] = useState();
const scrollToTop = () => {
rootElement.current.scrollIntoView();
};
return {
scrollToTop: scrollToTop,
setRootElement: setRootElement
};
};
And in a different component:
const LongList = () => {
const { scrollToTop } = useScrollToTop();
return (
<div>
....
<button onClick={() => scrollToTop()} />
</div>
);
}
The setRootElemet works okay, it saves the element that I pass to it but when I call scrollToTop() the element is undefined. What am I missing here?
As hooks are essentially just functions, there is no state shared between calls. Each time you call useScrollToTop you are getting a new object with its own scrollToTop and setRootElement. When you call useScrollToTop in LongList, the returned setRootElement is never used and therefore that instance rootElement will never have a value.
What you need to do is have one call to useScrollToTop and pass the returned items to their respective components. Also, instead of using a state in the hook for the element, you can use a ref directly and return it.
Putting these together, assuming you have an App structure something like:
App
Header
LongList
Hook:
export const useScrollToTop = () => {
const rootElement = useRef();
const scrollToTop = () => {
rootElement.current.scrollIntoView();
};
return {
scrollToTop,
rootElement,
};
};
App:
...
const { scrollToTop, rootElement } = useScrollToTop();
return (
...
<Header rootElementRef={rootElement} />
<LongList scrollToTop={scrollToTop} />
...
);
Header:
const Header = ({ rootElementRef }) => {
return (
<div ref={rootElementRef}>
...
</div>
);
}
LongList:
const LongList = ({ scrollToTop }) => {
return (
<div>
...
<button onClick={() => scrollToTop()} />
</div>
);
}
The issue probably is topElement would be null initially and useEffect would trigger setRootElement with null. You would need to keep topElement in state variable and check when it changes and set the value inside your JSX as
const [topElement, setTopElement] = useState(null);
useEffect(() => {topElement && setRootElement(topElement);}, [topElement])
return (
<div ref={(ref) => setTopElement(ref)}>
...
</div>
);

Resources