I'm testing the use-context-selector lib to prevent unnecessary rendering in react ContextApi. I create this simple application:
export const HomeProvider = (props: PropsWithChildren<{}>) => {
const [x, setX] = React.useState(0);
const [y, setY] = React.useState(0);
return (
<HomeContext.Provider value={{ x, setX, y, setY }}>
{props.children}
</HomeContext.Provider>
);
};
export const HomeModule = () => {
return (
<HomeProvider>
<CounterScreen></CounterScreen>
<br />
<br />
<MessageScreen></MessageScreen>
</HomeProvider>
);
};
The child components are:
export const MessageScreen = () => {
const x = useContextSelector(HomeContext, (context) => context.x);
const setX = useContextSelector(HomeContext, (context) => context.setX);
return (
<>
<h1>Teste X</h1>
{x} <button onClick={() => setX(x + 1)}>X</button>
</>
);
};
export const MessageScreen = () => {
const x = useContextSelector(HomeContext, (context) => context.x);
const setX = useContextSelector(HomeContext, (context) => context.setX);
return (
<>
<h1>Teste Y</h1>
{y} <button onClick={() => setY(y + 1)}>X</button>
</>
);
};
The result, when I click in any one of those button is that indeed the child components doesn't rerender:
https://i.stack.imgur.com/9adCh.png
But, when I use the option: Highlight updates when components render in settings, all components on the screen is highlighted.
So, am i missing something or i'm right about the child components not rerendering ?? I saw other videos on youtube that show that when using this lib, the component that didn't render was not highlighted.
Related
Is it possible to pass other components through a state? I'm trying to make a tab function like a web browser, and if the user clicks the tab, a component shows up.
In my app.js I have -
const[chosenTab, setChosenTab] = useState("")
return (
<>
<Main chosenTab = {chosenTab}/>
</>
);
In Main.js -
const Main = ({chosenTab}) => {
return (
<>
{chosenTab}
</>
)
}
With the code below, the logic works to display the name of the tab/other component, but doesn't work if I replace {chosenTab} with <{chosenTab}/> to pass it as a component rather than just html.
I don't think this would work as you've structured it - I'd be welcome to have someone prove me wrong though since that would be a neat trick.
Now if I had to solve this problem, I'd simply use a object to hold what I need:
const tabMap = {
"string1": <component1 />,
"string2": <component2 />,
"string3": <component3 />
}
const Main = ({chosenTab}) => {
return (
<>
{tabMap[chosenTab]}
</>
)
}
Even further, let's say you wanted to pass in custom props, you could make tabMap a function to do that.
You can pass component reference itself as a tab.
const TabA = () => <div>Tab A</div>
const TabB = () => <div>Tab B</div>
const Main = ({ ChosenTab }) => {
retur <ChosenTab />
}
const App = () => {
const [chosenTab, setChosenTab] = useState(() => TabA);
const changeTab = (tab) => setChosenTab(() => tab);
return <Main ChosenTab={chosenTab} />
}
export default App;
Or you can store your tabs in object, Map or Array and set state accordingly
const tabs = {
A: TabA,
B: TabB
}
const App = () => {
const [chosenTab, setChosenTab] = useState(() => tabs.A);
const changeTab = (tabKey) => setChosenTab(() => tabs[tabKey]);
return <Main ChosenTab={chosenTab} />
}
export default App;
I have this handleSubmit that returns me a key (verifyCode) that I should use in another component. How can I pass this verifyCode to another component?
const SendForm = ({ someValues }) => {
const handleSubmitAccount = () => {
dispatch(createAccount(id, username))
.then((response) => {
// I get this value from data.response, its works
const { verifyCode } = response;
})
.catch(() => {
});
};
return(
//the form with handleSubmitAccount()
)
}
export default SendForm;
The other component is not a child component, it is loaded after this submit step. But I don't know how to transfer the const verifyCode.
This is the view where the components are loaded, it's a step view, one is loaded after the other, I need to get the const verifyCode in FormConfirmation
<SendForm onSubmit={handleStepSubmit} onFieldSubmit={handleFieldSubmit} />
<FormConfirmation onSubmit={handleStepSubmit} onFieldSubmit={handleFieldSubmit} />
Does anyone know how I can do this?
You need to move up the state to a component that has both as children and then pass down a function that updates as a prop
import React from "react";
export default function App() {
const [value, setValue] = React.useState(0);
return (
<div className="App">
<Updater onClick={() => setValue(value + 1)} />
<ValueDisplay number={value} />
</div>
);
}
const Updater = (props) => <div onClick={props.onClick}>Update State</div>;
const ValueDisplay = (props) => <div>{props.number}</div>;
Check out the docs here
For more complex component structures or where your passing down many levels you may want to look into reactContext
import React from "react";
//Set Default Context
const valueContext = React.createContext({ value: 0, setValue: undefined });
export default function App() {
const [value, setValue] = React.useState(0);
return (
<div className="App">
{/** Pass in state and setter as value */}
<valueContext.Provider value={{ value: value, setValue }}>
<Updater />
<ValueDisplay />
</valueContext.Provider>
</div>
);
}
const Updater = () => {
/** Access context with hook */
const context = React.useContext(valueContext);
return (
<div onClick={() => context.setValue(context.value + 1)}>Update State</div>
);
};
const ValueDisplay = () => {
/** Access context with hook */
const context = React.useContext(valueContext);
return <div>{context?.value}</div>;
};
This is somewhat of a design or react patterns related question but I'm trying to figure out the best way to share sate and methods across different children.
I have a small app where the navigation from step to step and actual form data are rendered and handled in different sibling components. This is a codesandbox of roughly how the app functions.
What I was trying to figure out is the best way to share state between the sibling components. For instance, in the app linked above I need to validate the input from <AppStepOne /> when next is clicked and then move to <AppStepTwo />. Ideally I don't want to just have all the state live in the top level ExampleApp because there are a decent amount of steps and that can get ugly really fast.
The other though I had which I wanted to get some input on what using the react context api. I haven't worked with it before so I wanted to get some idea as if it's something that could possible work as clean solution for this.
Code for app above:
const ExampleApp = () => {
const [currentStep, setCurrentStep] = useState(1);
const getCurrentAppStep = () => {
switch (currentStep) {
case 1:
return {
app: <AppStepOne />,
navigation: (
<AppNavigation onNext={() => setCurrentStep(currentStep + 1)} />
)
};
case 2:
return {
app: <AppStepTwo />,
navigation: (
<AppNavigation onNext={() => setCurrentStep(currentStep + 1)} />
)
};
default:
return {
app: <AppStepOne />,
navigation: (
<AppNavigation onNext={() => setCurrentStep(currentStep + 1)} />
)
};
}
};
const myAppStep = getCurrentAppStep();
return (
<div>
<ParentComp>
<ChildOne>{myAppStep.app}</ChildOne>
<ChildTwo>{myAppStep.navigation}</ChildTwo>
</ParentComp>
</div>
);
};
const ParentComp = ({ children }) => {
return <div>{children}</div>;
};
const ChildOne = ({ children }) => {
return <div>{children}</div>;
};
const ChildTwo = ({ children }) => {
return <div>{children}</div>;
};
const AppStepOne = () => {
const [name, setName] = useState("");
return (
<div>
Name: <input onChange={(e) => setName(e.target.value)} />
</div>
);
};
const AppStepTwo = () => {
const [zipcode, setZipCode] = useState("");
return (
<div>
Zipcode: <input onChange={(e) => setZipCode(e.target.value)} />
</div>
);
};
const AppNavigation = ({ onNext }) => {
return <button onClick={onNext}>Next</button>;
};
I have a page with the following structure
const Upload = (props) => {
return (
<BaseLayout>
<ToolbarSelection />
<Box>
<FileDropArea />
</Box>
</BaseLayout>
)
}
I have a method which works in the component <FileDropArea />
This is the method used as example
const allSelection = () => {
setFiles((files) =>
files.map((file) => {
file.checked = true;
return file;
})
);
};
In React how can i call this method allSelection from the <ToolbarSelection /> component, where i have my simple button like <Button>All Selection</Button>
You need to use React Context like this:
//create a fileContext.js
const fileContext = React.createContext();
const useFileContext = () => React.useContext(fileContext);
const FileContextProvider = ({ children }) => {
const [files, setFiles] = useState([]);
const allSelection = () => {
setFiles((files) =>
files.map((file) => {
file.checked = true;
return file;
})
);
};
// if you have other methods which may change the files add them here
return (
<fileContext.Provider
value={{
files,
setFiles,
allSelection,
}}
>
{children}
</fileContext.Provider>
);
};
use fileContextProvider in your upload file
const Upload = (props) => {
return (
<FileContextProvider>
<BaseLayout>
<ToolbarSelection />
<Box>
<FileDropArea />
</Box>
</BaseLayout>
</FileContextProvider>
);
};
use it, for example in ToolbarSelection like this:
const ToolbarSelection = () => {
const {files, allSelection} = useFileContext();
// do other stuff
}
React Hooks
I assume you are looking to make the allSelection function reusable. Hooks are a great way to make logic reusable across components.
Create a custom hook useAllSelection. Note that hooks should have a use prefix.
const useAllSelection = (files) => {
const [files, setFiles] = useState([]);
const handleAllSelection = () => {
setFiles((files) =>
files.map((file) => {
file.checked = true;
return file;
})
);
};
return { handleAllSelection };
};
const ToolbarSelection = () => {
// import the hook and use
const { handleAllSelection } = useAllSelection();
return (
<button onClick={handleAllSelection}>All Selection</button>
)
}
ReactJS allows to perform this scenario in a different way. Let me explain it: if you press a button in the ToolbarSelection, pass the value of the new state of that button to FileDropArea as props. Then, in the FileDropArea render, call the method or not depending on the value of that property
const Upload = (props) => {
return (
<BaseLayout>
<ToolbarSelection
onSelectionClick={(value) => setSelected(value)}
/>
<Box>
<FileDropArea
selected = { /* state of a button in the Toolbar */}
/>
</Box>
</BaseLayout>
)
}
Note how the callback in the Toolbar changes the state, and how this new state is passed to FileDropArea as property
Normally this is how you use MUI Dialog. The code below is taken from the docs:
export default function AlertDialog() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Open Dialog
</Button>
<Dialog open={open} onClose={handleClose}>
{...}
</Dialog>
</div>
);
}
But I want it to create the Dialog imperatively, sort of like fire and forget. I do not want to embed the Dialog component in other components whenever I need to create them. Ideally I'd want to call it like this
createDialog(<>
<h1>My Dialog</h1>
<span>My dialog content</span>
<button onClick={() => closeDialog()}>Close</button>
</>)
So my component definition'd look like this
const createDialog = () => {
// ???
}
const closeDialog = () => {
// ???
}
export default function AlertDialog() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => setOpen(true);
const handleClose = () => {
createDialog(<>
<h1>My Dialog</h1>
<span>My dialog content</span>
<button onClick={() => closeDialog()}>Close</button>
</>)
};
return (
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Open Dialog
</Button>
);
}
You can reuse dialogs using React's Provider pattern. The official React document has explained in good detail so I won't cover it again here.
First create a custom Provider component in this case I'll call DialogProvider. This component will manage a list of Dialogs in local state.
const DialogContext = React.createContext();
export default function DialogProvider({ children }) {
const [dialogs, setDialogs] = React.useState([]);
return (
<DialogContext.Provider {...}>
{children}
</DialogContext.Provider>
);
}
As you can see, we have an array of dialogs here, it contains the dialog props that will be mapped to the actually <Dialog /> component when rendering.
export default function DialogProvider({ children }) {
const [dialogs, setDialogs] = React.useState([]);
return (
<DialogContext.Provider {...}>
{children}
{dialogs.map((dialog, i) => {
return <DialogContainer key={i} {...dialog} />;
})}
</DialogContext.Provider>
);
}
The <DialogContainer/> is the parent component of the <Dialog/>. Put anything that you want to be reusable in there. Here is a minimum example to get you started.
function DialogContainer(props: DialogContainerProps) {
const { children, open, onClose, onKill } = props;
return (
<Dialog open={open} onClose={onClose} onExited={onKill}>
{children}
</Dialog>
);
}
We can create and remove the dialog using setState as normal.
const [dialogs, setDialogs] = React.useState([]);
const createDialog = (option) => {
const dialog = { ...option, open: true };
setDialogs((dialogs) => [...dialogs, dialog]);
};
const closeDialog = () => {
setDialogs((dialogs) => {
const latestDialog = dialogs.pop();
if (!latestDialog) return dialogs;
if (latestDialog.onClose) latestDialog.onClose();
return [...dialogs].concat({ ...latestDialog, open: false });
});
};
But how do we call them in other components when we defined them here? Well, remember we're using Provider component here, which means we can pass the context data down so other components can reference, in this case we want to pass the createDialog and closeDialog down.
const [dialogs, setDialogs] = React.useState([]);
const createDialog = (option) => {/*...*/};
const closeDialog = () => {/*...*/};
const contextValue = React.useRef([createDialog, closeDialog]);
return (
<DialogContext.Provider value={contextValue.current}>
{children}
{dialogs.map((dialog, i) => ...)}
</DialogContext.Provider>
);
We're almost done here, now we need to add the DialogProvider to the component tree.
export default function App() {
return (
<DialogProvider>
<App {...} />
</DialogProvider>
);
}
But before we can use them, we should create a hook to easily access the context from the parent. So in your DialogProvider.jsx
export const useDialog = () => React.useContext(DialogContext);
Now we can use it like this.
import { useDialog } from "./DialogProvider";
export default function Content() {
const [openDialog, closeDialog] = useDialog();
const onOpenDialog = () => {
openDialog({
children: (
<>
<DialogTitle>This dialog is opened imperatively</DialogTitle>
<DialogContent>Some content</DialogContent>
<DialogActions>
<Button color="primary" onClick={closeDialog}>Close</Button>
</DialogActions>
</>
)
});
};
return (
<Button variant="contained" onClick={onOpenDialog}>
Show dialog
</Button>
);
}
Live Demo
You can play around in the live demo here