Is possible to build compound component with separated files? REACT - reactjs

I'm trying to understand React's compound pattern. In all exercises all components are in one file. Is it possible to build component with that pattern with external components?
I would achieve that scenario:
src:
components:
Main
Component1
Component2
Component3
// ONE FILE Main.js
import {CompoundComponent1, CompoundComponent2, CompoundComponent3} './foo'
const Main = () => {
const [on, setOn] = React.useState(false)
const toggle = () => setOn(!on)
const CompoundComponent1 = Component1;
const CompoundComponent2 = Component2;
const CompoundComponent3 = Component3;
return <Switch on={on} onClick={toggle} />
}
Main.C1 = CompoundComponent1
Main.C2 = CompoundComponent2
Main.C3 = CompoundComponent3
// ONE FILE END
App.js
const App = () => {
<Main>
<Main.C1>FOO</Main.C1>
// etc.
</Main>
}

I think that i found solution.
import * as React from 'react'
import {Switch} from '../switch'
import {ToggleOn} from './02/ToggleOn'
import {ToggleOff} from './02/ToggleOff'
import {ToggleButton} from './02/ToggleButton'
function Toggle() {
const [on, setOn] = React.useState(false)
const toggle = () => setOn(!on)
return <Switch on={on} onClick={toggle} />
}
Toggle.ToggleOn = ToggleOn
Toggle.ToggleOff = ToggleOff
Toggle.ToggleButton = ToggleButton
function App() {
return (
<div>
<Toggle>
<Toggle.ToggleOn>Turn ON</Toggle.ToggleOn>
<Toggle.ToggleOff>Turn OFF</Toggle.ToggleOff>
<Toggle.ToggleButton />
</Toggle>
</div>
)
}
export default App
In separated files:
export const ToggleButton = ({on, toggle}) => (
<Switch on={on} onClick={toggle} />
)
export const ToggleOn = ({on, children}) => {
if (on) {
return children
}
return null
}

Related

How can I send state to another component in react?

I would like to ask how can I send state from component to another component?
I have 3 components and I want to send data between them. So I have an input component where I handle an IP call and I want to send this shortedLink state to another component, so I can render that data. I don't know that is it clear what I want to do, but I hope so :D
import ShortedLinks from './ShortedLinks'
const testimonials = () => {
return (
<div>
<ShortedLinks />
</div>
);
};
export default testimonials;
const shortedLinks = () => {
return (
<div>
<h1>I want to get the state here</h1>
</div>
);
};
export default shortedLinks;
const InputSection = () => {
const [shortedLink, setShortedLink] = useState("")
return (...);
};
export default InputSection;
You can use the props to achieve it like this :
import ShortedLinks from './ShortedLinks'
const Testimonials = () => {
const [shortedLink, setShortedLink] = useState("")
return (
<div>
<ShortedLinks shortedLink={shortedLink} /> // Pass props here
</div>
);
};
export default Testimonials;
And then in your ShortedLinks component
const ShortedLinks = ({shortedLink}) => { // Get props here
return (
<div>
<h1>{shortedLink}</h1>
</div>
);
};
export default ShortedLinks;
And if you can't use the props like this you can use the useContext like this :
import React,{ useState, createContext } from "react";
export const ShortedLinkContext = createContext('');
const InputSection = () => {
const [shortedLink, setShortedLink] = useState("")
return (
<ShortedLinkContext.Provider value={shortedLink}>
....
</ShortedLinkContext.Provider>
);
};
export default InputSection;
And finally you can comsume the context here :
import {ShortedLinkContext} from ....
const ShortedLinks = () => {
const shortedLink = useContext(ShortedLinkContext);
return (
<div>
<h1>{shortedLink}</h1>
</div>
);
};
export default shortedLinks;
Enjoy :)

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
};

React lazy recursive import

I have requirement where I need to load component from dynamic folders. For example I have following folders inside components
components
-default
-component-one
-component-two
-component-three
-custom
-component-three
Suppose if componentFolder state set to custom folder then it should load from custom folder .if any component not found in custom folder then it should be load from default folder.
So my question is ,can we possible to import recursively ?
function App() {
const [componentFolder, setComponentFolder] = React.useState("default")
const Home = React.lazy(() => import("./components/" +componentFolder+ "/Home"));
return (
<div className="App">
<Suspense fallback="laoding">
<Home></Home>
</Suspense>
</div>
);
}
the below link has same requirement as i asked
How to check if a pariticular fileExists in reactjs
If you are using Webpack then you can use require.context to load modules dynamically:
import React, { Suspense } from "react";
const load = async (path, file) => {
const defaultPath = "default";
const files = require.context("./components", true, /\.js$/);
try {
return files(`./${path}/${file}.js`);
} catch (err) {
return files(`./${defaultPath}/${file}.js`);
}
};
export default function App() {
const [componentFolder, setComponentFolder] = React.useState("default");
const Home = React.lazy(() => load(componentFolder, "Home"));
return (
<div className="App">
<Suspense fallback="loading">
<Home />
</Suspense>
</div>
);
}
Since lazy returns a promise, you can use its catch block to return another lazy (promise) when the original module was not found.
An example:
import { lazy, Suspense, useState } from "react";
const rotate = {
custom: "default",
default: "custom",
};
function App() {
const [folder, setFolder] = useState("custom");
const [name, setName] = useState("component1");
// Here: catch and return another lazy (promise)
const Component = lazy(() =>
import("./components/" + folder + "/" + name).catch(
(err) => import("./components/" + rotate[folder] + "/" + name)
)
);
return (
<div>
<Suspense fallback="laoding">
<Component />
</Suspense>
<button onClick={() => setFolder(rotate[folder])}>toggle folder</button>
<br />
<button onClick={() => setName("component1")}>load component 1</button>
<button onClick={() => setName("component2")}>load component 2</button>
<button onClick={() => setName("component3")}>load component 3</button>
</div>
);
}
Here is a demo.
Note that Component, defined/created inside App component, will be recreated at every rerender of App. It will cause Component to reset its state when App rerenders.
Based in the others answers and comments here I came up with this:
https://codesandbox.io/s/so-react-lazy-recursive-import-2dqlp?file=/src/App.js
import React, { lazy, Suspense } from "react";
// test the code by removing the _ in front of file names
/*
components/
comp 🚫 3? a root file will not trigger -> go to default
/default
comp 👈 3! nice ^^ (but if it not exists will throw an error)
/custom
comp 👈 2?
/client
comp 👈 1?
/omgStop
heIsAlreadyDead (but works)
/otherClient ...
*/
const recursiveImport = async (
componentName,
targetTree,
defaultTree = "./components/default"
) => {
console.count("paths tested");
if (!targetTree) {
return import(defaultTree + "/" + componentName);
}
return import("./components/" + targetTree + "/" + componentName).catch(
() => {
const newTreeArr = targetTree.split("/");
newTreeArr.pop();
const newTree = newTreeArr.join("/");
return recursiveImport(componentName, newTree, defaultTree);
}
);
};
export default function App() {
const targetTree = "custom/client1";
const Component = lazy(() => recursiveImport("Test", targetTree));
return (
<div>
<Suspense fallback="loading">{<Component />}</Suspense>
</div>
);
}
Folder structure:
This solves all of your requirements?
Simple and objective
const Recipe = React.lazy(() =>
import(`docs/app/Recipes/${props.componentName}`)
.catch(() => ({ default: () => <div>Not found</div> }))
);
I was trying something, endup with a simple solution that you should reach before:
https://codesandbox.io/s/awesome-violet-fr7np?file=/src/App.js

Using Draft js mention plugin with react hooks

I have been trying to get draft js mention plugin to work with react hooks but can't seem to figure what's wrong with the code. Appreciate any help on this.
import React, { useRef, useState, useEffect } from "react";
import { EditorState } from "draft-js";
import Editor from "draft-js-plugins-editor";
import createMentionPlugin, { defaultSuggestionsFilter } from "draft-js-mention-plugin";
import mentions from "./mentions";
export default function MentionEditor() {
const [editorState, setEditorState] = useState(EditorState.createEmpty());
const [suggestions, setSuggestions] = useState(mentions);
const editor = useRef(null);
useEffect(() => {
editor.current.focus();
}, [])
const mentionPlugin = createMentionPlugin();
const { MentionSuggestions } = mentionPlugin;
const plugins = [mentionPlugin];
const onSearchChange = ({ value }) => {
setSuggestions(defaultSuggestionsFilter(value, mentions))
};
return (
<div style={{ border: "1px solid gray" }}>
<Editor
editorState={editorState}
onChange={editorState => setEditorState(editorState)}
plugins={plugins}
ref={editor}
/>
<MentionSuggestions
onSearchChange={onSearchChange}
suggestions={suggestions}
/>
</div>
);
}
You need to move the draft-js plugin configuration outside the component arrow function. This is a pretty basic Draft-JS implementation using a functional component and hooks:
import React, { useState, useRef } from 'react'
import { EditorState } from 'draft-js'
import Editor from 'draft-js-plugins-editor'
import createMentionPlugin, { defaultSuggestionsFilter } from 'draft-js-mention-plugin'
import 'draft-js/dist/Draft.css'
import 'draft-js-mention-plugin/lib/plugin.css'
import mentions from "./mentions"
// Draft-JS-Mentions plugin configuration
const mentionPlugin = createMentionPlugin()
const { MentionSuggestions } = mentionPlugin
const plugins = [mentionPlugin]
const MyEditor= () => {
const [suggestions, setSuggestions] = useState(mentions)
// Draft-JS editor configuration
const [editorState, setEditorState] = useState(
() => EditorState.createEmpty(),
)
const editor = useRef(null)
// Check editor text for mentions
const onSearchChange = ({ value }) => {
setSuggestions(defaultSuggestionsFilter(value, mentions))
}
const onAddMention = () => {
}
// Focus on editor window
const focusEditor = () => {
editor.current.focus()
}
return (
<div onClick={() => focusEditor()}>
<Editor
ref={editor}
editorState={editorState}
plugins={plugins}
onChange={editorState => setEditorState(editorState)}
placeholder={'Type here...'}
/>
<MentionSuggestions
onSearchChange={onSearchChange}
suggestions={suggestions}
onAddMention={onAddMention}
/>
</div>
)
}
export default MyEditor
Just move these lines outside component and it will work:
const mentionPlugin = createMentionPlugin();
const { MentionSuggestions } = mentionPlugin;
const plugins = [mentionPlugin];
export default function MentionEditor() {
const [editorState, setEditorState] = useState(EditorState.createEmpty());
.. ... ...
}
!!!!!!!!!!!!!!!! PAY ATTENTION !!!!!!!!!!!!
The onSearchChange method will be triggered once the '#' character is typed, so in this case it will return just 5 items that fit the empty string...
To prevent this to be happened, just check that the value we want to search is not empty:
const onSearchChange = ({ value }) => {
if (value) {
setSuggestions(defaultSuggestionsFilter(value, mentions));
}
};

How to change context value in functional component?

I have a context named StatusContext like this:
export const statusCtxInit = {
open: false,
toggleOpen() {
this.open = !this.open;
}
};
const StatusContext = React.createContext(statusCtxInit);
export default StatusContext
The whole app is wrapping with the provider:
// ...
<StatusContext.Provider value={statusCtxInit}>
// ...
To use the values of my context I use useContext in my FC and it works when I get the value.
function MyComp() {
const status = useContext(StatusContext);
return (
<div>
{status.open
? `It's Open`
: `It's Closed`}
<button onClick={() => status.toggleOpen()}>
Toggle
</button>
</div>
)
}
export default MyComp
On the other hand, I also want to change the context by calling the toggleOpen but, it does not work as I want. Actually the value changes but not effect the MyComp.
What I did wrong? What shall I do?
import React from 'react';
const statusContext = React.createContext();
const {Provider} = statusContext;
// custom provider
export const StatusProvider = ({children}) => {
const [isOpen, setOpen] = React.useState(false)
const toggle = () => setOpen(v => !v)
return (
<Provider value={{isOpen, toggle}}>
{children}
</Provider>
)
}
//custom hook
export const useStatus = () => React.useContext(StatusContext)
//usage
function MyComp() {
const status = useStatus()
return (
<div>
{status.isOpen
? `It's Open`
: `It's Closed`}
<button onClick={() => status.toggle()}>
Toggle
</button>
</div>
)
}
export default MyComp

Resources