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
Related
I'm trying to pass 2 functions in on onChange event.
I know how to do this in the same file but I'm struggling to do it when I have one function in the child and another one in the parent component.
This is my child component:
export const ImageUpload = () => {
const [selectedFile, setSelectedFile] = useState()
const [preview, setPreview] = useState()
// create a preview as a side effect, whenever selected file is changed
useEffect(() => {
if (!selectedFile) {
setPreview(undefined)
return
}
const objectUrl = URL.createObjectURL(selectedFile)
setPreview(objectUrl)
// free memory when ever this component is unmounted
return () => URL.revokeObjectURL(objectUrl)
}, [selectedFile])
const onSelectFile = e => {
if (!e.target.files || e.target.files.length === 0) {
setSelectedFile(undefined)
return
}
// I've kept this example simple by using the first image instead of multiple
setSelectedFile(e.target.files[0])
}
return (
<div>
<input type='file'
onChange={onSelectFile}
/>
{selectedFile && <img src={preview} />}
</div>
)
}
My parent is:
<ImageUpload
// onChange= add an extra function here
/>
How I should add an extra function on onChange on the parent
You can pass function in props like this
<ImageUpload
changeFunction = {your function here}
/>
and accept the props and use two function onChange like this.
export const ImageUpload = (props) => {
const [selectedFile, setSelectedFile] = useState()
const [preview, setPreview] = useState()
// create a preview as a side effect, whenever selected file is changed
useEffect(() => {
if (!selectedFile) {
setPreview(undefined)
return
}
const objectUrl = URL.createObjectURL(selectedFile)
setPreview(objectUrl)
// free memory when ever this component is unmounted
return () => URL.revokeObjectURL(objectUrl)
}, [selectedFile])
const onSelectFile = e => {
if (!e.target.files || e.target.files.length === 0) {
setSelectedFile(undefined)
return
}
// I've kept this example simple by using the first image instead of multiple
setSelectedFile(e.target.files[0])
}
return (
<div>
<input type='file'
onChange={onSelectFile; props.changeFunction}
/>
{selectedFile && <img src={preview} />}
</div>
)
}
pass a function to child from parent
export const ImageUpload = ({onChange}) => {
const onSelectFile = e => {
onChange() //call here
}
}
in parent
const onChange = () => {
}
<ImageUpload
onChange={onChange}
/>
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'm trying to figure out an architecture to allow a parent component, to wait for all the child components to finish rendering and then do some work. Unfortunately in this case I need to do the rendering outside of React and it's done asynchronously instead.
This makes things a little complex. So in my example I want the doSomethingAfterRender() function in the ParentComponent to be called once, after all the ChildComponent customRender calls have completed.
I do have one potential solution, though it doesn't feel very clean which is to use a debounce on the doSomethingAfterRender() function. I'd much rather use a more deterministic approach to only calling this function once if possible.
I'm wondering if anyone has a better suggestion for handling this?
ParentComponent.js
const Parent = (props) => {
// This is the function I need to call
const doSomethingAfterRender = useCallback(
async (params) => {
await doSomething();
},
);
// Extend the child components to provide the doSomethingAfterRender callback down
const childrenWithProps = React.Children.map(props.children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { doSomethingAfterRender });
}
return child;
});
return (
<React.Fragment>
<...someDOM....>
{childrenWithProps}
</React.Fragment>
);
}
ChildComponent.js (this is actually a HoC)
const withXYZ = (WrappedComponent) =>
({ doSomethingAfterRender, ...props }) => {
// I need to wait for this function to complete, on all child components
const doRender = useCallback(
async () => {
await customRender();
// Call the doSomething...
if (doSomethingAfterRender) {
doSomethingAfterRender();
}
},
[doSomethingAfterRender]
);
return (
<React.Fragment>
<... some DOM ...>
<WrappedComponent {...props} renderLoop={renderLoop} layer={layer} />
</React.Fragment>
);
};
App.js
const Child = withXYZ(CustomWrappedComponent);
const App = () => {
return {
<ParentComponent>
<Child />
<Child />
<Child />
</ParentComponent>
};
}
If I understood correctly. I would do something like: useState with useRef.
This way I would trigger only once the Parent and that is when all Child components have finished with their respective async tasks.
Child.js
const child = ({ childRef, updateParent }) => {
const [text, setText] = useState("Not Set");
useEffect(() => {
if (typeof childRef.current !== "boolean") {
const display = (childRef.current += 1);
setTimeout(() => {
setText(`Child Now ${display}`);
if (childRef.current === 2) {
updateParent(true);
childRef.current = false;
}
}, 3000);
}
}, []);
return (
<>
<div>Test {text}</div>
</>
);
}
const Parent = ()=>{
const childRef = useRef(0);
const [shouldUpdate, setShouldUpdate] = useState(false);
useEffect(() => {
if (shouldUpdate) {
console.log("updating");
}
}, [shouldUpdate]);
return (
<div className="App">
{childRef.current}
<Child childRef={childRef} updateParent={setShouldUpdate} />
<Child childRef={childRef} updateParent={setShouldUpdate} />
<Child childRef={childRef} updateParent={setShouldUpdate} />
</div>
);
}